diff --git a/.editorconfig b/.editorconfig index dbf903ae14..5f3b4d684a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,9 +8,10 @@ root = true # Use 4 spaces as indentation [*] insert_final_newline = true +end_of_line = lf indent_style = space indent_size = 4 # Trim trailing whitespace, limited support. # https://github.com/editorconfig/editorconfig/wiki/Property-research:-Trim-trailing-spaces -trim_trailing_whitespace = true \ No newline at end of file +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore index 6a4c95895d..9f22544f35 100644 --- a/.gitignore +++ b/.gitignore @@ -4,26 +4,33 @@ *.aps *.pch *.vspscc +*.orig +*.suo +*.vs10x +*.ndproj + .DS_Store ._.DS_Store + [Bb]in [Db]ebug*/ obj/ [Rr]elease*/ + _ReSharper*/ + _NCrunch_*/ *.ncrunchsolution *.ncrunchsolution.user *.ncrunchproject *.crunchsolution.cache + [Tt]est[Rr]esult* [Bb]uild[Ll]og.* *.[Pp]ublish.xml -*.suo [sS]ource [sS]andbox umbraco.config -*.vs10x App_Data/TEMP/* [Uu]mbraco/[Pp]resentation/[Uu]mbraco/[Pp]lugins/* [Uu]mbraco/[Pp]resentation/[Uu]ser[Cc]ontrols/* @@ -46,12 +53,11 @@ src/Umbraco.Web.UI/Web.*.config.transformed umbraco/presentation/umbraco/plugins/uComponents/uComponentsInstaller.ascx umbraco/presentation/packages/uComponents/MultiNodePicker/CustomTreeService.asmx -*.ncrunchsolution + src/Umbraco.Tests/config/applications.config src/Umbraco.Tests/config/trees.config src/Umbraco.Web.UI/web.config src/Umbraco.Web.UI/Config/ClientDependency.config -*.orig src/Umbraco.Tests/config/404handlers.config src/Umbraco.Web.UI/[Vv]iews/*.cshtml src/Umbraco.Web.UI/[Vv]iews/*.vbhtml @@ -89,6 +95,7 @@ src/Umbraco.Web.UI/[Uu]mbraco/[Aa]ssets/* src/Umbraco.Web.UI.Client/[Bb]uild/* src/Umbraco.Web.UI.Client/[Bb]uild/[Bb]elle/ src/Umbraco.Web.UI/[Uu]ser[Cc]ontrols/ + src/Umbraco.Web.UI.Client/src/[Ll]ess/*.css tools/NDepend/ @@ -97,9 +104,8 @@ src/*.psess src/*.vspx src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/routes.js.* NDependOut/* -*.ndproj QueryResult.htm -*.ndproj + src/Umbraco.Web.UI/[Uu]mbraco/[Aa]ssets/* src/Umbraco.Web.UI/[Uu]mbraco/[Ll]ib/* src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.html @@ -113,6 +119,7 @@ build/ApiDocs/* build/ApiDocs/Output/* src/Umbraco.Web.UI.Client/bower_components/* /src/Umbraco.Web.UI/Umbraco/preview +/src/Umbraco.Web.UI/Umbraco/preview.old #Ignore Rule for output of generated documentation files from Grunt docserve src/Umbraco.Web.UI.Client/docs/api @@ -125,9 +132,13 @@ src/*.boltdata/ src/umbraco.sln.ide/* src/.vs/ src/Umbraco.Web.UI/umbraco/js/install.loader.js +src/Umbraco.Web.UI/js/* src/Umbraco.Tests/media tools/docfx/* apidocs/_site/* +src/*/project.lock.json +src/.idea/* + apidocs/api/* build/docs.zip build/ui-docs.zip @@ -136,8 +147,8 @@ build/csharp-docs.zip src/packages/ src/PrecompiledWeb/* - +# build build.out/ build.tmp/ -build/Modules/*/temp/ -/src/.idea/* +build/hooks/ +build/temp/ diff --git a/BUILD.md b/BUILD.md index b9c4e36d20..30ea950778 100644 --- a/BUILD.md +++ b/BUILD.md @@ -55,6 +55,10 @@ The best solution is to unblock the Zip file before un-zipping: right-click the 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). +If PowerShell does not execute the script due to a security error, you may have to run the following command from an administrator prompt: + + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned + # Build The Umbraco Build solution relies on a PowerShell module. The module needs to be imported into PowerShell. From within Umbraco's repository root: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..3baa5dbe66 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,80 @@ +# Code of Conduct + +## 1. Purpose + +A primary goal of Umbraco CMS is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). + +This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. + +We invite all those who participate in Umbraco CMS to help us create safe and positive experiences for everyone. + +## 2. Open Source Citizenship + +A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. + +Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. + +If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. + +## 3. Expected Behavior + +The following behaviors are expected and requested of all community members: + +* Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. +* Exercise consideration and respect in your speech and actions. +* Attempt collaboration before conflict. +* Refrain from demeaning, discriminatory, or harassing behavior and speech. +* Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. +* Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. + +## 4. Unacceptable Behavior + +The following behaviors are considered harassment and are unacceptable within our community: + +* Violence, threats of violence or violent language directed against another person. +* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. +* Posting or displaying sexually explicit or violent material. +* Posting or threatening to post other people’s personally identifying information ("doxing"). +* Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. +* Inappropriate photography or recording. +* Inappropriate physical contact. You should have someone’s consent before touching them. +* Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. +* Deliberate intimidation, stalking or following (online or in person). +* Advocating for, or encouraging, any of the above behavior. +* Sustained disruption of community events, including talks and presentations. + +## 5. Consequences of Unacceptable Behavior + +Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. + +Anyone asked to stop unacceptable behavior is expected to comply immediately. + +If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). + +## 6. Reporting Guidelines + +If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. Please contact Sebastiaan Janssen - [sj@umbraco.dk](mailto:sj@umbraco.dk). + +Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. + +## 7. Addressing Grievances + +If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Umbraco with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. + +## 8. Scope + +We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business. + +This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. + +## 9. Contact info + +Sebastiaan Janssen - [sj@umbraco.dk](mailto:sj@umbraco.dk) + +## 10. License and attribution + +This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). + +Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). + +Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..0e87f9824d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,207 @@ +# 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 💖. + +#### Table Of Contents + +[Code of Conduct](#code-of-conduct) + +[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 do I even begin?](#how-do-i-even-begin) + +[Problems?](#problems) + +[Credits](#credits) + +## 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). + +## 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](ISSUE_TEMPLATE.md), 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.org/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 question 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 Atom, 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.org/projects/). + +### Your First Code Contribution + +Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` and issues](http://issues.umbraco.org/issues/U4?q=%28project%3A+%7BU4%7D+Difficulty%3A+%7BVery+Easy%7D+%23Easy+%23Unresolved+Priority%3A+Normal+%23Major+%23Show-stopper+State%3A+-%7BIn+Progress%7D+sort+by%3A+votes+Affected+versions%3A+-6.*+Affected+versions%3A+-4.*%29+OR+%28tag%3A+%7BUp+For+Grabs%7D+%23Unresolved+%29). + +The issue list is sorted by total number of upvotes. While not perfect, number of upvotes is a reasonable proxy for impact a given change will have. + +### 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.org/documentation/Reference/) is generated + +Again, these are guidelines, not strict requirements. + +## 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 bower + 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.org/apidocs/ui/#/api) (to be found in `src\Umbraco.Web.UI.Client\src`) + * [The rest](https://our.umbraco.org/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?](tools/contributing/defaultbranch.png) + +### Building Umbraco from source code + +The easiest way to get started is to run `build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details. + +Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 ([the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile. + +![Gulp build in Visual Studio](tools/contributing/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)) + +## How do I even 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](tools/contributing/forkrepository.png) + + * **Clone** - when GitHub has created your fork, you can clone it in your favorite Git tool + + ![Clone the fork](tools/contributing/clonefork.png) + + * **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](#building-umbraco-from-source-code) + * **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will happily give feedback + * **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-U4-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `U4-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](tools/contributing/createpullrequest.png) + +The Umbraco development 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. + +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! + +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 at this and we'll be nice about it, thanking you for spending your valueable time. + +Remember, if an issue is in the `Up for grabs` list or you've asked for some feedback before you send us a PR, your PR will not be closed as unwanted. + +## Problems? + +Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.org/forum/contributing-to-umbraco-cms/) forum, the team monitors that one closely! + +## 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! diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..db1e5c88bd --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +### Prerequisites + +- [ ] I have written a descriptive pull-request title +- [ ] I have linked this PR to an issue on the tracker at http://issues.umbraco.org + +### Description + + + + + diff --git a/README.md b/README.md index 80fed871e2..24aa108c11 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,17 @@ -[![Build status](https://ci.appveyor.com/api/projects/status/6by6harxtxt0ocdx/branch/dev-v7?svg=true)](https://ci.appveyor.com/project/Umbraco/umbraco-cms-b2cri/branch/dev-v7) - Umbraco CMS =========== The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 443,000 websites worldwide: [https://umbraco.com](https://umbraco.com) [![ScreenShot](vimeo.png)](https://vimeo.com/172382998/) -## Umbraco CMS ## +## 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. - -## Building Umbraco from source ## - -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. - -Note that you can always [download a nightly build](http://nightly.umbraco.org/?container=umbraco-750) so you don't have to build the code yourself. - -## Watch an introduction video ## +## Watch an introduction video [![ScreenShot](http://umbraco.com/images/whatisumbraco.png)](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/) -## Umbraco - The Friendly CMS ## +## 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 - or 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, out of the box. @@ -30,22 +21,24 @@ Used by more than 443,000 active websites including Carlsberg, Segway, Amazon an To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies) -## Why Open Source? ## +## 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. -## Downloading ## +## Trying out Umbraco CMS -The downloadable Umbraco releases live at [https://our.umbraco.org/download](https://our.umbraco.org/download). +[Umbraco Cloud](https://umbraco.com) is the easiest and fastest way to use Umbraco yet with full support for all your custom .NET code and intergrations. 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. -## Forums ## +If you want to DIY you can [download Umbraco](https://our.umbraco.org/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 yourself and handling deployments and upgrades is all down to you. -Peer-to-peer support is available 24/7 at the community forum on [https://our.umbraco.org](https://our.umbraco.org). +## Community -## Contribute to Umbraco ## +Our friendly community is available 24/7 at the community hub we call ["Our Umbraco"](https://our.umbraco.org). Our Umbraco feature forums for questions and answers, documentation, downloadable plugins for Umbraco and a rich collection of community resources. -Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](https://our.umbraco.org/contribute). +## Contribute to Umbraco -## Found a bug? ## +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](https://our.umbraco.org/contribute/report-an-issue-or-request-a-feature). diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 4c9a33fd23..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,62 +0,0 @@ -version: '{build}' -shallow_clone: true - -init: - - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) - -build_script: -- cmd: >- - SET SLN=%CD% - - SET SRC=%SLN%\src - - SET PACKAGES=%SRC%\packages - - CD build - - SET "release=" - - FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SET "release=%%i" - - ECHO "Restoring NuGet into %PACKAGES%" - - %SRC%\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL - - %SRC%\.nuget\NuGet.exe restore %SRC%\umbraco.sln -Verbosity Quiet -NonInteractive -PackagesDirectory %PACKAGES% - - ECHO Building Release %release% build%APPVEYOR_BUILD_NUMBER% - - SET PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% - - SET MSBUILD="C:\Program Files (x86)\MSBuild\14.0\Bin\MsBuild.exe" - - XCOPY "%SRC%\Umbraco.Tests\unit-test-log4net.CI.config" "%SRC%\Umbraco.Tests\unit-test-log4net.config" /Y - - %MSBUILD% "%SLN%/src/Umbraco.Tests/Umbraco.Tests.csproj" /consoleloggerparameters:Summary;ErrorsOnly;WarningsOnly /p:NugetPackagesDirectory=%PACKAGES% - - build.bat -integration -release:%release% -comment:build%APPVEYOR_BUILD_NUMBER% -nugetfolder:%PACKAGES% - -test: - assemblies: src\Umbraco.Tests\bin\Debug\Umbraco.Tests.dll -artifacts: -- path: build\UmbracoCms.* - name: UmbracoFiles -- path: build\msbuild.log - name: BuildLog -deploy: -- provider: AzureBlob - storage_account_name: umbraconightlies - storage_access_key: - secure: bmEMml2SF7QLHULiePa/a01XOeIa2SxJeXuaZ+1R27b+Vb2nNUQVYiPlUyF2cZAFSHI/zO/LekRsVU1rTescGhJjF7SSjKybymI3p+F/OWpwqiu2WfFee1ofXBFx8QHw - container: umbraco-750 - artifact: UmbracoFiles - on: - branch: dev-v7 -notifications: -- provider: Slack - auth_token: - secure: v2csJi2V5ghR0rPdODK8GJdOGNCA+XaK84iQ9MdPOClqB+VU+40ybdKp6gPirGSH - channel: '#build-umbraco-core' - on_build_success: false - on_build_failure: true - on_build_status_changed: false \ No newline at end of file diff --git a/build.bat b/build.bat deleted file mode 100644 index 29a5e07a5a..0000000000 --- a/build.bat +++ /dev/null @@ -1,14 +0,0 @@ -@ECHO OFF -powershell .\build\build.ps1 - -IF ERRORLEVEL 1 ( - GOTO :error -) ELSE ( - GOTO :EOF -) - -:error -ECHO. -ECHO Can not run build\build.ps1. -ECHO If this is due to a SecurityError then please refer to BUILD.md for help! -ECHO. diff --git a/build/azuregalleryrelease.ps1 b/build/Azure/azuregalleryrelease.ps1 similarity index 100% rename from build/azuregalleryrelease.ps1 rename to build/Azure/azuregalleryrelease.ps1 diff --git a/build/Modules/Umbraco.Build/Build-UmbracoDocs.ps1 b/build/Modules/Umbraco.Build/Build-UmbracoDocs.ps1 deleted file mode 100644 index f0691fb5ae..0000000000 --- a/build/Modules/Umbraco.Build/Build-UmbracoDocs.ps1 +++ /dev/null @@ -1,111 +0,0 @@ -# - -function Build-UmbracoDocs -{ - $uenv = Get-UmbracoBuildEnv - - $src = "$($uenv.SolutionRoot)\src" - $out = "$($uenv.SolutionRoot)\build.out" - $tmp = "$($uenv.SolutionRoot)\build.tmp" - - $buildTemp = "$PSScriptRoot\temp" - $cache = 2 - - Prepare-Build -keep $uenv - - ################ Do the UI docs - # get a temp clean node env (will restore) - Sandbox-Node $uenv - - Write-Host "Executing gulp docs" - - push-location "$($uenv.SolutionRoot)\src\Umbraco.Web.UI.Client" - write "node version is:" > $tmp\belle-docs.log - &node -v >> $tmp\belle-docs.log 2>&1 - write "npm version is:" >> $tmp\belle-docs.log 2>&1 - &npm -v >> $tmp\belle-docs.log 2>&1 - write "executing npm install" >> $tmp\belle-docs.log 2>&1 - &npm install >> $tmp\belle-docs.log 2>&1 - write "executing bower install" >> $tmp\belle-docs.log 2>&1 - &npm install -g bower >> $tmp\belle-docs.log 2>&1 - write "installing gulp" >> $tmp\belle-docs.log 2>&1 - &npm install -g gulp >> $tmp\belle-docs.log 2>&1 - write "installing gulp-cli" >> $tmp\belle-docs.log 2>&1 - &npm install -g gulp-cli --quiet >> $tmp\belle-docs.log 2>&1 - write "building docs using gulp" >> $tmp\belle-docs.log 2>&1 - &gulp docs >> $tmp\belle-docs.log 2>&1 - pop-location - - Write-Host "Completed gulp docs build" - - # fixme - should we filter the log to find errors? - #get-content .\build.tmp\belle-docs.log | %{ if ($_ -match "build") { write $_}} - - # change baseUrl - $baseUrl = "https://our.umbraco.org/apidocs/ui/" - $indexPath = "$src/Umbraco.Web.UI.Client/docs/api/index.html" - (Get-Content $indexPath).Replace("location.href.replace(rUrl, indexFile)", "'$baseUrl'") ` - | Set-Content $indexPath - - # restore - Restore-Node - - # zip - &$uenv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Client\docs\api\*.*" ` - > $null - - ################ Do the c# docs - - Write-Host "Build C# documentation" - - # Build the solution in debug mode - # FIXME no only a simple compilation should be enough! - # FIXME we MUST handle msbuild & co error codes! - # FIXME deal with weird things in gitconfig? - #Build-Umbraco -Configuration Debug - Restore-NuGet $uenv - Compile-Umbraco $uenv "Debug" # FIXME different log file! - Restore-WebConfig "$src\Umbraco.Web.UI" - - # ensure we have docfx - Get-DocFx $uenv $buildTemp - - # clear - $docFxOutput = "$($uenv.SolutionRoot)\apidocs\_site" - if (test-path($docFxOutput)) - { - Remove-Directory $docFxOutput - } - - # run - $docFxJson = "$($uenv.SolutionRoot)\apidocs\docfx.json" - push-location "$($uenv.SolutionRoot)\build" # silly docfx.json wants this - - Write-Host "Run DocFx metadata" - Write-Host "Logging to $tmp\docfx.metadata.log" - &$uenv.DocFx metadata $docFxJson > "$tmp\docfx.metadata.log" - Write-Host "Run DocFx build" - Write-Host "Logging to $tmp\docfx.build.log" - &$uenv.DocFx build $docFxJson > "$tmp\docfx.build.log" - - pop-location - - # zip - &$uenv.Zip a -tzip -r "$out\csharp-docs.zip" "$docFxOutput\*.*" ` - > $null -} - -function Get-DocFx($uenv, $buildTemp) -{ - $docFx = "$buildTemp\docfx" - if (-not (test-path $docFx)) - { - Write-Host "Download DocFx..." - $source = "https://github.com/dotnet/docfx/releases/download/v2.19.2/docfx.zip" - Invoke-WebRequest $source -OutFile "$buildTemp\docfx.zip" - - &$uenv.Zip x "$buildTemp\docfx.zip" -o"$buildTemp\docfx" -aos > $nul - Remove-File "$buildTemp\docfx.zip" - } - $uenv | add-member -memberType NoteProperty -name DocFx -value "$docFx\docfx.exe" -} \ No newline at end of file diff --git a/build/Modules/Umbraco.Build/Get-UmbracoBuildEnv.ps1 b/build/Modules/Umbraco.Build/Get-UmbracoBuildEnv.ps1 deleted file mode 100644 index 2df42e5fd8..0000000000 --- a/build/Modules/Umbraco.Build/Get-UmbracoBuildEnv.ps1 +++ /dev/null @@ -1,181 +0,0 @@ -# -# Get-UmbracoBuildEnv -# Gets the Umbraco build environment -# Downloads tools if necessary -# -function Get-UmbracoBuildEnv -{ - # store tools in the module's directory - # and cache them for two days - $path = "$PSScriptRoot\temp" - $cache = 2 - - if (-not (test-path $path)) - { - mkdir $path > $null - } - - # ensure we have NuGet - $nuget = "$path\nuget.exe" - $source = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" - if ((test-path $nuget) -and ((ls $nuget).CreationTime -lt [DateTime]::Now.AddDays(-$cache))) - { - Remove-File $nuget - } - if (-not (test-path $nuget)) - { - Write-Host "Download NuGet..." - Invoke-WebRequest $source -OutFile $nuget - } - - # ensure we have 7-Zip - $sevenZip = "$path\7za.exe" - if ((test-path $sevenZip) -and ((ls $sevenZip).CreationTime -lt [DateTime]::Now.AddDays(-$cache))) - { - Remove-File $sevenZip - } - if (-not (test-path $sevenZip)) - { - Write-Host "Download 7-Zip..." - &$nuget install 7-Zip.CommandLine -OutputDirectory $path -Verbosity quiet - $dir = ls "$path\7-Zip.CommandLine.*" | sort -property Name -descending | select -first 1 - $file = ls -path "$dir" -name 7za.exe -recurse - $file = ls -path "$dir" -name 7za.exe -recurse | select -first 1 #A select is because there is tools\7za.exe & tools\x64\7za.exe - mv "$dir\$file" $sevenZip - Remove-Directory $dir - } - - # ensure we have vswhere - $vswhere = "$path\vswhere.exe" - if ((test-path $vswhere) -and ((ls $vswhere).CreationTime -lt [DateTime]::Now.AddDays(-$cache))) - { - Remove-File $vswhere - } - if (-not (test-path $vswhere)) - { - Write-Host "Download VsWhere..." - &$nuget install vswhere -OutputDirectory $path -Verbosity quiet - $dir = ls "$path\vswhere.*" | sort -property Name -descending | select -first 1 - $file = ls -path "$dir" -name vswhere.exe -recurse - mv "$dir\$file" $vswhere - Remove-Directory $dir - } - - # ensure we have semver - $semver = "$path\Semver.dll" - if ((test-path $semver) -and ((ls $semver).CreationTime -lt [DateTime]::Now.AddDays(-$cache))) - { - Remove-File $semver - } - if (-not (test-path $semver)) - { - Write-Host "Download Semver..." - &$nuget install semver -OutputDirectory $path -Verbosity quiet - $dir = ls "$path\semver.*" | sort -property Name -descending | select -first 1 - $file = "$dir\lib\net452\Semver.dll" - if (-not (test-path $file)) - { - Write-Error "Failed to file $file" - return - } - mv "$file" $semver - Remove-Directory $dir - } - - try - { - [Reflection.Assembly]::LoadFile($semver) > $null - } - catch - { - Write-Error -Exception $_.Exception -Message "Failed to load $semver" - break - } - - # ensure we have node - $node = "$path\node-v6.9.1-win-x86" - $source = "http://nodejs.org/dist/v6.9.1/node-v6.9.1-win-x86.7z" - if (-not (test-path $node)) - { - Write-Host "Download Node..." - Invoke-WebRequest $source -OutFile "$path\node-v6.9.1-win-x86.7z" - &$sevenZip x "$path\node-v6.9.1-win-x86.7z" -o"$path" -aos > $nul - Remove-File "$path\node-v6.9.1-win-x86.7z" - } - - # note: why? node already brings everything we need! - ## ensure we have npm - #$npm = "$path\npm.*" - #$getNpm = $true - #if (test-path $npm) - #{ - # $getNpm = $false - # $tmpNpm = ls "$path\npm.*" | sort -property Name -descending | select -first 1 - # if ($tmpNpm.CreationTime -lt [DateTime]::Now.AddDays(-$cache)) - # { - # $getNpm = $true - # } - # else - # { - # $npm = $tmpNpm.ToString() - # } - #} - #if ($getNpm) - #{ - # Write-Host "Download Npm..." - # &$nuget install npm -OutputDirectory $path -Verbosity quiet - # $npm = ls "$path\npm.*" | sort -property Name -descending | select -first 1 - # $npm.CreationTime = [DateTime]::Now - # $npm = $npm.ToString() - #} - - # find visual studio - # will not work on VSO but VSO does not need it - $vsPath = "" - $vsVer = "" - $msBuild = $null - &$vswhere | foreach { - if ($_.StartsWith("installationPath:")) { $vsPath = $_.SubString("installationPath:".Length).Trim() } - if ($_.StartsWith("installationVersion:")) { $vsVer = $_.SubString("installationVersion:".Length).Trim() } - } - if ($vsPath -ne "") - { - $vsVerParts = $vsVer.Split('.') - $vsMajor = [int]::Parse($vsVerParts[0]) - $vsMinor = [int]::Parse($vsVerParts[1]) - if ($vsMajor -eq 15) { - $msBuild = "$vsPath\MSBuild\$vsMajor.0\Bin" - } - elseif ($vsMajor -eq 14) { - $msBuild = "c:\Program Files (x86)\MSBuild\$vsMajor\Bin" - } - else - { - $msBuild = $null - } - } - - $vs = $null - if ($msBuild) - { - $vs = new-object -typeName PsObject - $vs | add-member -memberType NoteProperty -name Path -value $vsPath - $vs | add-member -memberType NoteProperty -name Major -value $vsMajor - $vs | add-member -memberType NoteProperty -name Minor -value $vsMinor - $vs | add-member -memberType NoteProperty -name MsBuild -value "$msBuild\MsBuild.exe" - } - - $solutionRoot = Get-FullPath "$PSScriptRoot\..\..\.." - - $uenv = new-object -typeName PsObject - $uenv | add-member -memberType NoteProperty -name SolutionRoot -value $solutionRoot - $uenv | add-member -memberType NoteProperty -name VisualStudio -value $vs - $uenv | add-member -memberType NoteProperty -name NuGet -value $nuget - $uenv | add-member -memberType NoteProperty -name Zip -value $sevenZip - $uenv | add-member -memberType NoteProperty -name VsWhere -value $vswhere - $uenv | add-member -memberType NoteProperty -name Semver -value $semver - $uenv | add-member -memberType NoteProperty -name NodePath -value $node - #$uenv | add-member -memberType NoteProperty -name NpmPath -value $npm - - return $uenv -} diff --git a/build/Modules/Umbraco.Build/Get-UmbracoVersion.ps1 b/build/Modules/Umbraco.Build/Get-UmbracoVersion.ps1 deleted file mode 100644 index a3ce784f14..0000000000 --- a/build/Modules/Umbraco.Build/Get-UmbracoVersion.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -# -# Get-UmbracoVersion -# Gets the Umbraco version -# -function Get-UmbracoVersion -{ - $uenv = Get-UmbracoBuildEnv - - # parse SolutionInfo and retrieve the version string - $filepath = "$($uenv.SolutionRoot)\src\SolutionInfo.cs" - $text = [System.IO.File]::ReadAllText($filepath) - $match = [System.Text.RegularExpressions.Regex]::Matches($text, "AssemblyInformationalVersion\(`"(.+)?`"\)") - $version = $match.Groups[1] - - # semver-parse the version string - $semver = [SemVer.SemVersion]::Parse($version) - $release = "" + $semver.Major + "." + $semver.Minor + "." + $semver.Patch - - $versions = new-object -typeName PsObject - $versions | add-member -memberType NoteProperty -name Semver -value $semver - $versions | add-member -memberType NoteProperty -name Release -value $release - $versions | add-member -memberType NoteProperty -name Comment -value $semver.PreRelease - $versions | add-member -memberType NoteProperty -name Build -value $semver.Build - - return $versions -} diff --git a/build/Modules/Umbraco.Build/Get-VisualStudio.ps1 b/build/Modules/Umbraco.Build/Get-VisualStudio.ps1 deleted file mode 100644 index cc88984eb2..0000000000 --- a/build/Modules/Umbraco.Build/Get-VisualStudio.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -# finds msbuild -function Get-VisualStudio($vswhere) -{ - $vsPath = "" - $vsVer = "" - &$vswhere | foreach { - if ($_.StartsWith("installationPath:")) { $vsPath = $_.SubString("installationPath:".Length).Trim() } - if ($_.StartsWith("installationVersion:")) { $vsVer = $_.SubString("installationVersion:".Length).Trim() } - } - if ($vsPath -eq "") { return $null } - - $vsVerParts = $vsVer.Split('.') - $vsMajor = [int]::Parse($vsVerParts[0]) - $vsMinor = [int]::Parse($vsVerParts[1]) - if ($vsMajor -eq 15) { - $msBuild = "$vsPath\MSBuild\$vsMajor.$vsMinor\Bin" - } - elseif ($vsMajor -eq 14) { - $msBuild = "c:\Program Files (x86)\MSBuild\$vsMajor\Bin" - } - else { return $null } - $msBuild = "$msBuild\MsBuild.exe" - - $vs = new-object -typeName PsObject - $vs | add-member -memberType NoteProperty -name Path -value $vsPath - $vs | add-member -memberType NoteProperty -name Major -value $vsMajor - $vs | add-member -memberType NoteProperty -name Minor -value $vsMinor - $vs | add-member -memberType NoteProperty -name MsBuild -value $msBuild - return $vs -} diff --git a/build/Modules/Umbraco.Build/Set-UmbracoContinuousVersion.ps1 b/build/Modules/Umbraco.Build/Set-UmbracoContinuousVersion.ps1 deleted file mode 100644 index 8996777292..0000000000 --- a/build/Modules/Umbraco.Build/Set-UmbracoContinuousVersion.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -# -# Set-UmbracoContinuousVersion -# Sets the Umbraco version for continuous integration -# -# -Version -# where is a Semver valid version -# eg 1.2.3, 1.2.3-alpha, 1.2.3-alpha+456 -# -# -BuildNumber -# where is a string coming from the build server -# eg 34, 126, 1 -# -function Set-UmbracoContinuousVersion -{ - param ( - [Parameter(Mandatory=$true)] - [string] - $version, - [Parameter(Mandatory=$true)] - [string] - $buildNumber - ) - - Write-Host "Version is currently set to $version" - - $umbracoVersion = "$($version.Trim())-alpha$($buildNumber)" - Write-Host "Setting Umbraco Version to $umbracoVersion" - - Set-UmbracoVersion $umbracoVersion -} \ No newline at end of file diff --git a/build/Modules/Umbraco.Build/Set-UmbracoVersion.ps1 b/build/Modules/Umbraco.Build/Set-UmbracoVersion.ps1 deleted file mode 100644 index 19681dcba0..0000000000 --- a/build/Modules/Umbraco.Build/Set-UmbracoVersion.ps1 +++ /dev/null @@ -1,117 +0,0 @@ -# -# Set-UmbracoVersion -# Sets the Umbraco version -# -# -Version -# where is a Semver valid version -# eg 1.2.3, 1.2.3-alpha, 1.2.3-alpha+456 -# -function Set-UmbracoVersion -{ - param ( - [Parameter(Mandatory=$true)] - [string] - $version - ) - - $uenv = Get-UmbracoBuildEnv - - try - { - [Reflection.Assembly]::LoadFile($uenv.Semver) > $null - } - catch - { - Write-Error "Failed to load $uenv.Semver" - break - } - - # validate input - $ok = [Regex]::Match($version, "^[0-9]+\.[0-9]+\.[0-9]+(\-[a-z0-9]+)?(\+[0-9]+)?$") - if (-not $ok.Success) - { - Write-Error "Invalid version $version" - break - } - - # parse input - try - { - $semver = [SemVer.SemVersion]::Parse($version) - } - catch - { - Write-Error "Invalid version $version" - break - } - - # - $release = "" + $semver.Major + "." + $semver.Minor + "." + $semver.Patch - - # edit files and set the proper versions and dates - Write-Host "Update UmbracoVersion.cs" - Replace-FileText "$($uenv.SolutionRoot)\src\Umbraco.Core\Configuration\UmbracoVersion.cs" ` - "(\d+)\.(\d+)\.(\d+)(.(\d+))?" ` - "$release" - Replace-FileText "$($uenv.SolutionRoot)\src\Umbraco.Core\Configuration\UmbracoVersion.cs" ` - "CurrentComment { get { return `"(.+)`"" ` - "CurrentComment { get { return `"$($semver.PreRelease)`"" - Write-Host "Update SolutionInfo.cs" - Replace-FileText "$($uenv.SolutionRoot)\src\SolutionInfo.cs" ` - "AssemblyFileVersion\(`"(.+)?`"\)" ` - "AssemblyFileVersion(`"$release`")" - Replace-FileText "$($uenv.SolutionRoot)\src\SolutionInfo.cs" ` - "AssemblyInformationalVersion\(`"(.+)?`"\)" ` - "AssemblyInformationalVersion(`"$semver`")" - $year = [System.DateTime]::Now.ToString("yyyy") - Replace-FileText "$($uenv.SolutionRoot)\src\SolutionInfo.cs" ` - "AssemblyCopyright\(`"Copyright © Umbraco (\d{4})`"\)" ` - "AssemblyCopyright(`"Copyright © Umbraco $year`")" - - # edit csproj and set IIS Express port number - # this is a raw copy of ReplaceIISExpressPortNumber.exe - # it probably can be achieved in a much nicer way - l8tr - $source = @" - using System; - using System.IO; - using System.Xml; - using System.Globalization; - - namespace Umbraco - { - public static class PortUpdater - { - public static void Update(string path, string release) - { - XmlDocument xmlDocument = new XmlDocument(); - string fullPath = Path.GetFullPath(path); - xmlDocument.Load(fullPath); - int result = 1; - int.TryParse(release.Replace(`".`", `"`"), out result); - while (result < 1024) - result *= 10; - XmlNode xmlNode1 = xmlDocument.GetElementsByTagName(`"IISUrl`").Item(0); - if (xmlNode1 != null) - xmlNode1.InnerText = `"http://localhost:`" + (object) result; - XmlNode xmlNode2 = xmlDocument.GetElementsByTagName(`"DevelopmentServerPort`").Item(0); - if (xmlNode2 != null) - xmlNode2.InnerText = result.ToString((IFormatProvider) CultureInfo.InvariantCulture); - xmlDocument.Save(fullPath); - } - } - } -"@ - - $assem = ( - "System.Xml", - "System.IO", - "System.Globalization" - ) - - Write-Host "Update Umbraco.Web.UI.csproj" - add-type -referencedAssemblies $assem -typeDefinition $source -language CSharp - $csproj = "$($uenv.SolutionRoot)\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" - [Umbraco.PortUpdater]::Update($csproj, $release) - - return $semver -} diff --git a/build/Modules/Umbraco.Build/Umbraco.Build.psm1 b/build/Modules/Umbraco.Build/Umbraco.Build.psm1 deleted file mode 100644 index dde1ef6467..0000000000 --- a/build/Modules/Umbraco.Build/Umbraco.Build.psm1 +++ /dev/null @@ -1,615 +0,0 @@ - -# Umbraco.Build.psm1 -# -# $env:PSModulePath = "$pwd\build\Modules\;$env:PSModulePath" -# Import-Module Umbraco.Build -Force -DisableNameChecking -# -# PowerShell Modules: -# https://msdn.microsoft.com/en-us/library/dd878324%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 -# -# PowerShell Module Manifest: -# https://msdn.microsoft.com/en-us/library/dd878337%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 -# -# See also -# http://www.powershellmagazine.com/2014/08/15/pstip-taking-control-of-verbose-and-debug-output-part-5/ - - -. "$PSScriptRoot\Utilities.ps1" -. "$PSScriptRoot\Get-VisualStudio.ps1" - -. "$PSScriptRoot\Get-UmbracoBuildEnv.ps1" -. "$PSScriptRoot\Set-UmbracoVersion.ps1" -. "$PSScriptRoot\Set-UmbracoContinuousVersion.ps1" -. "$PSScriptRoot\Get-UmbracoVersion.ps1" -. "$PSScriptRoot\Verify-NuGet.ps1" - -. "$PSScriptRoot\Build-UmbracoDocs.ps1" - -# -# Prepares the build -# -function Prepare-Build -{ - param ( - $uenv, # an Umbraco build environment (see Get-UmbracoBuildEnv) - - [Alias("k")] - [switch] - $keep = $false - ) - - Write-Host ">> Prepare Build" - - $src = "$($uenv.SolutionRoot)\src" - $tmp = "$($uenv.SolutionRoot)\build.tmp" - $out = "$($uenv.SolutionRoot)\build.out" - - # clear - Write-Host "Clear folders and files" - - Remove-Directory "$src\Umbraco.Web.UI.Client\bower_components" - - if (-not $keep) - { - Remove-Directory "$tmp" - Remove-Directory "$out" - } - - if (-not (Test-Path "$tmp")) - { - mkdir "$tmp" > $null - } - if (-not (Test-Path "$out")) - { - mkdir "$out" > $null - } - - # ensure proper web.config - $webUi = "$src\Umbraco.Web.UI" - Store-WebConfig $webUi - Write-Host "Create clean web.config" - Copy-File "$webUi\web.Template.config" "$webUi\web.config" -} - -function Clear-EnvVar($var) -{ - $value = [Environment]::GetEnvironmentVariable($var) - if (test-path "env:$var") { rm "env:$var" } - return $value -} - -function Set-EnvVar($var, $value) -{ - if ($value) - { - [Environment]::SetEnvironmentVariable($var, $value) - } - else - { - if (test-path "env:$var") { rm "env:$var" } - } -} - -function Sandbox-Node -{ - param ( - $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) - ) - - $global:node_path = $env:path - $nodePath = $uenv.NodePath - $gitExe = (get-command git).Source - $gitPath = [System.IO.Path]::GetDirectoryName($gitExe) - $env:path = "$nodePath;$gitPath" - - $global:node_nodepath = Clear-EnvVar "NODEPATH" - $global:node_npmcache = Clear-EnvVar "NPM_CONFIG_CACHE" - $global:node_npmprefix = Clear-EnvVar "NPM_CONFIG_PREFIX" -} - -function Restore-Node -{ - $env:path = $node_path - - Set-EnvVar "NODEPATH" $node_nodepath - Set-EnvVar "NPM_CONFIG_CACHE" $node_npmcache - Set-EnvVar "NPM_CONFIG_PREFIX" $node_npmprefix -} - -# -# Builds the Belle UI project -# -function Compile-Belle -{ - param ( - $uenv, # an Umbraco build environment (see Get-UmbracoBuildEnv) - $version # an Umbraco version object (see Get-UmbracoVersion) - ) - - $tmp = "$($uenv.SolutionRoot)\build.tmp" - $src = "$($uenv.SolutionRoot)\src" - - Write-Host ">> Compile Belle" - Write-Host "Logging to $tmp\belle.log" - - # get a temp clean node env (will restore) - Sandbox-Node $uenv - - push-location "$($uenv.SolutionRoot)\src\Umbraco.Web.UI.Client" - write "node version is:" > $tmp\belle.log - &node -v >> $tmp\belle.log 2>&1 - write "npm version is:" >> $tmp\belle.log 2>&1 - &npm -v >> $tmp\belle.log 2>&1 - write "cleaning npm cache" >> $tmp\belle.log 2>&1 - &npm cache clean >> $tmp\belle.log 2>&1 - write "installing bower" >> $tmp\belle.log 2>&1 - &npm install -g bower >> $tmp\belle.log 2>&1 - write "installing gulp" >> $tmp\belle.log 2>&1 - &npm install -g gulp >> $tmp\belle.log 2>&1 - write "installing gulp-cli" >> $tmp\belle.log 2>&1 - &npm install -g gulp-cli --quiet >> $tmp\belle.log 2>&1 - write "executing npm install" >> $tmp\belle.log 2>&1 - &npm install >> $tmp\belle.log 2>&1 - write "executing gulp build for version $version" >> $tmp\belle.log 2>&1 - &gulp build --buildversion=$version.Release >> $tmp\belle.log 2>&1 - pop-location - - # fixme - should we filter the log to find errors? - #get-content .\build.tmp\belle.log | %{ if ($_ -match "build") { write $_}} - - # restore - Restore-Node - - # setting node_modules folder to hidden - # used to prevent VS13 from crashing on it while loading the websites project - # also makes sure aspnet compiler does not try to handle rogue files and chokes - # in VSO with Microsoft.VisualC.CppCodeProvider -related errors - # use get-item -force 'cos it might be hidden already - write "Set hidden attribute on node_modules" - $dir = get-item -force "$src\Umbraco.Web.UI.Client\node_modules" - $dir.Attributes = $dir.Attributes -bor ([System.IO.FileAttributes]::Hidden) -} - -# -# Compiles Umbraco -# -function Compile-Umbraco -{ - param ( - $uenv, # an Umbraco build environment (see Get-UmbracoBuildEnv) - [string] $buildConfiguration = "Release" - ) - - $src = "$($uenv.SolutionRoot)\src" - $tmp = "$($uenv.SolutionRoot)\build.tmp" - $out = "$($uenv.SolutionRoot)\build.out" - - if ($uenv.VisualStudio -eq $null) - { - Write-Error "Build environment does not provide VisualStudio." - break - } - - $toolsVersion = "4.0" - if ($uenv.VisualStudio.Major -eq 15) - { - $toolsVersion = "15.0" - } - - Write-Host ">> Compile Umbraco" - Write-Host "Logging to $tmp\msbuild.umbraco.log" - - # beware of the weird double \\ at the end of paths - # see http://edgylogic.com/blog/powershell-and-external-commands-done-right/ - &$uenv.VisualStudio.MsBuild "$src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" ` - /p:WarningLevel=0 ` - /p:Configuration=$buildConfiguration ` - /p:Platform=AnyCPU ` - /p:UseWPP_CopyWebApplication=True ` - /p:PipelineDependsOnBuild=False ` - /p:OutDir=$tmp\bin\\ ` - /p:WebProjectOutputDir=$tmp\WebApp\\ ` - /p:Verbosity=minimal ` - /t:Clean`;Rebuild ` - /tv:$toolsVersion ` - /p:UmbracoBuild=True ` - > $tmp\msbuild.umbraco.log - - # /p:UmbracoBuild tells the csproj that we are building from PS -} - -# -# Prepare Tests -# -function Prepare-Tests -{ - param ( - $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) - ) - - $src = "$($uenv.SolutionRoot)\src" - $tmp = "$($uenv.SolutionRoot)\build.tmp" - - Write-Host ">> Prepare Tests" - - # fixme - idea is to avoid rebuilding everything for tests - # but because of our weird assembly versioning (with .* stuff) - # everything gets rebuilt all the time... - #Copy-Files "$tmp\bin" "." "$tmp\tests" - - # data - Write-Host "Copy data files" - if (-Not (Test-Path -Path "$tmp\tests\Packaging" ) ) - { - Write-Host "Create packaging directory" - mkdir "$tmp\tests\Packaging" > $null - } - Copy-Files "$src\Umbraco.Tests\Packaging\Packages" "*" "$tmp\tests\Packaging\Packages" - - # required for package install tests - if (-Not (Test-Path -Path "$tmp\tests\bin" ) ) - { - Write-Host "Create bin directory" - mkdir "$tmp\tests\bin" > $null - } -} - -# -# Compiles Tests -# -function Compile-Tests -{ - param ( - $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) - ) - - $src = "$($uenv.SolutionRoot)\src" - $tmp = "$($uenv.SolutionRoot)\build.tmp" - $out = "$tmp\tests" - - $buildConfiguration = "Release" - - if ($uenv.VisualStudio -eq $null) - { - Write-Error "Build environment does not provide VisualStudio." - break - } - - $toolsVersion = "4.0" - if ($uenv.VisualStudio.Major -eq 15) - { - $toolsVersion = "15.0" - } - - Write-Host ">> Compile Tests" - Write-Host "Logging to $tmp\msbuild.tests.log" - - # beware of the weird double \\ at the end of paths - # see http://edgylogic.com/blog/powershell-and-external-commands-done-right/ - &$uenv.VisualStudio.MsBuild "$src\Umbraco.Tests\Umbraco.Tests.csproj" ` - /p:WarningLevel=0 ` - /p:Configuration=$buildConfiguration ` - /p:Platform=AnyCPU ` - /p:UseWPP_CopyWebApplication=True ` - /p:PipelineDependsOnBuild=False ` - /p:OutDir=$out\\ ` - /p:Verbosity=minimal ` - /t:Build ` - /tv:$toolsVersion ` - /p:UmbracoBuild=True ` - /p:NugetPackages=$src\packages ` - > $tmp\msbuild.tests.log - - # /p:UmbracoBuild tells the csproj that we are building from PS -} - -# -# Cleans things up and prepare files after compilation -# -function Prepare-Packages -{ - param ( - $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) - ) - - Write-Host ">> Prepare Packages" - - $src = "$($uenv.SolutionRoot)\src" - $tmp = "$($uenv.SolutionRoot)\build.tmp" - $out = "$($uenv.SolutionRoot)\build.out" - - $buildConfiguration = "Release" - - # restore web.config - Restore-WebConfig "$src\Umbraco.Web.UI" - - # cleanup build - Write-Host "Clean build" - Remove-File "$tmp\bin\*.dll.config" - Remove-File "$tmp\WebApp\bin\*.dll.config" - - # cleanup presentation - Write-Host "Cleanup presentation" - Remove-Directory "$tmp\WebApp\umbraco.presentation" - - # create directories - Write-Host "Create directories" - mkdir "$tmp\Configs" > $null - mkdir "$tmp\Configs\Lang" > $null - mkdir "$tmp\WebApp\App_Data" > $null - #mkdir "$tmp\WebApp\Media" > $null - #mkdir "$tmp\WebApp\Views" > $null - - # copy various files - Write-Host "Copy xml documentation" - cp -force "$tmp\bin\*.xml" "$tmp\WebApp\bin" - - Write-Host "Copy transformed configs and langs" - # note: exclude imageprocessor/*.config as imageprocessor pkg installs them - Copy-Files "$tmp\WebApp\config" "*.config" "$tmp\Configs" ` - { -not $_.RelativeName.StartsWith("imageprocessor") } - Copy-Files "$tmp\WebApp\config" "*.js" "$tmp\Configs" - Copy-Files "$tmp\WebApp\config\lang" "*.xml" "$tmp\Configs\Lang" - Copy-File "$tmp\WebApp\web.config" "$tmp\Configs\web.config.transform" - - Write-Host "Copy transformed web.config" - Copy-File "$src\Umbraco.Web.UI\web.$buildConfiguration.Config.transformed" "$tmp\WebApp\web.config" - - # offset the modified timestamps on all umbraco dlls, as WebResources - # break if date is in the future, which, due to timezone offsets can happen. - Write-Host "Offset dlls timestamps" - ls -r "$tmp\*.dll" | foreach { - $_.CreationTime = $_.CreationTime.AddHours(-11) - $_.LastWriteTime = $_.LastWriteTime.AddHours(-11) - } - - # copy libs - Write-Host "Copy SqlCE libraries" - Copy-Files "$src\packages\SqlServerCE.4.0.0.1" "*.*" "$tmp\bin" ` - { -not $_.Extension.StartsWith(".nu") -and -not $_.RelativeName.StartsWith("lib\") } - Copy-Files "$src\packages\SqlServerCE.4.0.0.1" "*.*" "$tmp\WebApp\bin" ` - { -not $_.Extension.StartsWith(".nu") -and -not $_.RelativeName.StartsWith("lib\") } - - # copy Belle - Write-Host "Copy Belle" - Copy-Files "$src\Umbraco.Web.UI\umbraco\assets" "*" "$tmp\WebApp\umbraco\assets" - Copy-Files "$src\Umbraco.Web.UI\umbraco\js" "*" "$tmp\WebApp\umbraco\js" - Copy-Files "$src\Umbraco.Web.UI\umbraco\lib" "*" "$tmp\WebApp\umbraco\lib" - Copy-Files "$src\Umbraco.Web.UI\umbraco\views" "*" "$tmp\WebApp\umbraco\views" - Copy-Files "$src\Umbraco.Web.UI\umbraco\preview" "*" "$tmp\WebApp\umbraco\preview" -} - -# -# Creates the Zip packages -# -function Package-Zip -{ - param ( - $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) - ) - - Write-Host ">> Create Zip packages" - - $src = "$($uenv.SolutionRoot)\src" - $tmp = "$($uenv.SolutionRoot)\build.tmp" - $out = "$($uenv.SolutionRoot)\build.out" - - Write-Host "Zip all binaries" - &$uenv.Zip a -r "$out\UmbracoCms.AllBinaries.$($version.Semver).zip" ` - "$tmp\bin\*" ` - "-x!dotless.Core.*" ` - > $null - - Write-Host "Zip cms" - &$uenv.Zip a -r "$out\UmbracoCms.$($version.Semver).zip" ` - "$tmp\WebApp\*" ` - "-x!dotless.Core.*" "-x!Content_Types.xml" "-x!*.pdb"` - > $null -} - -# -# Prepares NuGet -# -function Prepare-NuGet -{ - param ( - $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) - ) - - Write-Host ">> Prepare NuGet" - - $src = "$($uenv.SolutionRoot)\src" - $tmp = "$($uenv.SolutionRoot)\build.tmp" - $out = "$($uenv.SolutionRoot)\build.out" - - # add Web.config transform files to the NuGet package - Write-Host "Add web.config transforms to NuGet package" - mv "$tmp\WebApp\Views\Web.config" "$tmp\WebApp\Views\Web.config.transform" - - # fixme - that one does not exist in .bat build either? - #mv "$tmp\WebApp\Xslt\Web.config" "$tmp\WebApp\Xslt\Web.config.transform" -} - -# -# Restores NuGet -# -function Restore-NuGet -{ - param ( - $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) - ) - - $src = "$($uenv.SolutionRoot)\src" - $tmp = "$($uenv.SolutionRoot)\build.tmp" - - Write-Host ">> Restore NuGet" - Write-Host "Logging to $tmp\nuget.restore.log" - - &$uenv.NuGet restore "$src\Umbraco.sln" > "$tmp\nuget.restore.log" -} - -# -# Copies the Azure Gallery script to output -# -function Prepare-AzureGallery -{ - param ( - $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) - ) - - $src = "$($uenv.SolutionRoot)\src" - $tmp = "$($uenv.SolutionRoot)\build.tmp" - $out = "$($uenv.SolutionRoot)\build.out" - $psScript = "$($uenv.SolutionRoot)\build\azuregalleryrelease.ps1" - - Write-Host ">> Copy azuregalleryrelease.ps1 to output folder" - Copy-Item $psScript $out -} - -# -# Creates the NuGet packages -# -function Package-NuGet -{ - param ( - $uenv, # an Umbraco build environment (see Get-UmbracoBuildEnv) - $version # an Umbraco version object (see Get-UmbracoVersion) - ) - - $src = "$($uenv.SolutionRoot)\src" - $tmp = "$($uenv.SolutionRoot)\build.tmp" - $out = "$($uenv.SolutionRoot)\build.out" - $nuspecs = "$($uenv.SolutionRoot)\build\NuSpecs" - - Write-Host ">> Create NuGet packages" - - # see https://docs.microsoft.com/en-us/nuget/schema/nuspec - # note - warnings about SqlCE native libs being outside of 'lib' folder, - # nothing much we can do about it as it's intentional yet there does not - # seem to be a way to disable the warning - - &$uenv.NuGet Pack "$nuspecs\UmbracoCms.Core.nuspec" ` - -Properties BuildTmp="$tmp" ` - -Version $version.Semver.ToString() ` - -Symbols -Verbosity quiet -outputDirectory $out - - &$uenv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" ` - -Properties BuildTmp="$tmp" ` - -Version $version.Semver.ToString() ` - -Verbosity quiet -outputDirectory $out -} - -# -# Builds Umbraco -# -function Build-Umbraco -{ - [CmdletBinding()] - param ( - [string] - $target = "all", - [string] - $buildConfiguration = "Release" - ) - - $target = $target.ToLowerInvariant() - Write-Host ">> Build-Umbraco <$target> <$buildConfiguration>" - - Write-Host "Get Build Environment" - $uenv = Get-UmbracoBuildEnv - - Write-Host "Get Version" - $version = Get-UmbracoVersion - Write-Host "Version $($version.Semver)" - - if ($target -eq "pre-build") - { - Prepare-Build $uenv - #Compile-Belle $uenv $version - - # set environment variables - $env:UMBRACO_VERSION=$version.Semver.ToString() - $env:UMBRACO_RELEASE=$version.Release - $env:UMBRACO_COMMENT=$version.Comment - $env:UMBRACO_BUILD=$version.Build - - # set environment variable for VSO - # https://github.com/Microsoft/vsts-tasks/issues/375 - # https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md - Write-Host ("##vso[task.setvariable variable=UMBRACO_VERSION;]$($version.Semver.ToString())") - Write-Host ("##vso[task.setvariable variable=UMBRACO_RELEASE;]$($version.Release)") - Write-Host ("##vso[task.setvariable variable=UMBRACO_COMMENT;]$($version.Comment)") - Write-Host ("##vso[task.setvariable variable=UMBRACO_BUILD;]$($version.Build)") - - Write-Host ("##vso[task.setvariable variable=UMBRACO_TMP;]$($uenv.SolutionRoot)\build.tmp") - } - elseif ($target -eq "pre-tests") - { - Prepare-Tests $uenv - } - elseif ($target -eq "compile-tests") - { - Compile-Tests $uenv - } - elseif ($target -eq "compile-umbraco") - { - Compile-Umbraco $uenv $buildConfiguration - } - elseif ($target -eq "pre-packages") - { - Prepare-Packages $uenv - } - elseif ($target -eq "pre-nuget") - { - Prepare-NuGet $uenv - } - elseif ($target -eq "restore-nuget") - { - Restore-NuGet $uenv - } - elseif ($target -eq "pkg-zip") - { - Package-Zip $uenv - } - elseif ($target -eq "compile-belle") - { - Compile-Belle $uenv $version - } - elseif ($target -eq "prepare-azuregallery") - { - Prepare-AzureGallery $uenv - } - elseif ($target -eq "all") - { - Prepare-Build $uenv - Restore-NuGet $uenv - Compile-Belle $uenv $version - Compile-Umbraco $uenv $buildConfiguration - Prepare-Tests $uenv - Compile-Tests $uenv - # not running tests... - Prepare-Packages $uenv - Package-Zip $uenv - Verify-NuGet $uenv - Prepare-NuGet $uenv - Package-NuGet $uenv $version - Prepare-AzureGallery $uenv - } - else - { - Write-Error "Unsupported target `"$target`"." - } -} - -# -# export functions -# -Export-ModuleMember -function Get-UmbracoBuildEnv -Export-ModuleMember -function Set-UmbracoVersion -Export-ModuleMember -function Set-UmbracoContinuousVersion -Export-ModuleMember -function Get-UmbracoVersion -Export-ModuleMember -function Build-Umbraco -Export-ModuleMember -function Build-UmbracoDocs -Export-ModuleMember -function Verify-NuGet - -#eof \ No newline at end of file diff --git a/build/Modules/Umbraco.Build/Utilities.ps1 b/build/Modules/Umbraco.Build/Utilities.ps1 deleted file mode 100644 index 8ca24885cd..0000000000 --- a/build/Modules/Umbraco.Build/Utilities.ps1 +++ /dev/null @@ -1,95 +0,0 @@ -# returns the full path if $file is relative to $pwd -function Get-FullPath($file) -{ - $path = [System.IO.Path]::Combine($pwd, $file) - $path = [System.IO.Path]::GetFullPath($path) - return $path -} - -# removes a directory, doesn't complain if it does not exist -function Remove-Directory($dir) -{ - remove-item $dir -force -recurse -errorAction SilentlyContinue > $null -} - -# removes a file, doesn't complain if it does not exist -function Remove-File($file) -{ - remove-item $file -force -errorAction SilentlyContinue > $null -} - -# copies a file, creates target dir if needed -function Copy-File($source, $target) -{ - $ignore = new-item -itemType file -path $target -force - cp -force $source $target -} - -# copies files to a directory -function Copy-Files($source, $select, $target, $filter) -{ - $files = ls -r "$source\$select" - $files | foreach { - $relative = $_.FullName.SubString($source.Length+1) - $_ | add-member -memberType NoteProperty -name RelativeName -value $relative - } - if ($filter -ne $null) { - $files = $files | where $filter - } - $files | - foreach { - if ($_.PsIsContainer) { - $ignore = new-item -itemType directory -path "$target\$($_.RelativeName)" -force - } - else { - Copy-File $_.FullName "$target\$($_.RelativeName)" - } - } -} - -# regex-replaces content in a file -function Replace-FileText($filename, $source, $replacement) -{ - $filepath = Get-FullPath $filename - $text = [System.IO.File]::ReadAllText($filepath) - $text = [System.Text.RegularExpressions.Regex]::Replace($text, $source, $replacement) - $utf8bom = New-Object System.Text.UTF8Encoding $true - [System.IO.File]::WriteAllText($filepath, $text, $utf8bom) -} - -# store web.config -function Store-WebConfig($webUi) -{ - if (test-path "$webUi\web.config") - { - if (test-path "$webUi\web.config.temp-build") - { - Write-Host "Found existing web.config.temp-build" - $i = 0 - while (test-path "$webUi\web.config.temp-build.$i") - { - $i = $i + 1 - } - Write-Host "Save existing web.config as web.config.temp-build.$i" - Write-Host "(WARN: the original web.config.temp-build will be restored during post-build)" - mv "$webUi\web.config" "$webUi\web.config.temp-build.$i" - } - else - { - Write-Host "Save existing web.config as web.config.temp-build" - Write-Host "(will be restored during post-build)" - mv "$webUi\web.config" "$webUi\web.config.temp-build" - } - } -} - -# restore web.config -function Restore-WebConfig($webUi) -{ - if (test-path "$webUi\web.config.temp-build") - { - Write-Host "Restoring existing web.config" - Remove-File "$webUi\web.config" - mv "$webUi\web.config.temp-build" "$webUi\web.config" - } -} \ No newline at end of file diff --git a/build/Modules/Umbraco.Build/Verify-NuGet.ps1 b/build/Modules/Umbraco.Build/Verify-NuGet.ps1 deleted file mode 100644 index 1a2239393c..0000000000 --- a/build/Modules/Umbraco.Build/Verify-NuGet.ps1 +++ /dev/null @@ -1,444 +0,0 @@ -# -# Verify-NuGet -# - -function Format-Dependency -{ - param ( $d ) - - $m = $d.Id + " " - if ($d.MinInclude) { $m = $m + "[" } - else { $m = $m + "(" } - $m = $m + $d.MinVersion - if ($d.MaxVersion -ne $d.MinVersion) { $m = $m + "," + $d.MaxVersion } - if ($d.MaxInclude) { $m = $m + "]" } - else { $m = $m + ")" } - - return $m -} - -function Write-NuSpec -{ - param ( $name, $deps ) - - Write-Host "" - Write-Host "$name NuSpec dependencies:" - - foreach ($d in $deps) - { - $m = Format-Dependency $d - Write-Host " $m" - } -} - -function Write-Package -{ - param ( $name, $pkgs ) - - Write-Host "" - Write-Host "$name packages:" - - foreach ($p in $pkgs) - { - Write-Host " $($p.Id) $($p.Version)" - } -} - -function Verify-NuGet -{ - param ( - $uenv # an Umbraco build environment (see Get-UmbracoBuildEnv) - ) - - if ($uenv -eq $null) - { - $uenv = Get-UmbracoBuildEnv - } - - $source = @" - - using System; - using System.Collections.Generic; - using System.Linq; - using System.IO; - using System.Xml; - using System.Xml.Serialization; - using Semver; - - namespace Umbraco.Build - { - public class NuGet - { - public static Dependency[] GetNuSpecDependencies(string filename) - { - NuSpec nuspec; - var serializer = new XmlSerializer(typeof(NuSpec)); - using (var reader = new StreamReader(filename)) - { - nuspec = (NuSpec) serializer.Deserialize(reader); - } - var nudeps = nuspec.Metadata.Dependencies; - var deps = new List(); - foreach (var nudep in nudeps) - { - var dep = new Dependency(); - dep.Id = nudep.Id; - - var parts = nudep.Version.Split(','); - if (parts.Length == 1) - { - dep.MinInclude = parts[0].StartsWith("["); - dep.MaxInclude = parts[0].EndsWith("]"); - - SemVersion version; - if (!SemVersion.TryParse(parts[0].Substring(1, parts[0].Length-2).Trim(), out version)) continue; - dep.MinVersion = dep.MaxVersion = version; //parts[0].Substring(1, parts[0].Length-2).Trim(); - } - else - { - SemVersion version; - if (!SemVersion.TryParse(parts[0].Substring(1).Trim(), out version)) continue; - dep.MinVersion = version; //parts[0].Substring(1).Trim(); - if (!SemVersion.TryParse(parts[1].Substring(0, parts[1].Length-1).Trim(), out version)) continue; - dep.MaxVersion = version; //parts[1].Substring(0, parts[1].Length-1).Trim(); - dep.MinInclude = parts[0].StartsWith("["); - dep.MaxInclude = parts[1].EndsWith("]"); - } - - deps.Add(dep); - } - return deps.ToArray(); - } - - public static IEnumerable DistinctBy(/*this*/ IEnumerable source, Func keySelector) - { - HashSet knownKeys = new HashSet(); - foreach (TSource element in source) - { - if (knownKeys.Add(keySelector(element))) - { - yield return element; - } - } - } - - public static Package[] GetProjectsPackages(string src, string[] projects) - { - var l = new List(); - foreach (var project in projects) - { - var path = Path.Combine(src, project); - var packageConfig = Path.Combine(path, "packages.config"); - if (File.Exists(packageConfig)) - ReadPackagesConfig(packageConfig, l); - var csprojs = Directory.GetFiles(path, "*.csproj"); - foreach (var csproj in csprojs) - { - ReadCsProj(csproj, l); - } - } - IEnumerable p = l.OrderBy(x => x.Id); - p = DistinctBy(p, x => x.Id + ":::" + x.Version); - return p.ToArray(); - } - - public static object[] GetPackageErrors(Package[] pkgs) - { - return pkgs - .GroupBy(x => x.Id) - .Where(x => x.Count() > 1) - .ToArray(); - } - - public static object[] GetNuSpecErrors(Package[] pkgs, Dependency[] deps) - { - var d = pkgs.ToDictionary(x => x.Id, x => x.Version); - return deps - .Select(x => - { - SemVersion v; - if (!d.TryGetValue(x.Id, out v)) return null; - - var ok = true; - - /* - if (x.MinInclude) - { - if (v < x.MinVersion) ok = false; - } - else - { - if (v <= x.MinVersion) ok = false; - } - - if (x.MaxInclude) - { - if (v > x.MaxVersion) ok = false; - } - else - { - if (v >= x.MaxVersion) ok = false; - } - */ - - if (!x.MinInclude || v != x.MinVersion) ok = false; - - return ok ? null : new { Dependency = x, Version = v }; - }) - .Where(x => x != null) - .ToArray(); - } - - /* - public static Package[] GetProjectPackages(string path) - { - var l = new List(); - var packageConfig = Path.Combine(path, "packages.config"); - if (File.Exists(packageConfig)) - ReadPackagesConfig(packageConfig, l); - var csprojs = Directory.GetFiles(path, "*.csproj"); - foreach (var csproj in csprojs) - { - ReadCsProj(csproj, l); - } - return l.ToArray(); - } - */ - - public static string GetDirectoryName(string filename) - { - return Path.GetFileName(Path.GetDirectoryName(filename)); - } - - public static void ReadPackagesConfig(string filename, List packages) - { - //Console.WriteLine("read " + filename); - - PackagesConfigPackages pkgs; - var serializer = new XmlSerializer(typeof(PackagesConfigPackages)); - using (var reader = new StreamReader(filename)) - { - pkgs = (PackagesConfigPackages) serializer.Deserialize(reader); - } - foreach (var p in pkgs.Packages) - { - SemVersion version; - if (!SemVersion.TryParse(p.Version, out version)) continue; - packages.Add(new Package { Id = p.Id, Version = version, Project = GetDirectoryName(filename) }); - } - } - - public static void ReadCsProj(string filename, List packages) - { - //Console.WriteLine("read " + filename); - - // if xmlns then it's not a VS2017 with PackageReference - var text = File.ReadAllLines(filename); - var line = text.FirstOrDefault(x => x.Contains(" x.Packages != null).SelectMany(x => x.Packages)) - { - var sversion = p.VersionE ?? p.VersionA; - SemVersion version; - if (!SemVersion.TryParse(sversion, out version)) continue; - packages.Add(new Package { Id = p.Id, Version = version, Project = GetDirectoryName(filename) }); - } - } - - public class Dependency - { - public string Id { get; set; } - public SemVersion MinVersion { get; set; } - public SemVersion MaxVersion { get; set; } - public bool MinInclude { get; set; } - public bool MaxInclude { get; set; } - } - - public class Package - { - public string Id { get; set; } - public SemVersion Version { get; set; } - public string Project { get; set; } - } - - [XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd")] - [XmlRoot(Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", IsNullable = false, ElementName = "package")] - public class NuSpec - { - [XmlElement("metadata")] - public NuSpecMetadata Metadata { get; set; } - } - - [XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "metadata")] - public class NuSpecMetadata - { - [XmlArray("dependencies")] - [XmlArrayItem("dependency", IsNullable = false)] - public NuSpecDependency[] Dependencies { get; set; } - } - - [XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "dependencies")] - public class NuSpecDependency - { - [XmlAttribute(AttributeName = "id")] - public string Id { get; set; } - - [XmlAttribute(AttributeName = "version")] - public string Version { get; set; } - } - - [XmlType(AnonymousType = true)] - [XmlRoot(Namespace = "", IsNullable = false, ElementName = "packages")] - public class PackagesConfigPackages - { - [XmlElement("package")] - public PackagesConfigPackage[] Packages { get; set; } - } - - [XmlType(AnonymousType = true, TypeName = "package")] - public class PackagesConfigPackage - { - [XmlAttribute(AttributeName = "id")] - public string Id { get; set; } - - [XmlAttribute(AttributeName = "version")] - public string Version { get; set; } - } - - [XmlType(AnonymousType = true)] - [XmlRoot(Namespace = "", IsNullable = false, ElementName = "Project")] - public class CsProjProject - { - [XmlElement("ItemGroup")] - public CsProjItemGroup[] ItemGroups { get; set; } - } - - [XmlType(AnonymousType = true, TypeName = "ItemGroup")] - public class CsProjItemGroup - { - [XmlElement("PackageReference")] - public CsProjPackageReference[] Packages { get; set; } - } - - [XmlType(AnonymousType = true, TypeName = "PackageReference")] - public class CsProjPackageReference - { - [XmlAttribute(AttributeName = "Include")] - public string Id { get; set; } - - [XmlAttribute(AttributeName = "Version")] - public string VersionA { get; set; } - - [XmlElement("Version")] - public string VersionE { get; set;} - } - } - } - -"@ - - Write-Host ">> Verify NuGet consistency" - - $assem = ( - "System.Xml", - "System.Core", # "System.Collections.Generic" - "System.Linq", - "System.Xml.Serialization", - "System.IO", - "System.Globalization", - $uenv.Semver - ) - - try - { - # as long as the code hasn't changed it's fine to re-add, but if the code - # has changed this will throw - better warn the dev that we have an issue - add-type -referencedAssemblies $assem -typeDefinition $source -language CSharp - } - catch - { - if ($_.FullyQualifiedErrorId.StartsWith("TYPE_ALREADY_EXISTS,")) - { Write-Error "Failed to add type, did you change the code?" } - else - { Write-Error $_ } - } - if (-not $?) { break } - - $nuspecs = ( - "UmbracoCms", - "UmbracoCms.Core" - ) - - $projects = ( - "Umbraco.Core", - "Umbraco.Web", - "Umbraco.Web.UI", - "UmbracoExamine"#, - #"Umbraco.Tests", - #"Umbraco.Tests.Benchmarks" - ) - - $src = "$($uenv.SolutionRoot)\src" - $pkgs = [Umbraco.Build.NuGet]::GetProjectsPackages($src, $projects) - if (-not $?) { break } - #Write-Package "All" $pkgs - - $errs = [Umbraco.Build.NuGet]::GetPackageErrors($pkgs) - if (-not $?) { break } - - if ($errs.Length -gt 0) - { - Write-Host "" - } - foreach ($err in $errs) - { - Write-Host $err.Key - foreach ($e in $err) - { - Write-Host " $($e.Version) required by $($e.Project)" - } - } - if ($errs.Length -gt 0) - { - Write-Error "Found non-consolidated package dependencies" - break - } - - $nuerr = $false - $nupath = "$($uenv.SolutionRoot)\build\NuSpecs" - foreach ($nuspec in $nuspecs) - { - $deps = [Umbraco.Build.NuGet]::GetNuSpecDependencies("$nupath\$nuspec.nuspec") - if (-not $?) { break } - #Write-NuSpec $nuspec $deps - - $errs = [Umbraco.Build.NuGet]::GetNuSpecErrors($pkgs, $deps) - if (-not $?) { break } - - if ($errs.Length -gt 0) - { - Write-Host "" - Write-Host "$nuspec requires:" - $nuerr = $true - } - foreach ($err in $errs) - { - $m = Format-Dependency $err.Dependency - Write-Host " $m but projects require $($err.Version)" - } - } - - if ($nuerr) - { - Write-Error "Found inconsistent NuGet dependencies" - break - } -} \ No newline at end of file diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 6425c306ea..ad690227b1 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -1,106 +1,72 @@ - - - - UmbracoCms.Core - 7.0.0 - Umbraco Cms Core Binaries - Umbraco HQ - Umbraco HQ - http://opensource.org/licenses/MIT - http://umbraco.com/ - http://umbraco.com/media/357769/100px_transparent.png - false - Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms-package to setup Umbraco in Visual Studio as an ASP.NET project. - Contains the core assemblies needed to run Umbraco Cms - en-US - umbraco - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + UmbracoCms.Core + 8.0.0 + Umbraco Cms Core Binaries + Umbraco HQ + Umbraco HQ + http://opensource.org/licenses/MIT + http://umbraco.com/ + http://umbraco.com/media/357769/100px_transparent.png + false + Contains the core assemblies needed to run Umbraco Cms. This package only contains assemblies and can be used for package development. Use the UmbracoCms-package to setup Umbraco in Visual Studio as an ASP.NET project. + Contains the core assemblies needed to run Umbraco Cms + en-US + umbraco + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index d3ab992a23..241a46d2c2 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -1,53 +1,51 @@ - - - - UmbracoCms - 7.0.0 - Umbraco Cms - Umbraco HQ - Umbraco HQ - http://opensource.org/licenses/MIT - http://umbraco.com/ - http://umbraco.com/media/357769/100px_transparent.png - false - Installs Umbraco Cms in your Visual Studio ASP.NET project - Installs Umbraco Cms in your Visual Studio ASP.NET project - en-US - umbraco - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + UmbracoCms + 8.0.0 + Umbraco Cms + Umbraco HQ + Umbraco HQ + http://opensource.org/licenses/MIT + http://umbraco.com/ + http://umbraco.com/media/357769/100px_transparent.png + false + Installs Umbraco Cms in your Visual Studio ASP.NET project + Installs Umbraco Cms in your Visual Studio ASP.NET project + en-US + umbraco + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt index e85b22a902..2b6da733a1 100644 --- a/build/NuSpecs/tools/ReadmeUpgrade.txt +++ b/build/NuSpecs/tools/ReadmeUpgrade.txt @@ -8,7 +8,7 @@ ---------------------------------------------------- -*** IMPORTANT NOTICE FOR 7.7 UPGRADES *** +*** IMPORTANT NOTICE FOR UPGRADES FROM VERSIONS BELOW 7.7.0 *** Be sure to read the version specific upgrade information before proceeding: https://our.umbraco.org/documentation/Getting-Started/Setup/Upgrading/version-specific#version-7-7-0 diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 5ee5ed32c6..7af4a38c14 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -61,7 +61,7 @@ - + > @@ -343,7 +343,7 @@ - + diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1 index c53b2cd7e5..ad3a00651b 100644 --- a/build/NuSpecs/tools/install.core.ps1 +++ b/build/NuSpecs/tools/install.core.ps1 @@ -33,26 +33,16 @@ if ($project) { robocopy $umbracoBinFolder $umbracoBinBackupPath /e /LOG:$copyLogsPath\UmbracoBinBackup.log # Delete files Umbraco ships with - if(Test-Path $umbracoBinFolder\businesslogic.dll) { Remove-Item $umbracoBinFolder\businesslogic.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\cms.dll) { Remove-Item $umbracoBinFolder\cms.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\controls.dll) { Remove-Item $umbracoBinFolder\controls.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\interfaces.dll) { Remove-Item $umbracoBinFolder\interfaces.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\log4net.dll) { Remove-Item $umbracoBinFolder\log4net.dll -Force -Confirm:$false } if(Test-Path $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll) { Remove-Item $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\SQLCE4Umbraco.dll) { Remove-Item $umbracoBinFolder\SQLCE4Umbraco.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\TidyNet.dll) { Remove-Item $umbracoBinFolder\TidyNet.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\umbraco.dll) { Remove-Item $umbracoBinFolder\umbraco.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.DataLayer.dll) { Remove-Item $umbracoBinFolder\umbraco.DataLayer.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\umbraco.editorControls.dll) { Remove-Item $umbracoBinFolder\umbraco.editorControls.dll -Force -Confirm:$false } - if(Test-Path $umbracoBinFolder\umbraco.MacroEngines.dll) { Remove-Item $umbracoBinFolder\umbraco.MacroEngines.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.providers.dll) { Remove-Item $umbracoBinFolder\umbraco.providers.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\UmbracoExamine.dll) { Remove-Item $umbracoBinFolder\UmbracoExamine.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" @@ -102,4 +92,4 @@ if ($project) { 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 } } -} \ No newline at end of file +} diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index 0e62fb0749..592dc951e0 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -98,10 +98,51 @@ if ($project) { $umbracoUIXMLDestination = Join-Path $projectPath "Umbraco\Config\Create\UI.xml" Copy-Item $umbracoUIXMLSource $umbracoUIXMLDestination -Force } else { + # This part only runs for upgrades + $upgradeViewSource = Join-Path $umbracoFolderSource "Views\install\*" $upgradeView = Join-Path $umbracoFolder "Views\install\" Write-Host "Copying2 ${upgradeViewSource} to ${upgradeView}" Copy-Item $upgradeViewSource $upgradeView -Force + + Try + { + # Disable tours for upgrades, presumably Umbraco experience is already available + $umbracoSettingsConfigPath = Join-Path $configFolder "umbracoSettings.config" + $content = (Get-Content $umbracoSettingsConfigPath).Replace('','') + # Saves with UTF-8 encoding without BOM which makes sure Umbraco can still read it + # Reference: https://stackoverflow.com/a/32951824/5018 + [IO.File]::WriteAllLines($umbracoSettingsConfigPath, $content) + } + Catch + { + # Not a big problem if this fails, let it go + } + + Try + { + $uiXmlConfigPath = Join-Path $umbracoFolder -ChildPath "Config" | Join-Path -ChildPath "create" | Join-Path -ChildPath "UI.xml" + $uiXmlFile = Join-Path $umbracoFolder -ChildPath "Config" | Join-Path -ChildPath "create" | Join-Path -ChildPath "UI.xml" + + $uiXml = New-Object System.Xml.XmlDocument + $uiXml.PreserveWhitespace = $true + + $uiXml.Load($uiXmlFile) + $createExists = $uiXml.SelectNodes("//nodeType[@alias='macros']/tasks/create") + + if($createExists.Count -eq 0) + { + $macrosTasksNode = $uiXml.SelectNodes("//nodeType[@alias='macros']/tasks") + + #Creating: + $createNode = $uiXml.CreateElement("create") + $createNode.SetAttribute("assembly", "umbraco") + $createNode.SetAttribute("type", "macroTasks") + $macrosTasksNode.AppendChild($createNode) + $uiXml.Save($uiXmlFile) + } + } + Catch { } } $installFolder = Join-Path $projectPath "Install" diff --git a/build/RevertToEmptyInstall.bat b/build/RevertToEmptyInstall.bat index b8abe4e64e..aeb0cdac72 100644 --- a/build/RevertToEmptyInstall.bat +++ b/build/RevertToEmptyInstall.bat @@ -22,7 +22,7 @@ echo Removing bin files del ..\src\Umbraco.Web.UI\bin\*.* echo Building solution -%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe ..\src\umbraco.sln /t:Clean,Build +"%ProgramFiles(x86)%"\MSBuild\14.0\Bin\MSBuild.exe ..\src\umbraco.sln /t:Clean,Build echo Resetting installedPackages.config echo ^^^ >..\src\Umbraco.Web.UI\App_Data\packages\installed\installedPackages.config @@ -86,7 +86,7 @@ echo Removing bin files FOR %%A IN (..\src\Umbraco.Web.UI\bin\*.*) DO DEL %%A echo Building solution -%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe ..\src\umbraco.sln /t:Clean,Build +"%ProgramFiles(x86)%"\MSBuild\14.0\Bin\MSBuild.exe ..\src\umbraco.sln /t:Clean,Build echo Resetting installedPackages.config echo ^^^ >..\src\Umbraco.Web.UI\App_Data\packages\installed\installedPackages.config diff --git a/build/build-bootstrap.ps1 b/build/build-bootstrap.ps1 new file mode 100644 index 0000000000..c1b2cb0fbe --- /dev/null +++ b/build/build-bootstrap.ps1 @@ -0,0 +1,76 @@ + + # this script should be dot-sourced into the build.ps1 scripts + # right after the parameters declaration + # ie + # . "$PSScriptRoot\build-bootstrap.ps1" + + # THIS FILE IS DISTRIBUTED AS PART OF UMBRACO.BUILD + # DO NOT MODIFY IT - ALWAYS USED THE COMMON VERSION + + # ################################################################ + # BOOTSTRAP + # ################################################################ + + # reset errors + $error.Clear() + + # ensure we have temp folder for downloads + $scriptRoot = "$PSScriptRoot" + $scriptTemp = "$scriptRoot\temp" + if (-not (test-path $scriptTemp)) { mkdir $scriptTemp > $null } + + # get NuGet + $cache = 4 + $nuget = "$scriptTemp\nuget.exe" + if (-not $local) + { + $source = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" + if ((test-path $nuget) -and ((ls $nuget).CreationTime -lt [DateTime]::Now.AddDays(-$cache))) + { + Remove-Item $nuget -force -errorAction SilentlyContinue > $null + } + if (-not (test-path $nuget)) + { + Write-Host "Download NuGet..." + Invoke-WebRequest $source -OutFile $nuget + if (-not $?) { throw "Failed to download NuGet." } + } + } + elseif (-not (test-path $nuget)) + { + throw "Failed to locate NuGet.exe." + } + + # get the build system + if (-not $local) + { + $solutionRoot = "$scriptRoot\.." + $nugetConfig = @{$true="$solutionRoot\src\NuGet.config.user";$false="$solutionRoot\src\NuGet.config"}[(test-path "$solutionRoot\src\NuGet.config.user")] + &$nuget install Umbraco.Build -OutputDirectory $scriptTemp -Verbosity quiet -PreRelease -ConfigFile $nugetConfig + if (-not $?) { throw "Failed to download Umbraco.Build." } + } + + # ensure we have the build system + $ubuildPath = ls "$scriptTemp\Umbraco.Build.*" | sort -property CreationTime -descending | select -first 1 + if (-not $ubuildPath) + { + throw "Failed to locate the build system." + } + + # boot the build system + # this creates $global:ubuild + return &"$ubuildPath\ps\Boot.ps1" + + # at that point the build.ps1 script must boot the build system + # eg + # $ubuild.Boot($ubuildPath.FullName, [System.IO.Path]::GetFullPath("$scriptRoot\.."), + # @{ Local = $local; With7Zip = $false; WithNode = $false }, + # @{ continue = $continue }) + # if (-not $?) { throw "Failed to boot the build system." } + # + # and it's good practice to report + # eg + # Write-Host "Umbraco.Whatever Build" + # Write-Host "Umbraco.Build v$($ubuild.BuildVersion)" + + # eof \ No newline at end of file diff --git a/build/build.ps1 b/build/build.ps1 index 72d8287d9c..0db043cafe 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -1,67 +1,477 @@ -param ( + + param ( + # get, don't execute [Parameter(Mandatory=$false)] - [string] - $version, + [Alias("g")] + [switch] $get = $false, + # run local, don't download, assume everything is ready [Parameter(Mandatory=$false)] - [Alias("mo")] - [switch] - $moduleOnly = $false -) + [Alias("l")] + [Alias("loc")] + [switch] $local = $false, -# the script can run either from the solution root, -# or from the ./build directory - anything else fails -if ([System.IO.Path]::GetFileName($pwd) -eq "build") -{ - $mpath = [System.IO.Path]::GetDirectoryName($pwd) + "\build\Modules\" -} -else -{ - $mpath = "$pwd\build\Modules\" -} + # keep the build directories, don't clear them + [Parameter(Mandatory=$false)] + [Alias("c")] + [Alias("cont")] + [switch] $continue = $false + ) -# look for the module and throw if not found -if (-not [System.IO.Directory]::Exists($mpath + "Umbraco.Build")) -{ - Write-Error "Could not locate Umbraco build Powershell module." - break -} + # ################################################################ + # BOOTSTRAP + # ################################################################ -# add the module path (if not already there) -if (-not $env:PSModulePath.Contains($mpath)) -{ - $env:PSModulePath = "$mpath;$env:PSModulePath" -} + # create and boot the buildsystem + $ubuild = &"$PSScriptRoot\build-bootstrap.ps1" + if (-not $?) { return } + $ubuild.Boot($PSScriptRoot, + @{ Local = $local; }, + @{ Continue = $continue }) + if ($ubuild.OnError()) { return } -# force-import (or re-import) the module -Write-Host "Import Umbraco build Powershell module" -Import-Module Umbraco.Build -Force -DisableNameChecking + Write-Host "Umbraco Cms Build" + Write-Host "Umbraco.Build v$($ubuild.BuildVersion)" -# module only? -if ($moduleOnly) -{ - if (-not [string]::IsNullOrWhiteSpace($version)) + # ################################################################ + # TASKS + # ################################################################ + + $ubuild.DefineMethod("SetMoreUmbracoVersion", { - Write-Host "(module only: ignoring version parameter)" - } - else + param ( $semver ) + + $release = "" + $semver.Major + "." + $semver.Minor + "." + $semver.Patch + + Write-Host "Update UmbracoVersion.cs" + $this.ReplaceFileText("$($this.SolutionRoot)\src\Umbraco.Core\Configuration\UmbracoVersion.cs", ` + "(\d+)\.(\d+)\.(\d+)(.(\d+))?", ` + "$release") + $this.ReplaceFileText("$($this.SolutionRoot)\src\Umbraco.Core\Configuration\UmbracoVersion.cs", ` + "CurrentComment => `"(.+)`"", ` + "CurrentComment => `"$($semver.PreRelease)`"") + + Write-Host "Update IIS Express port in csproj" + $updater = New-Object "Umbraco.Build.ExpressPortUpdater" + $csproj = "$($this.SolutionRoot)\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" + $updater.Update($csproj, $release) + }) + + $ubuild.DefineMethod("SandboxNode", { - Write-Host "(module only)" + $global:node_path = $env:path + $nodePath = $this.BuildEnv.NodePath + $gitExe = (Get-Command git).Source + $gitPath = [System.IO.Path]::GetDirectoryName($gitExe) + $env:path = "$nodePath;$gitPath" + + $global:node_nodepath = $this.ClearEnvVar("NODEPATH") + $global:node_npmcache = $this.ClearEnvVar("NPM_CONFIG_CACHE") + $global:node_npmprefix = $this.ClearEnvVar("NPM_CONFIG_PREFIX") + }) + + $ubuild.DefineMethod("RestoreNode", + { + $env:path = $node_path + + $this.SetEnvVar("NODEPATH", $node_nodepath) + $this.SetEnvVar("NPM_CONFIG_CACHE", $node_npmcache) + $this.SetEnvVar("NPM_CONFIG_PREFIX", $node_npmprefix) + }) + + $ubuild.DefineMethod("CompileBelle", + { + $src = "$($this.SolutionRoot)\src" + $log = "$($this.BuildTemp)\belle.log" + + Write-Host "Compile Belle" + Write-Host "Logging to $log" + + # get a temp clean node env (will restore) + $this.SandboxNode() + + # stupid PS is going to gather all "warnings" in $error + # so we have to take care of it else they'll bubble and kill the build + if ($error.Count -gt 0) { return } + + Push-Location "$($this.SolutionRoot)\src\Umbraco.Web.UI.Client" + Write-Output "" > $log + + Write-Output "### node version is:" > $log + &node -v >> $log 2>&1 + if (-not $?) { throw "Failed to report node version." } + + Write-Output "### npm version is:" >> $log 2>&1 + &npm -v >> $log 2>&1 + if (-not $?) { throw "Failed to report npm version." } + + Write-Output "### clean npm cache" >> $log 2>&1 + &npm cache clean --force >> $log 2>&1 + $error.Clear() # that one can fail 'cos security bug - ignore + + Write-Output "### npm install" >> $log 2>&1 + &npm install >> $log 2>&1 + Write-Output ">> $? $($error.Count)" >> $log 2>&1 + + Write-Output "### install bower" >> $log 2>&1 + &npm install -g bower >> $log 2>&1 + $error.Clear() # that one fails 'cos bower is deprecated - ignore + + Write-Output "### install gulp" >> $log 2>&1 + &npm install -g gulp >> $log 2>&1 + $error.Clear() # that one fails 'cos deprecated stuff - ignore + + Write-Output "### install gulp-cli" >> $log 2>&1 + &npm install -g gulp-cli --quiet >> $log 2>&1 + if (-not $?) { throw "Failed to install gulp-cli" } # that one is expected to work + + Write-Output "### gulp build for version $($this.Version.Release)" >> $log 2>&1 + &gulp build --buildversion=$this.Version.Release >> $log 2>&1 + if (-not $?) { throw "Failed to build" } # that one is expected to work + + Pop-Location + + # fixme - should we filter the log to find errors? + #get-content .\build.tmp\belle.log | %{ if ($_ -match "build") { write $_}} + + # restore + $this.RestoreNode() + + # setting node_modules folder to hidden + # used to prevent VS13 from crashing on it while loading the websites project + # also makes sure aspnet compiler does not try to handle rogue files and chokes + # in VSO with Microsoft.VisualC.CppCodeProvider -related errors + # use get-item -force 'cos it might be hidden already + Write-Host "Set hidden attribute on node_modules" + $dir = Get-Item -force "$src\Umbraco.Web.UI.Client\node_modules" + $dir.Attributes = $dir.Attributes -bor ([System.IO.FileAttributes]::Hidden) + }) + + $ubuild.DefineMethod("CompileUmbraco", + { + $buildConfiguration = "Release" + + $src = "$($this.SolutionRoot)\src" + $log = "$($this.BuildTemp)\msbuild.umbraco.log" + + if ($this.BuildEnv.VisualStudio -eq $null) + { + throw "Build environment does not provide VisualStudio." + } + + Write-Host "Compile Umbraco" + Write-Host "Logging to $log" + + # beware of the weird double \\ at the end of paths + # see http://edgylogic.com/blog/powershell-and-external-commands-done-right/ + &$this.BuildEnv.VisualStudio.MsBuild "$src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" ` + /p:WarningLevel=0 ` + /p:Configuration=$buildConfiguration ` + /p:Platform=AnyCPU ` + /p:UseWPP_CopyWebApplication=True ` + /p:PipelineDependsOnBuild=False ` + /p:OutDir="$($this.BuildTemp)\bin\\" ` + /p:WebProjectOutputDir="$($this.BuildTemp)\WebApp\\" ` + /p:Verbosity=minimal ` + /t:Clean`;Rebuild ` + /tv:"$($this.BuildEnv.VisualStudio.ToolsVersion)" ` + /p:UmbracoBuild=True ` + > $log + + if (-not $?) { throw "Failed to compile Umbraco.Web.UI." } + + # /p:UmbracoBuild tells the csproj that we are building from PS, not VS + }) + + $ubuild.DefineMethod("PrepareTests", + { + Write-Host "Prepare Tests" + + # fixme - idea is to avoid rebuilding everything for tests + # but because of our weird assembly versioning (with .* stuff) + # everything gets rebuilt all the time... + #Copy-Files "$tmp\bin" "." "$tmp\tests" + + # data + Write-Host "Copy data files" + if (-not (Test-Path -Path "$($this.BuildTemp)\tests\Packaging" )) + { + Write-Host "Create packaging directory" + mkdir "$($this.BuildTemp)\tests\Packaging" > $null + } + $this.CopyFiles("$($this.SolutionRoot)\src\Umbraco.Tests\Packaging\Packages", "*", "$($this.BuildTemp)\tests\Packaging\Packages") + + # required for package install tests + if (-not (Test-Path -Path "$($this.BuildTemp)\tests\bin" )) + { + Write-Host "Create bin directory" + mkdir "$($this.BuildTemp)\tests\bin" > $null + } + }) + + $ubuild.DefineMethod("CompileTests", + { + $buildConfiguration = "Release" + $log = "$($this.BuildTemp)\msbuild.tests.log" + + if ($this.BuildEnv.VisualStudio -eq $null) + { + throw "Build environment does not provide VisualStudio." + } + + Write-Host "Compile Tests" + Write-Host "Logging to $log" + + # beware of the weird double \\ at the end of paths + # see http://edgylogic.com/blog/powershell-and-external-commands-done-right/ + &$this.BuildEnv.VisualStudio.MsBuild "$($this.SolutionRoot)\src\Umbraco.Tests\Umbraco.Tests.csproj" ` + /p:WarningLevel=0 ` + /p:Configuration=$buildConfiguration ` + /p:Platform=AnyCPU ` + /p:UseWPP_CopyWebApplication=True ` + /p:PipelineDependsOnBuild=False ` + /p:OutDir="$($this.BuildTemp)\tests\\" ` + /p:Verbosity=minimal ` + /t:Build ` + /tv:"$($this.BuildEnv.VisualStudio.ToolsVersion)" ` + /p:UmbracoBuild=True ` + /p:NugetPackages="$($this.SolutionRoot)\src\packages" ` + > $log + + if (-not $?) { throw "Failed to compile tests." } + + # /p:UmbracoBuild tells the csproj that we are building from PS + }) + + $ubuild.DefineMethod("PreparePackages", + { + Write-Host "Prepare Packages" + + $src = "$($this.SolutionRoot)\src" + $tmp = "$($this.BuildTemp)" + $out = "$($this.BuildOutput)" + + $buildConfiguration = "Release" + + # restore web.config + $this.TempRestoreFile("$src\Umbraco.Web.UI\web.config") + + # cleanup build + Write-Host "Clean build" + $this.RemoveFile("$tmp\bin\*.dll.config") + $this.RemoveFile("$tmp\WebApp\bin\*.dll.config") + + # cleanup presentation + Write-Host "Cleanup presentation" + $this.RemoveDirectory("$tmp\WebApp\umbraco.presentation") + + # create directories + Write-Host "Create directories" + mkdir "$tmp\Configs" > $null + mkdir "$tmp\Configs\Lang" > $null + mkdir "$tmp\WebApp\App_Data" > $null + #mkdir "$tmp\WebApp\Media" > $null + #mkdir "$tmp\WebApp\Views" > $null + + # copy various files + Write-Host "Copy xml documentation" + Copy-Item -force "$tmp\bin\*.xml" "$tmp\WebApp\bin" + + Write-Host "Copy transformed configs and langs" + # note: exclude imageprocessor/*.config as imageprocessor pkg installs them + $this.CopyFiles("$tmp\WebApp\config", "*.config", "$tmp\Configs", ` + { -not $_.RelativeName.StartsWith("imageprocessor") }) + $this.CopyFiles("$tmp\WebApp\config", "*.js", "$tmp\Configs") + $this.CopyFiles("$tmp\WebApp\config\lang", "*.xml", "$tmp\Configs\Lang") + $this.CopyFile("$tmp\WebApp\web.config", "$tmp\Configs\web.config.transform") + + Write-Host "Copy transformed web.config" + $this.CopyFile("$src\Umbraco.Web.UI\web.$buildConfiguration.Config.transformed", "$tmp\WebApp\web.config") + + # offset the modified timestamps on all umbraco dlls, as WebResources + # break if date is in the future, which, due to timezone offsets can happen. + Write-Host "Offset dlls timestamps" + Get-ChildItem -r "$tmp\*.dll" | ForEach-Object { + $_.CreationTime = $_.CreationTime.AddHours(-11) + $_.LastWriteTime = $_.LastWriteTime.AddHours(-11) + } + + # copy libs + Write-Host "Copy SqlCE libraries" + $nugetPackages = [System.Environment]::ExpandEnvironmentVariables("%userprofile%\.nuget\packages") + $this.CopyFiles("$nugetPackages\SqlServerCE\4.0.0.1", "*.*", "$tmp\bin", ` + { -not $_.Extension.StartsWith(".nu") -and -not $_.RelativeName.StartsWith("lib\") }) + $this.CopyFiles("$nugetPackages\SqlServerCE\4.0.0.1", "*.*", "$tmp\WebApp\bin", ` + { -not $_.Extension.StartsWith(".nu") -and -not $_.RelativeName.StartsWith("lib\") }) + + # copy Belle + Write-Host "Copy Belle" + $this.CopyFiles("$src\Umbraco.Web.UI\umbraco\assets", "*", "$tmp\WebApp\umbraco\assets") + $this.CopyFiles("$src\Umbraco.Web.UI\umbraco\js", "*", "$tmp\WebApp\umbraco\js") + $this.CopyFiles("$src\Umbraco.Web.UI\umbraco\lib", "*", "$tmp\WebApp\umbraco\lib") + $this.CopyFiles("$src\Umbraco.Web.UI\umbraco\views", "*", "$tmp\WebApp\umbraco\views") + }) + + $ubuild.DefineMethod("PackageZip", + { + Write-Host "Create Zip packages" + + $src = "$($this.SolutionRoot)\src" + $tmp = $this.BuildTemp + $out = $this.BuildOutput + + Write-Host "Zip all binaries" + &$this.BuildEnv.Zip a -r "$out\UmbracoCms.AllBinaries.$($this.Version.Semver).zip" ` + "$tmp\bin\*" ` + "-x!dotless.Core.*" ` + > $null + if (-not $?) { throw "Failed to zip UmbracoCms.AllBinaries." } + + Write-Host "Zip cms" + &$this.BuildEnv.Zip a -r "$out\UmbracoCms.$($this.Version.Semver).zip" ` + "$tmp\WebApp\*" ` + "-x!dotless.Core.*" "-x!Content_Types.xml" "-x!*.pdb" ` + > $null + if (-not $?) { throw "Failed to zip UmbracoCms." } + }) + + $ubuild.DefineMethod("PrepareBuild", + { + Write-Host "Clear folders and files" + $this.RemoveDirectory("$($this.SolutionRoot)\src\Umbraco.Web.UI.Client\bower_components") + + $this.TempStoreFile("$($this.SolutionRoot)\src\Umbraco.Web.UI\web.config") + Write-Host "Create clean web.config" + $this.CopyFile("$($this.SolutionRoot)\src\Umbraco.Web.UI\web.Template.config", "$($this.SolutionRoot)\src\Umbraco.Web.UI\web.config") + + Write-host "Set environment" + $env:UMBRACO_VERSION=$this.Version.Semver.ToString() + $env:UMBRACO_RELEASE=$this.Version.Release + $env:UMBRACO_COMMENT=$this.Version.Comment + $env:UMBRACO_BUILD=$this.Version.Build + + if ($args -and $args[0] -eq "vso") + { + Write-host "Set VSO environment" + # set environment variable for VSO + # https://github.com/Microsoft/vsts-tasks/issues/375 + # https://github.com/Microsoft/vsts-tasks/blob/master/docs/authoring/commands.md + Write-Host ("##vso[task.setvariable variable=UMBRACO_VERSION;]$($this.Version.Semver.ToString())") + Write-Host ("##vso[task.setvariable variable=UMBRACO_RELEASE;]$($this.Version.Release)") + Write-Host ("##vso[task.setvariable variable=UMBRACO_COMMENT;]$($this.Version.Comment)") + Write-Host ("##vso[task.setvariable variable=UMBRACO_BUILD;]$($this.Version.Build)") + + Write-Host ("##vso[task.setvariable variable=UMBRACO_TMP;]$($this.SolutionRoot)\build.tmp") + } + }) + + $ubuild.DefineMethod("PrepareNuGet", + { + Write-Host "Prepare NuGet" + + # add Web.config transform files to the NuGet package + Write-Host "Add web.config transforms to NuGet package" + mv "$($this.BuildTemp)\WebApp\Views\Web.config" "$($this.BuildTemp)\WebApp\Views\Web.config.transform" + + # fixme - that one does not exist in .bat build either? + #mv "$($this.BuildTemp)\WebApp\Xslt\Web.config" "$($this.BuildTemp)\WebApp\Xslt\Web.config.transform" + }) + + $ubuild.DefineMethod("RestoreNuGet", + { + Write-Host "Restore NuGet" + Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log" + &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\Umbraco.sln" -ConfigFile $this.BuildEnv.NuGetConfig > "$($this.BuildTemp)\nuget.restore.log" + if (-not $?) { throw "Failed to restore NuGet packages." } + }) + + $ubuild.DefineMethod("PackageNuGet", + { + $nuspecs = "$($this.SolutionRoot)\build\NuSpecs" + + Write-Host "Create NuGet packages" + + # see https://docs.microsoft.com/en-us/nuget/schema/nuspec + # note - warnings about SqlCE native libs being outside of 'lib' folder, + # nothing much we can do about it as it's intentional yet there does not + # seem to be a way to disable the warning + + &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Core.nuspec" ` + -Properties BuildTmp="$($this.BuildTemp)" ` + -Version "$($this.Version.Semver.ToString())" ` + -Symbols -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log" + if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Core." } + + &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" ` + -Properties BuildTmp="$($this.BuildTemp)" ` + -Version $this.Version.Semver.ToString() ` + -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cms.log" + if (-not $?) { throw "Failed to pack NuGet UmbracoCms." } + + # run hook + if ($this.HasMethod("PostPackageNuGet")) + { + Write-Host "Run PostPackageNuGet hook" + $this.PostPackageNuGet(); + if (-not $?) { throw "Failed to run hook." } + } + }) + + $ubuild.DefineMethod("VerifyNuGet", + { + $this.VerifyNuGetConsistency( + ("UmbracoCms", "UmbracoCms.Core"), + ("Umbraco.Core", "Umbraco.Web", "Umbraco.Web.UI", "Umbraco.Examine")) + if ($this.OnError()) { return } + }) + + $ubuild.DefineMethod("PrepareAzureGallery", + { + Write-Host "Prepare Azure Gallery" + $this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput) + }) + + $ubuild.DefineMethod("Build", + { + $this.PrepareBuild() + if ($this.OnError()) { return } + $this.RestoreNuGet() + if ($this.OnError()) { return } + $this.CompileBelle() + if ($this.OnError()) { return } + $this.CompileUmbraco() + if ($this.OnError()) { return } + $this.PrepareTests() + if ($this.OnError()) { return } + $this.CompileTests() + if ($this.OnError()) { return } + # not running tests + $this.PreparePackages() + if ($this.OnError()) { return } + $this.PackageZip() + if ($this.OnError()) { return } + $this.VerifyNuGet() + if ($this.OnError()) { return } + $this.PrepareNuGet() + if ($this.OnError()) { return } + $this.PackageNuGet() + if ($this.OnError()) { return } + $this.PrepareAzureGallery() + if ($this.OnError()) { return } + }) + + # ################################################################ + # RUN + # ################################################################ + + # configure + $ubuild.ReleaseBranches = @( "master" ) + + # run + if (-not $get) + { + $ubuild.Build() + if ($ubuild.OnError()) { return } } - break -} - -# get build environment -Write-Host "Setup Umbraco build Environment" -$uenv = Get-UmbracoBuildEnv - -# set the version if any -if (-not [string]::IsNullOrWhiteSpace($version)) -{ - Write-Host "Set Umbraco version to $version" - Set-UmbracoVersion $version -} - -# full umbraco build -Write-Host "Build Umbraco" -Build-Umbraco \ No newline at end of file + Write-Host "Done" + if ($get) { return $ubuild } diff --git a/build/setversion.ps1 b/build/setversion.ps1 deleted file mode 100644 index 99f1534bf5..0000000000 --- a/build/setversion.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -# Usage: powershell .\setversion.ps1 7.6.8 -# Or: powershell .\setversion 7.6.8-beta001 - -param ( - [Parameter(Mandatory=$true)] - [string] - $version -) - -# report -Write-Host "Setting Umbraco version to $version" - -# import Umbraco Build PowerShell module - $pwd is ./build -$env:PSModulePath = "$pwd\Modules\;$env:PSModulePath" -Import-Module Umbraco.Build -Force -DisableNameChecking - -# run commands -$version = Set-UmbracoVersion -Version $version \ No newline at end of file diff --git a/src/NuGet.Config b/src/NuGet.Config index dcccfdd5c1..722f368692 100644 --- a/src/NuGet.Config +++ b/src/NuGet.Config @@ -2,6 +2,7 @@ - + + \ No newline at end of file diff --git a/src/SQLCE4Umbraco/Properties/AssemblyInfo.cs b/src/SQLCE4Umbraco/Properties/AssemblyInfo.cs deleted file mode 100644 index cd12cfbb0a..0000000000 --- a/src/SQLCE4Umbraco/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SqlCE4Umbraco")] -[assembly: AssemblyDescription("Umbraco specific Sql Ce Provider")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyProduct("Umbraco CMS")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("04436b0a-1dc6-4ee1-9d96-4c04f1a9f429")] - -[assembly: InternalsVisibleTo("Umbraco.Tests")] \ No newline at end of file diff --git a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj deleted file mode 100644 index 454a6cf2ce..0000000000 --- a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj +++ /dev/null @@ -1,101 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {5BA5425F-27A7-4677-865E-82246498AA2E} - Library - Properties - SQLCE4Umbraco - SQLCE4Umbraco - v4.5 - 512 - - - - - - - - - ..\ - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\SQLCE4Umbraco.XML - false - - - - - - ..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll - - - ..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll - - - - - - - - - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - Designer - - - - - - {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} - umbraco.datalayer - - - - - - - - - \ No newline at end of file diff --git a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings deleted file mode 100644 index 662f95686e..0000000000 --- a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp50 \ No newline at end of file diff --git a/src/SQLCE4Umbraco/SqlCEDataReader.cs b/src/SQLCE4Umbraco/SqlCEDataReader.cs deleted file mode 100644 index 88734237a3..0000000000 --- a/src/SQLCE4Umbraco/SqlCEDataReader.cs +++ /dev/null @@ -1,44 +0,0 @@ -/************************************************************************************ - * - * Umbraco Data Layer - * MIT Licensed work - * 2008 Ruben Verborgh - * - ***********************************************************************************/ - -using System.Data.SqlServerCe; -using umbraco.DataLayer; - -namespace SqlCE4Umbraco -{ - /// - /// Class that adapts a SqlDataReader.SqlDataReader to a RecordsReaderAdapter. - /// - public class SqlCeDataReaderHelper : RecordsReaderAdapter - { - #region Public Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The data reader. - public SqlCeDataReaderHelper(System.Data.SqlServerCe.SqlCeDataReader dataReader) : base(dataReader) { } - - #endregion - - #region RecordsReaderAdapter Members - - /// - /// Gets a value indicating whether this instance has records. - /// - /// - /// true if this instance has records; otherwise, false. - /// - public override bool HasRecords - { - get { return DataReader.HasRows; } - } - - #endregion - } -} diff --git a/src/SQLCE4Umbraco/SqlCEHelper.cs b/src/SQLCE4Umbraco/SqlCEHelper.cs deleted file mode 100644 index a8567abd67..0000000000 --- a/src/SQLCE4Umbraco/SqlCEHelper.cs +++ /dev/null @@ -1,253 +0,0 @@ -/************************************************************************************ - * - * Umbraco Data Layer - * MIT Licensed work - * 2008 Ruben Verborgh - * - ***********************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.SqlServerCe; -using System.Linq; -using System.Xml; -using System.Diagnostics; -using umbraco.DataLayer; -using umbraco.DataLayer.SqlHelpers.SqlServer; - -namespace SqlCE4Umbraco -{ - /// - /// Sql Helper for an SQL Server database. - /// - public class SqlCEHelper : SqlHelper - { - /// - /// Initializes a new instance of the class. - /// - /// The connection string. - public SqlCEHelper(string connectionString) : base(connectionString) - { - m_Utility = new SqlCEUtility(this); - } - - /// - /// Checks if the actual database exists, if it doesn't then it will create it - /// - internal void CreateEmptyDatabase() - { - var localConnection = new SqlCeConnection(ConnectionString); - if (!System.IO.File.Exists(ReplaceDataDirectory(localConnection.Database))) - { - using (var sqlCeEngine = new SqlCeEngine(ConnectionString)) - { - sqlCeEngine.CreateDatabase(); - } - } - } - - /// - /// Most likely only will be used for unit tests but will remove all tables from the database - /// - internal void ClearDatabase() - { - // drop constraints before tables to avoid exceptions - // looping on try/catching exceptions was not really nice - - // http://stackoverflow.com/questions/536350/drop-all-the-tables-stored-procedures-triggers-constriants-and-all-the-depend - - var localConnection = new SqlCeConnection(ConnectionString); - if (System.IO.File.Exists(ReplaceDataDirectory(localConnection.Database))) - { - List tables; - - // drop foreign keys - // SQL may need "where constraint_catalog=DB_NAME() and ..." - tables = new List(); - using (var reader = ExecuteReader("select table_name from information_schema.table_constraints where constraint_type = 'FOREIGN KEY' order by table_name")) - { - while (reader.Read()) tables.Add(reader.GetString("table_name").Trim()); - } - - foreach (var table in tables) - { - var constraints = new List(); - using (var reader = ExecuteReader("select constraint_name from information_schema.table_constraints where constraint_type = 'FOREIGN KEY' and table_name = '" + table + "' order by constraint_name")) - { - while (reader.Read()) constraints.Add(reader.GetString("constraint_name").Trim()); - } - foreach (var constraint in constraints) - { - // SQL may need "[dbo].[table]" - ExecuteNonQuery("alter table [" + table + "] drop constraint [" + constraint + "]"); - } - } - - // drop primary keys - // SQL may need "where constraint_catalog=DB_NAME() and ..." - tables = new List(); - using (var reader = ExecuteReader("select table_name from information_schema.table_constraints where constraint_type = 'PRIMARY KEY' order by table_name")) - { - while (reader.Read()) tables.Add(reader.GetString("table_name").Trim()); - } - - foreach (var table in tables) - { - var constraints = new List(); - using (var reader = ExecuteReader("select constraint_name from information_schema.table_constraints where constraint_type = 'PRIMARY KEY' and table_name = '" + table + "' order by constraint_name")) - { - while (reader.Read()) constraints.Add(reader.GetString("constraint_name").Trim()); - } - foreach (var constraint in constraints) - { - // SQL may need "[dbo].[table]" - ExecuteNonQuery("alter table [" + table + "] drop constraint [" + constraint + "]"); - } - } - - // drop tables - tables = new List(); - using (var reader = ExecuteReader("select table_name from information_schema.tables where table_type <> 'VIEW' order by table_name")) - { - while (reader.Read()) tables.Add(reader.GetString("table_name").Trim()); - } - - foreach (var table in tables) - { - ExecuteNonQuery("drop table [" + table + "]"); - } - } - } - - /// - /// Drops all foreign keys on a table. - /// - /// The name of the table. - /// To be used in unit tests. - internal void DropForeignKeys(string table) - { - var constraints = new List(); - using (var reader = ExecuteReader("select constraint_name from information_schema.table_constraints where constraint_type = 'FOREIGN KEY' and table_name = '" + table + "' order by constraint_name")) - { - while (reader.Read()) constraints.Add(reader.GetString("constraint_name").Trim()); - } - foreach (var constraint in constraints) - { - // SQL may need "[dbo].[table]" - ExecuteNonQuery("alter table [" + table + "] drop constraint [" + constraint + "]"); - } - } - - /// - /// Replaces the data directory with a local path. - /// - /// The path. - /// A local path with the resolved 'DataDirectory' mapping. - private string ReplaceDataDirectory(string path) - { - if (!string.IsNullOrWhiteSpace(path) && path.Contains("|DataDirectory|")) - { - var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory") as string; - if (!string.IsNullOrEmpty(dataDirectory)) - { - path = path.Contains(@"|\") - ? path.Replace("|DataDirectory|", dataDirectory) - : path.Replace("|DataDirectory|", dataDirectory + System.IO.Path.DirectorySeparatorChar); - } - } - - return path; - } - - /// - /// Creates a new parameter for use with this specific implementation of ISqlHelper. - /// - /// Name of the parameter. - /// Value of the parameter. - /// A new parameter of the correct type. - /// Abstract factory pattern - public override IParameter CreateParameter(string parameterName, object value) - { - return new SqlCEParameter(parameterName, value); - } - - /// - /// Executes a command that returns a single value. - /// - /// The command text. - /// The parameters. - /// The return value of the command. - protected override object ExecuteScalar(string commandText, SqlCeParameter[] parameters) - { -#if DEBUG && DebugDataLayer - // Log Query Execution - Trace.TraceInformation(GetType().Name + " SQL ExecuteScalar: " + commandText); -#endif - using (var cc = UseCurrentConnection) - { - return SqlCeApplicationBlock.ExecuteScalar( - (SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction, - CommandType.Text, commandText, parameters); - } - } - - /// - /// Executes a command and returns the number of rows affected. - /// - /// The command text. - /// The parameters. - /// - /// The number of rows affected by the command. - /// - protected override int ExecuteNonQuery(string commandText, SqlCeParameter[] parameters) - { -#if DEBUG && DebugDataLayer - // Log Query Execution - Trace.TraceInformation(GetType().Name + " SQL ExecuteNonQuery: " + commandText); -#endif - - using (var cc = UseCurrentConnection) - { - return SqlCeApplicationBlock.ExecuteNonQuery( - (SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction, - CommandType.Text, commandText, parameters); - } - } - - /// - /// Executes a command and returns a records reader containing the results. - /// - /// The command text. - /// The parameters. - /// - /// A data reader containing the results of the command. - /// - protected override IRecordsReader ExecuteReader(string commandText, SqlCeParameter[] parameters) - { -#if DEBUG && DebugDataLayer - // Log Query Execution - Trace.TraceInformation(GetType().Name + " SQL ExecuteReader: " + commandText); -#endif - - using (var cc = UseCurrentConnection) - { - return new SqlCeDataReaderHelper(SqlCeApplicationBlock.ExecuteReader( - (SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction, - CommandType.Text, commandText, parameters)); - } - } - - - internal IRecordsReader ExecuteReader(string commandText) - { - return ExecuteReader(commandText, new SqlCEParameter(string.Empty, string.Empty)); - } - - - internal int ExecuteNonQuery(string commandText) - { - return ExecuteNonQuery(commandText, new SqlCEParameter(string.Empty, string.Empty)); - } - } -} \ No newline at end of file diff --git a/src/SQLCE4Umbraco/SqlCEInstaller.cs b/src/SQLCE4Umbraco/SqlCEInstaller.cs deleted file mode 100644 index 80a669e59c..0000000000 --- a/src/SQLCE4Umbraco/SqlCEInstaller.cs +++ /dev/null @@ -1,141 +0,0 @@ -/************************************************************************************ - * - * Umbraco Data Layer - * MIT Licensed work - * 2008 Ruben Verborgh - * - ***********************************************************************************/ - -using System; -using System.Resources; -using SQLCE4Umbraco; -using umbraco.DataLayer.Utility.Installer; -using System.Diagnostics; - -namespace SqlCE4Umbraco -{ - /// - /// Database installer for an SQL Server data source. - /// - [Obsolete("The legacy installers are no longer used and will be removed from the codebase in the future")] - public class SqlCEInstaller : DefaultInstallerUtility - { - #region Private Constants - - /// The latest database version this installer supports. - private const DatabaseVersion LatestVersionSupported = DatabaseVersion.Version4_8; - - /// The specifications to determine the database version. - private static readonly VersionSpecs[] m_VersionSpecs = new VersionSpecs[] { - new VersionSpecs("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS LEFT OUTER JOIN umbracoApp ON appAlias = appAlias WHERE CONSTRAINT_NAME = 'FK_umbracoUser2app_umbracoApp'", 0, DatabaseVersion.Version4_8), - new VersionSpecs("SELECT id FROM umbracoNode WHERE id = -21", 1, DatabaseVersion.Version4_1), - new VersionSpecs("SELECT action FROM umbracoAppTree",DatabaseVersion.Version4), - new VersionSpecs("SELECT description FROM cmsContentType",DatabaseVersion.Version3), - new VersionSpecs("SELECT id FROM sysobjects",DatabaseVersion.None) }; - - #endregion - - #region Public Properties - - /// - /// This ensures that the database exists, then runs the base method - /// - public override bool CanConnect - { - get - { - using (var sqlHelper = SqlHelper) - sqlHelper.CreateEmptyDatabase(); - return base.CanConnect; - } - } - - /// - /// Gets a value indicating whether the installer can upgrade the data source. - /// - /// - /// true if the installer can upgrade the data source; otherwise, false. - /// - /// Empty data sources can't be upgraded, just installed. - public override bool CanUpgrade - { - get - { - return CurrentVersion == DatabaseVersion.Version4_1; - } - } - - #endregion - - #region Protected Properties - - /// - /// Gets the version specification for evaluation by DetermineCurrentVersion. - /// Only first matching specification is taken into account. - /// - /// The version specifications. - protected override VersionSpecs[] VersionSpecs - { - get { return m_VersionSpecs; } - } - - #endregion - - #region Public Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The SQL helper. - public SqlCEInstaller(SqlCEHelper sqlHelper) : base(sqlHelper, LatestVersionSupported) - { } - - #endregion - - #region DefaultInstaller Members - - /// - /// Returns the sql to do a full install - /// - protected override string FullInstallSql - { - get { return string.Empty; } - } - - - /// - /// Returns the sql to do an upgrade - /// - protected override string UpgradeSql - { - get { return string.Empty; } - } - - // We need to override this as the default way of detection a db connection checks for systables that doesn't exist - // in a CE db - protected override DatabaseVersion DetermineCurrentVersion() - { - DatabaseVersion version = base.DetermineCurrentVersion(); - if (version != DatabaseVersion.Unavailable) - { - return version; - } - - // verify connection - try - { - using (var sqlHelper = SqlHelper) - if (SqlCeApplicationBlock.VerifyConnection(sqlHelper.ConnectionString)) - return DatabaseVersion.None; - } - catch (Exception e) - { - Trace.WriteLine(e.ToString()); - } - - return DatabaseVersion.Unavailable; - } - - #endregion - } -} diff --git a/src/SQLCE4Umbraco/SqlCEParameter.cs b/src/SQLCE4Umbraco/SqlCEParameter.cs deleted file mode 100644 index 9aa0b4c400..0000000000 --- a/src/SQLCE4Umbraco/SqlCEParameter.cs +++ /dev/null @@ -1,34 +0,0 @@ -/************************************************************************************ - * - * Umbraco Data Layer - * MIT Licensed work - * 2008 Ruben Verborgh - * - ***********************************************************************************/ - -using System; -using System.Data.SqlServerCe; -using System.Data.SqlTypes; -using umbraco.DataLayer; - -namespace SqlCE4Umbraco -{ - /// - /// Parameter class for the SqlCEHelper. - /// - public class SqlCEParameter : SqlParameterAdapter - { - #region Public Constructors - - /// - /// Initializes a new instance of the class. - /// - /// Name of the parameter. - /// Value of the parameter. - public SqlCEParameter(string parameterName, object value) - : base(new SqlCeParameter(parameterName, value)) - { } - - #endregion - } -} diff --git a/src/SQLCE4Umbraco/SqlCETableUtility.cs b/src/SQLCE4Umbraco/SqlCETableUtility.cs deleted file mode 100644 index cac6e0d784..0000000000 --- a/src/SQLCE4Umbraco/SqlCETableUtility.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using umbraco.DataLayer.Utility.Table; -using umbraco.DataLayer; -using umbraco; - -namespace SqlCE4Umbraco -{ - /// - /// SQL Server implementation of . - /// - [Obsolete("The legacy installers are no longer used and will be removed from the codebase in the future")] - public class SqlCETableUtility : DefaultTableUtility - { - /// - /// Initializes a new instance of the class. - /// - /// The SQL helper. - public SqlCETableUtility(SqlCEHelper sqlHelper) - : base(sqlHelper) - { } - - #region DefaultTableUtility members - - /// - /// Gets the table with the specified name. - /// - /// The name. - /// The table, or null if no table with that name exists. - public override ITable GetTable(string name) - { - if (name == null) - throw new ArgumentNullException("name"); - - ITable table = null; - - // get name in correct casing - using (var sqlHelper = SqlHelper) - name = sqlHelper.ExecuteScalar("SELECT name FROM sys.tables WHERE name=@name", - sqlHelper.CreateParameter("name", name)); - if (name != null) - { - table = new DefaultTable(name); - - using (var sqlHelper = SqlHelper) - using (IRecordsReader reader = sqlHelper.ExecuteReader( - @"SELECT c.name AS Name, st.name AS DataType, c.max_length, c.is_nullable, c.is_identity - FROM sys.tables AS t - JOIN sys.columns AS c ON t.object_id = c.object_id - JOIN sys.schemas AS s ON s.schema_id = t.schema_id - JOIN sys.types AS ty ON ty.user_type_id = c.user_type_id - JOIN sys.types st ON ty.system_type_id = st.user_type_id - WHERE t.name = @name - ORDER BY c.column_id", sqlHelper.CreateParameter("name", name))) - { - while (reader.Read()) - { - table.AddField(table.CreateField(reader.GetString("Name"), GetType(reader))); - } - } - } - - return table; - } - - /// - /// Saves or updates the table. - /// - /// The table to be saved. - public override void SaveTable(ITable table) - { - if (table == null) - throw new ArgumentNullException("table"); - - ITable oldTable = GetTable(table.Name); - - // create the table if it didn't exist, update fields otherwise - if (oldTable == null) - { - CreateTable(table); - } - else - { - foreach (IField field in table) - { - // create the field if it did't exist in the old table - if (oldTable.FindField(field.Name)==null) - { - CreateColumn(table, field); - } - } - } - } - - #endregion - - #region Protected Helper Methods - - /// - /// Creates the table in the data source. - /// - /// The table. - protected virtual void CreateTable(ITable table) - { - Debug.Assert(table != null); - - // create enumerator and check for first field - IEnumerator fieldEnumerator = table.GetEnumerator(); - bool hasNext = fieldEnumerator.MoveNext(); - if(!hasNext) - throw new ArgumentException("The table must contain at least one field."); - - // create query - StringBuilder createTableQuery = new StringBuilder(); - using (var sqlHelper = SqlHelper) - createTableQuery.AppendFormat("CREATE TABLE [{0}] (", sqlHelper.EscapeString(table.Name)); - - // add fields - while (hasNext) - { - // add field name and type - IField field = fieldEnumerator.Current; - createTableQuery.Append('[').Append(field.Name).Append(']'); - createTableQuery.Append(' ').Append(GetDatabaseType(field)); - - // append comma if a following field exists - hasNext = fieldEnumerator.MoveNext(); - if (hasNext) - { - createTableQuery.Append(','); - } - } - - // close CREATE TABLE x (...) bracket - createTableQuery.Append(')'); - - // execute query - try - { - using (var sqlHelper = SqlHelper) - sqlHelper.ExecuteNonQuery(createTableQuery.ToString()); - } - catch (Exception executeException) - { - throw new UmbracoException(String.Format("Could not create table '{0}'.", table), executeException); - } - } - - /// - /// Creates a new column in the table. - /// - /// The table. - /// The field used to create the column. - protected virtual void CreateColumn(ITable table, IField field) - { - Debug.Assert(table != null && field != null); - - StringBuilder addColumnQuery = new StringBuilder(); - using (var sqlHelper = SqlHelper) - addColumnQuery.AppendFormat("ALTER TABLE [{0}] ADD [{1}] {2}", - sqlHelper.EscapeString(table.Name), - sqlHelper.EscapeString(field.Name), - sqlHelper.EscapeString(GetDatabaseType(field))); - try - { - using (var sqlHelper = SqlHelper) - sqlHelper.ExecuteNonQuery(addColumnQuery.ToString()); - } - catch (Exception executeException) - { - throw new UmbracoException(String.Format("Could not create column '{0}' in table '{1}'.", - field, table.Name), executeException); - } - } - - /// - /// Gets the .Net type corresponding to the specified database data type. - /// - /// The data type reader. - /// The .Net type - protected virtual Type GetType(IRecordsReader dataTypeReader) - { - string typeName = dataTypeReader.GetString("DataType"); - - switch (typeName) - { - case "bit": return typeof(bool); - case "tinyint": return typeof(byte); - case "datetime": return typeof(DateTime); - // TODO: return different decimal type according to field precision - case "decimal": return typeof(decimal); - case "uniqueidentifier": return typeof(Guid); - case "smallint": return typeof(Int16); - case "int": return typeof(Int32); - case "bigint": return typeof(Int64); - case "nvarchar": return typeof(string); - default: - throw new ArgumentException(String.Format("Cannot convert database type '{0}' to a .Net type.", - typeName)); - } - } - - /// - /// Gets the database type corresponding to the field, complete with field properties. - /// - /// The field. - /// The database type. - protected virtual string GetDatabaseType(IField field) - { - StringBuilder fieldBuilder = new StringBuilder(); - - fieldBuilder.Append(GetDatabaseTypeName(field)); - fieldBuilder.Append(field.HasProperty(FieldProperties.Identity) ? " IDENTITY(1,1)" : String.Empty); - fieldBuilder.Append(field.HasProperty(FieldProperties.NotNull) ? " NOT NULL" : " NULL"); - - return fieldBuilder.ToString(); - } - - /// - /// Gets the name of the database type, without field properties. - /// - /// The field. - /// - protected virtual string GetDatabaseTypeName(IField field) - { - switch (field.DataType.FullName) - { - case "System.Boolean": return "bit"; - case "System.Byte": return "tinyint"; - case "System.DateTime": return "datetime"; - case "System.Decimal": return "decimal(28)"; - case "System.Double": return "decimal(15)"; - case "System.Single": return "decimal(7)"; - case "System.Guid": return "uniqueidentifier"; - case "System.Int16": return "smallint"; - case "System.Int32": return "int"; - case "System.Int64": return "bigint";; - case "System.String": - string type = "nvarchar"; - return field.Size == 0 ? type : String.Format("{0}({1})", type, field.Size); - default: - throw new ArgumentException(String.Format("Cannot convert '{0}' to a database type.", field)); - } - } - - #endregion - } -} diff --git a/src/SQLCE4Umbraco/SqlCEUtility.cs b/src/SQLCE4Umbraco/SqlCEUtility.cs deleted file mode 100644 index ff6905df10..0000000000 --- a/src/SQLCE4Umbraco/SqlCEUtility.cs +++ /dev/null @@ -1,56 +0,0 @@ -/************************************************************************************ - * - * Umbraco Data Layer - * MIT Licensed work - * 2008 Ruben Verborgh - * - ***********************************************************************************/ - -using System; -using umbraco.DataLayer.SqlHelpers.SqlServer; -using umbraco.DataLayer.Utility; -using umbraco.DataLayer.Utility.Installer; -using umbraco.DataLayer.Utility.Table; - -namespace SqlCE4Umbraco -{ - /// - /// Utility for an SQL Server data source. - /// - [Obsolete("The legacy installers are no longer used and will be removed from the codebase in the future")] - public class SqlCEUtility : DefaultUtility - { - #region Public Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The SQL helper. - public SqlCEUtility(SqlCEHelper sqlHelper) : base(sqlHelper) - { } - - #endregion - - #region DefaultUtility Members - - /// - /// Creates an installer. - /// - /// The SQL Server installer. - public override IInstallerUtility CreateInstaller() - { - return new SqlCEInstaller(SqlHelper); - } - - /// - /// Creates a table utility. - /// - /// The table utility - public override ITableUtility CreateTableUtility() - { - return new SqlCETableUtility(SqlHelper); - } - - #endregion - } -} diff --git a/src/SQLCE4Umbraco/SqlCeApplicationBlock.cs b/src/SQLCE4Umbraco/SqlCeApplicationBlock.cs deleted file mode 100644 index 2dd0f26e90..0000000000 --- a/src/SQLCE4Umbraco/SqlCeApplicationBlock.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlServerCe; -using System.Data; -using System.Diagnostics; -using SQLCE4Umbraco; - -namespace SqlCE4Umbraco -{ - public class SqlCeApplicationBlock - { - /// - /// - /// - /// - /// - /// - /// - /// - public static object ExecuteScalar( - string connectionString, - CommandType commandType, - string commandText, - params SqlCeParameter[] commandParameters - ) - { - try - { - using (var conn = SqlCeContextGuardian.Open(connectionString)) - { - return ExecuteScalarTry(conn, null, commandText, commandParameters); - } - } - catch (Exception ee) - { - throw new SqlCeProviderException("Error running Scalar: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee); - } - } - - public static object ExecuteScalar( - SqlCeConnection conn, SqlCeTransaction trx, - CommandType commandType, - string commandText, - params SqlCeParameter[] commandParameters) - { - try - { - return ExecuteScalarTry(conn, trx, commandText, commandParameters); - } - catch (Exception ee) - { - throw new SqlCeProviderException("Error running Scalar: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee); - } - } - - public static object ExecuteScalar( - SqlCeConnection conn, - CommandType commandType, - string commandText, - params SqlCeParameter[] commandParameters) - { - return ExecuteScalar(conn, null, commandType, commandText, commandParameters); - } - - private static object ExecuteScalarTry( - SqlCeConnection conn, SqlCeTransaction trx, - string commandText, - params SqlCeParameter[] commandParameters) - { - object retVal; - using (var cmd = trx == null ? new SqlCeCommand(commandText, conn) : new SqlCeCommand(commandText, conn, trx)) - { - AttachParameters(cmd, commandParameters); - Debug.WriteLine("---------------------------------SCALAR-------------------------------------"); - Debug.WriteLine(commandText); - Debug.WriteLine("----------------------------------------------------------------------------"); - retVal = cmd.ExecuteScalar(); - } - return retVal; - } - - /// - /// - /// - /// - /// - /// - /// - public static int ExecuteNonQuery( - string connectionString, - CommandType commandType, - string commandText, - params SqlCeParameter[] commandParameters - ) - { - try - { - using (var conn = SqlCeContextGuardian.Open(connectionString)) - { - return ExecuteNonQueryTry(conn, null, commandText, commandParameters); - } - } - catch (Exception ee) - { - throw new SqlCeProviderException("Error running NonQuery: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString()); - } - } - - public static int ExecuteNonQuery( - SqlCeConnection conn, - CommandType commandType, - string commandText, - params SqlCeParameter[] commandParameters - ) - { - return ExecuteNonQuery(conn, null, commandType, commandText, commandParameters); - } - - public static int ExecuteNonQuery( - SqlCeConnection conn, SqlCeTransaction trx, - CommandType commandType, - string commandText, - params SqlCeParameter[] commandParameters - ) - { - try - { - return ExecuteNonQueryTry(conn, trx, commandText, commandParameters); - } - catch (Exception ee) - { - throw new SqlCeProviderException("Error running NonQuery: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString()); - } - } - - private static int ExecuteNonQueryTry( - SqlCeConnection conn, SqlCeTransaction trx, - string commandText, - params SqlCeParameter[] commandParameters) - { - // this is for multiple queries in the installer - if (commandText.Trim().StartsWith("!!!")) - { - commandText = commandText.Trim().Trim('!'); - var commands = commandText.Split('|'); - var currentCmd = string.Empty; - - foreach (var command in commands) - { - try - { - currentCmd = command; - if (string.IsNullOrWhiteSpace(command)) continue; - var c = trx == null ? new SqlCeCommand(command, conn) : new SqlCeCommand(command, conn, trx); - c.ExecuteNonQuery(); - } - catch (Exception e) - { - Debug.WriteLine("*******************************************************************"); - Debug.WriteLine(currentCmd); - Debug.WriteLine(e); - Debug.WriteLine("*******************************************************************"); - } - } - return 1; - } - - Debug.WriteLine("----------------------------------------------------------------------------"); - Debug.WriteLine(commandText); - Debug.WriteLine("----------------------------------------------------------------------------"); - var cmd = new SqlCeCommand(commandText, conn); - AttachParameters(cmd, commandParameters); - var rowsAffected = cmd.ExecuteNonQuery(); - return rowsAffected; - } - - /// - /// - /// - /// - /// - /// - /// - /// - public static SqlCeDataReader ExecuteReader( - string connectionString, - CommandType commandType, - string commandText, - params SqlCeParameter[] commandParameters - ) - { - try - { - var conn = SqlCeContextGuardian.Open(connectionString); - return ExecuteReaderTry(conn, null, commandText, commandParameters); - } - catch (Exception ee) - { - throw new SqlCeProviderException("Error running Reader: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString()); - } - } - - public static SqlCeDataReader ExecuteReader( - SqlCeConnection conn, - CommandType commandType, - string commandText, - params SqlCeParameter[] commandParameters - ) - { - return ExecuteReader(conn, commandType, commandText, commandParameters); - } - - public static SqlCeDataReader ExecuteReader( - SqlCeConnection conn, SqlCeTransaction trx, - CommandType commandType, - string commandText, - params SqlCeParameter[] commandParameters - ) - { - try - { - return ExecuteReaderTry(conn, trx, commandText, commandParameters); - } - catch (Exception ee) - { - throw new SqlCeProviderException("Error running Reader: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString()); - } - } - - private static SqlCeDataReader ExecuteReaderTry( - SqlCeConnection conn, SqlCeTransaction trx, - string commandText, - params SqlCeParameter[] commandParameters) - { - Debug.WriteLine("---------------------------------READER-------------------------------------"); - Debug.WriteLine(commandText); - Debug.WriteLine("----------------------------------------------------------------------------"); - - try - { - var cmd = trx == null ? new SqlCeCommand(commandText, conn) : new SqlCeCommand(commandText, conn, trx); - AttachParameters(cmd, commandParameters); - return cmd.ExecuteReader(); - } - catch - { - conn.Close(); - throw; - } - } - - public static bool VerifyConnection(string connectionString) - { - using (var conn = SqlCeContextGuardian.Open(connectionString)) - { - return conn.State == ConnectionState.Open; - } - } - - private static void AttachParameters(SqlCeCommand command, IEnumerable commandParameters) - { - foreach (var parameter in commandParameters) - { - if ((parameter.Direction == ParameterDirection.InputOutput) && (parameter.Value == null)) - parameter.Value = DBNull.Value; - command.Parameters.Add(parameter); - } - } - } -} diff --git a/src/SQLCE4Umbraco/SqlCeContextGuardian.cs b/src/SQLCE4Umbraco/SqlCeContextGuardian.cs deleted file mode 100644 index 8206ac6c53..0000000000 --- a/src/SQLCE4Umbraco/SqlCeContextGuardian.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Data.SqlServerCe; -using System.Linq; -using System.Text; - -namespace SQLCE4Umbraco -{ - public static class SqlCeContextGuardian - { - private static SqlCeConnection _constantOpenConnection; - private static readonly object Lock = new object(); - - // Awesome SQL CE 4 speed improvement by Erik Ejskov Jensen - SQL CE 4 MVP - // It's not an issue with SQL CE 4 that we never close the connection - public static SqlCeConnection Open(string connectionString) - { - var connectionStringBuilder = new DbConnectionStringBuilder(); - try - { - connectionStringBuilder.ConnectionString = connectionString; - } - catch (Exception ex) - { - throw new ArgumentException("Bad connection string.", "connectionString", ex); - } - connectionStringBuilder.Remove("datalayer"); - - // SQL CE 4 performs better when there's always a connection open in the background - EnsureOpenBackgroundConnection(connectionStringBuilder.ConnectionString); - - SqlCeConnection conn = new SqlCeConnection(connectionStringBuilder.ConnectionString); - conn.Open(); - - return conn; - - } - - /// - /// Sometimes we need to ensure this is closed especially in unit tests - /// - internal static void CloseBackgroundConnection() - { - if (_constantOpenConnection != null) - _constantOpenConnection.Close(); - } - - private static void EnsureOpenBackgroundConnection(string connectionString) - { - lock (Lock) - { - if (_constantOpenConnection == null) - { - _constantOpenConnection = new SqlCeConnection(connectionString); - _constantOpenConnection.Open(); - } - else if (_constantOpenConnection.State != ConnectionState.Open) - _constantOpenConnection.Open(); - } - } - } -} diff --git a/src/SQLCE4Umbraco/SqlCeProviderException.cs b/src/SQLCE4Umbraco/SqlCeProviderException.cs deleted file mode 100644 index e7c105fd4d..0000000000 --- a/src/SQLCE4Umbraco/SqlCeProviderException.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace SqlCE4Umbraco -{ - public class SqlCeProviderException : Exception - { - public SqlCeProviderException() : base() - { - - } - - public SqlCeProviderException(string message) : base(message) - { - - } - } -} diff --git a/src/SQLCE4Umbraco/app.config b/src/SQLCE4Umbraco/app.config deleted file mode 100644 index 95a7e73c9e..0000000000 --- a/src/SQLCE4Umbraco/app.config +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/SQLCE4Umbraco/packages.config b/src/SQLCE4Umbraco/packages.config deleted file mode 100644 index e2435f3e8b..0000000000 --- a/src/SQLCE4Umbraco/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 77adcb9c8e..02fb9034b7 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -6,10 +6,15 @@ using System.Resources; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguageAttribute("en-US")] +[assembly: NeutralResourcesLanguage("en-US")] +// versions +// read https://stackoverflow.com/questions/64602/what-are-differences-between-assemblyversion-assemblyfileversion-and-assemblyin -[assembly: AssemblyVersion("1.0.*")] +// this is the ONLY ONE the CLR cares about for compatibility +// should change ONLY when "hard" breaking compatibility (manual change) +[assembly: AssemblyVersion("8.0.0")] -[assembly: AssemblyFileVersion("7.8.0")] -[assembly: AssemblyInformationalVersion("7.8.0")] \ No newline at end of file +// these are FYI and changed automatically +[assembly: AssemblyFileVersion("8.0.0")] +[assembly: AssemblyInformationalVersion("8.0.0-alpha.32")] diff --git a/src/Umbraco.Core/ActionsResolver.cs b/src/Umbraco.Core/ActionsResolver.cs deleted file mode 100644 index ff2eaa8553..0000000000 --- a/src/Umbraco.Core/ActionsResolver.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using umbraco.interfaces; - -namespace Umbraco.Core -{ - /// - /// A resolver to return all IAction objects - /// - public sealed class ActionsResolver : LazyManyObjectsResolverBase - { - /// - /// Constructor - /// - /// - /// - /// - internal ActionsResolver(IServiceProvider serviceProvider, ILogger logger, Func> packageActions) - : base(serviceProvider, logger, packageActions) - { - - } - - /// - /// Gets the implementations. - /// - public IEnumerable Actions - { - get - { - return Values; - } - } - - /// - /// This method will return a list of IAction's based on a string (letter) list. Each character in the list may represent - /// an IAction. This will associate any found IActions based on the Letter property of the IAction with the character being referenced. - /// - /// - /// returns a list of actions that have an associated letter found in the action string list - public IEnumerable FromActionSymbols(IEnumerable actions) - { - var allActions = Actions.ToArray(); - return actions - .Select(c => allActions.FirstOrDefault(a => a.Letter.ToString(CultureInfo.InvariantCulture) == c)) - .WhereNotNull() - .ToArray(); - } - - /// - /// Returns the string (letter) representation of the actions that make up the actions collection - /// - /// - public IEnumerable ToActionSymbols(IEnumerable actions) - { - return actions.Select(x => x.Letter.ToString(CultureInfo.InvariantCulture)).ToArray(); - } - - /// - /// Gets an Action if it exists. - /// - /// - /// - internal IAction GetAction() - where T : IAction - { - return Actions.SingleOrDefault(x => x.GetType() == typeof (T)); - } - - protected override IEnumerable CreateInstances() - { - var actions = new List(); - var foundIActions = InstanceTypes; - foreach (var type in foundIActions) - { - IAction typeInstance; - var instance = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static); - //if the singletone initializer is not found, try simply creating an instance of the IAction if it supports public constructors - if (instance == null) - typeInstance = ServiceProvider.GetService(type) as IAction; - else - typeInstance = instance.GetValue(null, null) as IAction; - - if (typeInstance != null) - { - actions.Add(typeInstance); - } - } - return actions; - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ActivatorHelper.cs b/src/Umbraco.Core/ActivatorHelper.cs deleted file mode 100644 index 90a21f7386..0000000000 --- a/src/Umbraco.Core/ActivatorHelper.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Umbraco.Core -{ - /// - /// Helper methods for Activation - /// - internal static class ActivatorHelper - { - /// - /// Creates an instance of a type using that type's default constructor. - /// - /// - /// - public static T CreateInstance() where T : class, new() - { - return new ActivatorServiceProvider().GetService(typeof (T)) as T; - } - - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ActivatorServiceProvider.cs b/src/Umbraco.Core/ActivatorServiceProvider.cs deleted file mode 100644 index 550a4a3ca8..0000000000 --- a/src/Umbraco.Core/ActivatorServiceProvider.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Umbraco.Core -{ - internal class ActivatorServiceProvider : IServiceProvider - { - public object GetService(Type serviceType) - { - return Activator.CreateInstance(serviceType); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs deleted file mode 100644 index 82ef562bc1..0000000000 --- a/src/Umbraco.Core/ApplicationContext.cs +++ /dev/null @@ -1,506 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Threading; -using Semver; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Profiling; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using Umbraco.Core.Sync; - -namespace Umbraco.Core -{ - /// - /// the Umbraco Application context - /// - /// - /// one per AppDomain, represents the global Umbraco application - /// - public class ApplicationContext : IDisposable - { - /// - /// Constructor - /// - /// - /// - /// - /// - public ApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, ProfilingLogger logger) - { - if (dbContext == null) throw new ArgumentNullException("dbContext"); - if (serviceContext == null) throw new ArgumentNullException("serviceContext"); - if (cache == null) throw new ArgumentNullException("cache"); - if (logger == null) throw new ArgumentNullException("logger"); - _databaseContext = dbContext; - _services = serviceContext; - ApplicationCache = cache; - ProfilingLogger = logger; - - Init(); - } - - /// - /// Constructor - /// - /// - /// - /// - [Obsolete("Use the other constructor specifying a ProfilingLogger instead")] - public ApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache) - : this(dbContext, serviceContext, cache, - new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler)) - { - } - - /// - /// Creates a basic app context - /// - /// - [Obsolete("Use the other constructor specifying a ProfilingLogger instead")] - public ApplicationContext(CacheHelper cache) - { - if (cache == null) throw new ArgumentNullException("cache"); - ApplicationCache = cache; - ProfilingLogger = new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler); - Init(); - } - - /// - /// Creates a basic app context - /// - /// - /// - public ApplicationContext(CacheHelper cache, ProfilingLogger logger) - { - if (cache == null) throw new ArgumentNullException("cache"); - if (logger == null) throw new ArgumentNullException("logger"); - ApplicationCache = cache; - ProfilingLogger = logger; - Init(); - } - - /// - /// A method used to set and/or ensure that a global ApplicationContext singleton is created. - /// - /// - /// The instance to set on the global application singleton - /// - /// If set to true and the singleton is already set, it will be replaced - /// - /// - /// This is NOT thread safe - /// - public static ApplicationContext EnsureContext(ApplicationContext appContext, bool replaceContext) - { - if (Current != null) - { - if (!replaceContext) - return Current; - } - Current = appContext; - return Current; - } - - /// - /// A method used to create and ensure that a global ApplicationContext singleton is created. - /// - /// - /// - /// If set to true will replace the current singleton instance - This should only be used for unit tests or on app - /// startup if for some reason the boot manager is not the umbraco boot manager. - /// - /// - /// - /// - /// - /// This is NOT thread safe - /// - [Obsolete("Use the other method specifying an ProfilingLogger instead")] - public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, bool replaceContext) - { - if (Current != null) - { - if (!replaceContext) - return Current; - } - var ctx = new ApplicationContext(dbContext, serviceContext, cache); - Current = ctx; - return Current; - } - - /// - /// A method used to create and ensure that a global ApplicationContext singleton is created. - /// - /// - /// - /// - /// If set to true will replace the current singleton instance - This should only be used for unit tests or on app - /// startup if for some reason the boot manager is not the umbraco boot manager. - /// - /// - /// - /// - /// - /// This is NOT thread safe - /// - public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, ProfilingLogger logger, bool replaceContext) - { - if (Current != null) - { - if (!replaceContext) - return Current; - } - var ctx = new ApplicationContext(dbContext, serviceContext, cache, logger); - Current = ctx; - return Current; - } - - /// - /// Singleton accessor - /// - public static ApplicationContext Current { get; internal set; } - - /// - /// Gets the scope provider. - /// - internal IScopeProvider ScopeProvider { get { return _databaseContext == null ? null : _databaseContext.ScopeProvider; } } - - /// - /// Returns the application wide cache accessor - /// - /// - /// Any caching that is done in the application (app wide) should be done through this property - /// - public CacheHelper ApplicationCache { get; private set; } - - /// - /// Exposes the global ProfilingLogger - this should generally not be accessed via the UmbracoContext and should normally just be exposed - /// on most base classes or injected with IoC - /// - public ProfilingLogger ProfilingLogger { get; private set; } - - // IsReady is set to true by the boot manager once it has successfully booted - // note - the original umbraco module checks on content.Instance in umbraco.dll - // now, the boot task that setup the content store ensures that it is ready - bool _isReady = false; - readonly ManualResetEventSlim _isReadyEvent = new ManualResetEventSlim(false); - private DatabaseContext _databaseContext; - private ServiceContext _services; - - public bool IsReady - { - get - { - return _isReady; - } - internal set - { - AssertIsNotReady(); - _isReady = value; - _isReadyEvent.Set(); - } - } - - public bool WaitForReady(int timeout) - { - return _isReadyEvent.WaitHandle.WaitOne(timeout); - } - - - // notes - // GlobalSettings.ConfigurationStatus returns the value that's in the web.config, so it's the "configured version" - // GlobalSettings.CurrentVersion returns the hard-coded "current version" - // the system is configured if they match - // if they don't, install runs, updates web.config (presumably) and updates GlobalSettings.ConfiguredStatus - - public bool IsConfigured - { - get { return _configured.Value; } - } - - /// - /// If the db is configured, there is a database context and there is an umbraco schema, but we are not 'configured' , then it means we are upgrading - /// - public bool IsUpgrading - { - get - { - if (IsConfigured == false - && DatabaseContext != null - && DatabaseContext.IsDatabaseConfigured) - { - var schemaresult = DatabaseContext.ValidateDatabaseSchema(); - if (schemaresult.ValidTables.Count > 0) return true; - } - - return false; - } - } - - /// - /// The application url. - /// - /// - /// The application url is the url that should be used by services to talk to the application, - /// eg keep alive or scheduled publishing services. It must exist on a global context because - /// some of these services may not run within a web context. - /// The format of the application url is: - /// - has a scheme (http or https) - /// - has the SystemDirectories.Umbraco path - /// - does not end with a slash - /// It is initialized on the first request made to the server, by UmbracoModule.EnsureApplicationUrl: - /// - if umbracoSettings:settings/web.routing/@umbracoApplicationUrl is set, use the value (new setting) - /// - if umbracoSettings:settings/scheduledTasks/@baseUrl is set, use the value (backward compatibility) - /// - otherwise, use the url of the (first) request. - /// Not locking, does not matter if several threads write to this. - /// See also issues: - /// - http://issues.umbraco.org/issue/U4-2059 - /// - http://issues.umbraco.org/issue/U4-6788 - /// - http://issues.umbraco.org/issue/U4-5728 - /// - http://issues.umbraco.org/issue/U4-5391 - /// - public string UmbracoApplicationUrl - { - get - { - ApplicationUrlHelper.EnsureApplicationUrl(this); - return _umbracoApplicationUrl; - } - } - - /// - /// Resets the url. - /// - public void ResetUmbracoApplicationUrl() - { - _umbracoApplicationUrl = null; - } - - // ReSharper disable once InconsistentNaming - internal string _umbracoApplicationUrl; - - internal List _umbracoApplicationDomains = new List(); - - internal string _umbracoApplicationDeploymentId; - - private Lazy _configured; - internal MainDom MainDom { get; private set; } - - private void Init() - { - MainDom = new MainDom(ProfilingLogger.Logger); - MainDom.Acquire(); - - //Create the lazy value to resolve whether or not the application is 'configured' - _configured = new Lazy(() => - { - try - { - var configStatus = ConfigurationStatus; - var currentVersion = UmbracoVersion.GetSemanticVersion(); - - var ok = - //we are not configured if this is null - string.IsNullOrWhiteSpace(configStatus) == false - //they must match - && configStatus == currentVersion; - - if (ok) - { - //The versions are the same in config, but are they the same in the database. We can only check this - // if we have a db context available, if we don't then we are not installed anyways - if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.CanConnect) - { - var found = Services.MigrationEntryService.FindEntry(Constants.System.UmbracoMigrationName, UmbracoVersion.GetSemanticVersion()); - if (found == null) - { - //we haven't executed this migration in this environment, so even though the config versions match, - // this db has not been updated. - ProfilingLogger.Logger.Debug(string.Format("The migration for version: '{0} has not been executed, there is no record in the database", currentVersion.ToSemanticString())); - ok = false; - } - } - } - else - { - ProfilingLogger.Logger.Debug(string.Format("CurrentVersion different from configStatus: '{0}','{1}'", currentVersion.ToSemanticString(), configStatus)); - } - - return ok; - } - catch (Exception ex) - { - LogHelper.Error("Error determining if application is configured, returning false", ex); - return false; - } - - }); - } - - private string ConfigurationStatus - { - get - { - try - { - return ConfigurationManager.AppSettings["umbracoConfigurationStatus"]; - } - catch - { - return String.Empty; - } - } - } - - /// - /// Gets the Current Version of the Umbraco Site before an upgrade - /// by using the last/most recent Umbraco Migration that has been run - /// - /// A SemVersion of the latest Umbraco DB Migration run - /// - /// NOTE: This existed in the InstallHelper previously but should really be here so it can be re-used if necessary - /// - internal SemVersion CurrentVersion() - { - //Set a default version of 0.0.0 - var version = new SemVersion(0); - - //If we have a db context available, if we don't then we are not installed anyways - if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.CanConnect) - version = DatabaseContext.ValidateDatabaseSchema().DetermineInstalledVersionByMigrations(Services.MigrationEntryService); - - if (version != new SemVersion(0)) - return version; - - // If we aren't able to get a result from the umbracoMigrations table then use the version in web.config, if it's available - if (string.IsNullOrWhiteSpace(GlobalSettings.ConfigurationStatus)) - return version; - - var configuredVersion = GlobalSettings.ConfigurationStatus; - - string currentComment = null; - - var current = configuredVersion.Split('-'); - if (current.Length > 1) - currentComment = current[1]; - - Version currentVersion; - if (Version.TryParse(current[0], out currentVersion)) - { - version = new SemVersion( - currentVersion.Major, - currentVersion.Minor, - currentVersion.Build, - string.IsNullOrWhiteSpace(currentComment) ? null : currentComment, - currentVersion.Revision > 0 ? currentVersion.Revision.ToString() : null); - } - - return version; - } - - private void AssertIsNotReady() - { - if (this.IsReady) - throw new Exception("ApplicationContext has already been initialized."); - } - - /// - /// Gets the current DatabaseContext - /// - /// - /// Internal set is generally only used for unit tests - /// - public DatabaseContext DatabaseContext - { - get - { - if (_databaseContext == null) - throw new InvalidOperationException("The DatabaseContext has not been set on the ApplicationContext"); - return _databaseContext; - } - internal set { _databaseContext = value; } - } - - /// - /// Gets the current ServiceContext - /// - /// - /// Internal set is generally only used for unit tests - /// - public ServiceContext Services - { - get - { - if (_services == null) - throw new InvalidOperationException("The ServiceContext has not been set on the ApplicationContext"); - return _services; - } - internal set { _services = value; } - } - - internal ServerRole GetCurrentServerRole() - { - var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2; - return registrar == null ? ServerRole.Unknown : registrar.GetCurrentServerRole(); - } - - private volatile bool _disposed; - private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim(); - - /// - /// This will dispose and reset all resources used to run the application - /// - /// - /// IMPORTANT: Never dispose this object if you require the Umbraco application to run, disposing this object - /// is generally used for unit testing and when your application is shutting down after you have booted Umbraco. - /// - void IDisposable.Dispose() - { - // Only operate if we haven't already disposed - if (_disposed) return; - - using (new WriteLock(_disposalLocker)) - { - // Check again now we're inside the lock - if (_disposed) return; - - //clear the cache - if (ApplicationCache != null) - { - ApplicationCache.RuntimeCache.ClearAllCache(); - ApplicationCache.IsolatedRuntimeCache.ClearAllCaches(); - } - //reset all resolvers - ResolverCollection.ResetAll(); - //reset resolution itself (though this should be taken care of by resetting any of the resolvers above) - Resolution.Reset(); - - //reset the instance objects - this.ApplicationCache = null; - if (_databaseContext != null) //need to check the internal field here - { - if (_databaseContext.ScopeProvider.AmbientScope != null) - { - var scope = _databaseContext.ScopeProvider.AmbientScope; - scope.Dispose(); - } - /* - if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.Database != null) - { - DatabaseContext.Database.Dispose(); - } - */ - } - this.DatabaseContext = null; - this.Services = null; - this._isReady = false; //set the internal field - - // Indicate that the instance has been disposed. - _disposed = true; - } - } - } -} diff --git a/src/Umbraco.Core/ApplicationEventHandler.cs b/src/Umbraco.Core/ApplicationEventHandler.cs deleted file mode 100644 index 8d97baac95..0000000000 --- a/src/Umbraco.Core/ApplicationEventHandler.cs +++ /dev/null @@ -1,119 +0,0 @@ -namespace Umbraco.Core -{ - /// - /// A plugin type that allows developers to execute code during the Umbraco bootup process - /// - /// - /// Allows you to override the methods that you would like to execute code for: ApplicationInitialized, ApplicationStarting, ApplicationStarted. - /// - /// By default none of these methods will execute if the Umbraco application is not configured or if the Umbraco database is not configured, however - /// if you need these methods to execute even if either of these are not configured you can override the properties: - /// ExecuteWhenApplicationNotConfigured and ExecuteWhenDatabaseNotConfigured - /// - public abstract class ApplicationEventHandler : IApplicationEventHandler - { - public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - if (ShouldExecute(applicationContext)) - { - ApplicationInitialized(umbracoApplication, applicationContext); - } - } - - public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - if (ShouldExecute(applicationContext)) - { - ApplicationStarting(umbracoApplication, applicationContext); - } - } - - public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - if (ShouldExecute(applicationContext)) - { - ApplicationStarted(umbracoApplication, applicationContext); - } - } - - /// - /// Overridable method to execute when the ApplicationContext is created and other static objects that require initialization have been setup - /// - /// - /// - protected virtual void ApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - - } - - /// - /// Overridable method to execute when All resolvers have been initialized but resolution is not frozen so they can be modified in this method - /// - /// - /// - protected virtual void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - - } - - /// - /// Overridable method to execute when Bootup is completed, this allows you to perform any other bootup logic required for the application. - /// Resolution is frozen so now they can be used to resolve instances. - /// - /// - /// - protected virtual void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - - } - - /// - /// Determine if the methods should execute based on the configuration status of the application. - /// - /// - /// - private bool ShouldExecute(ApplicationContext applicationContext) - { - if (applicationContext.IsConfigured && applicationContext.DatabaseContext.IsDatabaseConfigured) - { - return true; - } - - if (!applicationContext.IsConfigured && ExecuteWhenApplicationNotConfigured) - { - return true; - } - - if (!applicationContext.DatabaseContext.IsDatabaseConfigured && ExecuteWhenDatabaseNotConfigured) - { - return true; - } - return false; - } - - /// - /// A flag to determine if the overridable methods for this class will execute even if the - /// Umbraco application is not configured - /// - /// - /// An Umbraco Application is not configured when it requires a new install or upgrade. When the latest version in the - /// assembly does not match the version in the config. - /// - protected virtual bool ExecuteWhenApplicationNotConfigured - { - get { return false; } - } - - /// - /// A flag to determine if the overridable methods for this class will execute even if the - /// Umbraco database is not configured - /// - /// - /// The Umbraco database is not configured when we cannot connect to the database or when the database tables are not installed. - /// - protected virtual bool ExecuteWhenDatabaseNotConfigured - { - get { return false; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/AssemblyExtensions.cs b/src/Umbraco.Core/AssemblyExtensions.cs index b9e1f4d812..6b77a09b73 100644 --- a/src/Umbraco.Core/AssemblyExtensions.cs +++ b/src/Umbraco.Core/AssemblyExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -6,20 +6,20 @@ using System.Reflection; namespace Umbraco.Core { - internal static class AssemblyExtensions - { - /// - /// Returns the file used to load the assembly - /// - /// - /// - public static FileInfo GetAssemblyFile(this Assembly assembly) - { - var codeBase = assembly.CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - return new FileInfo(path); - } + internal static class AssemblyExtensions + { + /// + /// Returns the file used to load the assembly + /// + /// + /// + public static FileInfo GetAssemblyFile(this Assembly assembly) + { + var codeBase = assembly.CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + return new FileInfo(path); + } /// /// Returns true if the assembly is the App_Code assembly @@ -49,24 +49,24 @@ namespace Umbraco.Core /// /// /// - public static bool IsGlobalAsaxAssembly(this Assembly assembly) - { + public static bool IsGlobalAsaxAssembly(this Assembly assembly) + { //only way I can figure out how to test is by the name return assembly.FullName.StartsWith("App_global.asax"); - } + } - /// - /// Returns the file used to load the assembly - /// - /// - /// - public static FileInfo GetAssemblyFile(this AssemblyName assemblyName) - { - var codeBase = assemblyName.CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - return new FileInfo(path); - } + /// + /// Returns the file used to load the assembly + /// + /// + /// + public static FileInfo GetAssemblyFile(this AssemblyName assemblyName) + { + var codeBase = assemblyName.CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + return new FileInfo(path); + } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs index b6a3f8534e..158b132f26 100644 --- a/src/Umbraco.Core/AsyncLock.cs +++ b/src/Umbraco.Core/AsyncLock.cs @@ -69,11 +69,11 @@ namespace Umbraco.Core public Task LockAsync() { - var wait = _semaphore != null + var wait = _semaphore != null ? _semaphore.WaitAsync() : _semaphore2.WaitOneAsync(); - return wait.IsCompleted + return wait.IsCompleted ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()), this, CancellationToken.None, @@ -112,7 +112,7 @@ namespace Umbraco.Core return _releaser ?? CreateReleaser(); // anonymous vs named } - // note - before making those classes some structs, read + // note - before making those classes some structs, read // about "impure methods" and mutating readonly structs... private class NamedSemaphoreReleaser : CriticalFinalizerObject, IDisposable @@ -140,9 +140,9 @@ namespace Umbraco.Core _semaphore.Dispose(); } - // we WANT to release the semaphore because it's a system object, ie a critical + // we WANT to release the semaphore because it's a system object, ie a critical // non-managed resource - and if it is not released then noone else can acquire - // the lock - so we inherit from CriticalFinalizerObject which means that the + // the lock - so we inherit from CriticalFinalizerObject which means that the // finalizer "should" run in all situations - there is always a chance that it // does not run and the semaphore remains "acquired" but then chances are the // whole process (w3wp.exe...) is going down, at which point the semaphore will @@ -200,4 +200,4 @@ namespace Umbraco.Core } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Attempt.cs b/src/Umbraco.Core/Attempt.cs index ca2bbe35fe..f1b6d2e636 100644 --- a/src/Umbraco.Core/Attempt.cs +++ b/src/Umbraco.Core/Attempt.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Core { @@ -7,160 +7,120 @@ namespace Umbraco.Core /// public static class Attempt { + // note: + // cannot rely on overloads only to differenciate between with/without status + // in some cases it will always be ambiguous, so be explicit w/ 'WithStatus' methods + /// /// Creates a successful attempt with a result. /// - /// The type of the attempted operation result. + /// The type of the attempted operation result. /// The result of the attempt. /// The successful attempt. - public static Attempt Succeed(T result) + public static Attempt Succeed(TResult result) { - return Attempt.Succeed(result); + return Attempt.Succeed(result); + } + + /// + /// Creates a successful attempt with a result and a status. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// The status of the attempt. + /// The result of the attempt. + /// The successful attempt. + public static Attempt SucceedWithStatus(TStatus status, TResult result) + { + return Attempt.Succeed(status, result); + } + + /// + /// Creates a failed attempt. + /// + /// The type of the attempted operation result. + /// The failed attempt. + public static Attempt Fail() + { + return Attempt.Fail(); } /// /// Creates a failed attempt with a result. /// - /// The type of the attempted operation result. + /// The type of the attempted operation result. /// The result of the attempt. /// The failed attempt. - public static Attempt Fail(T result) + public static Attempt Fail(TResult result) { - return Attempt.Fail(result); + return Attempt.Fail(result); + } + + /// + /// Creates a failed attempt with a result and a status. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// The status of the attempt. + /// The result of the attempt. + /// The failed attempt. + public static Attempt FailWithStatus(TStatus status, TResult result) + { + return Attempt.Fail(status, result); } /// /// Creates a failed attempt with a result and an exception. /// - /// The type of the attempted operation result. + /// The type of the attempted operation result. /// The result of the attempt. /// The exception causing the failure of the attempt. /// The failed attempt. - public static Attempt Fail(T result, Exception exception) + public static Attempt Fail(TResult result, Exception exception) { - return Attempt.Fail(result, exception); + return Attempt.Fail(result, exception); + } + + + /// + /// Creates a failed attempt with a result, an exception and a status. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// The status of the attempt. + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt FailWithStatus(TStatus status, TResult result, Exception exception) + { + return Attempt.Fail(status, result, exception); } /// /// Creates a successful or a failed attempt, with a result. /// - /// The type of the attempted operation result. - /// A value indicating whether the attempt is successful. + /// The type of the attempted operation result. + /// A value indicating whether the attempt is successful. /// The result of the attempt. /// The attempt. - public static Attempt If(bool success, T result) + public static Attempt If(bool condition, TResult result) { - return Attempt.SucceedIf(success, result); - } - - - /// - /// Executes an attempt function, with callbacks. - /// - /// The type of the attempted operation result. - /// The attempt returned by the attempt function. - /// An action to execute in case the attempt succeeds. - /// An action to execute in case the attempt fails. - /// The outcome of the attempt. - /// Runs or depending on the - /// whether the attempt function reports a success or a failure. - public static Outcome Try(Attempt attempt, Action onSuccess, Action onFail = null) - { - if (attempt.Success) - { - onSuccess(attempt.Result); - return Outcome.Success; - } - - if (onFail != null) - { - onFail(attempt.Exception); - } - - return Outcome.Failure; + return Attempt.If(condition, result); } /// - /// Represents the outcome of an attempt. + /// Creates a successful or a failed attempt, with a result. /// - /// Can be a success or a failure, and allows for attempts chaining. - public struct Outcome + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// A value indicating whether the attempt is successful. + /// The status of the successful attempt. + /// The status of the failed attempt. + /// The result of the attempt. + /// The attempt. + public static Attempt IfWithStatus(bool condition, TStatus succStatus, TStatus failStatus, TResult result) { - private readonly bool _success; - - /// - /// Gets an outcome representing a success. - /// - public static readonly Outcome Success = new Outcome(true); - - /// - /// Gets an outcome representing a failure. - /// - public static readonly Outcome Failure = new Outcome(false); - - private Outcome(bool success) - { - _success = success; - } - - /// - /// Executes another attempt function, if the previous one failed, with callbacks. - /// - /// The type of the attempted operation result. - /// The attempt function to execute, returning an attempt. - /// An action to execute in case the attempt succeeds. - /// An action to execute in case the attempt fails. - /// If it executes, returns the outcome of the attempt, else returns a success outcome. - /// - /// Executes only if the previous attempt failed, else does not execute and return a success outcome. - /// If it executes, then runs or depending on the - /// whether the attempt function reports a success or a failure. - /// - public Outcome OnFailure(Func> nextFunction, Action onSuccess, Action onFail = null) - { - return _success - ? Success - : ExecuteNextFunction(nextFunction, onSuccess, onFail); - } - - /// - /// Executes another attempt function, if the previous one succeeded, with callbacks. - /// - /// The type of the attempted operation result. - /// The attempt function to execute, returning an attempt. - /// An action to execute in case the attempt succeeds. - /// An action to execute in case the attempt fails. - /// If it executes, returns the outcome of the attempt, else returns a failed outcome. - /// - /// Executes only if the previous attempt succeeded, else does not execute and return a success outcome. - /// If it executes, then runs or depending on the - /// whether the attempt function reports a success or a failure. - /// - public Outcome OnSuccess(Func> nextFunction, Action onSuccess, Action onFail = null) - { - return _success - ? ExecuteNextFunction(nextFunction, onSuccess, onFail) - : Failure; - } - - private static Outcome ExecuteNextFunction(Func> nextFunction, Action onSuccess, Action onFail = null) - { - var attempt = nextFunction(); - - if (attempt.Success) - { - onSuccess(attempt.Result); - return Success; - } - - if (onFail != null) - { - onFail(attempt.Exception); - } - - return Failure; - } + return Attempt.If(condition, succStatus, failStatus, result); } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/AttemptOfTResult.cs b/src/Umbraco.Core/AttemptOfTResult.cs new file mode 100644 index 0000000000..55b1ea0a1e --- /dev/null +++ b/src/Umbraco.Core/AttemptOfTResult.cs @@ -0,0 +1,133 @@ +using System; + +namespace Umbraco.Core +{ + /// + /// Represents the result of an operation attempt. + /// + /// The type of the attempted operation result. + [Serializable] + public struct Attempt + { + // private - use Succeed() or Fail() methods to create attempts + private Attempt(bool success, TResult result, Exception exception) + { + Success = success; + Result = result; + Exception = exception; + } + + /// + /// Gets a value indicating whether this was successful. + /// + public bool Success { get; } + + /// + /// Gets the exception associated with an unsuccessful attempt. + /// + public Exception Exception { get; } + + /// + /// Gets the attempt result. + /// + public TResult Result { get; } + + /// + /// Gets the attempt result, if successful, else a default value. + /// + public TResult ResultOr(TResult value) => Success ? Result : value; + + // optimize, use a singleton failed attempt + private static readonly Attempt Failed = new Attempt(false, default(TResult), null); + + /// + /// Creates a successful attempt. + /// + /// The successful attempt. + public static Attempt Succeed() + { + return new Attempt(true, default(TResult), null); + } + + /// + /// Creates a successful attempt with a result. + /// + /// The result of the attempt. + /// The successful attempt. + public static Attempt Succeed(TResult result) + { + return new Attempt(true, result, null); + } + + /// + /// Creates a failed attempt. + /// + /// The failed attempt. + public static Attempt Fail() + { + return Failed; + } + + /// + /// Creates a failed attempt with an exception. + /// + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(Exception exception) + { + return new Attempt(false, default(TResult), exception); + } + + /// + /// Creates a failed attempt with a result. + /// + /// The result of the attempt. + /// The failed attempt. + public static Attempt Fail(TResult result) + { + return new Attempt(false, result, null); + } + + /// + /// Creates a failed attempt with a result and an exception. + /// + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(TResult result, Exception exception) + { + return new Attempt(false, result, exception); + } + + /// + /// Creates a successful or a failed attempt. + /// + /// A value indicating whether the attempt is successful. + /// The attempt. + public static Attempt If(bool condition) + { + return condition ? new Attempt(true, default(TResult), null) : Failed; + } + + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// A value indicating whether the attempt is successful. + /// The result of the attempt. + /// The attempt. + public static Attempt If(bool condition, TResult result) + { + return new Attempt(condition, result, null); + } + + /// + /// Implicity operator to check if the attempt was successful without having to access the 'success' property + /// + /// + /// + public static implicit operator bool(Attempt a) + { + return a.Success; + } + } +} diff --git a/src/Umbraco.Core/AttemptOfTResultTStatus.cs b/src/Umbraco.Core/AttemptOfTResultTStatus.cs new file mode 100644 index 0000000000..ca66b0a961 --- /dev/null +++ b/src/Umbraco.Core/AttemptOfTResultTStatus.cs @@ -0,0 +1,142 @@ +using System; + +namespace Umbraco.Core +{ + /// + /// Represents the result of an operation attempt. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + [Serializable] + public struct Attempt + { + /// + /// Gets a value indicating whether this was successful. + /// + public bool Success { get; } + + /// + /// Gets the exception associated with an unsuccessful attempt. + /// + public Exception Exception { get; } + + /// + /// Gets the attempt result. + /// + public TResult Result { get; } + + /// + /// Gets the attempt status. + /// + public TStatus Status { get; } + + // private - use Succeed() or Fail() methods to create attempts + private Attempt(bool success, TResult result, TStatus status, Exception exception) + { + Success = success; + Result = result; + Status = status; + Exception = exception; + } + + /// + /// Creates a successful attempt. + /// + /// The status of the attempt. + /// The successful attempt. + public static Attempt Succeed(TStatus status) + { + return new Attempt(true, default(TResult), status, null); + } + + /// + /// Creates a successful attempt with a result. + /// + /// The status of the attempt. + /// The result of the attempt. + /// The successful attempt. + public static Attempt Succeed(TStatus status, TResult result) + { + return new Attempt(true, result, status, null); + } + + /// + /// Creates a failed attempt. + /// + /// The status of the attempt. + /// The failed attempt. + public static Attempt Fail(TStatus status) + { + return new Attempt(false, default(TResult), status, null); + } + + /// + /// Creates a failed attempt with an exception. + /// + /// The status of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(TStatus status, Exception exception) + { + return new Attempt(false, default(TResult), status, exception); + } + + /// + /// Creates a failed attempt with a result. + /// + /// The status of the attempt. + /// The result of the attempt. + /// The failed attempt. + public static Attempt Fail(TStatus status, TResult result) + { + return new Attempt(false, result, status, null); + } + + /// + /// Creates a failed attempt with a result and an exception. + /// + /// The status of the attempt. + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(TStatus status, TResult result, Exception exception) + { + return new Attempt(false, result, status, exception); + } + + /// + /// Creates a successful or a failed attempt. + /// + /// A value indicating whether the attempt is successful. + /// The status of the successful attempt. + /// The status of the failed attempt. + /// The attempt. + public static Attempt If(bool condition, TStatus succStatus, TStatus failStatus) + { + return new Attempt(condition, default(TResult), condition ? succStatus : failStatus, null); + } + + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// A value indicating whether the attempt is successful. + /// The status of the successful attempt. + /// The status of the failed attempt. + /// The result of the attempt. + /// The attempt. + public static Attempt If(bool condition, TStatus succStatus, TStatus failStatus, TResult result) + { + return new Attempt(condition, result, condition ? succStatus : failStatus, null); + } + + /// + /// Implicity operator to check if the attempt was successful without having to access the 'success' property + /// + /// + /// + public static implicit operator bool(Attempt a) + { + return a.Success; + } + } +} diff --git a/src/Umbraco.Core/Attempt{T}.cs b/src/Umbraco.Core/Attempt{T}.cs deleted file mode 100644 index fc8fa3dcc9..0000000000 --- a/src/Umbraco.Core/Attempt{T}.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System; -using Umbraco.Core.Dynamics; - -namespace Umbraco.Core -{ - /// - /// Represents the result of an operation attempt. - /// - /// The type of the attempted operation result. - [Serializable] - public struct Attempt - { - private readonly bool _success; - private readonly T _result; - private readonly Exception _exception; - - /// - /// Gets a value indicating whether this was successful. - /// - public bool Success - { - get { return _success; } - } - - /// - /// Gets the exception associated with an unsuccessful attempt. - /// - public Exception Exception { get { return _exception; } } - - /// - /// Gets the exception associated with an unsuccessful attempt. - /// - /// Keep it for backward compatibility sake. - [Obsolete(".Error is obsolete, you should use .Exception instead.", false)] - public Exception Error { get { return _exception; } } - - /// - /// Gets the attempt result. - /// - public T Result - { - get { return _result; } - } - - // optimize, use a singleton failed attempt - private static readonly Attempt Failed = new Attempt(false, default(T), null); - - /// - /// Represents an unsuccessful attempt. - /// - /// Keep it for backward compatibility sake. - [Obsolete(".Failed is obsolete, you should use Attempt.Fail() instead.", false)] - public static readonly Attempt False = Failed; - - // private - use Succeed() or Fail() methods to create attempts - private Attempt(bool success, T result, Exception exception) - { - _success = success; - _result = result; - _exception = exception; - } - - /// - /// Initialize a new instance of the struct with a result. - /// - /// A value indicating whether the attempt is successful. - /// The result of the attempt. - /// Keep it for backward compatibility sake. - [Obsolete("Attempt ctors are obsolete, you should use Attempt.Succeed(), Attempt.Fail() or Attempt.If() instead.", false)] - public Attempt(bool success, T result) - : this(success, result, null) - { } - - /// - /// Initialize a new instance of the struct representing a failed attempt, with an exception. - /// - /// The exception causing the failure of the attempt. - /// Keep it for backward compatibility sake. - [Obsolete("Attempt ctors are obsolete, you should use Attempt.Succeed(), Attempt.Fail() or Attempt.If() instead.", false)] - public Attempt(Exception exception) - : this(false, default(T), exception) - { } - - /// - /// Creates a successful attempt. - /// - /// The successful attempt. - public static Attempt Succeed() - { - return new Attempt(true, default(T), null); - } - - /// - /// Creates a successful attempt with a result. - /// - /// The result of the attempt. - /// The successful attempt. - public static Attempt Succeed(T result) - { - return new Attempt(true, result, null); - } - - /// - /// Creates a failed attempt. - /// - /// The failed attempt. - public static Attempt Fail() - { - return Failed; - } - - /// - /// Creates a failed attempt with an exception. - /// - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt Fail(Exception exception) - { - return new Attempt(false, default(T), exception); - } - - /// - /// Creates a failed attempt with a result. - /// - /// The result of the attempt. - /// The failed attempt. - public static Attempt Fail(T result) - { - return new Attempt(false, result, null); - } - - /// - /// Creates a failed attempt with a result and an exception. - /// - /// The result of the attempt. - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt Fail(T result, Exception exception) - { - return new Attempt(false, result, exception); - } - - /// - /// Creates a successful or a failed attempt. - /// - /// A value indicating whether the attempt is successful. - /// The attempt. - public static Attempt SucceedIf(bool condition) - { - return condition ? new Attempt(true, default(T), null) : Failed; - } - - /// - /// Creates a successful or a failed attempt, with a result. - /// - /// A value indicating whether the attempt is successful. - /// The result of the attempt. - /// The attempt. - public static Attempt SucceedIf(bool condition, T result) - { - return new Attempt(condition, result, null); - } - - /// - /// Implicity operator to check if the attempt was successful without having to access the 'success' property - /// - /// - /// - public static implicit operator bool(Attempt a) - { - return a.Success; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Auditing/Audit.cs b/src/Umbraco.Core/Auditing/Audit.cs deleted file mode 100644 index 418c900fb1..0000000000 --- a/src/Umbraco.Core/Auditing/Audit.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.ComponentModel; - -namespace Umbraco.Core.Auditing -{ - [Obsolete("Use Umbraco.Core.Services.IAuditService instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static class Audit - { - public static void Add(Umbraco.Core.Auditing.AuditTypes type, string comment, int userId, int objectId) - { - ApplicationContext.Current.Services.AuditService.Add( - Enum.Parse(type.ToString()), - comment, userId, objectId); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Auditing/AuditEventsComponent.cs b/src/Umbraco.Core/Auditing/AuditEventsComponent.cs new file mode 100644 index 0000000000..57457f9241 --- /dev/null +++ b/src/Umbraco.Core/Auditing/AuditEventsComponent.cs @@ -0,0 +1,353 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading; +using System.Web; +using Umbraco.Core.Components; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Core.Auditing +{ + public sealed class AuditEventsComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + private IAuditService _auditService; + private IUserService _userService; + private IEntityService _entityService; + + private IUser CurrentPerformingUser + { + get + { + var identity = Thread.CurrentPrincipal?.GetUmbracoIdentity(); + return identity == null + ? new User { Id = 0, Name = "SYSTEM", Email = "" } + : _userService.GetUserById(Convert.ToInt32(identity.Id)); + } + } + + private IUser GetPerformingUser(int userId) + { + var found = userId >= 0 ? _userService.GetUserById(userId) : null; + return found ?? new User {Id = 0, Name = "SYSTEM", Email = ""}; + } + + private string PerformingIp + { + get + { + var httpContext = HttpContext.Current == null ? (HttpContextBase) null : new HttpContextWrapper(HttpContext.Current); + var ip = httpContext.GetCurrentRequestIpAddress(); + if (ip.ToLowerInvariant().StartsWith("unknown")) ip = ""; + return ip; + } + } + + public void Initialize(IAuditService auditService, IUserService userService, IEntityService entityService) + { + _auditService = auditService; + _userService = userService; + _entityService = entityService; + + //BackOfficeUserManager.AccountLocked += ; + //BackOfficeUserManager.AccountUnlocked += ; + BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; + BackOfficeUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; + BackOfficeUserManager.LoginFailed += OnLoginFailed; + //BackOfficeUserManager.LoginRequiresVerification += ; + BackOfficeUserManager.LoginSuccess += OnLoginSuccess; + BackOfficeUserManager.LogoutSuccess += OnLogoutSuccess; + BackOfficeUserManager.PasswordChanged += OnPasswordChanged; + BackOfficeUserManager.PasswordReset += OnPasswordReset; + //BackOfficeUserManager.ResetAccessFailedCount += ; + + UserService.SavedUserGroup += OnSavedUserGroupWithUsers; + + UserService.SavedUser += OnSavedUser; + UserService.DeletedUser += OnDeletedUser; + UserService.UserGroupPermissionsAssigned += UserGroupPermissionAssigned; + + MemberService.Saved += OnSavedMember; + MemberService.Deleted += OnDeletedMember; + MemberService.AssignedRoles += OnAssignedRoles; + MemberService.RemovedRoles += OnRemovedRoles; + MemberService.Exported += OnMemberExported; + } + + private string FormatEmail(IMember member) + { + return member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>"; + } + + private string FormatEmail(IUser user) + { + return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; + } + + private void OnRemovedRoles(IMemberService sender, RolesEventArgs args) + { + var performingUser = CurrentPerformingUser; + var roles = string.Join(", ", args.Roles); + var members = sender.GetAllMembers(args.MemberIds).ToDictionary(x => x.Id, x => x); + foreach (var id in args.MemberIds) + { + members.TryGetValue(id, out var member); + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", + "umbraco/member/roles/removed", $"roles modified, removed {roles}"); + } + } + + private void OnAssignedRoles(IMemberService sender, RolesEventArgs args) + { + var performingUser = CurrentPerformingUser; + var roles = string.Join(", ", args.Roles); + var members = sender.GetAllMembers(args.MemberIds).ToDictionary(x => x.Id, x => x); + foreach (var id in args.MemberIds) + { + members.TryGetValue(id, out var member); + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", + "umbraco/member/roles/assigned", $"roles modified, assigned {roles}"); + } + } + + private void OnMemberExported(IMemberService sender, ExportedMemberEventArgs exportedMemberEventArgs) + { + var performingUser = CurrentPerformingUser; + var member = exportedMemberEventArgs.Member; + + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/exported", "exported member data"); + } + + private void OnSavedUserGroupWithUsers(IUserService sender, SaveEventArgs saveEventArgs) + { + var performingUser = CurrentPerformingUser; + foreach (var groupWithUser in saveEventArgs.SavedEntities) + { + var group = groupWithUser.UserGroup; + + var dp = string.Join(", ", ((UserGroup)group).GetWereDirtyProperties()); + var sections = ((UserGroup)group).WasPropertyDirty("AllowedSections") + ? string.Join(", ", group.AllowedSections) + : null; + var perms = ((UserGroup)group).WasPropertyDirty("Permissions") + ? string.Join(", ", group.Permissions) + : null; + + var sb = new StringBuilder(); + sb.Append($"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)};"); + if (sections != null) + sb.Append($", assigned sections: {sections}"); + if (perms != null) + { + if (sections != null) + sb.Append(", "); + sb.Append($"default perms: {perms}"); + } + + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + -1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})", + "umbraco/user-group/save", $"{sb}"); + + // now audit the users that have changed + + foreach (var user in groupWithUser.RemovedUsers) + { + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + user.Id, $"User \"{user.Name}\" {FormatEmail(user)}", + "umbraco/user-group/save", $"Removed user \"{user.Name}\" {FormatEmail(user)} from group {group.Id} \"{group.Name}\" ({group.Alias})"); + } + + foreach (var user in groupWithUser.AddedUsers) + { + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + user.Id, $"User \"{user.Name}\" {FormatEmail(user)}", + "umbraco/user-group/save", $"Added user \"{user.Name}\" {FormatEmail(user)} to group {group.Id} \"{group.Name}\" ({group.Alias})"); + } + } + } + + private void UserGroupPermissionAssigned(IUserService sender, SaveEventArgs saveEventArgs) + { + var performingUser = CurrentPerformingUser; + var perms = saveEventArgs.SavedEntities; + foreach (var perm in perms) + { + var group = sender.GetUserGroupById(perm.UserGroupId); + var assigned = string.Join(", ", perm.AssignedPermissions); + var entity = _entityService.Get(perm.EntityId); + + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + -1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})", + "umbraco/user-group/permissions-change", $"assigning {(string.IsNullOrWhiteSpace(assigned) ? "(nothing)" : assigned)} on id:{perm.EntityId} \"{entity.Name}\""); + } + } + + private void OnSavedMember(IMemberService sender, SaveEventArgs saveEventArgs) + { + var performingUser = CurrentPerformingUser; + var members = saveEventArgs.SavedEntities; + foreach (var member in members) + { + var dp = string.Join(", ", ((Member) member).GetWereDirtyProperties()); + + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}"); + } + } + + private void OnDeletedMember(IMemberService sender, DeleteEventArgs deleteEventArgs) + { + var performingUser = CurrentPerformingUser; + var members = deleteEventArgs.DeletedEntities; + foreach (var member in members) + { + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}"); + } + } + + private void OnSavedUser(IUserService sender, SaveEventArgs saveEventArgs) + { + var performingUser = CurrentPerformingUser; + var affectedUsers = saveEventArgs.SavedEntities; + foreach (var affectedUser in affectedUsers) + { + var groups = affectedUser.WasPropertyDirty("Groups") + ? string.Join(", ", affectedUser.Groups.Select(x => x.Alias)) + : null; + + var dp = string.Join(", ", ((User)affectedUser).GetWereDirtyProperties()); + + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", + "umbraco/user/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}"); + } + } + + private void OnDeletedUser(IUserService sender, DeleteEventArgs deleteEventArgs) + { + var performingUser = CurrentPerformingUser; + var affectedUsers = deleteEventArgs.DeletedEntities; + foreach (var affectedUser in affectedUsers) + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", + "umbraco/user/delete", "delete user"); + } + + private void OnLoginSuccess(object sender, EventArgs args) + { + if (args is IdentityAuditEventArgs identityArgs) + { + var performingUser = GetPerformingUser(identityArgs.PerformingUser); + WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/login", "login success"); + } + } + + private void OnLogoutSuccess(object sender, EventArgs args) + { + if (args is IdentityAuditEventArgs identityArgs) + { + var performingUser = GetPerformingUser(identityArgs.PerformingUser); + WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/logout", "logout success"); + } + } + + private void OnPasswordReset(object sender, EventArgs args) + { + if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) + { + WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/reset", "password reset"); + } + } + + private void OnPasswordChanged(object sender, EventArgs args) + { + if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) + { + WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/change", "password change"); + } + } + + private void OnLoginFailed(object sender, EventArgs args) + { + if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) + { + WriteAudit(identityArgs.PerformingUser, 0, identityArgs.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: ""); + } + } + + private void OnForgotPasswordChange(object sender, EventArgs args) + { + if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) + { + WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change"); + } + } + + private void OnForgotPasswordRequest(object sender, EventArgs args) + { + if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0) + { + WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request"); + } + } + + private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) + { + var performingUser = _userService.GetUserById(performingId); + + var performingDetails = performingUser == null + ? $"User UNKNOWN:{performingId}" + : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; + + WriteAudit(performingId, performingDetails, affectedId, ipAddress, eventType, eventDetails, affectedDetails); + } + + private void WriteAudit(IUser performingUser, int affectedId, string ipAddress, string eventType, string eventDetails) + { + var performingDetails = performingUser == null + ? $"User UNKNOWN" + : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; + + WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedId, ipAddress, eventType, eventDetails); + } + + private void WriteAudit(int performingId, string performingDetails, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) + { + if (affectedDetails == null) + { + var affectedUser = _userService.GetUserById(affectedId); + affectedDetails = affectedUser == null + ? $"User UNKNOWN:{affectedId}" + : $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}"; + } + + _auditService.Write(performingId, performingDetails, + ipAddress, + DateTime.UtcNow, + affectedId, affectedDetails, + eventType, eventDetails); + } + } +} diff --git a/src/Umbraco.Core/Auditing/AuditTypes.cs b/src/Umbraco.Core/Auditing/AuditTypes.cs deleted file mode 100644 index 5bb67a7ca5..0000000000 --- a/src/Umbraco.Core/Auditing/AuditTypes.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.ComponentModel; - -namespace Umbraco.Core.Auditing -{ - [Obsolete("Use Umbraco.Core.Models.AuditType instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public enum AuditTypes - { - /// - /// Used when new nodes are added - /// - New, - /// - /// Used when nodes are saved - /// - Save, - /// - /// Used when nodes are opened - /// - Open, - /// - /// Used when nodes are deleted - /// - Delete, - /// - /// Used when nodes are published - /// - Publish, - /// - /// Used when nodes are send to publishing - /// - SendToPublish, - /// - /// Used when nodes are unpublished - /// - UnPublish, - /// - /// Used when nodes are moved - /// - Move, - /// - /// Used when nodes are copied - /// - Copy, - /// - /// Used when nodes are assígned a domain - /// - AssignDomain, - /// - /// Used when public access are changed for a node - /// - PublicAccess, - /// - /// Used when nodes are sorted - /// - Sort, - /// - /// Used when a notification are send to a user - /// - Notify, - /// - /// General system notification - /// - System, - /// - /// Used when a node's content is rolled back to a previous version - /// - RollBack, - /// - /// Used when a package is installed - /// - PackagerInstall, - /// - /// Used when a package is uninstalled - /// - PackagerUninstall, - /// - /// Used when a node is send to translation - /// - SendToTranslate, - /// - /// Use this log action for custom log messages that should be shown in the audit trail - /// - Custom - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs b/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs index 14445d461f..c58bb409b0 100644 --- a/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs +++ b/src/Umbraco.Core/Auditing/IdentityAuditEventArgs.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Threading; using System.Web; using Umbraco.Core.Security; @@ -45,12 +46,8 @@ namespace Umbraco.Core.Auditing /// public string Username { get; private set; } - /// - /// Sets the properties on the event being raised, all parameters are optional except for the action being performed - /// - /// An action based on the AuditEvent enum - /// The client's IP address. This is usually automatically set but could be overridden if necessary - /// The Id of the user performing the action (if different from the user affected by the action) + [Obsolete("Use the method that has the affectedUser parameter instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public IdentityAuditEventArgs(AuditEvent action, string ipAddress, int performingUser = -1) { DateTimeUtc = DateTime.UtcNow; @@ -63,6 +60,35 @@ namespace Umbraco.Core.Auditing : performingUser; } + /// + /// Default constructor + /// + /// + /// + /// + /// + /// + public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, int performingUser = -1, int affectedUser = -1) + { + DateTimeUtc = DateTime.UtcNow; + Action = action; + + IpAddress = ipAddress; + Comment = comment; + AffectedUser = affectedUser; + + PerformingUser = performingUser == -1 + ? GetCurrentRequestBackofficeUserId() + : performingUser; + } + + /// + /// Creates an instance without a performing or affected user (the id will be set to -1) + /// + /// + /// + /// + /// public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment) { DateTimeUtc = DateTime.UtcNow; @@ -71,6 +97,22 @@ namespace Umbraco.Core.Auditing IpAddress = ipAddress; Username = username; Comment = comment; + + PerformingUser = -1; + } + + public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string username, string comment, int performingUser) + { + DateTimeUtc = DateTime.UtcNow; + Action = action; + + IpAddress = ipAddress; + Username = username; + Comment = comment; + + PerformingUser = performingUser == -1 + ? GetCurrentRequestBackofficeUserId() + : performingUser; } /// diff --git a/src/Umbraco.Core/BindingRedirects.cs b/src/Umbraco.Core/BindingRedirects.cs index 4fadb4db70..a93485085e 100644 --- a/src/Umbraco.Core/BindingRedirects.cs +++ b/src/Umbraco.Core/BindingRedirects.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; using System.Text.RegularExpressions; using System.Web; @@ -21,7 +21,7 @@ namespace Umbraco.Core private static readonly Regex Log4NetAssemblyPattern = new Regex("log4net, Version=([\\d\\.]+?), Culture=neutral, PublicKeyToken=\\w+$", RegexOptions.Compiled); private const string Log4NetReplacement = "log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a"; - + /// /// This is used to do an assembly binding redirect via code - normally required due to signature changes in assemblies /// @@ -30,12 +30,12 @@ namespace Umbraco.Core /// private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { - //log4net: + //log4net: if (Log4NetAssemblyPattern.IsMatch(args.Name) && args.Name != Log4NetReplacement) { return Assembly.Load(Log4NetAssemblyPattern.Replace(args.Name, Log4NetReplacement)); } - + //AutoMapper: // 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 @@ -43,7 +43,7 @@ namespace Umbraco.Core return Assembly.Load(args.Name.Replace(", PublicKeyToken=null", ", PublicKeyToken=be96cd2c38ef1005")); return null; - + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/CacheHelper.cs b/src/Umbraco.Core/Cache/CacheHelper.cs new file mode 100644 index 0000000000..a85285e427 --- /dev/null +++ b/src/Umbraco.Core/Cache/CacheHelper.cs @@ -0,0 +1,422 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Web; +using System.Web.Caching; + +namespace Umbraco.Core.Cache +{ + /// + /// Class that is exposed by the ApplicationContext for application wide caching purposes + /// + public class CacheHelper + { + public static CacheHelper NoCache { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance)); + + /// + /// Creates a cache helper with disabled caches + /// + /// + /// + /// Good for unit testing + /// + public static CacheHelper CreateDisabledCacheHelper() + { + // do *not* return NoCache + // NoCache is a special instance that is detected by RepositoryBase and disables all cache policies + // CreateDisabledCacheHelper is used in tests to use no cache, *but* keep all cache policies + return new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance)); + } + + /// + /// Initializes a new instance for use in the web + /// + public CacheHelper() + : this( + new HttpRuntimeCacheProvider(HttpRuntime.Cache), + new StaticCacheProvider(), + new HttpRequestCacheProvider(), + new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) + { + } + + /// + /// Initializes a new instance for use in the web + /// + /// + public CacheHelper(System.Web.Caching.Cache cache) + : this( + new HttpRuntimeCacheProvider(cache), + new StaticCacheProvider(), + new HttpRequestCacheProvider(), + new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) + { + } + + [Obsolete("Use the constructor the specifies all dependencies")] + [EditorBrowsable(EditorBrowsableState.Never)] + public CacheHelper( + IRuntimeCacheProvider httpCacheProvider, + ICacheProvider staticCacheProvider, + ICacheProvider requestCacheProvider) + : this(httpCacheProvider, staticCacheProvider, requestCacheProvider, new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) + { + } + + /// + /// Initializes a new instance based on the provided providers + /// + /// + /// + /// + /// + public CacheHelper( + IRuntimeCacheProvider httpCacheProvider, + ICacheProvider staticCacheProvider, + ICacheProvider requestCacheProvider, + IsolatedRuntimeCache isolatedCacheManager) + { + if (httpCacheProvider == null) throw new ArgumentNullException("httpCacheProvider"); + if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider"); + if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); + if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager"); + RuntimeCache = httpCacheProvider; + StaticCache = staticCacheProvider; + RequestCache = requestCacheProvider; + IsolatedRuntimeCache = isolatedCacheManager; + } + + /// + /// Returns the current Request cache + /// + public ICacheProvider RequestCache { get; internal set; } + + /// + /// Returns the current Runtime cache + /// + public ICacheProvider StaticCache { get; internal set; } + + /// + /// Returns the current Runtime cache + /// + public IRuntimeCacheProvider RuntimeCache { get; internal set; } + + /// + /// Returns the current Isolated Runtime cache manager + /// + public IsolatedRuntimeCache IsolatedRuntimeCache { get; internal set; } + + #region Legacy Runtime/Http Cache accessors + + /// + /// Clears the item in umbraco's runtime cache + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void ClearAllCache() + { + RuntimeCache.ClearAllCache(); + IsolatedRuntimeCache.ClearAllCaches(); + } + + /// + /// Clears the item in umbraco's runtime cache with the given key + /// + /// Key + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void ClearCacheItem(string key) + { + RuntimeCache.ClearCacheItem(key); + } + + + /// + /// Clears all objects in the System.Web.Cache with the System.Type name as the + /// input parameter. (using [object].GetType()) + /// + /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + public void ClearCacheObjectTypes(string typeName) + { + RuntimeCache.ClearCacheObjectTypes(typeName); + } + + /// + /// Clears all objects in the System.Web.Cache with the System.Type specified + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void ClearCacheObjectTypes() + { + RuntimeCache.ClearCacheObjectTypes(); + } + + /// + /// Clears all cache items that starts with the key passed. + /// + /// The start of the key + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void ClearCacheByKeySearch(string keyStartsWith) + { + RuntimeCache.ClearCacheByKeySearch(keyStartsWith); + } + + /// + /// Clears all cache items that have a key that matches the regular expression + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void ClearCacheByKeyExpression(string regexString) + { + RuntimeCache.ClearCacheByKeyExpression(regexString); + } + + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + return RuntimeCache.GetCacheItemsByKeySearch(keyStartsWith); + } + + /// + /// Returns a cache item by key, does not update the cache if it isn't there. + /// + /// + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public TT GetCacheItem(string cacheKey) + { + return RuntimeCache.GetCacheItem(cacheKey); + } + + /// + /// Gets (and adds if necessary) an item from the cache with all of the default parameters + /// + /// + /// + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public TT GetCacheItem(string cacheKey, Func getCacheItem) + { + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem); + + } + + /// + /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public TT GetCacheItem(string cacheKey, + TimeSpan timeout, Func getCacheItem) + { + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout); + + } + + /// + /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public TT GetCacheItem(string cacheKey, + CacheItemRemovedCallback refreshAction, TimeSpan timeout, + Func getCacheItem) + { + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); + + } + + /// + /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) + /// + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public TT GetCacheItem(string cacheKey, + CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout, + Func getCacheItem) + { + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); + + } + + /// + /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) + /// + /// + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] + public TT GetCacheItem(string cacheKey, + CacheItemPriority priority, + CacheItemRemovedCallback refreshAction, + CacheDependency cacheDependency, + TimeSpan timeout, + Func getCacheItem) + { + var cache = GetHttpRuntimeCacheProvider(RuntimeCache); + if (cache != null) + { + var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); + return result == null ? default(TT) : result.TryConvertTo().Result; + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + } + + /// + /// Gets (and adds if necessary) an item from the cache + /// + /// + /// + /// + /// + /// + /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] + public TT GetCacheItem(string cacheKey, + CacheItemPriority priority, + CacheDependency cacheDependency, + Func getCacheItem) + { + var cache = GetHttpRuntimeCacheProvider(RuntimeCache); + if (cache != null) + { + var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency); + return result == null ? default(TT) : result.TryConvertTo().Result; + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + } + + /// + /// Inserts an item into the cache, if it already exists in the cache it will be replaced + /// + /// + /// + /// + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void InsertCacheItem(string cacheKey, + CacheItemPriority priority, + Func getCacheItem) + { + RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); + + } + + /// + /// Inserts an item into the cache, if it already exists in the cache it will be replaced + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] + [EditorBrowsable(EditorBrowsableState.Never)] + public void InsertCacheItem(string cacheKey, + CacheItemPriority priority, + TimeSpan timeout, + Func getCacheItem) + { + RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); + } + + /// + /// Inserts an item into the cache, if it already exists in the cache it will be replaced + /// + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] + public void InsertCacheItem(string cacheKey, + CacheItemPriority priority, + CacheDependency cacheDependency, + TimeSpan timeout, + Func getCacheItem) + { + var cache = GetHttpRuntimeCacheProvider(RuntimeCache); + if (cache != null) + { + cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency); + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + } + + /// + /// Inserts an item into the cache, if it already exists in the cache it will be replaced + /// + /// + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] + public void InsertCacheItem(string cacheKey, + CacheItemPriority priority, + CacheItemRemovedCallback refreshAction, + CacheDependency cacheDependency, + TimeSpan? timeout, + Func getCacheItem) + { + var cache = GetHttpRuntimeCacheProvider(RuntimeCache); + if (cache != null) + { + cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); + } + throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); + } + #endregion + + private HttpRuntimeCacheProvider GetHttpRuntimeCacheProvider(IRuntimeCacheProvider runtimeCache) + { + HttpRuntimeCacheProvider cache; + var wrapper = RuntimeCache as IRuntimeCacheProviderWrapper; + if (wrapper != null) + { + cache = wrapper.InnerProvider as HttpRuntimeCacheProvider; + } + else + { + cache = RuntimeCache as HttpRuntimeCacheProvider; + } + return cache; + } + } + +} diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index c9991ba45a..68b4c3b157 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using Umbraco.Core.CodeAnnotations; @@ -28,10 +28,7 @@ namespace Umbraco.Core.Cache [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] public const string MacroCacheKey = "UmbracoMacroCache"; - public const string MacroHtmlCacheKey = "macroHtml_"; - public const string MacroControlCacheKey = "macroControl_"; - public const string MacroHtmlDateAddedCacheKey = "macroHtml_DateAdded_"; - public const string MacroControlDateAddedCacheKey = "macroControl_DateAdded_"; + public const string MacroContentCacheKey = "macroContent_"; // for macro contents [UmbracoWillObsolete("This cache key is only used for legacy 'library' member caching, remove in v8")] public const string MemberLibraryCacheKey = "UL_GetMember"; @@ -65,9 +62,8 @@ namespace Umbraco.Core.Cache [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")] public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:"; - + [Obsolete("No longer used and will be removed in v8")] - [EditorBrowsable(EditorBrowsableState.Never)] public const string PropertyTypeCacheKey = "UmbracoPropertyTypeCache"; [Obsolete("This is no longer used and will be removed from the codebase in the future")] @@ -94,4 +90,4 @@ namespace Umbraco.Core.Cache public const string IdToKeyCacheKey = "UI2K__"; public const string KeyToIdCacheKey = "UK2I__"; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs index 7e5b91ea6a..a3d55eb25f 100644 --- a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs +++ b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs @@ -67,4 +67,4 @@ namespace Umbraco.Core.Cache return result.TryConvertTo().Result; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs index 60ad69b6fc..dbb0c6cc81 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs @@ -1,78 +1,120 @@ using System; using Umbraco.Core.Events; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Sync; -using umbraco.interfaces; -using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Cache { /// - /// A base class for cache refreshers to inherit from that ensures the correct events are raised - /// when cache refreshing occurs. + /// A base class for cache refreshers that handles events. /// - /// The real cache refresher type, this is used for raising strongly typed events + /// The actual cache refresher type. + /// The actual cache refresher type is used for strongly typed events. public abstract class CacheRefresherBase : ICacheRefresher - where TInstanceType : ICacheRefresher + where TInstanceType : class, ICacheRefresher { /// - /// An event that is raised when cache is updated on an individual server + /// Initializes a new instance of the . + /// + /// A cache helper. + protected CacheRefresherBase(CacheHelper cacheHelper) + { + CacheHelper = cacheHelper; + } + + /// + /// Triggers when the cache is updated on the server. /// /// - /// This event will fire on each server configured for an Umbraco project whenever a cache refresher - /// is updated. + /// Triggers on each server configured for an Umbraco project whenever a cache refresher is updated. /// public static event TypedEventHandler CacheUpdated; - /// - /// Raises the event - /// - /// - /// - protected static void OnCacheUpdated(TInstanceType sender, CacheRefresherEventArgs args) - { - if (CacheUpdated != null) - { - CacheUpdated(sender, args); - } - } + #region Define /// - /// Returns the real instance of the object ('this') for use in strongly typed events + /// Gets the typed 'this' for events. /// - protected abstract TInstanceType Instance { get; } + protected abstract TInstanceType This { get; } - public abstract Guid UniqueIdentifier { get; } + /// + /// Gets the unique identifier of the refresher. + /// + public abstract Guid RefresherUniqueId { get; } + /// + /// Gets the name of the refresher. + /// public abstract string Name { get; } + #endregion + + #region Refresher + + /// + /// Refreshes all entities. + /// public virtual void RefreshAll() { - OnCacheUpdated(Instance, new CacheRefresherEventArgs(null, MessageType.RefreshAll)); - } - - public virtual void Refresh(int id) - { - OnCacheUpdated(Instance, new CacheRefresherEventArgs(id, MessageType.RefreshById)); - } - - public virtual void Remove(int id) - { - OnCacheUpdated(Instance, new CacheRefresherEventArgs(id, MessageType.RemoveById)); - } - - public virtual void Refresh(Guid id) - { - OnCacheUpdated(Instance, new CacheRefresherEventArgs(id, MessageType.RefreshById)); + OnCacheUpdated(This, new CacheRefresherEventArgs(null, MessageType.RefreshAll)); } /// - /// Clears the cache for all repository entities of this type + /// Refreshes an entity. /// - /// - internal void ClearAllIsolatedCacheByEntityType() - where TEntity : class, IAggregateRoot + /// The entity's identifier. + public virtual void Refresh(int id) { - ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.ClearCache(); + OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RefreshById)); } + + /// + /// Refreshes an entity. + /// + /// The entity's identifier. + public virtual void Refresh(Guid id) + { + OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RefreshById)); + } + + /// + /// Removes an entity. + /// + /// The entity's identifier. + public virtual void Remove(int id) + { + OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RemoveById)); + } + + #endregion + + #region Protected + + /// + /// Gets the cache helper. + /// + protected CacheHelper CacheHelper { get; } + + /// + /// Clears the cache for all repository entities of a specified type. + /// + /// The type of the entities. + protected void ClearAllIsolatedCacheByEntityType() + where TEntity : class, IEntity + { + CacheHelper.IsolatedRuntimeCache.ClearCache(); + } + + /// + /// Raises the CacheUpdated event. + /// + /// The event sender. + /// The event arguments. + protected static void OnCacheUpdated(TInstanceType sender, CacheRefresherEventArgs args) + { + CacheUpdated?.Invoke(sender, args); + } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollection.cs b/src/Umbraco.Core/Cache/CacheRefresherCollection.cs new file mode 100644 index 0000000000..e0b3cd48fd --- /dev/null +++ b/src/Umbraco.Core/Cache/CacheRefresherCollection.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Cache +{ + public class CacheRefresherCollection : BuilderCollectionBase + { + public CacheRefresherCollection(IEnumerable items) + : base(items) + { } + + public ICacheRefresher this[Guid id] + => this.FirstOrDefault(x => x.RefresherUniqueId == id); + } +} diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs new file mode 100644 index 0000000000..11ac05844b --- /dev/null +++ b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using LightInject; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Cache +{ + public class CacheRefresherCollectionBuilder : LazyCollectionBuilderBase + { + public CacheRefresherCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override CacheRefresherCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Core/Cache/CacheRefresherEventArgs.cs b/src/Umbraco.Core/Cache/CacheRefresherEventArgs.cs index 09d9226002..b7ae0477a6 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherEventArgs.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherEventArgs.cs @@ -16,4 +16,4 @@ namespace Umbraco.Core.Cache public object MessageObject { get; private set; } public MessageType MessageType { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs index 14fef80f0d..255d7b526d 100644 --- a/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Web.Caching; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Cache { @@ -16,23 +16,26 @@ namespace Umbraco.Core.Cache } /// - /// A wrapper for any IRuntimeCacheProvider that ensures that all inserts and returns + /// A wrapper for any IRuntimeCacheProvider that ensures that all inserts and returns /// are a deep cloned copy of the item when the item is IDeepCloneable and that tracks changes are /// reset if the object is TracksChangesEntityBase /// internal class DeepCloneRuntimeCacheProvider : IRuntimeCacheProvider, IRuntimeCacheProviderWrapper { - public IRuntimeCacheProvider InnerProvider { get; private set; } + public IRuntimeCacheProvider InnerProvider { get; } public DeepCloneRuntimeCacheProvider(IRuntimeCacheProvider innerProvider) { - if (innerProvider.GetType() == typeof(DeepCloneRuntimeCacheProvider)) - throw new InvalidOperationException("A " + typeof(DeepCloneRuntimeCacheProvider) + " cannot wrap another instance of " + typeof(DeepCloneRuntimeCacheProvider)); + var type = typeof (DeepCloneRuntimeCacheProvider); + + if (innerProvider.GetType() == type) + throw new InvalidOperationException($"A {type} cannot wrap another instance of {type}."); InnerProvider = innerProvider; } #region Clear - doesn't require any changes + public void ClearAllCache() { InnerProvider.ClearAllCache(); @@ -66,7 +69,8 @@ namespace Umbraco.Core.Cache public void ClearCacheByKeyExpression(string regexString) { InnerProvider.ClearCacheByKeyExpression(regexString); - } + } + #endregion public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) @@ -80,7 +84,7 @@ namespace Umbraco.Core.Cache return InnerProvider.GetCacheItemsByKeyExpression(regexString) .Select(CheckCloneableAndTracksChanges); } - + public object GetCacheItem(string cacheKey) { var item = InnerProvider.GetCacheItem(cacheKey); @@ -108,11 +112,11 @@ namespace Umbraco.Core.Cache var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache if (value == null) return null; // do not store null values (backward compat) - //Clone/reset to go into the cache + // clone / reset to go into the cache return CheckCloneableAndTracksChanges(value); }, timeout, isSliding, priority, removedCallback, dependentFiles); - //Clone/reset to go out of the cache + // clone / reset to go into the cache return CheckCloneableAndTracksChanges(cached); } @@ -124,8 +128,9 @@ namespace Umbraco.Core.Cache var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache if (value == null) return null; // do not store null values (backward compat) + // clone / reset to go into the cache return CheckCloneableAndTracksChanges(value); - }, timeout, isSliding, priority, removedCallback, dependentFiles); + }, timeout, isSliding, priority, removedCallback, dependentFiles); } private static object CheckCloneableAndTracksChanges(object input) @@ -133,11 +138,10 @@ namespace Umbraco.Core.Cache var cloneable = input as IDeepCloneable; if (cloneable != null) { - input = cloneable.DeepClone(); + input = cloneable.DeepClone(); } - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) var tracksChanges = input as IRememberBeingDirty; if (tracksChanges != null) { @@ -148,4 +152,4 @@ namespace Umbraco.Core.Cache return input; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 57d12c4223..8387f547d3 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -1,7 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache @@ -18,32 +18,26 @@ namespace Umbraco.Core.Cache /// If options.GetAllCacheValidateCount then we check against the db when getting many entities. /// internal class DefaultRepositoryCachePolicy : RepositoryCachePolicyBase - where TEntity : class, IAggregateRoot + where TEntity : class, IEntity { private static readonly TEntity[] EmptyEntities = new TEntity[0]; // const private readonly RepositoryCachePolicyOptions _options; - public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) - : base(cache) + public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options) + : base(cache, scopeAccessor) { - if (options == null) throw new ArgumentNullException("options"); - _options = options; - } - - public override IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) - { - return new ScopedRepositoryCachePolicy(this, runtimeCache, scope); + _options = options ?? throw new ArgumentNullException(nameof(options)); } protected string GetEntityCacheKey(object id) { - if (id == null) throw new ArgumentNullException("id"); + if (id == null) throw new ArgumentNullException(nameof(id)); return GetEntityTypeCacheKey() + id; } protected string GetEntityTypeCacheKey() { - return string.Format("uRepo_{0}_", typeof(TEntity).Name); + return $"uRepo_{typeof (TEntity).Name}_"; } protected virtual void InsertEntity(string cacheKey, TEntity entity) @@ -74,7 +68,7 @@ namespace Umbraco.Core.Cache /// public override void Create(TEntity entity, Action persistNew) { - if (entity == null) throw new ArgumentNullException("entity"); + if (entity == null) throw new ArgumentNullException(nameof(entity)); try { @@ -106,7 +100,7 @@ namespace Umbraco.Core.Cache /// public override void Update(TEntity entity, Action persistUpdated) { - if (entity == null) throw new ArgumentNullException("entity"); + if (entity == null) throw new ArgumentNullException(nameof(entity)); try { @@ -138,7 +132,7 @@ namespace Umbraco.Core.Cache /// public override void Delete(TEntity entity, Action persistDeleted) { - if (entity == null) throw new ArgumentNullException("entity"); + if (entity == null) throw new ArgumentNullException(nameof(entity)); try { @@ -244,7 +238,7 @@ namespace Umbraco.Core.Cache /// public override void ClearAll() { - Cache.ClearAllCache(); + Cache.ClearCacheByKeySearch(GetEntityTypeCacheKey()); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProvider.cs b/src/Umbraco.Core/Cache/DictionaryCacheProvider.cs new file mode 100644 index 0000000000..98dceb80b0 --- /dev/null +++ b/src/Umbraco.Core/Cache/DictionaryCacheProvider.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Cache +{ + internal class DictionaryCacheProvider : ICacheProvider + { + private readonly ConcurrentDictionary> _items + = new ConcurrentDictionary>(); + + // for tests + internal ConcurrentDictionary> Items => _items; + + public void ClearAllCache() + { + _items.Clear(); + } + + public void ClearCacheItem(string key) + { + _items.TryRemove(key, out _); + } + + public void ClearCacheObjectTypes(string typeName) + { + var type = TypeFinder.GetTypeByName(typeName); + if (type == null) return; + var isInterface = type.IsInterface; + + foreach (var kvp in _items + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // get non-created as NonCreatedValue & exceptions as null + var value = DictionaryCacheProviderBase.GetSafeLazyValue(x.Value, true); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); + })) + _items.TryRemove(kvp.Key, out _); + } + + public void ClearCacheObjectTypes() + { + var typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; + + foreach (var kvp in _items + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // compare on exact type, don't use "is" + // get non-created as NonCreatedValue & exceptions as null + var value = DictionaryCacheProviderBase.GetSafeLazyValue(x.Value, true); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); + })) + _items.TryRemove(kvp.Key, out _); + } + + public void ClearCacheObjectTypes(Func predicate) + { + var typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; + + foreach (var kvp in _items + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // compare on exact type, don't use "is" + // get non-created as NonCreatedValue & exceptions as null + var value = DictionaryCacheProviderBase.GetSafeLazyValue(x.Value, true); + if (value == null) return true; + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return (isInterface ? (value is T) : (value.GetType() == typeOfT)) + // run predicate on the 'public key' part only, ie without prefix + && predicate(x.Key, (T)value); + })) + _items.TryRemove(kvp.Key, out _); + } + + public void ClearCacheByKeySearch(string keyStartsWith) + { + foreach (var ikvp in _items + .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith))) + _items.TryRemove(ikvp.Key, out _); + } + + public void ClearCacheByKeyExpression(string regexString) + { + foreach (var ikvp in _items + .Where(kvp => Regex.IsMatch(kvp.Key, regexString))) + _items.TryRemove(ikvp.Key, out _); + } + + public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + return _items + .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)) + .Select(kvp => DictionaryCacheProviderBase.GetSafeLazyValue(kvp.Value)) + .Where(x => x != null); + } + + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + return _items + .Where(kvp => Regex.IsMatch(kvp.Key, regexString)) + .Select(kvp => DictionaryCacheProviderBase.GetSafeLazyValue(kvp.Value)) + .Where(x => x != null); + } + + public object GetCacheItem(string cacheKey) + { + _items.TryGetValue(cacheKey, out var result); // else null + return result == null ? null : DictionaryCacheProviderBase.GetSafeLazyValue(result); // return exceptions as null + } + + public object GetCacheItem(string cacheKey, Func getCacheItem) + { + var result = _items.GetOrAdd(cacheKey, k => DictionaryCacheProviderBase.GetSafeLazy(getCacheItem)); + + var value = result.Value; // will not throw (safe lazy) + if (!(value is DictionaryCacheProviderBase.ExceptionHolder eh)) + return value; + + // and... it's in the cache anyway - so contrary to other cache providers, + // which would trick with GetSafeLazyValue, we need to remove by ourselves, + // in order NOT to cache exceptions + + _items.TryRemove(cacheKey, out result); + eh.Exception.Throw(); // throw once! + return null; // never reached + } + } +} diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs index ae840d7b0e..6baa2cf0ed 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs @@ -2,7 +2,9 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Runtime.ExceptionServices; using System.Text.RegularExpressions; +using Umbraco.Core.Composing; namespace Umbraco.Core.Cache { @@ -44,7 +46,7 @@ namespace Umbraco.Core.Cache } catch (Exception e) { - return new ExceptionHolder(e); + return new ExceptionHolder(ExceptionDispatchInfo.Capture(e)); } }); } @@ -74,12 +76,12 @@ namespace Umbraco.Core.Cache internal class ExceptionHolder { - public ExceptionHolder(Exception e) + public ExceptionHolder(ExceptionDispatchInfo e) { Exception = e; } - public Exception Exception { get; private set; } + public ExceptionDispatchInfo Exception { get; } } #region Clear @@ -252,4 +254,4 @@ namespace Umbraco.Core.Cache #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index 41c0249877..8cef4e0296 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -1,8 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Collections; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache @@ -19,28 +19,23 @@ namespace Umbraco.Core.Cache /// keep as a whole in memory. /// internal class FullDataSetRepositoryCachePolicy : RepositoryCachePolicyBase - where TEntity : class, IAggregateRoot + where TEntity : class, IEntity { private readonly Func _entityGetId; private readonly bool _expires; - public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func entityGetId, bool expires) - : base(cache) + public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, IScopeAccessor scopeAccessor, Func entityGetId, bool expires) + : base(cache, scopeAccessor) { _entityGetId = entityGetId; _expires = expires; } - public override IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) - { - return new ScopedRepositoryCachePolicy(this, runtimeCache, scope); - } - protected static readonly TId[] EmptyIds = new TId[0]; // const protected string GetEntityTypeCacheKey() { - return string.Format("uRepo_{0}_", typeof(TEntity).Name); + return $"uRepo_{typeof (TEntity).Name}_"; } protected void InsertEntities(TEntity[] entities) @@ -56,20 +51,22 @@ namespace Umbraco.Core.Cache // set to ListCloneBehavior.CloneOnce ie it will clone *once* when inserting, // and then will *not* clone when retrieving. + var key = GetEntityTypeCacheKey(); + if (_expires) { - Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => new DeepCloneableList(entities), TimeSpan.FromMinutes(5), true); + Cache.InsertCacheItem(key, () => new DeepCloneableList(entities), TimeSpan.FromMinutes(5), true); } else { - Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => new DeepCloneableList(entities)); + Cache.InsertCacheItem(key, () => new DeepCloneableList(entities)); } } /// public override void Create(TEntity entity, Action persistNew) { - if (entity == null) throw new ArgumentNullException("entity"); + if (entity == null) throw new ArgumentNullException(nameof(entity)); try { @@ -84,7 +81,7 @@ namespace Umbraco.Core.Cache /// public override void Update(TEntity entity, Action persistUpdated) { - if (entity == null) throw new ArgumentNullException("entity"); + if (entity == null) throw new ArgumentNullException(nameof(entity)); try { @@ -99,7 +96,7 @@ namespace Umbraco.Core.Cache /// public override void Delete(TEntity entity, Action persistDeleted) { - if (entity == null) throw new ArgumentNullException("entity"); + if (entity == null) throw new ArgumentNullException(nameof(entity)); try { @@ -120,7 +117,7 @@ namespace Umbraco.Core.Cache // see note in InsertEntities - what we get here is the original // cached entity, not a clone, so we need to manually ensure it is deep-cloned. - return entity == null ? null : (TEntity) entity.DeepClone(); + return (TEntity)entity?.DeepClone(); } /// @@ -128,11 +125,11 @@ namespace Umbraco.Core.Cache { // get all from the cache -- and only the cache, then look for the entity var all = Cache.GetCacheItem>(GetEntityTypeCacheKey()); - var entity = all == null ? null : all.FirstOrDefault(x => _entityGetId(x).Equals(id)); + var entity = all?.FirstOrDefault(x => _entityGetId(x).Equals(id)); // see note in InsertEntities - what we get here is the original // cached entity, not a clone, so we need to manually ensure it is deep-cloned. - return entity == null ? null : (TEntity)entity.DeepClone(); + return (TEntity) entity?.DeepClone(); } /// @@ -177,4 +174,4 @@ namespace Umbraco.Core.Cache Cache.ClearCacheItem(GetEntityTypeCacheKey()); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs index ac64e88e8a..6f97651042 100644 --- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs @@ -94,8 +94,8 @@ namespace Umbraco.Core.Cache get { - return HasContextItems - ? (IDisposable) new MonitorLock(ContextItems.SyncRoot) + return HasContextItems + ? (IDisposable) new MonitorLock(ContextItems.SyncRoot) : new NoopLocker(); } } @@ -136,8 +136,7 @@ namespace Umbraco.Core.Cache //return result.Value; var value = result.Value; // will not throw (safe lazy) - var eh = value as ExceptionHolder; - if (eh != null) throw eh.Exception; // throw once! + if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once! return value; } @@ -152,4 +151,4 @@ namespace Umbraco.Core.Cache { } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs index 0e98cd5318..ad46201c0c 100644 --- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs @@ -98,7 +98,7 @@ namespace Umbraco.Core.Cache // on the Lazy lock to ensure that getCacheItem runs once and everybody waits on it, while the global // application lock has been released. - // NOTE + // NOTE // The Lazy value creation may produce a null value. // Must make sure (for backward compatibility) that we pretend they are not in the cache. // So if we find an entry in the cache that already has its value created and is null, @@ -153,8 +153,7 @@ namespace Umbraco.Core.Cache //return result.Value; value = result.Value; // will not throw (safe lazy) - var eh = value as ExceptionHolder; - if (eh != null) throw new Exception("Exception while creating a value.", eh.Exception); // throw once! + if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once! return value; } @@ -215,4 +214,4 @@ namespace Umbraco.Core.Cache #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/ICacheProvider.cs b/src/Umbraco.Core/Cache/ICacheProvider.cs index e53123d7e8..da0c1cc918 100644 --- a/src/Umbraco.Core/Cache/ICacheProvider.cs +++ b/src/Umbraco.Core/Cache/ICacheProvider.cs @@ -55,7 +55,7 @@ namespace Umbraco.Core.Cache IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); IEnumerable GetCacheItemsByKeyExpression(string regexString); - + /// /// Returns an item with a given key /// diff --git a/src/Umbraco.Core/Cache/ICacheRefresher.cs b/src/Umbraco.Core/Cache/ICacheRefresher.cs index e50c11fa35..d254c770eb 100644 --- a/src/Umbraco.Core/Cache/ICacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ICacheRefresher.cs @@ -1,18 +1,33 @@ -using umbraco.interfaces; +using System; +using Umbraco.Core.Composing; namespace Umbraco.Core.Cache { + /// + /// The IcacheRefresher Interface is used for loadbalancing. + /// + /// + public interface ICacheRefresher : IDiscoverable + { + Guid RefresherUniqueId { get; } + string Name { get; } + void RefreshAll(); + void Refresh(int id); + void Remove(int id); + void Refresh(Guid id); + } + /// /// Strongly type cache refresher that is able to refresh cache of real instances of objects as well as IDs /// /// /// /// This is much better for performance when we're not running in a load balanced environment so we can refresh the cache - /// against a already resolved object instead of looking the object back up by id. + /// against a already resolved object instead of looking the object back up by id. /// interface ICacheRefresher : ICacheRefresher { void Refresh(T instance); void Remove(T instance); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs index 006cc4fec0..a9ecd6050c 100644 --- a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs @@ -1,6 +1,4 @@ -using umbraco.interfaces; - -namespace Umbraco.Core.Cache +namespace Umbraco.Core.Cache { /// /// A cache refresher that supports refreshing or removing cache based on a custom Json payload @@ -10,7 +8,7 @@ namespace Umbraco.Core.Cache /// /// Refreshes, clears, etc... any cache based on the information provided in the json /// - /// - void Refresh(string jsonPayload); + /// + void Refresh(string json); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs index 416cb223d7..322e65654c 100644 --- a/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs @@ -1,16 +1,14 @@ -using umbraco.interfaces; - -namespace Umbraco.Core.Cache +namespace Umbraco.Core.Cache { /// /// A cache refresher that supports refreshing cache based on a custom payload /// - interface IPayloadCacheRefresher : IJsonCacheRefresher + interface IPayloadCacheRefresher : IJsonCacheRefresher { /// /// Refreshes, clears, etc... any cache based on the information provided in the payload /// - /// - void Refresh(object payload); + /// + void Refresh(TPayload[] payloads); } } diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs index eeb4f77de3..305ed00f9a 100644 --- a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs @@ -1,33 +1,13 @@ using System; using System.Collections.Generic; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { internal interface IRepositoryCachePolicy - where TEntity : class, IAggregateRoot + where TEntity : class, IEntity { - // note: - // at the moment each repository instance creates its corresponding cache policy instance - // we could reduce allocations by using static cache policy instances but then we would need - // to modify all methods here to pass the repository and cache eg: - // - // TEntity Get(TRepository repository, IRuntimeCacheProvider cache, TId id); - // - // it is not *that* complicated but then RepositoryBase needs to have a TRepository generic - // type parameter and it all becomes convoluted - keeping it simple for the time being. - - /// - /// Creates a scoped version of this cache policy. - /// - /// The global isolated runtime cache for this policy. - /// The scope. - /// When a policy is scoped, it means that it has been created with a scoped - /// isolated runtime cache, and now it needs to be wrapped into something that can apply - /// changes to the global isolated runtime cache. - IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope); - /// /// Gets an entity from the cache, else from the repository. /// @@ -94,4 +74,4 @@ namespace Umbraco.Core.Cache /// void ClearAll(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs index 7f38257d1c..202bcbaa9e 100644 --- a/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs @@ -14,8 +14,8 @@ namespace Umbraco.Core.Cache public interface IRuntimeCacheProvider : ICacheProvider { object GetCacheItem( - string cacheKey, - Func getCacheItem, + string cacheKey, + Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, diff --git a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs index da20f7eb73..ee73a17532 100644 --- a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs +++ b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; namespace Umbraco.Core.Cache @@ -7,7 +7,7 @@ namespace Umbraco.Core.Cache /// Used to get/create/manipulate isolated runtime cache /// /// - /// This is useful for repository level caches to ensure that cache lookups by key are fast so + /// This is useful for repository level caches to ensure that cache lookups by key are fast so /// that the repository doesn't need to search through all keys on a global scale. /// public class IsolatedRuntimeCache @@ -29,7 +29,7 @@ namespace Umbraco.Core.Cache /// Returns an isolated runtime cache for a given type /// /// - /// + /// public IRuntimeCacheProvider GetOrCreateCache() { return _isolatedCache.GetOrAdd(typeof(T), type => CacheFactory(type)); @@ -38,7 +38,7 @@ namespace Umbraco.Core.Cache /// /// Returns an isolated runtime cache for a given type /// - /// + /// public IRuntimeCacheProvider GetOrCreateCache(Type type) { return _isolatedCache.GetOrAdd(type, t => CacheFactory(t)); @@ -88,4 +88,4 @@ namespace Umbraco.Core.Cache } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs index 48dd008a3d..f2ffac4608 100644 --- a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs @@ -1,19 +1,29 @@ using Umbraco.Core.Sync; -using umbraco.interfaces; namespace Umbraco.Core.Cache { /// - /// Provides a base class for "json" cache refreshers. + /// A base class for "json" cache refreshers. /// - /// The actual cache refresher type. - /// Ensures that the correct events are raised when cache refreshing occurs. - public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher - where TInstance : ICacheRefresher - { + /// The actual cache refresher type. + /// The actual cache refresher type is used for strongly typed events. + public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher + where TInstanceType : class, ICacheRefresher + { + /// + /// Initializes a new instance of the . + /// + /// A cache helper. + protected JsonCacheRefresherBase(CacheHelper cacheHelper) : base(cacheHelper) + { } + + /// + /// Refreshes as specified by a json payload. + /// + /// The json payload. public virtual void Refresh(string json) - { - OnCacheUpdated(Instance, new CacheRefresherEventArgs(json, MessageType.RefreshByJson)); + { + OnCacheUpdated(This, new CacheRefresherEventArgs(json, MessageType.RefreshByJson)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs new file mode 100644 index 0000000000..a3e7335d7f --- /dev/null +++ b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Cache +{ + internal class NoCacheRepositoryCachePolicy : IRepositoryCachePolicy + where TEntity : class, IEntity + { + private NoCacheRepositoryCachePolicy() { } + + public static NoCacheRepositoryCachePolicy Instance { get; } = new NoCacheRepositoryCachePolicy(); + + public IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) + { + throw new NotImplementedException(); + } + + public TEntity Get(TId id, Func performGet, Func> performGetAll) + { + return performGet(id); + } + + public TEntity GetCached(TId id) + { + return null; + } + + public bool Exists(TId id, Func performExists, Func> performGetAll) + { + return performExists(id); + } + + public void Create(TEntity entity, Action persistNew) + { + persistNew(entity); + } + + public void Update(TEntity entity, Action persistUpdated) + { + persistUpdated(entity); + } + + public void Delete(TEntity entity, Action persistDeleted) + { + persistDeleted(entity); + } + + public TEntity[] GetAll(TId[] ids, Func> performGetAll) + { + return performGetAll(ids).ToArray(); + } + + public void ClearAll() + { } + } +} diff --git a/src/Umbraco.Core/Cache/NoRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/NoRepositoryCachePolicy.cs deleted file mode 100644 index 54891af48b..0000000000 --- a/src/Umbraco.Core/Cache/NoRepositoryCachePolicy.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.Cache -{ - internal class NoRepositoryCachePolicy : IRepositoryCachePolicy - where TEntity : class, IAggregateRoot - { - private static readonly NoRepositoryCachePolicy StaticInstance = new NoRepositoryCachePolicy(); - - private NoRepositoryCachePolicy() - { } - - public static NoRepositoryCachePolicy Instance { get { return StaticInstance; } } - - public IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) - { - throw new NotImplementedException(); - } - - public TEntity Get(TId id, Func performGet, Func> performGetAll) - { - return performGet(id); - } - - public TEntity GetCached(TId id) - { - return null; - } - - public bool Exists(TId id, Func performExists, Func> performGetAll) - { - return performExists(id); - } - - public void Create(TEntity entity, Action persistNew) - { - persistNew(entity); - } - - public void Update(TEntity entity, Action persistUpdated) - { - persistUpdated(entity); - } - - public void Delete(TEntity entity, Action persistDeleted) - { - persistDeleted(entity); - } - - public TEntity[] GetAll(TId[] ids, Func> performGetAll) - { - return performGetAll(ids).ToArray(); - } - - public void ClearAll() - { } - } -} diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs index 6b797307ac..1228feb350 100644 --- a/src/Umbraco.Core/Cache/NullCacheProvider.cs +++ b/src/Umbraco.Core/Cache/NullCacheProvider.cs @@ -10,6 +10,10 @@ namespace Umbraco.Core.Cache /// public class NullCacheProvider : IRuntimeCacheProvider { + private NullCacheProvider() { } + + public static NullCacheProvider Instance { get; } = new NullCacheProvider(); + public virtual void ClearAllCache() { } @@ -59,4 +63,4 @@ namespace Umbraco.Core.Cache public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs index feccba03b4..70e37addb8 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -5,6 +5,7 @@ using System.Runtime.Caching; using System.Text.RegularExpressions; using System.Threading; using System.Web.Caching; +using Umbraco.Core.Composing; using CacheItemPriority = System.Web.Caching.CacheItemPriority; namespace Umbraco.Core.Cache @@ -224,8 +225,7 @@ namespace Umbraco.Core.Cache //return result.Value; var value = result.Value; // will not throw (safe lazy) - var eh = value as DictionaryCacheProviderBase.ExceptionHolder; - if (eh != null) throw eh.Exception; // throw once! + if (value is DictionaryCacheProviderBase.ExceptionHolder eh) eh.Exception.Throw(); // throw once! return value; } @@ -296,4 +296,4 @@ namespace Umbraco.Core.Cache return policy; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs index b3ea2ff7b1..22bc46e020 100644 --- a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs @@ -1,17 +1,39 @@ -using Umbraco.Core.Sync; -using umbraco.interfaces; +using Newtonsoft.Json; +using Umbraco.Core.Sync; namespace Umbraco.Core.Cache { /// - /// Provides a base class for "payload" cache refreshers. + /// A base class for "payload" class refreshers. /// - /// The actual cache refresher type. - /// Ensures that the correct events are raised when cache refreshing occurs. - public abstract class PayloadCacheRefresherBase : JsonCacheRefresherBase, IPayloadCacheRefresher - where TInstance : ICacheRefresher + /// The actual cache refresher type. + /// The payload type. + /// The actual cache refresher type is used for strongly typed events. + public abstract class PayloadCacheRefresherBase : JsonCacheRefresherBase, IPayloadCacheRefresher + where TInstanceType : class, ICacheRefresher { - protected abstract object Deserialize(string json); + /// + /// Initializes a new instance of the . + /// + /// A cache helper. + protected PayloadCacheRefresherBase(CacheHelper cacheHelper) : base(cacheHelper) + { } + + #region Json + + /// + /// Deserializes a json payload into an object payload. + /// + /// The json payload. + /// The deserialized object payload. + protected virtual TPayload[] Deserialize(string json) + { + return JsonConvert.DeserializeObject(json); + } + + #endregion + + #region Refresher public override void Refresh(string json) { @@ -19,9 +41,15 @@ namespace Umbraco.Core.Cache Refresh(payload); } - public virtual void Refresh(object payload) + /// + /// Refreshes as specified by a payload. + /// + /// The payload. + public virtual void Refresh(TPayload[] payloads) { - OnCacheUpdated(Instance, new CacheRefresherEventArgs(payload, MessageType.RefreshByPayload)); + OnCacheUpdated(This, new CacheRefresherEventArgs(payloads, MessageType.RefreshByPayload)); } + + #endregion } } diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs index 0b5d2b15c5..2d21d410a7 100644 --- a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs @@ -1,6 +1,6 @@ -using System; +using System; using System.Collections.Generic; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache @@ -11,17 +11,35 @@ namespace Umbraco.Core.Cache /// The type of the entity. /// The type of the identifier. internal abstract class RepositoryCachePolicyBase : IRepositoryCachePolicy - where TEntity : class, IAggregateRoot + where TEntity : class, IEntity { - protected RepositoryCachePolicyBase(IRuntimeCacheProvider cache) + private readonly IRuntimeCacheProvider _globalCache; + private readonly IScopeAccessor _scopeAccessor; + + protected RepositoryCachePolicyBase(IRuntimeCacheProvider globalCache, IScopeAccessor scopeAccessor) { - if (cache == null) throw new ArgumentNullException("cache"); - Cache = cache; + _globalCache = globalCache ?? throw new ArgumentNullException(nameof(globalCache)); + _scopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor)); } - public abstract IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope); - - protected IRuntimeCacheProvider Cache { get; private set; } + protected IRuntimeCacheProvider Cache + { + get + { + var ambientScope = _scopeAccessor.AmbientScope; + switch (ambientScope.RepositoryCacheMode) + { + case RepositoryCacheMode.Default: + return _globalCache; + case RepositoryCacheMode.Scoped: + return ambientScope.IsolatedRuntimeCache.GetOrCreateCache(); + case RepositoryCacheMode.None: + return NullCacheProvider.Instance; + default: + throw new NotSupportedException($"Repository cache mode {ambientScope.RepositoryCacheMode} is not supported."); + } + } + } /// public abstract TEntity Get(TId id, Func performGet, Func> performGetAll); @@ -46,6 +64,5 @@ namespace Umbraco.Core.Cache /// public abstract void ClearAll(); - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs index 14cef76db6..72baa05564 100644 --- a/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Core.Cache { @@ -30,22 +30,22 @@ namespace Umbraco.Core.Cache /// /// Callback required to get count for GetAllCacheValidateCount /// - public Func PerformCount { get; private set; } + public Func PerformCount { get; set; } /// /// True/false as to validate the total item count when all items are returned from cache, the default is true but this - /// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the normal - /// GetAll method. + /// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the normal + /// GetAll method. /// /// /// setting this to return false will improve performance of GetAll cache with no params but should only be used /// for specific circumstances /// - public bool GetAllCacheValidateCount { get; private set; } + public bool GetAllCacheValidateCount { get; set; } /// /// True if the GetAll method will cache that there are zero results so that the db is not hit when there are no results found /// public bool GetAllCacheAllowZeroCount { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs deleted file mode 100644 index f4244a49a1..0000000000 --- a/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.Cache -{ - internal class ScopedRepositoryCachePolicy : IRepositoryCachePolicy - where TEntity : class, IAggregateRoot - { - private readonly IRepositoryCachePolicy _cachePolicy; - private readonly IRuntimeCacheProvider _globalIsolatedCache; - private readonly IScope _scope; - - public ScopedRepositoryCachePolicy(IRepositoryCachePolicy cachePolicy, IRuntimeCacheProvider globalIsolatedCache, IScope scope) - { - _cachePolicy = cachePolicy; - _globalIsolatedCache = globalIsolatedCache; - _scope = scope; - } - - public IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) - { - throw new InvalidOperationException(); // obviously - } - - public TEntity Get(TId id, Func performGet, Func> performGetAll) - { - // loads into the local cache only, ok for now - return _cachePolicy.Get(id, performGet, performGetAll); - } - - public TEntity GetCached(TId id) - { - // loads into the local cache only, ok for now - return _cachePolicy.GetCached(id); - } - - public bool Exists(TId id, Func performExists, Func> performGetAll) - { - // loads into the local cache only, ok for now - return _cachePolicy.Exists(id, performExists, performGetAll); - } - - public void Create(TEntity entity, Action persistNew) - { - // writes into the local cache - _cachePolicy.Create(entity, persistNew); - } - - public void Update(TEntity entity, Action persistUpdated) - { - // writes into the local cache - _cachePolicy.Update(entity, persistUpdated); - } - - public void Delete(TEntity entity, Action persistDeleted) - { - // deletes the local cache - _cachePolicy.Delete(entity, persistDeleted); - } - - public TEntity[] GetAll(TId[] ids, Func> performGetAll) - { - // loads into the local cache only, ok for now - return _cachePolicy.GetAll(ids, performGetAll); - } - - public void ClearAll() - { - // clears the local cache - _cachePolicy.ClearAll(); - } - } -} diff --git a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs index 7ba7d445fe..d89524d4f9 100644 --- a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { @@ -13,10 +14,10 @@ namespace Umbraco.Core.Cache /// Used by DictionaryRepository. /// internal class SingleItemsOnlyRepositoryCachePolicy : DefaultRepositoryCachePolicy - where TEntity : class, IAggregateRoot + where TEntity : class, IEntity { - public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) - : base(cache, options) + public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options) + : base(cache, scopeAccessor, options) { } protected override void InsertEntities(TId[] ids, TEntity[] entities) @@ -24,4 +25,4 @@ namespace Umbraco.Core.Cache // nop } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/StaticCacheProvider.cs b/src/Umbraco.Core/Cache/StaticCacheProvider.cs index c7fd00d39a..bb63768b1e 100644 --- a/src/Umbraco.Core/Cache/StaticCacheProvider.cs +++ b/src/Umbraco.Core/Cache/StaticCacheProvider.cs @@ -48,7 +48,7 @@ namespace Umbraco.Core.Cache public virtual void ClearCacheByKeyExpression(string regexString) { - StaticCache.RemoveAll(kvp => Regex.IsMatch(kvp.Key, regexString)); + StaticCache.RemoveAll(kvp => Regex.IsMatch(kvp.Key, regexString)); } public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) @@ -61,7 +61,7 @@ namespace Umbraco.Core.Cache public IEnumerable GetCacheItemsByKeyExpression(string regexString) { return (from KeyValuePair c in StaticCache - where Regex.IsMatch(c.Key, regexString) + where Regex.IsMatch(c.Key, regexString) select c.Value).ToList(); } @@ -74,6 +74,6 @@ namespace Umbraco.Core.Cache public virtual object GetCacheItem(string cacheKey, Func getCacheItem) { return StaticCache.GetOrAdd(cacheKey, key => getCacheItem()); - } + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs b/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs index ba7b0a5a6b..e1070f8cc9 100644 --- a/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs @@ -1,25 +1,36 @@ using Umbraco.Core.Sync; -using umbraco.interfaces; namespace Umbraco.Core.Cache { /// - /// A base class for cache refreshers to inherit from that ensures the correct events are raised - /// when cache refreshing occurs. + /// A base class for "typed" cache refreshers. /// - /// The real cache refresher type, this is used for raising strongly typed events - /// The entity type that this refresher can update cache for + /// The actual cache refresher type. + /// The entity type. + /// The actual cache refresher type is used for strongly typed events. public abstract class TypedCacheRefresherBase : CacheRefresherBase, ICacheRefresher - where TInstanceType : ICacheRefresher + where TInstanceType : class, ICacheRefresher { + /// + /// Initializes a new instance of the . + /// + /// A cache helper. + protected TypedCacheRefresherBase(CacheHelper cacheHelper) + : base(cacheHelper) + { } + + #region Refresher + public virtual void Refresh(TEntityType instance) { - OnCacheUpdated(Instance, new CacheRefresherEventArgs(instance, MessageType.RefreshByInstance)); + OnCacheUpdated(This, new CacheRefresherEventArgs(instance, MessageType.RefreshByInstance)); } public virtual void Remove(TEntityType instance) { - OnCacheUpdated(Instance, new CacheRefresherEventArgs(instance, MessageType.RemoveByInstance)); + OnCacheUpdated(This, new CacheRefresherEventArgs(instance, MessageType.RemoveByInstance)); } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs deleted file mode 100644 index dbf3f89714..0000000000 --- a/src/Umbraco.Core/CacheHelper.cs +++ /dev/null @@ -1,435 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Web; -using System.Web.Caching; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; - -namespace Umbraco.Core -{ - /// - /// Class that is exposed by the ApplicationContext for application wide caching purposes - /// - public class CacheHelper - { - private static readonly ICacheProvider NullRequestCache = new NullCacheProvider(); - private static readonly ICacheProvider NullStaticCache = new NullCacheProvider(); - private static readonly IRuntimeCacheProvider NullRuntimeCache = new NullCacheProvider(); - private static readonly IsolatedRuntimeCache NullIsolatedCache = new IsolatedRuntimeCache(_ => NullRuntimeCache); - private static readonly CacheHelper NullCache = new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, NullIsolatedCache); - - public static CacheHelper NoCache { get { return NullCache; } } - - /// - /// Creates a cache helper with disabled caches - /// - /// - /// - /// Good for unit testing - /// - public static CacheHelper CreateDisabledCacheHelper() - { - // do *not* return NoCache - // NoCache is a special instance that is detected by RepositoryBase and disables all cache policies - // CreateDisabledCacheHelper is used in tests to use no cache, *but* keep all cache policies - return new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, NullIsolatedCache); - } - - /// - /// Initializes a new instance for use in the web - /// - public CacheHelper() - : this( - new HttpRuntimeCacheProvider(HttpRuntime.Cache), - new StaticCacheProvider(), - new HttpRequestCacheProvider(), - new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) - { - } - - /// - /// Initializes a new instance for use in the web - /// - /// - public CacheHelper(System.Web.Caching.Cache cache) - : this( - new HttpRuntimeCacheProvider(cache), - new StaticCacheProvider(), - new HttpRequestCacheProvider(), - new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) - { - } - - [Obsolete("Use the constructor the specifies all dependencies")] - [EditorBrowsable(EditorBrowsableState.Never)] - public CacheHelper( - IRuntimeCacheProvider httpCacheProvider, - ICacheProvider staticCacheProvider, - ICacheProvider requestCacheProvider) - : this(httpCacheProvider, staticCacheProvider, requestCacheProvider, new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())) - { - } - - /// - /// Initializes a new instance based on the provided providers - /// - /// - /// - /// - /// - public CacheHelper( - IRuntimeCacheProvider httpCacheProvider, - ICacheProvider staticCacheProvider, - ICacheProvider requestCacheProvider, - IsolatedRuntimeCache isolatedCacheManager) - { - if (httpCacheProvider == null) throw new ArgumentNullException("httpCacheProvider"); - if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider"); - if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); - if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager"); - RuntimeCache = httpCacheProvider; - StaticCache = staticCacheProvider; - RequestCache = requestCacheProvider; - IsolatedRuntimeCache = isolatedCacheManager; - } - - /// - /// Returns the current Request cache - /// - public ICacheProvider RequestCache { get; internal set; } - - /// - /// Returns the current Runtime cache - /// - public ICacheProvider StaticCache { get; internal set; } - - /// - /// Returns the current Runtime cache - /// - public IRuntimeCacheProvider RuntimeCache { get; internal set; } - - /// - /// Returns the current Isolated Runtime cache manager - /// - public IsolatedRuntimeCache IsolatedRuntimeCache { get; internal set; } - - #region Legacy Runtime/Http Cache accessors - - /// - /// Clears the item in umbraco's runtime cache - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void ClearAllCache() - { - RuntimeCache.ClearAllCache(); - IsolatedRuntimeCache.ClearAllCaches(); - } - - /// - /// Clears the item in umbraco's runtime cache with the given key - /// - /// Key - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void ClearCacheItem(string key) - { - RuntimeCache.ClearCacheItem(key); - } - - - /// - /// Clears all objects in the System.Web.Cache with the System.Type name as the - /// input parameter. (using [object].GetType()) - /// - /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - public void ClearCacheObjectTypes(string typeName) - { - RuntimeCache.ClearCacheObjectTypes(typeName); - } - - /// - /// Clears all objects in the System.Web.Cache with the System.Type specified - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void ClearCacheObjectTypes() - { - RuntimeCache.ClearCacheObjectTypes(); - } - - /// - /// Clears all cache items that starts with the key passed. - /// - /// The start of the key - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void ClearCacheByKeySearch(string keyStartsWith) - { - RuntimeCache.ClearCacheByKeySearch(keyStartsWith); - } - - /// - /// Clears all cache items that have a key that matches the regular expression - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void ClearCacheByKeyExpression(string regexString) - { - RuntimeCache.ClearCacheByKeyExpression(regexString); - } - - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) - { - return RuntimeCache.GetCacheItemsByKeySearch(keyStartsWith); - } - - /// - /// Returns a cache item by key, does not update the cache if it isn't there. - /// - /// - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public TT GetCacheItem(string cacheKey) - { - return RuntimeCache.GetCacheItem(cacheKey); - } - - /// - /// Gets (and adds if necessary) an item from the cache with all of the default parameters - /// - /// - /// - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public TT GetCacheItem(string cacheKey, Func getCacheItem) - { - return RuntimeCache.GetCacheItem(cacheKey, getCacheItem); - - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public TT GetCacheItem(string cacheKey, - TimeSpan timeout, Func getCacheItem) - { - return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout); - - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public TT GetCacheItem(string cacheKey, - CacheItemRemovedCallback refreshAction, TimeSpan timeout, - Func getCacheItem) - { - return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); - - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public TT GetCacheItem(string cacheKey, - CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout, - Func getCacheItem) - { - return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); - - } - - /// - /// Gets (and adds if necessary) an item from the cache with the specified absolute expiration date (from NOW) - /// - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - /// - [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] - public TT GetCacheItem(string cacheKey, - CacheItemPriority priority, - CacheItemRemovedCallback refreshAction, - CacheDependency cacheDependency, - TimeSpan timeout, - Func getCacheItem) - { - var cache = GetHttpRuntimeCacheProvider(RuntimeCache); - if (cache != null) - { - var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); - return result == null ? default(TT) : result.TryConvertTo().Result; - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); - } - - /// - /// Gets (and adds if necessary) an item from the cache - /// - /// - /// - /// - /// - /// - /// - [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] - public TT GetCacheItem(string cacheKey, - CacheItemPriority priority, - CacheDependency cacheDependency, - Func getCacheItem) - { - var cache = GetHttpRuntimeCacheProvider(RuntimeCache); - if (cache != null) - { - var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency); - return result == null ? default(TT) : result.TryConvertTo().Result; - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); - } - - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - Func getCacheItem) - { - RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); - - } - - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - TimeSpan timeout, - Func getCacheItem) - { - RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); - } - - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] - public void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - CacheDependency cacheDependency, - TimeSpan timeout, - Func getCacheItem) - { - var cache = GetHttpRuntimeCacheProvider(RuntimeCache); - if (cache != null) - { - cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency); - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); - } - - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - [Obsolete("Do not use this method, we no longer support the caching overloads with references to CacheDependency, use the overloads specifying a file collection instead")] - public void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - CacheItemRemovedCallback refreshAction, - CacheDependency cacheDependency, - TimeSpan? timeout, - Func getCacheItem) - { - var cache = GetHttpRuntimeCacheProvider(RuntimeCache); - if (cache != null) - { - cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); - } - throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider)); - } - #endregion - - private HttpRuntimeCacheProvider GetHttpRuntimeCacheProvider(IRuntimeCacheProvider runtimeCache) - { - HttpRuntimeCacheProvider cache; - var wrapper = RuntimeCache as IRuntimeCacheProviderWrapper; - if (wrapper != null) - { - cache = wrapper.InnerProvider as HttpRuntimeCacheProvider; - } - else - { - cache = RuntimeCache as HttpRuntimeCacheProvider; - } - return cache; - } - } - -} diff --git a/src/Umbraco.Core/CacheRefreshersResolver.cs b/src/Umbraco.Core/CacheRefreshersResolver.cs deleted file mode 100644 index 4047a06b62..0000000000 --- a/src/Umbraco.Core/CacheRefreshersResolver.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using umbraco.interfaces; - -namespace Umbraco.Core -{ - - - /// - /// A resolver to return all ICacheRefresher objects - /// - internal sealed class CacheRefreshersResolver : LegacyTransientObjectsResolver - { - /// - /// Constructor - /// - /// - /// - /// - internal CacheRefreshersResolver(IServiceProvider serviceProvider, ILogger logger, Func> refreshers) - : base(serviceProvider, logger, refreshers) - { - - } - - - /// - /// Gets the implementations. - /// - public IEnumerable CacheRefreshers - { - get - { - EnsureIsInitialized(); - return Values; - } - } - - protected override Guid GetUniqueIdentifier(ICacheRefresher obj) - { - return obj.UniqueIdentifier; - } - } -} diff --git a/src/Umbraco.Core/CodeAnnotations/ActionMetadataAttribute.cs b/src/Umbraco.Core/CodeAnnotations/ActionMetadataAttribute.cs index 697c6b7652..9ef87e9a5f 100644 --- a/src/Umbraco.Core/CodeAnnotations/ActionMetadataAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/ActionMetadataAttribute.cs @@ -1,12 +1,13 @@ -using System; +using System; +using Umbraco.Core.Exceptions; namespace Umbraco.Core.CodeAnnotations { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] internal class ActionMetadataAttribute : Attribute { - public string Category { get; private set; } - public string Name { get; private set; } + public string Category { get; } + public string Name { get; } /// /// Constructor used to assign a Category, since no name is assigned it will try to be translated from the language files based on the action's alias @@ -14,7 +15,7 @@ namespace Umbraco.Core.CodeAnnotations /// public ActionMetadataAttribute(string category) { - if (string.IsNullOrWhiteSpace(category)) throw new ArgumentException("Value cannot be null or whitespace.", "category"); + if (string.IsNullOrWhiteSpace(category)) throw new ArgumentNullOrEmptyException(nameof(category)); Category = category; } @@ -25,10 +26,10 @@ namespace Umbraco.Core.CodeAnnotations /// public ActionMetadataAttribute(string category, string name) { - if (string.IsNullOrWhiteSpace(category)) throw new ArgumentException("Value cannot be null or whitespace.", "category"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + if (string.IsNullOrWhiteSpace(category)) throw new ArgumentNullOrEmptyException(nameof(category)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); Category = category; Name = name; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs b/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs index 226b74266b..ae864e85be 100644 --- a/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs @@ -32,4 +32,4 @@ namespace Umbraco.Core.CodeAnnotations return this._friendlyName; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoExperimentalFeatureAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoExperimentalFeatureAttribute.cs index 568561c078..e1377ff266 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoExperimentalFeatureAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoExperimentalFeatureAttribute.cs @@ -1,35 +1,35 @@ -using System; +using System; namespace Umbraco.Core.CodeAnnotations { - /// - /// Marks the program elements that Umbraco is experimenting with and could become public. - /// - /// - /// Indicates that Umbraco is experimenting with code that potentially could become - /// public, but we're not sure, and the code is not stable and can be refactored at any time. - /// The issue tracker should contain more details, discussion, and planning. - /// - [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] - internal sealed class UmbracoExperimentalFeatureAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a description. - /// - /// The text string that describes what is intended. - public UmbracoExperimentalFeatureAttribute(string description) - { + /// + /// Marks the program elements that Umbraco is experimenting with and could become public. + /// + /// + /// Indicates that Umbraco is experimenting with code that potentially could become + /// public, but we're not sure, and the code is not stable and can be refactored at any time. + /// The issue tracker should contain more details, discussion, and planning. + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] + internal sealed class UmbracoExperimentalFeatureAttribute : Attribute + { + /// + /// Initializes a new instance of the class with a description. + /// + /// The text string that describes what is intended. + public UmbracoExperimentalFeatureAttribute(string description) + { - } + } - /// - /// Initializes a new instance of the class with a tracker url and a description. - /// - /// The url of a tracker issue containing more details, discussion, and planning. - /// The text string that describes what is intended. - public UmbracoExperimentalFeatureAttribute(string trackerUrl, string description) - { - - } - } -} \ No newline at end of file + /// + /// Initializes a new instance of the class with a tracker url and a description. + /// + /// The url of a tracker issue containing more details, discussion, and planning. + /// The text string that describes what is intended. + public UmbracoExperimentalFeatureAttribute(string trackerUrl, string description) + { + + } + } +} diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs index 8a44ba1589..d2c431254f 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs @@ -23,4 +23,4 @@ namespace Umbraco.Core.CodeAnnotations public Type ModelType { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoProposedPublicAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoProposedPublicAttribute.cs index b34726960d..24d46224c5 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoProposedPublicAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoProposedPublicAttribute.cs @@ -1,36 +1,36 @@ -using System; +using System; namespace Umbraco.Core.CodeAnnotations { - /// - /// Marks the program elements that Umbraco is considering making public. - /// - /// - /// Indicates that Umbraco considers making the (currently internal) program element public - /// at some point in the future, but the decision is not fully made yet and is still pending reviews. - /// Note that it is not a commitment to make the program element public. It may not ever become public. - /// The issue tracker should contain more details, discussion, and planning. - /// - [AttributeUsage(AttributeTargets.All, AllowMultiple=false, Inherited=false)] - internal sealed class UmbracoProposedPublicAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a description. - /// - /// The text string that describes what is intended. - public UmbracoProposedPublicAttribute(string description) - { + /// + /// Marks the program elements that Umbraco is considering making public. + /// + /// + /// Indicates that Umbraco considers making the (currently internal) program element public + /// at some point in the future, but the decision is not fully made yet and is still pending reviews. + /// Note that it is not a commitment to make the program element public. It may not ever become public. + /// The issue tracker should contain more details, discussion, and planning. + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple=false, Inherited=false)] + internal sealed class UmbracoProposedPublicAttribute : Attribute + { + /// + /// Initializes a new instance of the class with a description. + /// + /// The text string that describes what is intended. + public UmbracoProposedPublicAttribute(string description) + { - } + } - /// - /// Initializes a new instance of the class with a tracker url and a description. - /// - /// The url of a tracker issue containing more details, discussion, and planning. - /// The text string that describes what is intended. - public UmbracoProposedPublicAttribute(string trackerUrl, string description) - { - - } - } -} \ No newline at end of file + /// + /// Initializes a new instance of the class with a tracker url and a description. + /// + /// The url of a tracker issue containing more details, discussion, and planning. + /// The text string that describes what is intended. + public UmbracoProposedPublicAttribute(string trackerUrl, string description) + { + + } + } +} diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs index 0bb9de6c86..0666b68166 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Core.CodeAnnotations { @@ -12,4 +12,4 @@ namespace Umbraco.Core.CodeAnnotations UdiType = udiType; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoWillObsoleteAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoWillObsoleteAttribute.cs index 745d74bb50..3d129a8a0a 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoWillObsoleteAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoWillObsoleteAttribute.cs @@ -5,33 +5,33 @@ using System.Text; namespace Umbraco.Core.CodeAnnotations { - /// - /// Marks the program elements that Umbraco will obsolete. - /// - /// - /// Indicates that Umbraco will obsolete the program element at some point in the future, but we do not want to - /// explicitely mark it [Obsolete] yet to avoid warning messages showing when developers compile Umbraco. - /// - [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] - internal sealed class UmbracoWillObsoleteAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a description. - /// - /// The text string that describes what is intended. - public UmbracoWillObsoleteAttribute(string description) - { + /// + /// Marks the program elements that Umbraco will obsolete. + /// + /// + /// Indicates that Umbraco will obsolete the program element at some point in the future, but we do not want to + /// explicitely mark it [Obsolete] yet to avoid warning messages showing when developers compile Umbraco. + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] + internal sealed class UmbracoWillObsoleteAttribute : Attribute + { + /// + /// Initializes a new instance of the class with a description. + /// + /// The text string that describes what is intended. + public UmbracoWillObsoleteAttribute(string description) + { - } + } - /// - /// Initializes a new instance of the class with a tracker url and a description. - /// - /// The url of a tracker issue containing more details, discussion, and planning. - /// The text string that describes what is intended. - public UmbracoWillObsoleteAttribute(string trackerUrl, string description) - { - - } - } + /// + /// Initializes a new instance of the class with a tracker url and a description. + /// + /// The url of a tracker issue containing more details, discussion, and planning. + /// The text string that describes what is intended. + public UmbracoWillObsoleteAttribute(string trackerUrl, string description) + { + + } + } } diff --git a/src/Umbraco.Core/Collections/CompositeIntStringKey.cs b/src/Umbraco.Core/Collections/CompositeIntStringKey.cs new file mode 100644 index 0000000000..eb9db80990 --- /dev/null +++ b/src/Umbraco.Core/Collections/CompositeIntStringKey.cs @@ -0,0 +1,43 @@ +using System; + +namespace Umbraco.Core.Collections +{ + /// + /// Represents a composite key of (int, string) for fast dictionaries. + /// + /// + /// The integer part of the key must be greater than, or equal to, zero. + /// The string part of the key is case-insensitive. + /// Null is a valid value for both parts. + /// + public struct CompositeIntStringKey : IEquatable + { + private readonly int _key1; + private readonly string _key2; + + /// + /// Initializes a new instance of the struct. + /// + public CompositeIntStringKey(int? key1, string key2) + { + if (key1 < 0) throw new ArgumentOutOfRangeException(nameof(key1)); + _key1 = key1 ?? -1; + _key2 = key2?.ToLowerInvariant() ?? "NULL"; + } + + public bool Equals(CompositeIntStringKey other) + => _key2 == other._key2 && _key1 == other._key1; + + public override bool Equals(object obj) + => obj is CompositeIntStringKey other && _key2 == other._key2 && _key1 == other._key1; + + public override int GetHashCode() + => _key2.GetHashCode() * 31 + _key1; + + public static bool operator ==(CompositeIntStringKey key1, CompositeIntStringKey key2) + => key1._key2 == key2._key2 && key1._key1 == key2._key1; + + public static bool operator !=(CompositeIntStringKey key1, CompositeIntStringKey key2) + => key1._key2 != key2._key2 || key1._key1 != key2._key1; + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs index 1a4e7ae1a9..cc555afe55 100644 --- a/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs +++ b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs @@ -10,7 +10,8 @@ namespace Umbraco.Core.Collections /// /// Initializes a new instance of the struct. /// - public CompositeTypeTypeKey(Type type1, Type type2) : this() + public CompositeTypeTypeKey(Type type1, Type type2) + : this() { Type1 = type1; Type2 = type2; @@ -19,12 +20,12 @@ namespace Umbraco.Core.Collections /// /// Gets the first type. /// - public Type Type1 { get; private set; } + public Type Type1 { get; } /// /// Gets the second type. /// - public Type Type2 { get; private set; } + public Type Type2 { get; } /// public bool Equals(CompositeTypeTypeKey other) @@ -35,7 +36,7 @@ namespace Umbraco.Core.Collections /// public override bool Equals(object obj) { - var other = obj is CompositeTypeTypeKey ? (CompositeTypeTypeKey)obj : default(CompositeTypeTypeKey); + var other = obj is CompositeTypeTypeKey key ? key : default; return Type1 == other.Type1 && Type2 == other.Type2; } diff --git a/src/Umbraco.Core/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs similarity index 97% rename from src/Umbraco.Core/ConcurrentHashSet.cs rename to src/Umbraco.Core/Collections/ConcurrentHashSet.cs index 1e318b36d1..39cb701e69 100644 --- a/src/Umbraco.Core/ConcurrentHashSet.cs +++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs @@ -1,167 +1,167 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace Umbraco.Core -{ - /// - /// A thread-safe representation of a . - /// Enumerating this collection is thread-safe and will only operate on a clone that is generated before returning the enumerator. - /// - /// - [Serializable] - public class ConcurrentHashSet : ICollection - { - private readonly HashSet _innerSet = new HashSet(); - private readonly ReaderWriterLockSlim _instanceLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - /// 1 - public IEnumerator GetEnumerator() - { - return GetThreadSafeClone().GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - /// 2 - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// Removes the first occurrence of a specific object from the . - /// - /// - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - /// - /// The object to remove from the .The is read-only. - public bool Remove(T item) - { - using (new WriteLock(_instanceLocker)) - { - return _innerSet.Remove(item); - } - } - - - /// - /// Gets the number of elements contained in the . - /// - /// - /// The number of elements contained in the . - /// - /// 2 - public int Count - { - get { return GetThreadSafeClone().Count; } - } - - /// - /// Gets a value indicating whether the is read-only. - /// - /// - /// true if the is read-only; otherwise, false. - /// - public bool IsReadOnly - { - get { return false; } - } - - /// - /// Adds an item to the . - /// - /// The object to add to the .The is read-only. - public void Add(T item) - { - using (new WriteLock(_instanceLocker)) - { - _innerSet.Add(item); - } - } - - /// - /// Attempts to add an item to the collection - /// - /// - /// - public bool TryAdd(T item) - { - var clone = GetThreadSafeClone(); - if (clone.Contains(item)) return false; - using (new WriteLock(_instanceLocker)) - { - //double check - if (_innerSet.Contains(item)) return false; - _innerSet.Add(item); - return true; - } - } - - /// - /// Removes all items from the . - /// - /// The is read-only. - public void Clear() - { - using (new WriteLock(_instanceLocker)) - { - _innerSet.Clear(); - } - } - - /// - /// Determines whether the contains a specific value. - /// - /// - /// true if is found in the ; otherwise, false. - /// - /// The object to locate in the . - public bool Contains(T item) - { - return GetThreadSafeClone().Contains(item); - } - - /// - /// Copies the elements of the to an , starting at a specified index. - /// - /// The one-dimensional that is the destination of the elements copied from the . The array must have zero-based indexing.The zero-based index in at which copying begins. is a null reference (Nothing in Visual Basic). is less than zero. is equal to or greater than the length of the -or- The number of elements in the source is greater than the available space from to the end of the destination . - public void CopyTo(T[] array, int index) - { - var clone = GetThreadSafeClone(); - clone.CopyTo(array, index); - } - - private HashSet GetThreadSafeClone() - { - HashSet clone = null; - using (new WriteLock(_instanceLocker)) - { - clone = new HashSet(_innerSet, _innerSet.Comparer); - } - return clone; - } - - /// - /// Copies the elements of the to an , starting at a particular index. - /// - /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. The zero-based index in at which copying begins. is null. is less than zero. is multidimensional.-or- The number of elements in the source is greater than the available space from to the end of the destination . The type of the source cannot be cast automatically to the type of the destination . 2 - public void CopyTo(Array array, int index) - { - var clone = GetThreadSafeClone(); - Array.Copy(clone.ToArray(), 0, array, index, clone.Count); - } - } -} \ No newline at end of file +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Umbraco.Core.Collections +{ + /// + /// A thread-safe representation of a . + /// Enumerating this collection is thread-safe and will only operate on a clone that is generated before returning the enumerator. + /// + /// + [Serializable] + public class ConcurrentHashSet : ICollection + { + private readonly HashSet _innerSet = new HashSet(); + private readonly ReaderWriterLockSlim _instanceLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + /// 1 + public IEnumerator GetEnumerator() + { + return GetThreadSafeClone().GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + /// 2 + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Removes the first occurrence of a specific object from the . + /// + /// + /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + /// + /// The object to remove from the .The is read-only. + public bool Remove(T item) + { + using (new WriteLock(_instanceLocker)) + { + return _innerSet.Remove(item); + } + } + + + /// + /// Gets the number of elements contained in the . + /// + /// + /// The number of elements contained in the . + /// + /// 2 + public int Count + { + get { return GetThreadSafeClone().Count; } + } + + /// + /// Gets a value indicating whether the is read-only. + /// + /// + /// true if the is read-only; otherwise, false. + /// + public bool IsReadOnly + { + get { return false; } + } + + /// + /// Adds an item to the . + /// + /// The object to add to the .The is read-only. + public void Add(T item) + { + using (new WriteLock(_instanceLocker)) + { + _innerSet.Add(item); + } + } + + /// + /// Attempts to add an item to the collection + /// + /// + /// + public bool TryAdd(T item) + { + var clone = GetThreadSafeClone(); + if (clone.Contains(item)) return false; + using (new WriteLock(_instanceLocker)) + { + //double check + if (_innerSet.Contains(item)) return false; + _innerSet.Add(item); + return true; + } + } + + /// + /// Removes all items from the . + /// + /// The is read-only. + public void Clear() + { + using (new WriteLock(_instanceLocker)) + { + _innerSet.Clear(); + } + } + + /// + /// Determines whether the contains a specific value. + /// + /// + /// true if is found in the ; otherwise, false. + /// + /// The object to locate in the . + public bool Contains(T item) + { + return GetThreadSafeClone().Contains(item); + } + + /// + /// Copies the elements of the to an , starting at a specified index. + /// + /// The one-dimensional that is the destination of the elements copied from the . The array must have zero-based indexing.The zero-based index in at which copying begins. is a null reference (Nothing in Visual Basic). is less than zero. is equal to or greater than the length of the -or- The number of elements in the source is greater than the available space from to the end of the destination . + public void CopyTo(T[] array, int index) + { + var clone = GetThreadSafeClone(); + clone.CopyTo(array, index); + } + + private HashSet GetThreadSafeClone() + { + HashSet clone = null; + using (new WriteLock(_instanceLocker)) + { + clone = new HashSet(_innerSet, _innerSet.Comparer); + } + return clone; + } + + /// + /// Copies the elements of the to an , starting at a particular index. + /// + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. The zero-based index in at which copying begins. is null. is less than zero. is multidimensional.-or- The number of elements in the source is greater than the available space from to the end of the destination . The type of the source cannot be cast automatically to the type of the destination . 2 + public void CopyTo(Array array, int index) + { + var clone = GetThreadSafeClone(); + Array.Copy(clone.ToArray(), 0, array, index, clone.Count); + } + } +} diff --git a/src/Umbraco.Core/Collections/DeepCloneableList.cs b/src/Umbraco.Core/Collections/DeepCloneableList.cs index 5067562aa7..48d35efde9 100644 --- a/src/Umbraco.Core/Collections/DeepCloneableList.cs +++ b/src/Umbraco.Core/Collections/DeepCloneableList.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Collections { @@ -15,12 +13,12 @@ namespace Umbraco.Core.Collections internal class DeepCloneableList : List, IDeepCloneable, IRememberBeingDirty { private readonly ListCloneBehavior _listCloneBehavior; - + public DeepCloneableList(ListCloneBehavior listCloneBehavior) { _listCloneBehavior = listCloneBehavior; } - + public DeepCloneableList(IEnumerable collection, ListCloneBehavior listCloneBehavior) : base(collection) { _listCloneBehavior = listCloneBehavior; @@ -94,26 +92,27 @@ namespace Umbraco.Core.Collections return this.OfType().Any(x => x.WasDirty()); } - /// - /// Always returns false, the list has no properties we need to report - /// - /// - /// + /// + /// Always return false, the list has no properties that can be dirty. public bool IsPropertyDirty(string propName) { return false; } - /// - /// Always returns false, the list has no properties we need to report - /// - /// - /// + /// + /// Always return false, the list has no properties that can be dirty. public bool WasPropertyDirty(string propertyName) { return false; } + /// + /// Always return an empty enumerable, the list has no properties that can be dirty. + public IEnumerable GetDirtyProperties() + { + return Enumerable.Empty(); + } + public void ResetDirtyProperties() { foreach (var dc in this.OfType()) @@ -122,20 +121,26 @@ namespace Umbraco.Core.Collections } } - public void ForgetPreviouslyDirtyProperties() + public void ResetWereDirtyProperties() { foreach (var dc in this.OfType()) { - dc.ForgetPreviouslyDirtyProperties(); + dc.ResetWereDirtyProperties(); } } - public void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public void ResetDirtyProperties(bool rememberDirty) { foreach (var dc in this.OfType()) { - dc.ResetDirtyProperties(rememberPreviouslyChangedProperties); + dc.ResetDirtyProperties(rememberDirty); } } + + /// Always return an empty enumerable, the list has no properties that can be dirty. + public IEnumerable GetWereDirtyProperties() + { + return Enumerable.Empty(); + } } } diff --git a/src/Umbraco.Core/Collections/ListCloneBehavior.cs b/src/Umbraco.Core/Collections/ListCloneBehavior.cs index 4fe935f7ff..14c6a0841f 100644 --- a/src/Umbraco.Core/Collections/ListCloneBehavior.cs +++ b/src/Umbraco.Core/Collections/ListCloneBehavior.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Collections +namespace Umbraco.Core.Collections { internal enum ListCloneBehavior { @@ -17,4 +17,4 @@ namespace Umbraco.Core.Collections /// Always } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/ObservableDictionary.cs b/src/Umbraco.Core/Collections/ObservableDictionary.cs similarity index 94% rename from src/Umbraco.Core/ObservableDictionary.cs rename to src/Umbraco.Core/Collections/ObservableDictionary.cs index 7e988e2b05..caa2be92a8 100644 --- a/src/Umbraco.Core/ObservableDictionary.cs +++ b/src/Umbraco.Core/Collections/ObservableDictionary.cs @@ -1,172 +1,170 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Core -{ - /// - /// An ObservableDictionary - /// - /// - /// Assumes that the key will not change and is unique for each element in the collection. - /// Collection is not thread-safe, so calls should be made single-threaded. - /// - /// The type of elements contained in the BindableCollection - /// The type of the indexing key - public class ObservableDictionary : ObservableCollection - { - protected Dictionary Indecies = new Dictionary(); - protected Func KeySelector; - - /// - /// Create new ObservableDictionary - /// - /// Selector function to create key from value - public ObservableDictionary(Func keySelector) - : base() - { - if (keySelector == null) throw new ArgumentException("keySelector"); - KeySelector = keySelector; - } - - #region Protected Methods - protected override void InsertItem(int index, TValue item) - { - var key = KeySelector(item); - if (Indecies.ContainsKey(key)) - throw new DuplicateKeyException(key.ToString()); - - if (index != this.Count) - { - foreach (var k in Indecies.Keys.Where(k => Indecies[k] >= index).ToList()) - { - Indecies[k]++; - } - } - - base.InsertItem(index, item); - Indecies[key] = index; - - } - - protected override void ClearItems() - { - base.ClearItems(); - Indecies.Clear(); - } - - - protected override void RemoveItem(int index) - { - var item = this[index]; - var key = KeySelector(item); - - base.RemoveItem(index); - - Indecies.Remove(key); - - foreach (var k in Indecies.Keys.Where(k => Indecies[k] > index).ToList()) - { - Indecies[k]--; - } - } - #endregion - - public virtual bool ContainsKey(TKey key) - { - return Indecies.ContainsKey(key); - } - - /// - /// Gets or sets the element with the specified key. If setting a new value, new value must have same key. - /// - /// Key of element to replace - /// - public virtual TValue this[TKey key] - { - - get { return this[Indecies[key]]; } - set - { - //confirm key matches - if (!KeySelector(value).Equals(key)) - throw new InvalidOperationException("Key of new value does not match"); - - if (!Indecies.ContainsKey(key)) - { - this.Add(value); - } - else - { - this[Indecies[key]] = value; - } - } - } - - /// - /// Replaces element at given key with new value. New value must have same key. - /// - /// Key of element to replace - /// New value - /// - /// - /// False if key not found - public virtual bool Replace(TKey key, TValue value) - { - if (!Indecies.ContainsKey(key)) return false; - //confirm key matches - if (!KeySelector(value).Equals(key)) - throw new InvalidOperationException("Key of new value does not match"); - - this[Indecies[key]] = value; - return true; - - } - - public virtual bool Remove(TKey key) - { - if (!Indecies.ContainsKey(key)) return false; - - this.RemoveAt(Indecies[key]); - return true; - - } - - /// - /// Allows us to change the key of an item - /// - /// - /// - public virtual void ChangeKey(TKey currentKey, TKey newKey) - { - if (!Indecies.ContainsKey(currentKey)) - { - throw new InvalidOperationException("No item with the key " + currentKey + "was found in the collection"); - } - if (ContainsKey(newKey)) - { - throw new DuplicateKeyException(newKey.ToString()); - } - - var currentIndex = Indecies[currentKey]; - - Indecies.Remove(currentKey); - Indecies.Add(newKey, currentIndex); - } - - internal class DuplicateKeyException : Exception - { - - public string Key { get; private set; } - public DuplicateKeyException(string key) - : base("Attempted to insert duplicate key " + key + " in collection") - { - Key = key; - } - } - - } -} +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Umbraco.Core.Collections +{ + /// + /// An ObservableDictionary + /// + /// + /// Assumes that the key will not change and is unique for each element in the collection. + /// Collection is not thread-safe, so calls should be made single-threaded. + /// + /// The type of elements contained in the BindableCollection + /// The type of the indexing key + public class ObservableDictionary : ObservableCollection + { + protected Dictionary Indecies = new Dictionary(); + protected Func KeySelector; + + /// + /// Create new ObservableDictionary + /// + /// Selector function to create key from value + public ObservableDictionary(Func keySelector) + : base() + { + if (keySelector == null) throw new ArgumentException("keySelector"); + KeySelector = keySelector; + } + + #region Protected Methods + protected override void InsertItem(int index, TValue item) + { + var key = KeySelector(item); + if (Indecies.ContainsKey(key)) + throw new DuplicateKeyException(key.ToString()); + + if (index != this.Count) + { + foreach (var k in Indecies.Keys.Where(k => Indecies[k] >= index).ToList()) + { + Indecies[k]++; + } + } + + base.InsertItem(index, item); + Indecies[key] = index; + + } + + protected override void ClearItems() + { + base.ClearItems(); + Indecies.Clear(); + } + + + protected override void RemoveItem(int index) + { + var item = this[index]; + var key = KeySelector(item); + + base.RemoveItem(index); + + Indecies.Remove(key); + + foreach (var k in Indecies.Keys.Where(k => Indecies[k] > index).ToList()) + { + Indecies[k]--; + } + } + #endregion + + public virtual bool ContainsKey(TKey key) + { + return Indecies.ContainsKey(key); + } + + /// + /// Gets or sets the element with the specified key. If setting a new value, new value must have same key. + /// + /// Key of element to replace + /// + public virtual TValue this[TKey key] + { + + get { return this[Indecies[key]]; } + set + { + //confirm key matches + if (!KeySelector(value).Equals(key)) + throw new InvalidOperationException("Key of new value does not match"); + + if (!Indecies.ContainsKey(key)) + { + this.Add(value); + } + else + { + this[Indecies[key]] = value; + } + } + } + + /// + /// Replaces element at given key with new value. New value must have same key. + /// + /// Key of element to replace + /// New value + /// + /// + /// False if key not found + public virtual bool Replace(TKey key, TValue value) + { + if (!Indecies.ContainsKey(key)) return false; + //confirm key matches + if (!KeySelector(value).Equals(key)) + throw new InvalidOperationException("Key of new value does not match"); + + this[Indecies[key]] = value; + return true; + + } + + public virtual bool Remove(TKey key) + { + if (!Indecies.ContainsKey(key)) return false; + + this.RemoveAt(Indecies[key]); + return true; + + } + + /// + /// Allows us to change the key of an item + /// + /// + /// + public virtual void ChangeKey(TKey currentKey, TKey newKey) + { + if (!Indecies.ContainsKey(currentKey)) + { + throw new InvalidOperationException("No item with the key " + currentKey + "was found in the collection"); + } + if (ContainsKey(newKey)) + { + throw new DuplicateKeyException(newKey.ToString()); + } + + var currentIndex = Indecies[currentKey]; + + Indecies.Remove(currentKey); + Indecies.Add(newKey, currentIndex); + } + + internal class DuplicateKeyException : Exception + { + + public string Key { get; private set; } + public DuplicateKeyException(string key) + : base("Attempted to insert duplicate key " + key + " in collection") + { + Key = key; + } + } + + } +} diff --git a/src/Umbraco.Core/OrderedHashSet.cs b/src/Umbraco.Core/Collections/OrderedHashSet.cs similarity index 89% rename from src/Umbraco.Core/OrderedHashSet.cs rename to src/Umbraco.Core/Collections/OrderedHashSet.cs index 801f1a9a41..beae17adea 100644 --- a/src/Umbraco.Core/OrderedHashSet.cs +++ b/src/Umbraco.Core/Collections/OrderedHashSet.cs @@ -1,17 +1,17 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; -namespace Umbraco.Core +namespace Umbraco.Core.Collections { /// /// A custom collection similar to HashSet{T} which only contains unique items, however this collection keeps items in order - /// and is customizable to keep the newest or oldest equatable item + /// and is customizable to keep the newest or oldest equatable item /// /// internal class OrderedHashSet : KeyedCollection { private readonly bool _keepOldest; - public OrderedHashSet(bool keepOldest = true) + public OrderedHashSet(bool keepOldest = true) { _keepOldest = keepOldest; } @@ -39,7 +39,7 @@ namespace Umbraco.Core } base.InsertItem(index, item); } - } + } } protected override T GetKeyForItem(T item) @@ -47,4 +47,4 @@ namespace Umbraco.Core return item; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Collections/TopoGraph.cs b/src/Umbraco.Core/Collections/TopoGraph.cs new file mode 100644 index 0000000000..b8ded4a458 --- /dev/null +++ b/src/Umbraco.Core/Collections/TopoGraph.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Collections +{ + public class TopoGraph + { + internal const string CycleDependencyError = "Cyclic dependency."; + internal const string MissingDependencyError = "Missing dependency."; + + public class Node + { + public Node(TKey key, TItem item, IEnumerable dependencies) + { + Key = key; + Item = item; + Dependencies = dependencies; + } + + public TKey Key { get; } + public TItem Item { get; } + public IEnumerable Dependencies { get; } + } + + public static Node CreateNode(TKey key, TItem item, IEnumerable dependencies) + => new Node(key, item, dependencies); + } + + /// + /// Represents a generic DAG that can be topologically sorted. + /// + /// The type of the keys. + /// The type of the items. + public class TopoGraph : TopoGraph + { + private readonly Func _getKey; + private readonly Func> _getDependencies; + private readonly Dictionary _items = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// A method that returns the key of an item. + /// A method that returns the dependency keys of an item. + public TopoGraph(Func getKey, Func> getDependencies) + { + _getKey = getKey; + _getDependencies = getDependencies; + } + + /// + /// Adds an item to the graph. + /// + /// The item. + public void AddItem(TItem item) + { + var key = _getKey(item); + _items[key] = item; + } + + /// + /// Adds items to the graph. + /// + /// The items. + public void AddItems(IEnumerable items) + { + foreach (var item in items) + AddItem(item); + } + + /// + /// Gets the sorted items. + /// + /// A value indicating whether to throw on cycles, or just ignore the branch. + /// A value indicating whether to throw on missing dependency, or just ignore the dependency. + /// A value indicating whether to reverse the order. + /// The (topologically) sorted items. + public IEnumerable GetSortedItems(bool throwOnCycle = true, bool throwOnMissing = true, bool reverse = false) + { + var sorted = new TItem[_items.Count]; + var visited = new HashSet(); + var index = reverse ? _items.Count - 1 : 0; + var incr = reverse ? -1 : +1; + + foreach (var item in _items.Values) + Visit(item, visited, sorted, ref index, incr, throwOnCycle, throwOnMissing); + + return sorted; + } + + private static bool Contains(TItem[] items, TItem item, int start, int count) + { + return Array.IndexOf(items, item, start, count) >= 0; + } + + private void Visit(TItem item, ISet visited, TItem[] sorted, ref int index, int incr, bool throwOnCycle, bool throwOnMissing) + { + if (visited.Contains(item)) + { + // visited but not sorted yet = cycle + var start = incr > 0 ? 0 : index; + var count = incr > 0 ? index : sorted.Length - index; + if (throwOnCycle && Contains(sorted, item, start, count) == false) + throw new Exception(CycleDependencyError); + return; + } + + visited.Add(item); + + var keys = _getDependencies(item); + var dependencies = keys == null ? null : FindDependencies(keys, throwOnMissing); + + if (dependencies != null) + foreach (var dep in dependencies) + Visit(dep, visited, sorted, ref index, incr, throwOnCycle, throwOnMissing); + + sorted[index] = item; + index += incr; + } + + private IEnumerable FindDependencies(IEnumerable keys, bool throwOnMissing) + { + foreach (var key in keys) + { + TItem value; + if (_items.TryGetValue(key, out value)) + yield return value; + else if (throwOnMissing) + throw new Exception(MissingDependencyError); + } + } + } +} diff --git a/src/Umbraco.Core/Components/BootLoader.cs b/src/Umbraco.Core/Components/BootLoader.cs new file mode 100644 index 0000000000..0b2374a6bb --- /dev/null +++ b/src/Umbraco.Core/Components/BootLoader.cs @@ -0,0 +1,367 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using LightInject; +using Umbraco.Core.Collections; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Components +{ + // note: this class is NOT thread-safe in any ways + + internal class BootLoader + { + private readonly IServiceContainer _container; + private readonly ProfilingLogger _proflog; + private readonly ILogger _logger; + private IUmbracoComponent[] _components; + private bool _booted; + + private const int LogThresholdMilliseconds = 100; + + /// + /// Initializes a new instance of the class. + /// + /// The application container. + public BootLoader(IServiceContainer container) + { + if (container == null) throw new ArgumentNullException(nameof(container)); + _container = container; + _proflog = container.GetInstance(); + _logger = container.GetInstance(); + } + + private class EnableInfo + { + public bool Enabled; + public int Weight = -1; + } + + public void Boot(IEnumerable componentTypes, RuntimeLevel level) + { + if (_booted) throw new InvalidOperationException("Can not boot, has already booted."); + + var orderedComponentTypes = PrepareComponentTypes(componentTypes, level); + + InstanciateComponents(orderedComponentTypes); + ComposeComponents(level); + + using (var scope = _container.GetInstance().CreateScope()) + { + InitializeComponents(); + scope.Complete(); + } + + // rejoice! + _booted = true; + } + + private IEnumerable PrepareComponentTypes(IEnumerable componentTypes, RuntimeLevel level) + { + using (_proflog.DebugDuration("Preparing component types.", "Prepared component types.")) + { + return PrepareComponentTypes2(componentTypes, level); + } + } + + private IEnumerable PrepareComponentTypes2(IEnumerable componentTypes, RuntimeLevel level) + { + // create a list, remove those that cannot be enabled due to runtime level + var componentTypeList = componentTypes + .Where(x => + { + var attr = x.GetCustomAttribute(); + return attr == null || level >= attr.MinLevel; + }) + .ToList(); + + // cannot remove that one - ever + if (componentTypeList.Contains(typeof(UmbracoCoreComponent)) == false) + componentTypeList.Add(typeof(UmbracoCoreComponent)); + + // enable or disable components + EnableDisableComponents(componentTypeList); + + // sort the components according to their dependencies + var requirements = new Dictionary>(); + foreach (var type in componentTypeList) requirements[type] = null; + foreach (var type in componentTypeList) + { + GatherRequirementsFromRequireAttribute(type, componentTypeList, requirements); + GatherRequirementsFromRequiredAttribute(type, componentTypeList, requirements); + } + + // only for debugging, this is verbose + //_logger.Debug(GetComponentsReport(requirements)); + + // sort components + var graph = new TopoGraph>>(kvp => kvp.Key, kvp => kvp.Value); + graph.AddItems(requirements); + List sortedComponentTypes; + try + { + sortedComponentTypes = graph.GetSortedItems().Select(x => x.Key).ToList(); + } + catch (Exception e) + { + // in case of an error, force-dump everything to log + _logger.Info(GetComponentsReport(requirements)); + _logger.Error("Failed to sort components.", e); + throw; + } + + // bit verbose but should help for troubleshooting + var text = "Ordered Components: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComponentTypes) + Environment.NewLine; + Console.WriteLine(text); + _logger.Debug(text); + + return sortedComponentTypes; + } + + private static string GetComponentsReport(Dictionary> requirements) + { + var text = new StringBuilder(); + text.AppendLine("Components & Dependencies:"); + text.AppendLine(); + + foreach (var kvp in requirements) + { + var type = kvp.Key; + + text.AppendLine(type.FullName); + foreach (var attribute in type.GetCustomAttributes()) + text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue + ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")")) + : "")); + foreach (var attribute in type.GetCustomAttributes()) + text.AppendLine(" -< " + attribute.RequiringType); + foreach (var i in type.GetInterfaces()) + { + text.AppendLine(" : " + i.FullName); + foreach (var attribute in i.GetCustomAttributes()) + text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue + ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")")) + : "")); + foreach (var attribute in i.GetCustomAttributes()) + text.AppendLine(" -< " + attribute.RequiringType); + } + if (kvp.Value != null) + foreach (var t in kvp.Value) + text.AppendLine(" = " + t); + text.AppendLine(); + } + text.AppendLine("/"); + text.AppendLine(); + return text.ToString(); + } + + private static void EnableDisableComponents(ICollection types) + { + var enabled = new Dictionary(); + + // process the enable/disable attributes + // these two attributes are *not* inherited and apply to *classes* only (not interfaces). + // remote declarations (when a component enables/disables *another* component) + // have priority over local declarations (when a component disables itself) so that + // ppl can enable components that, by default, are disabled. + // what happens in case of conflicting remote declarations is unspecified. more + // precisely, the last declaration to be processed wins, but the order of the + // declarations depends on the type finder and is unspecified. + foreach (var componentType in types) + { + foreach (var attr in componentType.GetCustomAttributes()) + { + var type = attr.EnabledType ?? componentType; + EnableInfo enableInfo; + if (enabled.TryGetValue(type, out enableInfo) == false) enableInfo = enabled[type] = new EnableInfo(); + var weight = type == componentType ? 1 : 2; + if (enableInfo.Weight > weight) continue; + + enableInfo.Enabled = true; + enableInfo.Weight = weight; + } + foreach (var attr in componentType.GetCustomAttributes()) + { + var type = attr.DisabledType ?? componentType; + if (type == typeof(UmbracoCoreComponent)) throw new InvalidOperationException("Cannot disable UmbracoCoreComponent."); + EnableInfo enableInfo; + if (enabled.TryGetValue(type, out enableInfo) == false) enableInfo = enabled[type] = new EnableInfo(); + var weight = type == componentType ? 1 : 2; + if (enableInfo.Weight > weight) continue; + + enableInfo.Enabled = false; + enableInfo.Weight = weight; + } + } + + // remove components that end up being disabled + foreach (var kvp in enabled.Where(x => x.Value.Enabled == false)) + types.Remove(kvp.Key); + } + + private static void GatherRequirementsFromRequireAttribute(Type type, ICollection types, IDictionary> requirements) + { + // get 'require' attributes + // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only + var requireAttributes = type + .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) // those marking interfaces + .Concat(type.GetCustomAttributes()); // those marking the component + + // what happens in case of conflicting attributes (different strong/weak for same type) is not specified. + foreach (var attr in requireAttributes) + { + if (attr.RequiredType == type) continue; // ignore self-requirements (+ exclude in implems, below) + + // requiring an interface = require any enabled component implementing that interface + // unless strong, and then require at least one enabled component implementing that interface + if (attr.RequiredType.IsInterface) + { + var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x)).ToList(); + if (implems.Count > 0) + { + if (requirements[type] == null) requirements[type] = new List(); + requirements[type].AddRange(implems); + } + else if (attr.Weak == false) // if explicitely set to !weak, is strong, else is weak + throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}."); + } + // requiring a class = require that the component is enabled + // unless weak, and then requires it if it is enabled + else + { + if (types.Contains(attr.RequiredType)) + { + if (requirements[type] == null) requirements[type] = new List(); + requirements[type].Add(attr.RequiredType); + } + else if (attr.Weak != true) // if not explicitely set to weak, is strong + throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}."); + } + } + } + + private static void GatherRequirementsFromRequiredAttribute(Type type, ICollection types, IDictionary> requirements) + { + // get 'required' attributes + // fixme explain + var requiredAttributes = type + .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) + .Concat(type.GetCustomAttributes()); + + foreach (var attr in requiredAttributes) + { + if (attr.RequiringType == type) continue; // ignore self-requirements (+ exclude in implems, below) + + if (attr.RequiringType.IsInterface) + { + var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x)).ToList(); + foreach (var implem in implems) + { + if (requirements[implem] == null) requirements[implem] = new List(); + requirements[implem].Add(type); + } + } + else + { + if (types.Contains(attr.RequiringType)) + { + if (requirements[attr.RequiringType] == null) requirements[attr.RequiringType] = new List(); + requirements[attr.RequiringType].Add(type); + } + } + } + } + + private void InstanciateComponents(IEnumerable types) + { + using (_proflog.DebugDuration("Instanciating components.", "Instanciated components.")) + { + _components = types.Select(x => (IUmbracoComponent) Activator.CreateInstance(x)).ToArray(); + } + } + + private void ComposeComponents(RuntimeLevel level) + { + using (_proflog.DebugDuration($"Composing components. (log when >{LogThresholdMilliseconds}ms)", "Composed components.")) + { + var composition = new Composition(_container, level); + foreach (var component in _components) + { + var componentType = component.GetType(); + using (_proflog.DebugDuration($"Composing {componentType.FullName}.", $"Composed {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + component.Compose(composition); + } + } + } + } + + private void InitializeComponents() + { + // use a container scope to ensure that PerScope instances are disposed + // components that require instances that should not survive should register them with PerScope lifetime + using (_proflog.DebugDuration($"Initializing components. (log when >{LogThresholdMilliseconds}ms)", "Initialized components.")) + using (_container.BeginScope()) + { + foreach (var component in _components) + { + var componentType = component.GetType(); + var initializers = componentType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.Name == "Initialize" && x.IsGenericMethod == false); + using (_proflog.DebugDuration($"Initializing {componentType.FullName}.", $"Initialised {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + foreach (var initializer in initializers) + { + var parameters = initializer.GetParameters() + .Select(x => GetParameter(componentType, x.ParameterType)) + .ToArray(); + initializer.Invoke(component, parameters); + } + } + } + } + } + + private object GetParameter(Type componentType, Type parameterType) + { + object param; + + try + { + param = _container.TryGetInstance(parameterType); + } + catch (Exception e) + { + throw new BootFailedException($"Could not get parameter of type {parameterType.FullName} for component {componentType.FullName}.", e); + } + + if (param == null) throw new BootFailedException($"Could not get parameter of type {parameterType.FullName} for component {componentType.FullName}."); + return param; + } + + public void Terminate() + { + if (_booted == false) + { + _proflog.Logger.Warn("Cannot terminate, has not booted."); + return; + } + + using (_proflog.DebugDuration($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated.")) + { + foreach (var component in _components) + { + var componentType = component.GetType(); + using (_proflog.DebugDuration($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + component.Terminate(); + } + } + } + } + } +} diff --git a/src/Umbraco.Core/Components/Composition.cs b/src/Umbraco.Core/Components/Composition.cs new file mode 100644 index 0000000000..671469c73a --- /dev/null +++ b/src/Umbraco.Core/Components/Composition.cs @@ -0,0 +1,37 @@ +using LightInject; + +namespace Umbraco.Core.Components +{ + /// + /// Represents a composition. + /// + /// Although a composition exposes the application's service container, people should use the + /// extension methods (such as PropertyEditors() or SetPublishedContentModelFactory()) and + /// avoid accessing the container. This is because everything needs to be properly registered and with + /// the proper lifecycle. These methods will take care of it. Directly registering into the container + /// may cause issues. + public class Composition + { + /// + /// Initializes a new instance of the class. + /// + /// A container. + /// The runtime level. + public Composition(IServiceContainer container, RuntimeLevel level) + { + Container = container; + RuntimeLevel = level; + } + + /// + /// Gets the container. + /// + /// Use with care! + public IServiceContainer Container { get; } + + /// + /// Gets the runtime level. + /// + public RuntimeLevel RuntimeLevel { get; } + } +} diff --git a/src/Umbraco.Core/Components/CompositionExtensions.cs b/src/Umbraco.Core/Components/CompositionExtensions.cs new file mode 100644 index 0000000000..7e94e4dc2b --- /dev/null +++ b/src/Umbraco.Core/Components/CompositionExtensions.cs @@ -0,0 +1,241 @@ +using System; +using System.Runtime.CompilerServices; +using LightInject; +using Umbraco.Core.Cache; +using Umbraco.Core.Dictionary; +using Umbraco.Core.Composing; +using Umbraco.Core.Migrations; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Strings; +using Umbraco.Core.Sync; +using Umbraco.Core._Legacy.PackageActions; + +namespace Umbraco.Core.Components +{ + /// + /// Provides extension methods to the class. + /// + public static class CompositionExtensions + { + #region Collection Builders + + /// + /// Gets the cache refreshers collection builder. + /// + /// The composition. + public static CacheRefresherCollectionBuilder CacheRefreshers(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the mappers collection builder. + /// + /// The composition. + public static MapperCollectionBuilder Mappers(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the package actions collection builder. + /// + /// The composition. + internal static PackageActionCollectionBuilder PackageActions(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the data editor collection builder. + /// + /// The composition. + public static DataEditorCollectionBuilder DataEditors(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the property value converters collection builder. + /// + /// The composition. + public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the url segment providers collection builder. + /// + /// The composition. + public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the validators collection builder. + /// + /// The composition. + internal static ManifestValueValidatorCollectionBuilder Validators(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the post-migrations collection builder. + /// + /// The composition. + internal static PostMigrationCollectionBuilder PostMigrations(this Composition composition) + => composition.Container.GetInstance(); + + #endregion + + #region Singleton + + /// + /// Sets the culture dictionary factory. + /// + /// The type of the factory. + /// The composition. + public static void SetCultureDictionaryFactory(this Composition composition) + where T : ICultureDictionaryFactory + { + composition.Container.RegisterSingleton(); + } + + /// + /// Sets the culture dictionary factory. + /// + /// The composition. + /// A function creating a culture dictionary factory. + public static void SetCultureDictionaryFactory(this Composition composition, Func factory) + { + composition.Container.RegisterSingleton(factory); + } + + /// + /// Sets the culture dictionary factory. + /// + /// The composition. + /// A factory. + public static void SetCultureDictionaryFactory(this Composition composition, ICultureDictionaryFactory factory) + { + composition.Container.RegisterSingleton(_ => factory); + } + + /// + /// Sets the published content model factory. + /// + /// The type of the factory. + /// The composition. + public static void SetPublishedContentModelFactory(this Composition composition) + where T : IPublishedModelFactory + { + composition.Container.RegisterSingleton(); + } + + /// + /// Sets the published content model factory. + /// + /// The composition. + /// A function creating a published content model factory. + public static void SetPublishedContentModelFactory(this Composition composition, Func factory) + { + composition.Container.RegisterSingleton(factory); + } + + /// + /// Sets the published content model factory. + /// + /// The composition. + /// A published content model factory. + public static void SetPublishedContentModelFactory(this Composition composition, IPublishedModelFactory factory) + { + composition.Container.RegisterSingleton(_ => factory); + } + + /// + /// Sets the server registrar. + /// + /// The type of the server registrar. + /// The composition. + public static void SetServerRegistrar(this Composition composition) + where T : IServerRegistrar + { + composition.Container.RegisterSingleton(); + } + + /// + /// Sets the server registrar. + /// + /// The composition. + /// A function creating a server registar. + public static void SetServerRegistrar(this Composition composition, Func factory) + { + composition.Container.RegisterSingleton(factory); + } + + /// + /// Sets the server registrar. + /// + /// The composition. + /// A server registrar. + public static void SetServerRegistrar(this Composition composition, IServerRegistrar registrar) + { + composition.Container.RegisterSingleton(_ => registrar); + } + + /// + /// Sets the server messenger. + /// + /// The type of the server registrar. + /// The composition. + public static void SetServerMessenger(this Composition composition) + where T : IServerMessenger + { + composition.Container.RegisterSingleton(); + } + + /// + /// Sets the server messenger. + /// + /// The composition. + /// A function creating a server messenger. + public static void SetServerMessenger(this Composition composition, Func factory) + { + composition.Container.RegisterSingleton(factory); + } + + /// + /// Sets the server messenger. + /// + /// The composition. + /// A server messenger. + public static void SetServerMessenger(this Composition composition, IServerMessenger registrar) + { + composition.Container.RegisterSingleton(_ => registrar); + } + + /// + /// Sets the short string helper. + /// + /// The type of the short string helper. + /// The composition. + public static void SetShortStringHelper(this Composition composition) + where T : IShortStringHelper + { + composition.Container.RegisterSingleton(); + } + + /// + /// Sets the short string helper. + /// + /// The composition. + /// A function creating a short string helper. + public static void SetShortStringHelper(this Composition composition, Func factory) + { + composition.Container.RegisterSingleton(factory); + } + + /// + /// Sets the short string helper. + /// + /// A composition. + /// A short string helper. + public static void SetShortStringHelper(this Composition composition, IShortStringHelper helper) + { + composition.Container.RegisterSingleton(_ => helper); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Components/DisableComponentAttribute.cs b/src/Umbraco.Core/Components/DisableComponentAttribute.cs new file mode 100644 index 0000000000..f7ff71e119 --- /dev/null +++ b/src/Umbraco.Core/Components/DisableComponentAttribute.cs @@ -0,0 +1,38 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a component should be disabled. + /// + /// + /// If a type is specified, disables the component of that type, else disables the component marked with the attribute. + /// This attribute is *not* inherited. + /// This attribute applies to classes only, it is not possible to enable/disable interfaces. + /// If a component ends up being both enabled and disabled: attributes marking the component itself have lower priority + /// than attributes on *other* components, eg if a component declares itself as disabled it is possible to enable it from + /// another component. Anything else is unspecified. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class DisableComponentAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public DisableComponentAttribute() + { } + + /// + /// Initializes a new instance of the class. + /// + public DisableComponentAttribute(Type disabledType) + { + DisabledType = disabledType; + } + + /// + /// Gets the disabled type, or null if it is the component marked with the attribute. + /// + public Type DisabledType { get; } + } +} diff --git a/src/Umbraco.Core/Components/EnableComponentAttribute.cs b/src/Umbraco.Core/Components/EnableComponentAttribute.cs new file mode 100644 index 0000000000..fa76dc2404 --- /dev/null +++ b/src/Umbraco.Core/Components/EnableComponentAttribute.cs @@ -0,0 +1,38 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a component should be enabled. + /// + /// + /// If a type is specified, enables the component of that type, else enables the component marked with the attribute. + /// This attribute is *not* inherited. + /// This attribute applies to classes only, it is not possible to enable/disable interfaces. + /// If a component ends up being both enabled and disabled: attributes marking the component itself have lower priority + /// than attributes on *other* components, eg if a component declares itself as disabled it is possible to enable it from + /// another component. Anything else is unspecified. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class EnableComponentAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public EnableComponentAttribute() + { } + + /// + /// Initializes a new instance of the class. + /// + public EnableComponentAttribute(Type enabledType) + { + EnabledType = enabledType; + } + + /// + /// Gets the enabled type, or null if it is the component marked with the attribute. + /// + public Type EnabledType { get; } + } +} diff --git a/src/Umbraco.Core/Components/IRuntimeComponent.cs b/src/Umbraco.Core/Components/IRuntimeComponent.cs new file mode 100644 index 0000000000..7f89d21e87 --- /dev/null +++ b/src/Umbraco.Core/Components/IRuntimeComponent.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Components +{ + public interface IRuntimeComponent : IUmbracoComponent + { } +} diff --git a/src/Umbraco.Core/Components/IUmbracoComponent.cs b/src/Umbraco.Core/Components/IUmbracoComponent.cs new file mode 100644 index 0000000000..d25b97c270 --- /dev/null +++ b/src/Umbraco.Core/Components/IUmbracoComponent.cs @@ -0,0 +1,21 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Components +{ + /// + /// Represents an Umbraco component. + /// + public interface IUmbracoComponent : IDiscoverable + { + /// + /// Composes the component. + /// + /// The composition. + void Compose(Composition composition); + + /// + /// Terminates the component. + /// + void Terminate(); + } +} diff --git a/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs b/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs new file mode 100644 index 0000000000..28ff286da3 --- /dev/null +++ b/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Core.Components +{ + [RequireComponent(typeof(IRuntimeComponent))] + public interface IUmbracoCoreComponent : IUmbracoComponent + { } +} diff --git a/src/Umbraco.Core/Components/IUmbracoUserComponent.cs b/src/Umbraco.Core/Components/IUmbracoUserComponent.cs new file mode 100644 index 0000000000..61a07cfd48 --- /dev/null +++ b/src/Umbraco.Core/Components/IUmbracoUserComponent.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Core.Components +{ + [RequireComponent(typeof(UmbracoCoreComponent))] + public interface IUmbracoUserComponent : IUmbracoComponent + { } +} diff --git a/src/Umbraco.Core/Components/RequireComponentAttribute.cs b/src/Umbraco.Core/Components/RequireComponentAttribute.cs new file mode 100644 index 0000000000..2d53413f99 --- /dev/null +++ b/src/Umbraco.Core/Components/RequireComponentAttribute.cs @@ -0,0 +1,59 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a component requires another component. + /// + /// + /// This attribute is *not* inherited. This means that a component class inheriting from + /// another component class does *not* inherit its requirements. However, the bootloader checks + /// the *interfaces* of every component for their requirements, so requirements declared on + /// interfaces are inherited by every component class implementing the interface. + /// When targetting a class, indicates a dependency on the component which must be enabled, + /// unless the requirement has explicitely been declared as weak (and then, only if the component + /// is enabled). + /// When targetting an interface, indicates a dependency on enabled components implementing + /// the interface. It could be no component at all, unless the requirement has explicitely been + /// declared as strong (and at least one component must be enabled). + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] + public class RequireComponentAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The type of the required component. + public RequireComponentAttribute(Type requiredType) + { + if (typeof(IUmbracoComponent).IsAssignableFrom(requiredType) == false) + throw new ArgumentException($"Type {requiredType.FullName} is invalid here because it does not implement {typeof(IUmbracoComponent).FullName}."); + RequiredType = requiredType; + } + + /// + /// Initializes a new instance of the class. + /// + /// The type of the required component. + /// A value indicating whether the requirement is weak. + public RequireComponentAttribute(Type requiredType, bool weak) + : this(requiredType) + { + Weak = weak; + } + + /// + /// Gets the required type. + /// + public Type RequiredType { get; } + + /// + /// Gets a value indicating whether the requirement is weak. + /// + /// Returns true if the requirement is weak (requires the other component if it + /// is enabled), false if the requirement is strong (requires the other component to be + /// enabled), and null if unspecified, in which case it is strong for classes and weak for + /// interfaces. + public bool? Weak { get; } + } +} diff --git a/src/Umbraco.Core/Components/RequiredComponentAttribute.cs b/src/Umbraco.Core/Components/RequiredComponentAttribute.cs new file mode 100644 index 0000000000..7895445179 --- /dev/null +++ b/src/Umbraco.Core/Components/RequiredComponentAttribute.cs @@ -0,0 +1,41 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a component is required by another component. + /// + /// + /// fixme + /// This attribute is *not* inherited. This means that a component class inheriting from + /// another component class does *not* inherit its requirements. However, the bootloader checks + /// the *interfaces* of every component for their requirements, so requirements declared on + /// interfaces are inherited by every component class implementing the interface. + /// When targetting a class, indicates a dependency on the component which must be enabled, + /// unless the requirement has explicitely been declared as weak (and then, only if the component + /// is enabled). + /// When targetting an interface, indicates a dependency on enabled components implementing + /// the interface. It could be no component at all, unless the requirement has explicitely been + /// declared as strong (and at least one component must be enabled). + /// + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] + public class RequiredComponentAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The type of the required component. + public RequiredComponentAttribute(Type requiringType) + { + if (typeof(IUmbracoComponent).IsAssignableFrom(requiringType) == false) + throw new ArgumentException($"Type {requiringType.FullName} is invalid here because it does not implement {typeof(IUmbracoComponent).FullName}."); + RequiringType = requiringType; + } + + /// + /// Gets the required type. + /// + public Type RequiringType { get; } + } +} diff --git a/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs b/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs new file mode 100644 index 0000000000..51920660d4 --- /dev/null +++ b/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace Umbraco.Core.Components +{ + [AttributeUsage(AttributeTargets.Class /*, AllowMultiple = false, Inherited = true*/)] + public class RuntimeLevelAttribute : Attribute + { + //public RuntimeLevelAttribute() + //{ } + + public RuntimeLevel MinLevel { get; set; } = RuntimeLevel.Boot; + } +} diff --git a/src/Umbraco.Core/Components/UmbracoComponentBase.cs b/src/Umbraco.Core/Components/UmbracoComponentBase.cs new file mode 100644 index 0000000000..476c4c6d59 --- /dev/null +++ b/src/Umbraco.Core/Components/UmbracoComponentBase.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Core.Components +{ + /// + /// Provides a base class for implementations. + /// + public abstract class UmbracoComponentBase : IUmbracoComponent + { + /// + public virtual void Compose(Composition composition) + { } + + /// + public virtual void Terminate() + { } + } +} diff --git a/src/Umbraco.Core/Components/UmbracoCoreComponent.cs b/src/Umbraco.Core/Components/UmbracoCoreComponent.cs new file mode 100644 index 0000000000..9f6709c494 --- /dev/null +++ b/src/Umbraco.Core/Components/UmbracoCoreComponent.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Components +{ + // the UmbracoCoreComponent requires all IUmbracoCoreComponent + // all user-level components should require the UmbracoCoreComponent + + [RequireComponent(typeof(IUmbracoCoreComponent))] + public class UmbracoCoreComponent : UmbracoComponentBase + { } +} diff --git a/src/Umbraco.Core/Composing/BuilderCollectionBase.cs b/src/Umbraco.Core/Composing/BuilderCollectionBase.cs new file mode 100644 index 0000000000..484a9b930f --- /dev/null +++ b/src/Umbraco.Core/Composing/BuilderCollectionBase.cs @@ -0,0 +1,43 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides a base class for builder collections. + /// + /// The type of the items. + public abstract class BuilderCollectionBase : IBuilderCollection + { + private readonly TItem[] _items; + + /// + /// Initializes a new instance of the with items. + /// + /// The items. + protected BuilderCollectionBase(IEnumerable items) + { + _items = items.ToArray(); + } + + /// + public int Count => _items.Length; + + /// + /// Gets an enumerator. + /// + public IEnumerator GetEnumerator() + { + return ((IEnumerable) _items).GetEnumerator(); + } + + /// + /// Gets an enumerator. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs new file mode 100644 index 0000000000..3fac2d3255 --- /dev/null +++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using LightInject; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides a base class for collection builders. + /// + /// The type of the builder. + /// The type of the collection. + /// The type of the items. + public abstract class CollectionBuilderBase : ICollectionBuilder + where TBuilder: CollectionBuilderBase + where TCollection : IBuilderCollection + { + private readonly List _types = new List(); + private readonly object _locker = new object(); + private Func, TCollection> _collectionCtor; + private ServiceRegistration[] _registrations; + + /// + /// Initializes a new instance of the + /// class with a service container. + /// + /// A service container. + protected CollectionBuilderBase(IServiceContainer container) + { + Container = container; + // ReSharper disable once DoNotCallOverridableMethodsInConstructor + Initialize(); + } + + /// + /// Gets the service container. + /// + protected IServiceContainer Container { get; } + + /// + /// Gets the internal list of types as an IEnumerable (immutable). + /// + public IEnumerable GetTypes() => _types; + + /// + /// Initializes a new instance of the builder. + /// + /// This is called by the constructor and, by default, registers the + /// collection automatically. + protected virtual void Initialize() + { + // compile the auto-collection constructor + var argType = typeof(IEnumerable); + var ctorArgTypes = new[] { argType }; + var constructor = typeof(TCollection).GetConstructor(ctorArgTypes); + if (constructor != null) + { + var exprArg = Expression.Parameter(argType, "items"); + var exprNew = Expression.New(constructor, exprArg); + var expr = Expression.Lambda, TCollection>>(exprNew, exprArg); + _collectionCtor = expr.Compile(); + } + // else _collectionCtor remains null, assuming CreateCollection has been overriden + + // we just don't want to support re-registering collections here + var registration = Container.GetAvailableService(); + if (registration != null) + throw new InvalidOperationException("Collection builders cannot be registered once the collection itself has been registered."); + + // register the collection + Container.Register(_ => CreateCollection(), CollectionLifetime); + } + + /// + /// Gets the collection lifetime. + /// + /// Return null for transient collections. + protected virtual ILifetime CollectionLifetime => new PerContainerLifetime(); + + /// + /// Configures the internal list of types. + /// + /// The action to execute. + /// Throws if the types have already been registered. + protected void Configure(Action> action) + { + lock (_locker) + { + if (_registrations != null) + throw new InvalidOperationException("Cannot configure a collection builder after its types have been resolved."); + action(_types); + } + } + + /// + /// Gets the types. + /// + /// The internal list of types. + /// The list of types to register. + /// Used by implementations to add types to the internal list, sort the list, etc. + protected virtual IEnumerable GetRegisteringTypes(IEnumerable types) + { + return types; + } + + private void RegisterTypes() + { + lock (_locker) + { + if (_registrations != null) return; + + var types = GetRegisteringTypes(_types).ToArray(); + foreach (var type in types) + EnsureType(type, "register"); + + var prefix = GetType().FullName + "_"; + var i = 0; + foreach (var type in types) + { + var name = $"{prefix}{i++:00000}"; + Container.Register(typeof(TItem), type, name); + } + + _registrations = Container.AvailableServices + .Where(x => x.ServiceName.StartsWith(prefix)) + .OrderBy(x => x.ServiceName) + .ToArray(); + } + } + + /// + /// Creates the collection items. + /// + /// The arguments. + /// The collection items. + protected virtual IEnumerable CreateItems(params object[] args) + { + RegisterTypes(); // will do it only once + + var type = typeof (TItem); + return _registrations + .Select(x => (TItem) Container.GetInstanceOrThrow(type, x.ServiceName, x.ImplementingType, args)) + .ToArray(); // safe + } + + /// + /// Creates a collection. + /// + /// A collection. + /// Creates a new collection each time it is invoked. + public virtual TCollection CreateCollection() + { + if (_collectionCtor == null) throw new InvalidOperationException("Collection auto-creation is not possible."); + return _collectionCtor(CreateItems()); + } + + protected Type EnsureType(Type type, string action) + { + if (typeof(TItem).IsAssignableFrom(type) == false) + throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TItem).FullName}."); + return type; + } + + /// + /// Gets a value indicating whether the collection contains a type. + /// + /// The type to look for. + /// A value indicating whether the collection contains the type. + /// Some builder implementations may use this to expose a public Has{T}() method, + /// when it makes sense. Probably does not make sense for lazy builders, for example. + public virtual bool Has() + where T : TItem + { + return _types.Contains(typeof (T)); + } + + /// + /// Gets a value indicating whether the collection contains a type. + /// + /// The type to look for. + /// A value indicating whether the collection contains the type. + /// Some builder implementations may use this to expose a public Has{T}() method, + /// when it makes sense. Probably does not make sense for lazy builders, for example. + public virtual bool Has(Type type) + { + EnsureType(type, "find"); + return _types.Contains(type); + } + } +} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs new file mode 100644 index 0000000000..82912163b6 --- /dev/null +++ b/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs @@ -0,0 +1,23 @@ +using LightInject; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Core.Composing.CompositionRoots +{ + /// + /// Sets up IoC container for Umbraco configuration classes + /// + public sealed class ConfigurationCompositionRoot : ICompositionRoot + { + public void Compose(IServiceRegistry container) + { + container.Register(factory => UmbracoConfig.For.UmbracoSettings()); + container.Register(factory => factory.GetInstance().Content); + container.Register(factory => factory.GetInstance().Templates); + container.Register(factory => factory.GetInstance().RequestHandler); + container.Register(factory => UmbracoConfig.For.GlobalSettings()); + + // fixme - other sections we need to add? + } + } +} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs new file mode 100644 index 0000000000..b68aef3c3d --- /dev/null +++ b/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs @@ -0,0 +1,13 @@ +using LightInject; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Composing.CompositionRoots +{ + public sealed class CoreMappingProfilesCompositionRoot : ICompositionRoot + { + public void Compose(IServiceRegistry container) + { + container.Register(); + } + } +} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs new file mode 100644 index 0000000000..56538a8058 --- /dev/null +++ b/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs @@ -0,0 +1,82 @@ +using System; +using LightInject; +using Umbraco.Core.Cache; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; + +namespace Umbraco.Core.Composing.CompositionRoots +{ + /// + /// Sets the IoC container for the umbraco data layer/repositories/sql/database/etc... + /// + public sealed class RepositoryCompositionRoot : ICompositionRoot + { + public const string DisabledCache = "DisabledCache"; + + public void Compose(IServiceRegistry container) + { + // register cache helpers + // the main cache helper is registered by CoreBootManager and is used by most repositories + // the disabled one is used by those repositories that have an annotated ctor parameter + container.RegisterSingleton(factory => CacheHelper.CreateDisabledCacheHelper(), DisabledCache); + + // resolve ctor dependency from GetInstance() runtimeArguments, if possible - 'factory' is + // the container, 'info' describes the ctor argument, and 'args' contains the args that + // were passed to GetInstance() - use first arg if it is the right type, + // + // for ... + //container.RegisterConstructorDependency((factory, info, args) => + //{ + // if (info.Member.DeclaringType != typeof(EntityContainerRepository)) return default; + // return args.Length > 0 && args[0] is Guid guid ? guid : default; + //}); + + // register repositories + // repos depend on various things, + // some repositories have an annotated ctor parameter to pick the right cache helper + + // repositories + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + + // repositories that depend on a filesystem + // these have an annotated ctor parameter to pick the right file system + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + } + } +} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs new file mode 100644 index 0000000000..d126cd6aa3 --- /dev/null +++ b/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using System.Linq; +using LightInject; +using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Core.Composing.CompositionRoots +{ + public sealed class ServicesCompositionRoot : ICompositionRoot + { + public void Compose(IServiceRegistry container) + { + // register a transient messages factory, which will be replaced by the web + // boot manager when running in a web context + container.RegisterSingleton(); + + // register the service context + container.RegisterSingleton(); + + // register the special idk map + container.RegisterSingleton(); + + // register the services + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.Register(factory => + { + var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/")); + var appPlugins = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)); + var configLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config + "/lang/")); + + var pluginLangFolders = appPlugins.Exists == false + ? Enumerable.Empty() + : 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 + var userLangFolders = configLangFolder.Exists == false + ? 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( + factory.GetInstance(), + factory.GetInstance().RuntimeCache, + mainLangFolder, + pluginLangFolders.Concat(userLangFolders)); + }); + container.RegisterSingleton(factory => new LocalizedTextService( + factory.GetInstance>(), + factory.GetInstance())); + + //TODO: These are replaced in the web project - we need to declare them so that + // something is wired up, just not sure this is very nice but will work for now. + container.RegisterSingleton(); + container.RegisterSingleton(); + } + } +} diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs new file mode 100644 index 0000000000..f462c1b6ea --- /dev/null +++ b/src/Umbraco.Core/Composing/Current.cs @@ -0,0 +1,158 @@ +using System; +using LightInject; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Dictionary; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Core.Sync; +using Umbraco.Core._Legacy.PackageActions; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides a static service locator for most singletons. + /// + /// + /// This class is initialized with the container via LightInjectExtensions.ConfigureUmbracoCore, + /// right after the container is created in UmbracoApplicationBase.HandleApplicationStart. + /// Obviously, this is a service locator, which some may consider an anti-pattern. And yet, + /// practically, it works. + /// + public static class Current + { + private static IServiceContainer _container; + + private static IShortStringHelper _shortStringHelper; + private static ILogger _logger; + private static IProfiler _profiler; + private static ProfilingLogger _profilingLogger; + + /// + /// Gets or sets the DI container. + /// + internal static IServiceContainer Container + { + get + { + if (_container == null) throw new Exception("No container has been set."); + return _container; + } + set + { + if (_container != null) throw new Exception("A container has already been set."); + _container = value; + } + } + + internal static bool HasContainer => _container != null; + + // for UNIT TESTS exclusively! + // resets *everything* that is 'current' + internal static void Reset() + { + _container = null; + + _shortStringHelper = null; + _logger = null; + _profiler = null; + _profilingLogger = null; + + Resetted?.Invoke(null, EventArgs.Empty); + } + + internal static event EventHandler Resetted; + + #region Getters + + // fixme - refactor + // we don't want Umbraco to die because the container has not been properly initialized, + // for some too-important things such as IShortStringHelper or loggers, so if it's not + // registered we setup a default one. We should really refactor our tests so that it does + // not happen. Will do when we get rid of IShortStringHelper. + + public static IShortStringHelper ShortStringHelper + => _shortStringHelper ?? (_shortStringHelper = _container?.TryGetInstance() + ?? new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(UmbracoConfig.For.UmbracoSettings()))); + + public static ILogger Logger + => _logger ?? (_logger = _container?.TryGetInstance() + ?? new DebugDiagnosticsLogger()); + + public static IProfiler Profiler + => _profiler ?? (_profiler = _container?.TryGetInstance() + ?? new LogProfiler(Logger)); + + public static ProfilingLogger ProfilingLogger + => _profilingLogger ?? (_profilingLogger = _container?.TryGetInstance()) + ?? new ProfilingLogger(Logger, Profiler); + + public static IRuntimeState RuntimeState + => Container.GetInstance(); + + public static TypeLoader TypeLoader + => Container.GetInstance(); + + public static FileSystems FileSystems + => Container.GetInstance(); + + public static UrlSegmentProviderCollection UrlSegmentProviders + => Container.GetInstance(); + + public static CacheRefresherCollection CacheRefreshers + => Container.GetInstance(); + + public static DataEditorCollection DataEditors + => Container.GetInstance(); + + public static PropertyEditorCollection PropertyEditors + => Container.GetInstance(); + + public static ParameterEditorCollection ParameterEditors + => Container.GetInstance(); + + internal static ManifestValueValidatorCollection ManifestValidators + => Container.GetInstance(); + + internal static PackageActionCollection PackageActions + => Container.GetInstance(); + + internal static PropertyValueConverterCollection PropertyValueConverters + => Container.GetInstance(); + + internal static IPublishedModelFactory PublishedModelFactory + => Container.GetInstance(); + + public static IServerMessenger ServerMessenger + => Container.GetInstance(); + + public static IServerRegistrar ServerRegistrar + => Container.GetInstance(); + + public static ICultureDictionaryFactory CultureDictionaryFactory + => Container.GetInstance(); + + public static CacheHelper ApplicationCache + => Container.GetInstance(); + + public static ServiceContext Services + => Container.GetInstance(); + + public static IScopeProvider ScopeProvider + => Container.GetInstance(); + + public static ISqlContext SqlContext + => Container.GetInstance(); + + public static IPublishedContentTypeFactory PublishedContentTypeFactory + => Container.GetInstance(); + + #endregion + } +} diff --git a/src/Umbraco.Core/Composing/HideFromTypeFinderAttribute.cs b/src/Umbraco.Core/Composing/HideFromTypeFinderAttribute.cs new file mode 100644 index 0000000000..54c846b944 --- /dev/null +++ b/src/Umbraco.Core/Composing/HideFromTypeFinderAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace Umbraco.Core.Composing +{ + /// + /// Notifies the TypeFinder that it should ignore the class marked with this attribute. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class HideFromTypeFinderAttribute : Attribute + { } +} diff --git a/src/Umbraco.Core/Composing/IBuilderCollection.cs b/src/Umbraco.Core/Composing/IBuilderCollection.cs new file mode 100644 index 0000000000..3ce729392d --- /dev/null +++ b/src/Umbraco.Core/Composing/IBuilderCollection.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Composing +{ + /// + /// Represents a builder collection, ie an immutable enumeration of items. + /// + /// The type of the items. + public interface IBuilderCollection : IEnumerable + { + /// + /// Gets the number of items in the collection. + /// + int Count { get; } + } +} diff --git a/src/Umbraco.Core/Composing/ICollectionBuilder.cs b/src/Umbraco.Core/Composing/ICollectionBuilder.cs new file mode 100644 index 0000000000..5efc03c9ac --- /dev/null +++ b/src/Umbraco.Core/Composing/ICollectionBuilder.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Composing +{ + /// + /// Represents a collection builder. + /// + /// The type of the collection. + /// The type of the items. + public interface ICollectionBuilder + where TCollection : IBuilderCollection + { + /// + /// Creates a collection. + /// + /// A collection. + /// Creates a new collection each time it is invoked. + TCollection CreateCollection(); + } +} diff --git a/src/Umbraco.Core/Composing/IDiscoverable.cs b/src/Umbraco.Core/Composing/IDiscoverable.cs new file mode 100644 index 0000000000..c7bbb57a14 --- /dev/null +++ b/src/Umbraco.Core/Composing/IDiscoverable.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Composing +{ + public interface IDiscoverable + { } +} diff --git a/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs new file mode 100644 index 0000000000..52e5a764fd --- /dev/null +++ b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LightInject; + +namespace Umbraco.Core.Composing +{ + /// + /// Implements a lazy collection builder. + /// + /// The type of the builder. + /// The type of the collection. + /// The type of the items. + public abstract class LazyCollectionBuilderBase : CollectionBuilderBase + where TBuilder : LazyCollectionBuilderBase + where TCollection : IBuilderCollection + { + private readonly List>> _producers1 = new List>>(); + private readonly List>> _producers2 = new List>>(); + private readonly List _excluded = new List(); + + /// + /// Initializes a new instance of the class. + /// + protected LazyCollectionBuilderBase(IServiceContainer container) + : base(container) + { } + + protected abstract TBuilder This { get; } + + /// + /// Clears all types in the collection. + /// + /// The buidler. + public TBuilder Clear() + { + Configure(types => + { + types.Clear(); + _producers1.Clear(); + _producers2.Clear(); + _excluded.Clear(); + }); + return This; + } + + /// + /// Adds a type to the collection. + /// + /// The type to add. + /// The builder. + public TBuilder Add() + where T : TItem + { + Configure(types => + { + var type = typeof(T); + if (types.Contains(type) == false) types.Add(type); + }); + return This; + } + + /// + /// Adds a type to the collection. + /// + /// The type to add. + /// The builder. + public TBuilder Add(Type type) + { + Configure(types => + { + EnsureType(type, "register"); + if (types.Contains(type) == false) types.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; + } + + /// + /// Adds a types producer to the collection. + /// + /// The types producer. + /// The builder. + public TBuilder Add(Func> producer) + { + Configure(types => + { + _producers1.Add(producer); + }); + return This; + } + + /// + /// Adds a types producer to the collection. + /// + /// The types producer. + /// The builder. + public TBuilder Add(Func> producer) + { + Configure(types => + { + _producers2.Add(producer); + }); + return This; + } + + /// + /// Excludes a type from the collection. + /// + /// The type to exclude. + /// The builder. + public TBuilder Exclude() + where T : TItem + { + Configure(types => + { + var type = typeof(T); + if (_excluded.Contains(type) == false) _excluded.Add(type); + }); + return This; + } + + /// + /// Excludes a type from the collection. + /// + /// The type to exclude. + /// The builder. + public TBuilder Exclude(Type type) + { + Configure(types => + { + EnsureType(type, "exclude"); + if (_excluded.Contains(type) == false) _excluded.Add(type); + }); + return This; + } + + protected override IEnumerable GetRegisteringTypes(IEnumerable types) + { + return types + .Union(_producers1.SelectMany(x => x())) + .Union(_producers2.SelectMany(x => x(Container))) + .Distinct() + .Select(x => EnsureType(x, "register")) + .Except(_excluded); + } + } +} diff --git a/src/Umbraco.Core/Composing/LightInjectExtensions.cs b/src/Umbraco.Core/Composing/LightInjectExtensions.cs new file mode 100644 index 0000000000..c7b51404fe --- /dev/null +++ b/src/Umbraco.Core/Composing/LightInjectExtensions.cs @@ -0,0 +1,392 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using LightInject; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides extensions to LightInject. + /// + public static class LightInjectExtensions + { + /// + /// Configure the container for Umbraco Core usage and assign to Current. + /// + /// The container. + /// The container is now the unique application container and is now accessible via Current.Container. + internal static void ConfigureUmbracoCore(this ServiceContainer container) + { + // supports annotated constructor injections + // eg to specify the service name on some services + container.EnableAnnotatedConstructorInjection(); + + // from the docs: "LightInject considers all read/write properties a dependency, but implements + // a loose strategy around property dependencies, meaning that it will NOT throw an exception + // in the case of an unresolved property dependency." + // + // in Umbraco we do NOT want to do property injection by default, so we have to disable it. + // from the docs, the following line will cause the container to "now only try to inject + // dependencies for properties that is annotated with the InjectAttribute." + // + // could not find it documented, but tests & code review shows that LightInject considers a + // property to be "injectable" when its setter exists and is not static, nor private, nor + // it is an index property. which means that eg protected or internal setters are OK. + container.EnableAnnotatedPropertyInjection(); + + // ensure that we do *not* scan assemblies + // we explicitely RegisterFrom our own composition roots and don't want them scanned + container.AssemblyScanner = new AssemblyScanner(/*container.AssemblyScanner*/); + + // see notes in MixedLightInjectScopeManagerProvider + container.ScopeManagerProvider = new MixedLightInjectScopeManagerProvider(); + + // self-register + container.Register(_ => container); + + // configure the current container + Current.Container = container; + } + + private class AssemblyScanner : IAssemblyScanner + { + //private readonly IAssemblyScanner _scanner; + + //public AssemblyScanner(IAssemblyScanner scanner) + //{ + // _scanner = scanner; + //} + + public void Scan(Assembly assembly, IServiceRegistry serviceRegistry, Func lifetime, Func shouldRegister, Func serviceNameProvider) + { + // nothing - we *could* scan non-Umbraco assemblies, though + } + + public void Scan(Assembly assembly, IServiceRegistry serviceRegistry) + { + // nothing - we *could* scan non-Umbraco assemblies, though + } + } + + /// + /// Registers a service implementation with a specified lifetime. + /// + /// The type of the service. + /// The type of the implementation. + /// The type of the lifetime. + /// The container. + public static void Register(this IServiceContainer container) + where TImplementation : TService + where TLifetime : ILifetime, new() + { + container.Register(new TLifetime()); + } + + /// + /// Registers a service implementation with a specified lifetime. + /// + /// The type of the service. + /// The type of the lifetime. + /// The container. + /// A factory. + public static void Register(this IServiceContainer container, Func factory) + where TLifetime : ILifetime, new() + { + container.Register(factory, new TLifetime()); + } + + /// + /// Registers several service implementations with a specified lifetime. + /// + /// The type of the service. + /// The type of the lifetime. + /// The container. + /// The types of the implementations. + public static void RegisterMany(this IServiceContainer container, IEnumerable implementations) + where TLifeTime : ILifetime, new() + { + foreach (var implementation in implementations) + { + // if "typeof (TService)" is there then "implementation.FullName" MUST be there too + container.Register(typeof(TService), implementation, implementation.FullName, new TLifeTime()); + } + } + + /// + /// Registers the TService with the factory that describes the dependencies of the service, as a singleton. + /// + public static void RegisterSingleton(this IServiceRegistry container, Func factory, string serviceName) + { + var registration = container.GetAvailableService(serviceName); + if (registration == null) + { + container.Register(factory, serviceName, new PerContainerLifetime()); + } + else + { + if (registration.Lifetime is PerContainerLifetime == false) + throw new InvalidOperationException("Existing registration lifetime is not PerContainer."); + UpdateRegistration(registration, null, factory); + } + } + + /// + /// Registers the TService with the TImplementation as a singleton. + /// + public static void RegisterSingleton(this IServiceRegistry container) + where TImplementation : TService + { + var registration = container.GetAvailableService(); + + if (registration == null) + { + container.Register(new PerContainerLifetime()); + } + else + { + if (registration.Lifetime is PerContainerLifetime == false) + throw new InvalidOperationException("Existing registration lifetime is not PerContainer."); + UpdateRegistration(registration, typeof(TImplementation), null); + } + } + + /// + /// Registers a concrete type as a singleton service. + /// + public static void RegisterSingleton(this IServiceRegistry container) + { + var registration = container.GetAvailableService(); + if (registration == null) + { + container.Register(new PerContainerLifetime()); + } + else + { + if (registration.Lifetime is PerContainerLifetime == false) + throw new InvalidOperationException("Existing registration lifetime is not PerContainer."); + UpdateRegistration(registration, typeof(TImplementation), null); + } + + } + + /// + /// Registers the TService with the factory that describes the dependencies of the service, as a singleton. + /// + /// + /// + /// + public static void RegisterSingleton(this IServiceRegistry container, Func factory) + { + var registration = container.GetAvailableService(); + if (registration == null) + container.Register(factory, new PerContainerLifetime()); + else + UpdateRegistration(registration, null, factory); + } + + // note - what's below ALSO applies to non-singleton ie transient services + // + // see https://github.com/seesharper/LightInject/issues/133 + // + // note: we *could* use tracking lifetimes for singletons to ensure they have not been resolved + // already but that would not work for transient as the transient lifetime is null (and that is + // optimized in LightInject) + // + // also, RegisterSingleton above is dangerous because ppl could still use Register with a + // PerContainerLifetime and it will not work + the default Register would not work either for other + // lifetimes + // + // all in all, not sure we want to let ppl have direct access to the container + // we might instead want to expose some methods in UmbracoComponentBase or whatever? + + /// + /// Updates a registration. + /// + private static void UpdateRegistration(Registration registration, Type implementingType, Delegate factoryExpression) + { + // if the container has compiled already then the registrations have been captured, + // and re-registering - although updating available services - does not modify the + // output of GetInstance + // + // so we have to rely on different methods + // + // assuming the service has NOT been resolved, both methods below work, but the first + // one looks simpler. it would be good to check whether the service HAS been resolved + // but I am not sure how to do it right now, depending on the lifetime + // + // if the service HAS been resolved then updating is probably a bad idea + + // not sure which is best? that one works, though, and looks simpler + registration.ImplementingType = implementingType; + registration.FactoryExpression = factoryExpression; + + //container.Override( + // r => r.ServiceType == typeof (TService) && (registration.ServiceName == null || r.ServiceName == registration.ServiceName), + // (f, r) => + // { + // r.ImplementingType = implementingType; + // r.FactoryExpression = factoryExpression; + // return r; + // }); + } + + /// + /// Gets the available service registrations for a service type. + /// + /// The service type. + /// The container. + /// The service registrations for the service type. + public static IEnumerable GetAvailableServices(this IServiceRegistry container) + { + var typeofTService = typeof(TService); + return container.AvailableServices.Where(x => x.ServiceType == typeofTService); + } + + /// + /// Gets the unique available service registration for a service type. + /// + /// The service type. + /// The container. + /// The unique service registration for the service type. + /// Can return null, but throws if more than one registration exist for the service type. + public static ServiceRegistration GetAvailableService(this IServiceRegistry container) + { + var typeofTService = typeof(TService); + return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService); + } + + /// + /// Gets the unique available service registration for a service type and a name. + /// + /// The service type. + /// The container. + /// The name. + /// The unique service registration for the service type and the name. + /// Can return null, but throws if more than one registration exist for the service type and the name. + public static ServiceRegistration GetAvailableService(this IServiceRegistry container, string name) + { + var typeofTService = typeof(TService); + return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService && x.ServiceName == name); + } + + /// + /// Gets an instance of a TService or throws a meaningful exception. + /// + /// The service type. + /// The container. + /// The instance. + public static TService GetInstanceOrThrow(this IServiceFactory factory) + { + if (factory == null) + throw new ArgumentNullException(nameof(factory)); + + try + { + return factory.GetInstance(); + } + catch (Exception e) + { + LightInjectException.TryThrow(e); + throw; + } + } + + /// + /// Gets an instance of a TService or throws a meaningful exception. + /// + /// The container. + /// The type of the service. + /// The name of the service. + /// The implementing type. + /// Arguments. + /// The instance. + internal static object GetInstanceOrThrow(this IServiceFactory factory, Type tService, string serviceName, Type implementingType, object[] args) + { + if (factory == null) + throw new ArgumentNullException(nameof(factory)); + + // fixme temp - STOP doing this, it confuses LightInject and then we get ugly exception traces + // we HAVE to let LightInject throw - and catch at THE OUTERMOST if InvalidOperationException in LightInject.Anything! + + return factory.GetInstance(tService, serviceName, args); + try + { + return factory.GetInstance(tService, serviceName, args); + } + catch (Exception e) + { + LightInjectException.TryThrow(e, implementingType); + throw; + } + } + + /// + /// Registers a base type for auto-registration. + /// + /// The base type. + /// The container. + /// + /// Any type that inherits/implements the base type will be auto-registered on-demand. + /// This methods works with actual types. Use the other overload for eg generic definitions. + /// + public static void RegisterAuto(this IServiceContainer container) + { + container.RegisterFallback((serviceType, serviceName) => + { + //Current.Logger.Debug(typeof(LightInjectExtensions), $"Fallback for type {serviceType.FullName}."); + // https://github.com/seesharper/LightInject/issues/173 + + if (typeof(T).IsAssignableFrom(serviceType)) + container.Register(serviceType); + return false; + }, null); + } + + /// + /// Registers a base type for auto-registration. + /// + /// The container. + /// The base type. + /// + /// Any type that inherits/implements the base type will be auto-registered on-demand. + /// This methods works with actual types, as well as generic definitions eg typeof(MyBase{}). + /// + public static void RegisterAuto(this IServiceContainer container, Type type) + { + container.RegisterFallback((serviceType, serviceName) => + { + //Current.Logger.Debug(typeof(LightInjectExtensions), $"Fallback for type {serviceType.FullName}."); + // https://github.com/seesharper/LightInject/issues/173 + + if (type.IsAssignableFromGtd(serviceType)) + container.Register(serviceType); + return false; + }, null); + } + + /// + /// Registers and instanciates a collection builder. + /// + /// The type of the collection builder. + /// The container. + /// The collection builder. + public static TBuilder RegisterCollectionBuilder(this IServiceContainer container) + { + // make sure it's not already registered + // we just don't want to support re-registering collection builders + var registration = container.GetAvailableService(); + if (registration != null) + throw new InvalidOperationException("Collection builders should be registered only once."); + + // register the builder - per container + var builderLifetime = new PerContainerLifetime(); + container.Register(builderLifetime); + + // return the builder + // (also initializes the builder) + return container.GetInstance(); + } + } +} diff --git a/src/Umbraco.Core/Composing/MixedLightInjectScopeManagerProvider.cs b/src/Umbraco.Core/Composing/MixedLightInjectScopeManagerProvider.cs new file mode 100644 index 0000000000..05bdbc446d --- /dev/null +++ b/src/Umbraco.Core/Composing/MixedLightInjectScopeManagerProvider.cs @@ -0,0 +1,41 @@ +using LightInject; +using LightInject.Web; + +namespace Umbraco.Core.Composing +{ + // by default, the container's scope manager provider is PerThreadScopeManagerProvider, + // and then container.EnablePerWebRequestScope() replaces it with PerWebRequestScopeManagerProvider, + // however if any delegate has been compiled already at that point, it captures the scope + // manager provider and changing it afterwards has no effect for that delegate. + // + // therefore, Umbraco uses the mixed scope manager provider, which initially wraps an instance + // of PerThreadScopeManagerProvider and then can replace that wrapped instance with an instance + // of PerWebRequestScopeManagerProvider - but all delegates see is the mixed one - and therefore + // they can transition without issues. + // + // the mixed provider is installed in container.ConfigureUmbracoCore() and then, + // when doing eg container.EnableMvc() or anything that does container.EnablePerWebRequestScope() + // we need to take great care to preserve the mixed scope manager provider! + + public class MixedLightInjectScopeManagerProvider : IScopeManagerProvider + { + private IScopeManagerProvider _provider; + + public MixedLightInjectScopeManagerProvider() + { + _provider = new PerThreadScopeManagerProvider(); + } + + public void EnablePerWebRequestScope() + { + if (_provider is PerWebRequestScopeManagerProvider) return; + _provider = new PerWebRequestScopeManagerProvider(); + } + + public IScopeManager GetScopeManager(IServiceFactory factory) + { + return _provider.GetScopeManager(factory); + } + } +} + diff --git a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs new file mode 100644 index 0000000000..4811551cd2 --- /dev/null +++ b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs @@ -0,0 +1,324 @@ +using System; +using System.Collections.Generic; +using LightInject; + +namespace Umbraco.Core.Composing +{ + /// + /// Implements an ordered collection builder. + /// + /// The type of the builder. + /// The type of the collection. + /// The type of the items. + public abstract class OrderedCollectionBuilderBase : CollectionBuilderBase + where TBuilder : OrderedCollectionBuilderBase + where TCollection : IBuilderCollection + { + /// + /// Initializes a new instance of the class. + /// + /// + protected OrderedCollectionBuilderBase(IServiceContainer container) + : base (container) + { } + + protected abstract TBuilder This { get; } + + /// + /// Clears all types in the collection. + /// + /// The buidler. + public TBuilder Clear() + { + Configure(types => types.Clear()); + return This; + } + + /// + /// Appends a type to the collection. + /// + /// The type to append. + /// The builder. + public TBuilder Append() + where T : TItem + { + Configure(types => + { + var type = typeof (T); + if (types.Contains(type)) types.Remove(type); + types.Add(type); + }); + return This; + } + + /// + /// Appends a type to the collection. + /// + /// The type to append. + /// The builder. + public TBuilder Append(Type type) + { + Configure(types => + { + EnsureType(type, "register"); + if (types.Contains(type)) types.Remove(type); + types.Add(type); + }); + return This; + } + + /// + /// Appends types to the collections. + /// + /// The types to append. + /// The builder. + public TBuilder Append(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; + } + + /// + /// Appends types to the collections. + /// + /// The types to append. + /// The builder. + public TBuilder Append(Func> types) + { + Configure(list => + { + foreach (var type in types(Container)) + { + // 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; + } + + /// + /// Appends a type after another type. + /// + /// The other type. + /// The type to append. + /// The builder. + /// Throws if both types are identical, or if the other type does not already belong to the collection. + public TBuilder AppendAfter() + where TAfter : TItem + where T : TItem + { + Configure(types => + { + var typeAfter = typeof (TAfter); + var type = typeof(T); + if (typeAfter == type) throw new InvalidOperationException(); + + var index = types.IndexOf(typeAfter); + if (index < 0) throw new InvalidOperationException(); + + if (types.Contains(type)) types.Remove(type); + index = types.IndexOf(typeAfter); // in case removing type changed index + types.Insert(index + 1, type); + }); + return This; + } + + /// + /// Inserts a type into the collection. + /// + /// The type to insert. + /// The optional index. + /// The builder. + /// Throws if the index is out of range. + public TBuilder Insert(int index = 0) + where T : TItem + { + Configure(types => + { + var type = typeof (T); + if (types.Contains(type)) types.Remove(type); + types.Insert(index, type); + }); + return This; + } + + /// + /// Inserts a type into the collection. + /// + /// The type to insert. + /// The builder. + /// Throws if the index is out of range. + public TBuilder Insert(Type type) + { + return Insert(0, type); + } + + /// + /// Inserts a type into the collection. + /// + /// The index. + /// The type to insert. + /// The builder. + /// Throws if the index is out of range. + public TBuilder Insert(int index, Type type) + { + Configure(types => + { + EnsureType(type, "register"); + if (types.Contains(type)) types.Remove(type); + types.Insert(index, type); + }); + return This; + } + + /// + /// Inserts a type before another type. + /// + /// The other type. + /// The type to insert. + /// The builder. + /// Throws if both types are identical, or if the other type does not already belong to the collection. + public TBuilder InsertBefore() + where TBefore : TItem + where T : TItem + { + Configure(types => + { + var typeBefore = typeof(TBefore); + var type = typeof(T); + if (typeBefore == type) throw new InvalidOperationException(); + + var index = types.IndexOf(typeBefore); + if (index < 0) throw new InvalidOperationException(); + + if (types.Contains(type)) types.Remove(type); + index = types.IndexOf(typeBefore); // in case removing type changed index + types.Insert(index, type); + }); + return This; + } + + /// + /// Inserts a type before another type. + /// + /// The other type. + /// The type to insert. + /// The builder. + /// Throws if both types are identical, or if the other type does not already belong to the collection. + public TBuilder InsertBefore(Type typeBefore, Type type) + { + Configure(types => + { + EnsureType(typeBefore, "find"); + EnsureType(type, "register"); + + if (typeBefore == type) throw new InvalidOperationException(); + + var index = types.IndexOf(typeBefore); + if (index < 0) throw new InvalidOperationException(); + + if (types.Contains(type)) types.Remove(type); + index = types.IndexOf(typeBefore); // in case removing type changed index + types.Insert(index, 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/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs similarity index 94% rename from src/Umbraco.Core/TypeFinder.cs rename to src/Umbraco.Core/Composing/TypeFinder.cs index 16dbd49e73..f244d1d1ce 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -1,627 +1,629 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security; -using System.Text; -using System.Web.Compilation; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; - -namespace Umbraco.Core -{ - /// - /// A utility class to find all classes of a certain type by reflection in the current bin folder - /// of the web application. - /// - public static class TypeFinder - { - private static volatile HashSet _localFilteredAssemblyCache; - private static readonly object LocalFilteredAssemblyCacheLocker = new object(); - private static readonly List NotifiedLoadExceptionAssemblies = new List(); - private static string[] _assembliesAcceptingLoadExceptions; - - private static string[] AssembliesAcceptingLoadExceptions - { - get - { - if (_assembliesAcceptingLoadExceptions != null) - return _assembliesAcceptingLoadExceptions; - - var s = ConfigurationManager.AppSettings["Umbraco.AssembliesAcceptingLoadExceptions"]; - return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) - ? new string[0] - : s.Split(',').Select(x => x.Trim()).ToArray(); - } - } - - private static bool AcceptsLoadExceptions(Assembly a) - { - if (AssembliesAcceptingLoadExceptions.Length == 0) - return false; - if (AssembliesAcceptingLoadExceptions.Length == 1 && AssembliesAcceptingLoadExceptions[0] == "*") - return true; - var name = a.GetName().Name; // simple name of the assembly - return AssembliesAcceptingLoadExceptions.Any(pattern => - { - if (pattern.Length > name.Length) return false; // pattern longer than name - if (pattern.Length == name.Length) return pattern.InvariantEquals(name); // same length, must be identical - if (pattern[pattern.Length] != '.') return false; // pattern is shorter than name, must end with dot - return name.StartsWith(pattern); // and name must start with pattern - }); - } - - /// - /// lazily load a reference to all assemblies and only local assemblies. - /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder - /// - /// - /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been - /// loaded in the CLR, not all assemblies. - /// See these threads: - /// http://issues.umbraco.org/issue/U5-198 - /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app - /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl - /// - internal static HashSet GetAllAssemblies() - { - return AllAssemblies.Value; - } - - //Lazy access to the all assemblies list - private static readonly Lazy> AllAssemblies = new Lazy>(() => - { - HashSet assemblies = null; - try - { - var isHosted = IOHelper.IsHosted; - - try - { - if (isHosted) - { - assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); - } - } - catch (InvalidOperationException e) - { - if ((e.InnerException is SecurityException) == false) - throw; - } - - if (assemblies == null) - { - //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have - // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. - var binFolder = IOHelper.GetRootDirectoryBinFolder(); - var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new HashSet(); - foreach (var a in binAssemblyFiles) - { - try - { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); - } - catch (Exception e) - { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } - } - } - } - - //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. - if (assemblies.Any() == false) - { - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) - { - assemblies.Add(a); - } - } - - //here we are trying to get the App_Code assembly - var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported - var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); - //check if the folder exists and if there are any files in it with the supported file extensions - if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) - { - try - { - var appCodeAssembly = Assembly.Load("App_Code"); - if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already - assemblies.Add(appCodeAssembly); - } - catch (FileNotFoundException ex) - { - //this will occur if it cannot load the assembly - LogHelper.Error(typeof(TypeFinder), "Could not load assembly App_Code", ex); - } - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - return assemblies; - }); - - /// - /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan - /// and exluding the ones passed in and excluding the exclusion list filter, the results of this are - /// cached for perforance reasons. - /// - /// - /// - internal static HashSet GetAssembliesWithKnownExclusions( - IEnumerable excludeFromResults = null) - { - lock (LocalFilteredAssemblyCacheLocker) - { - if (_localFilteredAssemblyCache != null) - return _localFilteredAssemblyCache; - - var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); - _localFilteredAssemblyCache = new HashSet(assemblies); - return _localFilteredAssemblyCache; - } - } - - /// - /// Return a distinct list of found local Assemblies and exluding the ones passed in and excluding the exclusion list filter - /// - /// - /// - /// - private static IEnumerable GetFilteredAssemblies( - IEnumerable excludeFromResults = null, - string[] exclusionFilter = null) - { - if (excludeFromResults == null) - excludeFromResults = new HashSet(); - if (exclusionFilter == null) - exclusionFilter = new string[] { }; - - return GetAllAssemblies() - .Where(x => excludeFromResults.Contains(x) == false - && x.GlobalAssemblyCache == false - && exclusionFilter.Any(f => x.FullName.StartsWith(f)) == false); - } - - /// - /// this is our assembly filter to filter out known types that def dont contain types we'd like to find or plugins - /// - /// - /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match - /// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll" - /// - internal static readonly string[] KnownAssemblyExclusionFilter = new[] - { - "mscorlib,", - "System.", - "Antlr3.", - "Autofac.", - "Autofac,", - "Castle.", - "ClientDependency.", - "DataAnnotationsExtensions.", - "DataAnnotationsExtensions,", - "Dynamic,", - "HtmlDiff,", - "Iesi.Collections,", - "log4net,", - "Microsoft.", - "Newtonsoft.", - "NHibernate.", - "NHibernate,", - "NuGet.", - "RouteDebugger,", - "SqlCE4Umbraco,", - "umbraco.datalayer,", - "umbraco.interfaces,", - //"umbraco.providers,", - //"Umbraco.Web.UI,", - "umbraco.webservices", - "Lucene.", - "Examine,", - "Examine.", - "ServiceStack.", - "MySql.", - "HtmlAgilityPack.", - "TidyNet.", - "ICSharpCode.", - "CookComputing.", - "AutoMapper,", - "AutoMapper.", - "AzureDirectory,", - "itextsharp,", - "UrlRewritingNet.", - "HtmlAgilityPack,", - "MiniProfiler,", - "Moq,", - "nunit.framework,", - "TidyNet,", - "WebDriver," - }; - - /// - /// Finds any classes derived from the type T that contain the attribute TAttribute - /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute() - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); - } - - /// - /// Finds any classes derived from the type T that contain the attribute TAttribute - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(assemblies, true); - } - - /// - /// Finds any classes derived from the type T that contain the attribute TAttribute - /// - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute( - IEnumerable assemblies, - bool onlyConcreteClasses) - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(typeof(T), assemblies, onlyConcreteClasses); - } - - /// - /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute - /// - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute( - Type assignTypeFrom, - IEnumerable assemblies, - bool onlyConcreteClasses) - where TAttribute : Attribute - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - - return GetClassesWithBaseType(assignTypeFrom, assemblies, onlyConcreteClasses, - //the additional filter will ensure that any found types also have the attribute applied. - t => t.GetCustomAttributes(false).Any()); - } - - /// - /// Searches all filtered local assemblies specified for classes of the type passed in. - /// - /// - /// - public static IEnumerable FindClassesOfType() - { - return FindClassesOfType(GetAssembliesWithKnownExclusions(), true); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - - return GetClassesWithBaseType(typeof(T), assemblies, onlyConcreteClasses); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies) - { - return FindClassesOfType(assemblies, true); - } - - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) - where T : Attribute - { - return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); - } - - /// - /// Finds any classes with the attribute. - /// - /// The attribute type - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute( - Type attributeType, - IEnumerable assemblies, - bool onlyConcreteClasses) - { - return GetClassesWithAttribute(attributeType, assemblies, onlyConcreteClasses); - } - - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies) - where T : Attribute - { - return FindClassesWithAttribute(assemblies, true); - } - - /// - /// Finds the classes with attribute in filtered local assemblies - /// - /// - /// - public static IEnumerable FindClassesWithAttribute() - where T : Attribute - { - return FindClassesWithAttribute(GetAssembliesWithKnownExclusions()); - } - - #region Private methods - - private static IEnumerable GetClassesWithAttribute( - Type attributeType, - IEnumerable assemblies, - bool onlyConcreteClasses) - { - if (typeof(Attribute).IsAssignableFrom(attributeType) == false) - throw new ArgumentException("Type " + attributeType + " is not an Attribute type."); - - var candidateAssemblies = new HashSet(assemblies); - var attributeAssemblyIsCandidate = candidateAssemblies.Contains(attributeType.Assembly); - candidateAssemblies.Remove(attributeType.Assembly); - var types = new List(); - - var stack = new Stack(); - stack.Push(attributeType.Assembly); - - while (stack.Count > 0) - { - var assembly = stack.Pop(); - - Type[] assemblyTypes = null; - if (assembly != attributeType.Assembly || attributeAssemblyIsCandidate) - { - // get all assembly types that can be assigned to baseType - try - { - assemblyTypes = GetTypesWithFormattedException(assembly) - .ToArray(); // in try block - } - catch (TypeLoadException ex) - { - LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly), ex); - continue; - } - - types.AddRange(assemblyTypes.Where(x => - x.IsClass // only classes - && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed - && x.IsNestedPrivate == false // exclude nested private - && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract - && x.GetCustomAttribute() == null // exclude hidden - && x.GetCustomAttributes(attributeType, false).Any())); // marked with the attribute - } - - if (assembly != attributeType.Assembly && assemblyTypes.Where(attributeType.IsAssignableFrom).Any() == false) - continue; - - foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) - { - candidateAssemblies.Remove(referencing); - stack.Push(referencing); - } - } - - return types; - } - - /// - /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly - /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type - /// deriving from the base type. - /// - /// - /// - /// - /// An additional filter to apply for what types will actually be included in the return value - /// - private static IEnumerable GetClassesWithBaseType( - Type baseType, - IEnumerable assemblies, - bool onlyConcreteClasses, - Func additionalFilter = null) - { - var candidateAssemblies = new HashSet(assemblies); - var baseTypeAssemblyIsCandidate = candidateAssemblies.Contains(baseType.Assembly); - candidateAssemblies.Remove(baseType.Assembly); - var types = new List(); - - var stack = new Stack(); - stack.Push(baseType.Assembly); - - while (stack.Count > 0) - { - var assembly = stack.Pop(); - - // get all assembly types that can be assigned to baseType - Type[] assemblyTypes = null; - if (assembly != baseType.Assembly || baseTypeAssemblyIsCandidate) - { - try - { - assemblyTypes = GetTypesWithFormattedException(assembly) - .Where(baseType.IsAssignableFrom) - .ToArray(); // in try block - } - catch (TypeLoadException ex) - { - LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly), ex); - continue; - } - - types.AddRange(assemblyTypes.Where(x => - x.IsClass // only classes - && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed - && x.IsNestedPrivate == false // exclude nested private - && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract - && x.GetCustomAttribute() == null // exclude hidden - && (additionalFilter == null || additionalFilter(x)))); // filter - } - - if (assembly != baseType.Assembly && assemblyTypes.All(x => x.IsSealed)) - continue; - - foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) - { - candidateAssemblies.Remove(referencing); - stack.Push(referencing); - } - } - - return types; - } - - internal static IEnumerable GetTypesWithFormattedException(Assembly a) - { - //if the assembly is dynamic, do not try to scan it - if (a.IsDynamic) - return Enumerable.Empty(); - - var getAll = a.GetCustomAttribute() == null; - - try - { - //we need to detect if an assembly is partially trusted, if so we cannot go interrogating all of it's types - //only its exported types, otherwise we'll get exceptions. - return getAll ? a.GetTypes() : a.GetExportedTypes(); - } - catch (TypeLoadException ex) // GetExportedTypes *can* throw TypeLoadException! - { - var sb = new StringBuilder(); - AppendCouldNotLoad(sb, a, getAll); - AppendLoaderException(sb, ex); - - // rethrow as ReflectionTypeLoadException (for consistency) with new message - throw new ReflectionTypeLoadException(new Type[0], new Exception[] { ex }, sb.ToString()); - } - catch (ReflectionTypeLoadException rex) // GetTypes throws ReflectionTypeLoadException - { - var sb = new StringBuilder(); - AppendCouldNotLoad(sb, a, getAll); - foreach (var loaderException in rex.LoaderExceptions.WhereNotNull()) - AppendLoaderException(sb, loaderException); +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Text; +using System.Web; +using System.Web.Compilation; +using System.Web.Hosting; +using Umbraco.Core.Composing; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Composing +{ + /// + /// A utility class to find all classes of a certain type by reflection in the current bin folder + /// of the web application. + /// + public static class TypeFinder + { + private static volatile HashSet _localFilteredAssemblyCache; + private static readonly object LocalFilteredAssemblyCacheLocker = new object(); + private static readonly List NotifiedLoadExceptionAssemblies = new List(); + private static string[] _assembliesAcceptingLoadExceptions; + + private static string[] AssembliesAcceptingLoadExceptions + { + get + { + if (_assembliesAcceptingLoadExceptions != null) + return _assembliesAcceptingLoadExceptions; + + var s = ConfigurationManager.AppSettings["Umbraco.AssembliesAcceptingLoadExceptions"]; + return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) + ? Array.Empty() + : s.Split(',').Select(x => x.Trim()).ToArray(); + } + } + + private static bool AcceptsLoadExceptions(Assembly a) + { + if (AssembliesAcceptingLoadExceptions.Length == 0) + return false; + if (AssembliesAcceptingLoadExceptions.Length == 1 && AssembliesAcceptingLoadExceptions[0] == "*") + return true; + var name = a.GetName().Name; // simple name of the assembly + return AssembliesAcceptingLoadExceptions.Any(pattern => + { + if (pattern.Length > name.Length) return false; // pattern longer than name + if (pattern.Length == name.Length) return pattern.InvariantEquals(name); // same length, must be identical + if (pattern[pattern.Length] != '.') return false; // pattern is shorter than name, must end with dot + return name.StartsWith(pattern); // and name must start with pattern + }); + } + + /// + /// lazily load a reference to all assemblies and only local assemblies. + /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder + /// + /// + /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been + /// loaded in the CLR, not all assemblies. + /// See these threads: + /// http://issues.umbraco.org/issue/U5-198 + /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app + /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl + /// + internal static HashSet GetAllAssemblies() + { + return AllAssemblies.Value; + } + + //Lazy access to the all assemblies list + private static readonly Lazy> AllAssemblies = new Lazy>(() => + { + HashSet assemblies = null; + try + { + var isHosted = IOHelper.IsHosted; + + try + { + if (isHosted) + { + assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); + } + } + catch (InvalidOperationException e) + { + if (e.InnerException is SecurityException == false) + throw; + } + + if (assemblies == null) + { + //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have + // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. + var binFolder = IOHelper.GetRootDirectoryBinFolder(); + var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; + //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + assemblies = new HashSet(); + foreach (var a in binAssemblyFiles) + { + try + { + var assName = AssemblyName.GetAssemblyName(a); + var ass = Assembly.Load(assName); + assemblies.Add(ass); + } + catch (Exception e) + { + if (e is SecurityException || e is BadImageFormatException) + { + //swallow these exceptions + } + else + { + throw; + } + } + } + } + + //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. + if (assemblies.Any() == false) + { + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + assemblies.Add(a); + } + } + + //here we are trying to get the App_Code assembly + var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported + var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); + //check if the folder exists and if there are any files in it with the supported file extensions + if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) + { + try + { + var appCodeAssembly = Assembly.Load("App_Code"); + if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already + assemblies.Add(appCodeAssembly); + } + catch (FileNotFoundException ex) + { + //this will occur if it cannot load the assembly + Current.Logger.Error(typeof(TypeFinder), "Could not load assembly App_Code", ex); + } + } + } + catch (InvalidOperationException e) + { + if (e.InnerException is SecurityException == false) + throw; + } + + return assemblies; + }); + + /// + /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan + /// and exluding the ones passed in and excluding the exclusion list filter, the results of this are + /// cached for perforance reasons. + /// + /// + /// + internal static HashSet GetAssembliesWithKnownExclusions( + IEnumerable excludeFromResults = null) + { + lock (LocalFilteredAssemblyCacheLocker) + { + if (_localFilteredAssemblyCache != null) + return _localFilteredAssemblyCache; + + var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); + _localFilteredAssemblyCache = new HashSet(assemblies); + return _localFilteredAssemblyCache; + } + } + + /// + /// Return a distinct list of found local Assemblies and exluding the ones passed in and excluding the exclusion list filter + /// + /// + /// + /// + private static IEnumerable GetFilteredAssemblies( + IEnumerable excludeFromResults = null, + string[] exclusionFilter = null) + { + if (excludeFromResults == null) + excludeFromResults = new HashSet(); + if (exclusionFilter == null) + exclusionFilter = new string[] { }; + + return GetAllAssemblies() + .Where(x => excludeFromResults.Contains(x) == false + && x.GlobalAssemblyCache == false + && exclusionFilter.Any(f => x.FullName.StartsWith(f)) == false); + } + + /// + /// this is our assembly filter to filter out known types that def dont contain types we'd like to find or plugins + /// + /// + /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match + /// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll" + /// + internal static readonly string[] KnownAssemblyExclusionFilter = new[] + { + "mscorlib,", + "System.", + "Antlr3.", + "Autofac.", + "Autofac,", + "Castle.", + "ClientDependency.", + "DataAnnotationsExtensions.", + "DataAnnotationsExtensions,", + "Dynamic,", + "HtmlDiff,", + "Iesi.Collections,", + "log4net,", + "Microsoft.", + "Newtonsoft.", + "NHibernate.", + "NHibernate,", + "NuGet.", + "RouteDebugger,", + "SqlCE4Umbraco,", + "umbraco.datalayer,", + "umbraco.interfaces,", + //"umbraco.providers,", + //"Umbraco.Web.UI,", + "umbraco.webservices", + "Lucene.", + "Examine,", + "Examine.", + "ServiceStack.", + "MySql.", + "HtmlAgilityPack.", + "TidyNet.", + "ICSharpCode.", + "CookComputing.", + "AutoMapper,", + "AutoMapper.", + "AzureDirectory,", + "itextsharp,", + "UrlRewritingNet.", + "HtmlAgilityPack,", + "MiniProfiler,", + "Moq,", + "nunit.framework,", + "TidyNet,", + "WebDriver," + }; + + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute() + where TAttribute : Attribute + { + return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); + } + + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) + where TAttribute : Attribute + { + return FindClassesOfTypeWithAttribute(assemblies, true); + } + + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute( + IEnumerable assemblies, + bool onlyConcreteClasses) + where TAttribute : Attribute + { + return FindClassesOfTypeWithAttribute(typeof(T), assemblies, onlyConcreteClasses); + } + + /// + /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute( + Type assignTypeFrom, + IEnumerable assemblies, + bool onlyConcreteClasses) + where TAttribute : Attribute + { + if (assemblies == null) throw new ArgumentNullException(nameof(assemblies)); + + return GetClassesWithBaseType(assignTypeFrom, assemblies, onlyConcreteClasses, + //the additional filter will ensure that any found types also have the attribute applied. + t => t.GetCustomAttributes(false).Any()); + } + + /// + /// Searches all filtered local assemblies specified for classes of the type passed in. + /// + /// + /// + public static IEnumerable FindClassesOfType() + { + return FindClassesOfType(GetAssembliesWithKnownExclusions(), true); + } + + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses) + { + if (assemblies == null) throw new ArgumentNullException(nameof(assemblies)); + + return GetClassesWithBaseType(typeof(T), assemblies, onlyConcreteClasses); + } + + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + public static IEnumerable FindClassesOfType(IEnumerable assemblies) + { + return FindClassesOfType(assemblies, true); + } + + /// + /// Finds the classes with attribute. + /// + /// + /// The assemblies. + /// if set to true only concrete classes. + /// + public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) + where T : Attribute + { + return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); + } + + /// + /// Finds any classes with the attribute. + /// + /// The attribute type + /// The assemblies. + /// if set to true only concrete classes. + /// + public static IEnumerable FindClassesWithAttribute( + Type attributeType, + IEnumerable assemblies, + bool onlyConcreteClasses) + { + return GetClassesWithAttribute(attributeType, assemblies, onlyConcreteClasses); + } + + /// + /// Finds the classes with attribute. + /// + /// + /// The assemblies. + /// + public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies) + where T : Attribute + { + return FindClassesWithAttribute(assemblies, true); + } + + /// + /// Finds the classes with attribute in filtered local assemblies + /// + /// + /// + public static IEnumerable FindClassesWithAttribute() + where T : Attribute + { + return FindClassesWithAttribute(GetAssembliesWithKnownExclusions()); + } + + #region Private methods + + private static IEnumerable GetClassesWithAttribute( + Type attributeType, + IEnumerable assemblies, + bool onlyConcreteClasses) + { + if (typeof(Attribute).IsAssignableFrom(attributeType) == false) + throw new ArgumentException("Type " + attributeType + " is not an Attribute type."); + + var candidateAssemblies = new HashSet(assemblies); + var attributeAssemblyIsCandidate = candidateAssemblies.Contains(attributeType.Assembly); + candidateAssemblies.Remove(attributeType.Assembly); + var types = new List(); + + var stack = new Stack(); + stack.Push(attributeType.Assembly); + + while (stack.Count > 0) + { + var assembly = stack.Pop(); + + Type[] assemblyTypes = null; + if (assembly != attributeType.Assembly || attributeAssemblyIsCandidate) + { + // get all assembly types that can be assigned to baseType + try + { + assemblyTypes = GetTypesWithFormattedException(assembly) + .ToArray(); // in try block + } + catch (TypeLoadException ex) + { + Current.Logger.Error(typeof(TypeFinder), $"Could not query types on {assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", ex); + continue; + } + + types.AddRange(assemblyTypes.Where(x => + x.IsClass // only classes + && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed + && x.IsNestedPrivate == false // exclude nested private + && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract + && x.GetCustomAttribute() == null // exclude hidden + && x.GetCustomAttributes(attributeType, false).Any())); // marked with the attribute + } + + if (assembly != attributeType.Assembly && assemblyTypes.Where(attributeType.IsAssignableFrom).Any() == false) + continue; + + foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) + { + candidateAssemblies.Remove(referencing); + stack.Push(referencing); + } + } + + return types; + } + + /// + /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly + /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type + /// deriving from the base type. + /// + /// + /// + /// + /// An additional filter to apply for what types will actually be included in the return value + /// + private static IEnumerable GetClassesWithBaseType( + Type baseType, + IEnumerable assemblies, + bool onlyConcreteClasses, + Func additionalFilter = null) + { + var candidateAssemblies = new HashSet(assemblies); + var baseTypeAssemblyIsCandidate = candidateAssemblies.Contains(baseType.Assembly); + candidateAssemblies.Remove(baseType.Assembly); + var types = new List(); + + var stack = new Stack(); + stack.Push(baseType.Assembly); + + while (stack.Count > 0) + { + var assembly = stack.Pop(); + + // get all assembly types that can be assigned to baseType + Type[] assemblyTypes = null; + if (assembly != baseType.Assembly || baseTypeAssemblyIsCandidate) + { + try + { + assemblyTypes = GetTypesWithFormattedException(assembly) + .Where(baseType.IsAssignableFrom) + .ToArray(); // in try block + } + catch (TypeLoadException ex) + { + Current.Logger.Error(typeof(TypeFinder), $"Could not query types on {assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", ex); + continue; + } + + types.AddRange(assemblyTypes.Where(x => + x.IsClass // only classes + && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed + && x.IsNestedPrivate == false // exclude nested private + && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract + && x.GetCustomAttribute(false) == null // exclude hidden + && (additionalFilter == null || additionalFilter(x)))); // filter + } + + if (assembly != baseType.Assembly && assemblyTypes.All(x => x.IsSealed)) + continue; + + foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) + { + candidateAssemblies.Remove(referencing); + stack.Push(referencing); + } + } + + return types; + } + + internal static IEnumerable GetTypesWithFormattedException(Assembly a) + { + //if the assembly is dynamic, do not try to scan it + if (a.IsDynamic) + return Enumerable.Empty(); + + var getAll = a.GetCustomAttribute() == null; + + try + { + //we need to detect if an assembly is partially trusted, if so we cannot go interrogating all of it's types + //only its exported types, otherwise we'll get exceptions. + return getAll ? a.GetTypes() : a.GetExportedTypes(); + } + catch (TypeLoadException ex) // GetExportedTypes *can* throw TypeLoadException! + { + var sb = new StringBuilder(); + AppendCouldNotLoad(sb, a, getAll); + AppendLoaderException(sb, ex); + + // rethrow as ReflectionTypeLoadException (for consistency) with new message + throw new ReflectionTypeLoadException(new Type[0], new Exception[] { ex }, sb.ToString()); + } + catch (ReflectionTypeLoadException rex) // GetTypes throws ReflectionTypeLoadException + { + var sb = new StringBuilder(); + AppendCouldNotLoad(sb, a, getAll); + foreach (var loaderException in rex.LoaderExceptions.WhereNotNull()) + AppendLoaderException(sb, loaderException); + + var ex = new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString()); - var ex = new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString()); - // rethrow with new message, unless accepted if (AcceptsLoadExceptions(a) == false) throw ex; - - // log a warning, and return what we can - lock (NotifiedLoadExceptionAssemblies) - { - if (NotifiedLoadExceptionAssemblies.Contains(a.FullName) == false) - { - NotifiedLoadExceptionAssemblies.Add(a.FullName); - LogHelper.WarnWithException(typeof(TypeFinder), "Could not load all types from " + a.GetName().Name + ".", ex); - } - } - return rex.Types.WhereNotNull().ToArray(); - } - } - - private static void AppendCouldNotLoad(StringBuilder sb, Assembly a, bool getAll) - { - sb.Append("Could not load "); - sb.Append(getAll ? "all" : "exported"); - sb.Append(" types from \""); - sb.Append(a.FullName); - sb.AppendLine("\" due to LoaderExceptions, skipping:"); - } - - private static void AppendLoaderException(StringBuilder sb, Exception loaderException) - { - sb.Append(". "); - sb.Append(loaderException.GetType().FullName); - - var tloadex = loaderException as TypeLoadException; - if (tloadex != null) - { - sb.Append(" on "); - sb.Append(tloadex.TypeName); - } - - sb.Append(": "); - sb.Append(loaderException.Message); - sb.AppendLine(); - } - - #endregion - - public static Type GetTypeByName(string typeName) - { - var type = BuildManager.GetType(typeName, false); - if (type != null) return type; - - //TODO: This isn't very elegant, and will have issues since the AppDomain.CurrentDomain - // doesn't actualy load in all assemblies, only the types that have been referenced so far. - // However, in a web context, the BuildManager will have executed which will force all assemblies - // to be loaded so it's fine for now. - - //now try fall back procedures. - type = Type.GetType(typeName); - if (type != null) return type; - return AppDomain.CurrentDomain.GetAssemblies() - .Select(x => x.GetType(typeName)) - .FirstOrDefault(x => x != null); - } - } -} + + // log a warning, and return what we can + lock (NotifiedLoadExceptionAssemblies) + { + if (NotifiedLoadExceptionAssemblies.Contains(a.FullName) == false) + { + NotifiedLoadExceptionAssemblies.Add(a.FullName); + Current.Logger.Warn(typeof (TypeFinder), ex, $"Could not load all types from {a.GetName().Name}."); + } + } + return rex.Types.WhereNotNull().ToArray(); + } + } + + private static void AppendCouldNotLoad(StringBuilder sb, Assembly a, bool getAll) + { + sb.Append("Could not load "); + sb.Append(getAll ? "all" : "exported"); + sb.Append(" types from \""); + sb.Append(a.FullName); + sb.AppendLine("\" due to LoaderExceptions, skipping:"); + } + + private static void AppendLoaderException(StringBuilder sb, Exception loaderException) + { + sb.Append(". "); + sb.Append(loaderException.GetType().FullName); + + var tloadex = loaderException as TypeLoadException; + if (tloadex != null) + { + sb.Append(" on "); + sb.Append(tloadex.TypeName); + } + + sb.Append(": "); + sb.Append(loaderException.Message); + sb.AppendLine(); + } + + #endregion + + public static Type GetTypeByName(string typeName) + { + var type = BuildManager.GetType(typeName, false); + if (type != null) return type; + + //TODO: This isn't very elegant, and will have issues since the AppDomain.CurrentDomain + // doesn't actualy load in all assemblies, only the types that have been referenced so far. + // However, in a web context, the BuildManager will have executed which will force all assemblies + // to be loaded so it's fine for now. + + //now try fall back procedures. + type = Type.GetType(typeName); + if (type != null) return type; + return AppDomain.CurrentDomain.GetAssemblies() + .Select(x => x.GetType(typeName)) + .FirstOrDefault(x => x != null); + } + } +} diff --git a/src/Umbraco.Core/Composing/TypeHelper.cs b/src/Umbraco.Core/Composing/TypeHelper.cs new file mode 100644 index 0000000000..1ab48b71f8 --- /dev/null +++ b/src/Umbraco.Core/Composing/TypeHelper.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// A utility class for type checking, this provides internal caching so that calls to these methods will be faster + /// than doing a manual type check in c# + /// + internal static class TypeHelper + { + private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache + = new ConcurrentDictionary, PropertyInfo[]>(); + private static readonly ConcurrentDictionary GetFieldsCache + = new ConcurrentDictionary(); + + private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; + + /// + /// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also deal with array types and return List{T} for those too. + /// If it cannot be done, null is returned. + /// + /// + /// + // fixme wtf is this and do we need it in v8? + internal static IList CreateGenericEnumerableFromObject(object obj) + { + var type = obj.GetType(); + + if (type.IsGenericType) + { + var genericTypeDef = type.GetGenericTypeDefinition(); + + if (genericTypeDef == typeof(IEnumerable<>) + || genericTypeDef == typeof(ICollection<>) + || genericTypeDef == typeof(Collection<>) + || genericTypeDef == typeof(IList<>) + || genericTypeDef == typeof(List<>) + //this will occur when Linq is used and we get the odd WhereIterator or DistinctIterators since those are special iterator types + || obj is IEnumerable) + { + //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> + var genericType = typeof(List<>).MakeGenericType(type.GetGenericArguments()); + //pass in obj to fill the list + return (IList)Activator.CreateInstance(genericType, obj); + } + } + + if (type.IsArray) + { + //if its an array, we'll use a List<> + var genericType = typeof(List<>).MakeGenericType(type.GetElementType()); + //pass in obj to fill the list + return (IList)Activator.CreateInstance(genericType, obj); + } + + return null; + } + + /// + /// Checks if the method is actually overriding a base method + /// + /// + /// + public static bool IsOverride(MethodInfo m) + { + return m.GetBaseDefinition().DeclaringType != m.DeclaringType; + } + + /// + /// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList + /// + /// The referenced assembly. + /// A list of assemblies. + /// + /// + /// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot + /// reference that assembly, same with the global.asax assembly. + /// + public static Assembly[] GetReferencingAssemblies(Assembly assembly, IEnumerable assemblies) + { + if (assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly()) + return EmptyAssemblies; + + + // find all assembly references that are referencing the current type's assembly since we + // should only be scanning those assemblies because any other assembly will definitely not + // contain sub type's of the one we're currently looking for + var name = assembly.GetName().Name; + return assemblies.Where(x => x == assembly || HasReference(x, name)).ToArray(); + } + + /// + /// Determines if an assembly references another assembly. + /// + /// + /// + /// + public static bool HasReference(Assembly assembly, string name) + { + // ReSharper disable once LoopCanBeConvertedToQuery - no! + foreach (var a in assembly.GetReferencedAssemblies()) + { + if (string.Equals(a.Name, name, StringComparison.Ordinal)) return true; + } + return false; + } + + /// + /// Returns true if the type is a class and is not static + /// + /// + /// + public static bool IsNonStaticClass(Type t) + { + return t.IsClass && IsStaticClass(t) == false; + } + + /// + /// Returns true if the type is a static class + /// + /// + /// + /// + /// In IL a static class is abstract and sealed + /// see: http://stackoverflow.com/questions/1175888/determine-if-a-type-is-static + /// + public static bool IsStaticClass(Type type) + { + return type.IsAbstract && type.IsSealed; + } + + /// + /// Finds a lowest base class amongst a collection of types + /// + /// + /// + /// + /// The term 'lowest' refers to the most base class of the type collection. + /// If a base type is not found amongst the type collection then an invalid attempt is returned. + /// + public static Attempt GetLowestBaseType(params Type[] types) + { + if (types.Length == 0) + return Attempt.Fail(); + + if (types.Length == 1) + return Attempt.Succeed(types[0]); + + foreach (var curr in types) + { + var others = types.Except(new[] {curr}); + + //is the curr type a common denominator for all others ? + var isBase = others.All(curr.IsAssignableFrom); + + //if this type is the base for all others + if (isBase) + { + return Attempt.Succeed(curr); + } + } + + return Attempt.Fail(); + } + + /// + /// Determines whether the type is assignable from the specified implementation, + /// and caches the result across the application using a . + /// + /// The type of the contract. + /// The implementation. + /// + /// true if [is type assignable from] [the specified contract]; otherwise, false. + /// + public static bool IsTypeAssignableFrom(Type contract, Type implementation) + { + return contract.IsAssignableFrom(implementation); + } + + /// + /// Determines whether the type is assignable from the specified implementation , + /// and caches the result across the application using a . + /// + /// The type of the contract. + /// The implementation. + public static bool IsTypeAssignableFrom(Type implementation) + { + return IsTypeAssignableFrom(typeof(TContract), implementation); + } + + /// + /// Determines whether the object instance is assignable from the specified implementation , + /// and caches the result across the application using a . + /// + /// The type of the contract. + /// The implementation. + public static bool IsTypeAssignableFrom(object implementation) + { + if (implementation == null) throw new ArgumentNullException(nameof(implementation)); + return IsTypeAssignableFrom(implementation.GetType()); + } + + /// + /// A method to determine whether represents a value type. + /// + /// The implementation. + public static bool IsValueType(Type implementation) + { + return implementation.IsValueType || implementation.IsPrimitive; + } + + /// + /// A method to determine whether is an implied value type (, or a string). + /// + /// The implementation. + public static bool IsImplicitValueType(Type implementation) + { + return IsValueType(implementation) || implementation.IsEnum || implementation == typeof (string); + } + + /// + /// Returns (and caches) a PropertyInfo from a type + /// + /// + /// + /// + /// + /// + /// + /// + public static PropertyInfo GetProperty(Type type, string name, + bool mustRead = true, + bool mustWrite = true, + bool includeIndexed = false, + bool caseSensitive = true) + { + return CachedDiscoverableProperties(type, mustRead, mustWrite, includeIndexed) + .FirstOrDefault(x => caseSensitive ? (x.Name == name) : x.Name.InvariantEquals(name)); + } + + /// + /// Gets (and caches) discoverable in the current for a given . + /// + /// The source. + /// + public static FieldInfo[] CachedDiscoverableFields(Type type) + { + return GetFieldsCache.GetOrAdd( + type, + x => type + .GetFields(BindingFlags.Public | BindingFlags.Instance) + .Where(y => y.IsInitOnly == false) + .ToArray()); + } + + /// + /// Gets (and caches) discoverable in the current for a given . + /// + /// The source. + /// true if the properties discovered are readable + /// true if the properties discovered are writable + /// true if the properties discovered are indexable + /// + public static PropertyInfo[] CachedDiscoverableProperties(Type type, bool mustRead = true, bool mustWrite = true, bool includeIndexed = false) + { + return GetPropertiesCache.GetOrAdd( + new Tuple(type, mustRead, mustWrite, includeIndexed), + x => type + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(y => (mustRead == false || y.CanRead) + && (mustWrite == false || y.CanWrite) + && (includeIndexed || y.GetIndexParameters().Any() == false)) + .ToArray()); + } + + #region Match Type + + //TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric + + // readings: + // http://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase + // http://stackoverflow.com/questions/2208043/generic-variance-in-c-sharp-4-0 + // http://stackoverflow.com/questions/8401738/c-sharp-casting-generics-covariance-and-contravariance + // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class + // http://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059 + + private static bool MatchGeneric(Type implementation, Type contract, IDictionary bindings) + { + // trying to match eg List with List + // or List>> with List>> + // classes are NOT invariant so List does not match List + + if (implementation.IsGenericType == false) return false; + + // must have the same generic type definition + var implDef = implementation.GetGenericTypeDefinition(); + var contDef = contract.GetGenericTypeDefinition(); + if (implDef != contDef) return false; + + // must have the same number of generic arguments + var implArgs = implementation.GetGenericArguments(); + var contArgs = contract.GetGenericArguments(); + if (implArgs.Length != contArgs.Length) return false; + + // generic arguments must match + // in insta we should have actual types (eg int, string...) + // in typea we can have generic parameters (eg ) + for (var i = 0; i < implArgs.Length; i++) + { + const bool variance = false; // classes are NOT invariant + if (MatchType(implArgs[i], contArgs[i], bindings, variance) == false) + return false; + } + + return true; + } + + public static bool MatchType(Type implementation, Type contract) + { + return MatchType(implementation, contract, new Dictionary()); + } + + internal static bool MatchType(Type implementation, Type contract, IDictionary bindings, bool variance = true) + { + if (contract.IsGenericType) + { + // eg type is List or List + // if we have variance then List can match IList + // if we don't have variance it can't - must have exact type + + // try to match implementation against contract + if (MatchGeneric(implementation, contract, bindings)) return true; + + // if no variance, fail + if (variance == false) return false; + + // try to match an ancestor of implementation against contract + var t = implementation.BaseType; + while (t != null) + { + if (MatchGeneric(t, contract, bindings)) return true; + t = t.BaseType; + } + + // try to match an interface of implementation against contract + return implementation.GetInterfaces().Any(i => MatchGeneric(i, contract, bindings)); + } + + if (contract.IsGenericParameter) + { + // eg + + if (bindings.ContainsKey(contract.Name)) + { + // already bound: ensure it's compatible + return bindings[contract.Name] == implementation; + } + + // not already bound: bind + bindings[contract.Name] = implementation; + return true; + } + + // not a generic type, not a generic parameter + // so normal class or interface + // about primitive types, value types, etc: + // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class + // if it's a primitive type... it needs to be == + + if (implementation == contract) return true; + if (contract.IsClass && implementation.IsClass && implementation.IsSubclassOf(contract)) return true; + if (contract.IsInterface && implementation.GetInterfaces().Contains(contract)) return true; + + return false; + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs new file mode 100644 index 0000000000..46157c1748 --- /dev/null +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -0,0 +1,823 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Web; +using System.Web.Compilation; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using File = System.IO.File; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides methods to find and instanciate types. + /// + /// + /// This class should be used to get all types, the class should never be used directly. + /// In most cases this class is not used directly but through extension methods that retrieve specific types. + /// This class caches the types it knows to avoid excessive assembly scanning and shorten startup times, relying + /// on a hash of the DLLs in the ~/bin folder to check for cache expiration. + /// + public class TypeLoader + { + private const string CacheKey = "umbraco-types.list"; + + private readonly IRuntimeCacheProvider _runtimeCache; + private readonly IGlobalSettings _globalSettings; + private readonly ProfilingLogger _logger; + + private readonly object _typesLock = new object(); + private readonly Dictionary _types = new Dictionary(); + + private string _cachedAssembliesHash; + private string _currentAssembliesHash; + private IEnumerable _assemblies; + private bool _reportedChange; + private static LocalTempStorage _localTempStorage = LocalTempStorage.Unknown; + private static string _fileBasePath; + + /// + /// Initializes a new instance of the class. + /// + /// The application runtime cache. + /// + /// A profiling logger. + /// Whether to detect changes using hashes. + internal TypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, ProfilingLogger logger, bool detectChanges = true) + { + _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); + _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + if (detectChanges) + { + //first check if the cached hash is string.Empty, if it is then we need + //do the check if they've changed + RequiresRescanning = CachedAssembliesHash != CurrentAssembliesHash || CachedAssembliesHash == string.Empty; + //if they have changed, we need to write the new file + if (RequiresRescanning) + { + // if the hash has changed, clear out the persisted list no matter what, this will force + // rescanning of all types including lazy ones. + // http://issues.umbraco.org/issue/U4-4789 + var typesListFilePath = GetTypesListFilePath(); + if (File.Exists(typesListFilePath)) + File.Delete(typesListFilePath); + + WriteCacheTypesHash(); + } + } + else + { + // if the hash has changed, clear out the persisted list no matter what, this will force + // rescanning of all types including lazy ones. + // http://issues.umbraco.org/issue/U4-4789 + var typesListFilePath = GetTypesListFilePath(); + if (File.Exists(typesListFilePath)) + File.Delete(typesListFilePath); + + // always set to true if we're not detecting (generally only for testing) + RequiresRescanning = true; + } + } + + /// + /// Gets or sets the set of assemblies to scan. + /// + /// + /// If not explicitely set, defaults to all assemblies except those that are know to not have any of the + /// types we might scan. Because we only scan for application types, this means we can safely exclude GAC assemblies + /// for example. + /// This is for unit tests. + /// + // internal for tests + internal IEnumerable AssembliesToScan + { + get => _assemblies ?? (_assemblies = TypeFinder.GetAssembliesWithKnownExclusions()); + set => _assemblies = value; + } + + /// + /// Gets the type lists. + /// + /// For unit tests. + // internal for tests + internal IEnumerable TypeLists => _types.Values; + + /// + /// Sets a type list. + /// + /// For unit tests. + // internal for tests + internal void AddTypeList(TypeList typeList) + { + _types[new TypeListKey(typeList.BaseType, typeList.AttributeType)] = typeList; + } + + #region Hashing + + /// + /// Gets a value indicating whether the assemblies in bin, app_code, global.asax, etc... have changed since they were last hashed. + /// + private bool RequiresRescanning { get; } + + /// + /// Gets the currently cached hash value of the scanned assemblies. + /// + /// The cached hash value, or string.Empty if no cache is found. + internal string CachedAssembliesHash + { + get + { + if (_cachedAssembliesHash != null) + return _cachedAssembliesHash; + + var typesHashFilePath = GetTypesHashFilePath(); + if (!File.Exists(typesHashFilePath)) return string.Empty; + + var hash = File.ReadAllText(typesHashFilePath, Encoding.UTF8); + + _cachedAssembliesHash = hash; + return _cachedAssembliesHash; + } + } + + /// + /// Gets the current assemblies hash based on creating a hash from the assemblies in various places. + /// + /// The current hash. + private string CurrentAssembliesHash + { + get + { + if (_currentAssembliesHash != null) + return _currentAssembliesHash; + + _currentAssembliesHash = GetFileHash(new List> + { + // the bin folder and everything in it + new Tuple(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Bin)), false), + // the app code folder and everything in it + new Tuple(new DirectoryInfo(IOHelper.MapPath("~/App_Code")), false), + // global.asax (the app domain also monitors this, if it changes will do a full restart) + new Tuple(new FileInfo(IOHelper.MapPath("~/global.asax")), false), + // trees.config - use the contents to create the hash since this gets resaved on every app startup! + new Tuple(new FileInfo(IOHelper.MapPath(SystemDirectories.Config + "/trees.config")), true) + }, _logger); + + return _currentAssembliesHash; + } + } + + /// + /// Writes the assembly hash file. + /// + private void WriteCacheTypesHash() + { + var typesHashFilePath = GetTypesHashFilePath(); + File.WriteAllText(typesHashFilePath, CurrentAssembliesHash, Encoding.UTF8); + } + + /// + /// Returns a unique hash for a combination of FileInfo objects. + /// + /// A collection of files. + /// A profiling logger. + /// The hash. + /// Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the + /// file properties (false) or the file contents (true). + private static string GetFileHash(IEnumerable> filesAndFolders, ProfilingLogger logger) + { + using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) + { + // get the distinct file infos to hash + var uniqInfos = new HashSet(); + var uniqContent = new HashSet(); + using (var generator = new HashGenerator()) + { + foreach (var fileOrFolder in filesAndFolders) + { + var info = fileOrFolder.Item1; + if (fileOrFolder.Item2) + { + // add each unique file's contents to the hash + // normalize the content for cr/lf and case-sensitivity + if (uniqContent.Add(info.FullName)) + { + if (File.Exists(info.FullName) == false) continue; + var content = RemoveCrLf(File.ReadAllText(info.FullName)); + generator.AddCaseInsensitiveString(content); + } + } + else + { + // add each unique folder/file to the hash + if (uniqInfos.Add(info.FullName)) + { + generator.AddFileSystemItem(info); + } + } + } + return generator.GenerateHash(); + } + } + } + + // fast! (yes, according to benchmarks) + private static string RemoveCrLf(string s) + { + var buffer = new char[s.Length]; + var count = 0; + // ReSharper disable once ForCanBeConvertedToForeach - no! + for (var i = 0; i < s.Length; i++) + { + if (s[i] != '\r' && s[i] != '\n') + buffer[count++] = s[i]; + } + return new string(buffer, 0, count); + } + + /// + /// Returns a unique hash for a combination of FileInfo objects. + /// + /// A collection of files. + /// A profiling logger. + /// The hash. + // internal for tests + internal static string GetFileHash(IEnumerable filesAndFolders, ProfilingLogger logger) + { + using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) + { + using (var generator = new HashGenerator()) + { + // get the distinct file infos to hash + var uniqInfos = new HashSet(); + + foreach (var fileOrFolder in filesAndFolders) + { + if (uniqInfos.Contains(fileOrFolder.FullName)) continue; + uniqInfos.Add(fileOrFolder.FullName); + generator.AddFileSystemItem(fileOrFolder); + } + return generator.GenerateHash(); + } + } + } + + #endregion + + #region Cache + + private const int ListFileOpenReadTimeout = 4000; // milliseconds + private const int ListFileOpenWriteTimeout = 2000; // milliseconds + + // internal for tests + internal Attempt> TryGetCached(Type baseType, Type attributeType) + { + var cache = _runtimeCache.GetCacheItem, IEnumerable>>(CacheKey, ReadCacheSafe, TimeSpan.FromMinutes(4)); + + cache.TryGetValue(Tuple.Create(baseType == null ? string.Empty : baseType.FullName, attributeType == null ? string.Empty : attributeType.FullName), out IEnumerable types); + return types == null + ? Attempt>.Fail() + : Attempt.Succeed(types); + } + + private Dictionary, IEnumerable> ReadCacheSafe() + { + try + { + return ReadCache(); + } + catch + { + try + { + var typesListFilePath = GetTypesListFilePath(); + File.Delete(typesListFilePath); + } + catch + { + // on-purpose, does not matter + } + } + + return new Dictionary, IEnumerable>(); + } + + // internal for tests + internal Dictionary, IEnumerable> ReadCache() + { + var cache = new Dictionary, IEnumerable>(); + + var typesListFilePath = GetTypesListFilePath(); + if (File.Exists(typesListFilePath) == false) + return cache; + + using (var stream = GetFileStream(typesListFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, ListFileOpenReadTimeout)) + using (var reader = new StreamReader(stream)) + { + while (true) + { + var baseType = reader.ReadLine(); + if (baseType == null) return cache; // exit + if (baseType.StartsWith("<")) break; // old xml + + var attributeType = reader.ReadLine(); + if (attributeType == null) break; + + var types = new List(); + while (true) + { + var type = reader.ReadLine(); + if (type == null) + { + types = null; // break 2 levels + break; + } + if (type == string.Empty) + { + cache[Tuple.Create(baseType, attributeType)] = types; + break; + } + types.Add(type); + } + + if (types == null) break; + } + } + + cache.Clear(); + return cache; + } + + // internal for tests + internal string GetTypesListFilePath() => GetFileBasePath() + ".list"; + + private string GetTypesHashFilePath() => GetFileBasePath() + ".hash"; + + private string GetFileBasePath() + { + var localTempStorage = _globalSettings.LocalTempStorageLocation; + if (_localTempStorage != localTempStorage) + { + string path; + switch (_globalSettings.LocalTempStorageLocation) + { + case LocalTempStorage.AspNetTemp: + path = Path.Combine(HttpRuntime.CodegenDir, "UmbracoData", "umbraco-types"); + break; + 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.ToSHA1(); + var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", appDomainHash); + path = Path.Combine(cachePath, "umbraco-types"); + break; + case LocalTempStorage.Default: + default: + var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/TypesCache"); + path = Path.Combine(tempFolder, "umbraco-types." + NetworkHelper.FileSafeMachineName); + break; + } + + _fileBasePath = path; + _localTempStorage = localTempStorage; + } + + // ensure that the folder exists + var directory = Path.GetDirectoryName(_fileBasePath); + if (directory == null) + throw new InvalidOperationException($"Could not determine folder for path \"{_fileBasePath}\"."); + if (Directory.Exists(directory) == false) + Directory.CreateDirectory(directory); + + return _fileBasePath; + } + + //private string GetFilePath(string extension) + //{ + // string path; + // switch (_globalSettings.LocalTempStorageLocation) + // { + // case LocalTempStorage.AspNetTemp: + // path = Path.Combine(HttpRuntime.CodegenDir, "UmbracoData", "umbraco-types." + extension); + // break; + // 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.ToSHA1(); + // var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", appDomainHash); + // path = Path.Combine(cachePath, "umbraco-types." + extension); + // break; + // case LocalTempStorage.Default: + // default: + // var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/TypesCache"); + // path = Path.Combine(tempFolder, "umbraco-types." + NetworkHelper.FileSafeMachineName + "." + extension); + // break; + // } + + // // ensure that the folder exists + // var directory = Path.GetDirectoryName(path); + // if (directory == null) + // throw new InvalidOperationException($"Could not determine folder for file \"{path}\"."); + // if (Directory.Exists(directory) == false) + // Directory.CreateDirectory(directory); + + // return path; + //} + + // internal for tests + internal void WriteCache() + { + var typesListFilePath = GetTypesListFilePath(); + using (var stream = GetFileStream(typesListFilePath, FileMode.Create, FileAccess.Write, FileShare.None, ListFileOpenWriteTimeout)) + using (var writer = new StreamWriter(stream)) + { + foreach (var typeList in _types.Values) + { + writer.WriteLine(typeList.BaseType == null ? string.Empty : typeList.BaseType.FullName); + writer.WriteLine(typeList.AttributeType == null ? string.Empty : typeList.AttributeType.FullName); + foreach (var type in typeList.Types) + writer.WriteLine(type.AssemblyQualifiedName); + writer.WriteLine(); + } + } + } + + // internal for tests + internal void UpdateCache() + { + // note + // at the moment we write the cache to disk every time we update it. ideally we defer the writing + // since all the updates are going to happen in a row when Umbraco starts. that being said, the + // file is small enough, so it is not a priority. + WriteCache(); + } + + /// + /// Removes cache files and internal cache. + /// + /// Generally only used for resetting cache, for example during the install process. + public void ClearTypesCache() + { + var typesListFilePath = GetTypesListFilePath(); + if (File.Exists(typesListFilePath)) + File.Delete(typesListFilePath); + + var typesHashFilePath = GetTypesHashFilePath(); + if (File.Exists(typesHashFilePath)) + File.Delete(typesHashFilePath); + + _runtimeCache.ClearCacheItem(CacheKey); + } + + private Stream GetFileStream(string path, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, int timeoutMilliseconds) + { + const int pauseMilliseconds = 250; + var attempts = timeoutMilliseconds / pauseMilliseconds; + while (true) + { + try + { + return new FileStream(path, fileMode, fileAccess, fileShare); + } + catch + { + if (--attempts == 0) + throw; + + _logger.Logger.Debug($"Attempted to get filestream for file {path} failed, {attempts} attempts left, pausing for {pauseMilliseconds} milliseconds"); + Thread.Sleep(pauseMilliseconds); + } + } + } + + #endregion + + #region Get Types + + /// + /// Gets class types inheriting from or implementing the specified type + /// + /// The type to inherit from or implement. + /// Indicates whether to use cache for type resolution. + /// A set of assemblies for type resolution. + /// All class types inheriting from or implementing the specified type. + /// Caching is disabled when using specific assemblies. + public IEnumerable GetTypes(bool cache = true, IEnumerable specificAssemblies = null) + { + // do not cache anything from specific assemblies + cache &= specificAssemblies == null; + + // if not caching, or not IDiscoverable, directly get types + if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false) + { + return GetTypesInternal( + typeof (T), null, + () => TypeFinder.FindClassesOfType(specificAssemblies ?? AssembliesToScan), + cache); + } + + // if caching and IDiscoverable + // filter the cached discovered types (and cache the result) + + var discovered = GetTypesInternal( + typeof (IDiscoverable), null, + () => TypeFinder.FindClassesOfType(AssembliesToScan), + true); + + return GetTypesInternal( + typeof (T), null, + () => discovered + .Where(x => typeof (T).IsAssignableFrom(x)), + true); + } + + /// + /// Gets class types inheriting from or implementing the specified type and marked with the specified attribute. + /// + /// The type to inherit from or implement. + /// The type of the attribute. + /// Indicates whether to use cache for type resolution. + /// A set of assemblies for type resolution. + /// All class types inheriting from or implementing the specified type and marked with the specified attribute. + /// Caching is disabled when using specific assemblies. + public IEnumerable GetTypesWithAttribute(bool cache = true, IEnumerable specificAssemblies = null) + where TAttribute : Attribute + { + // do not cache anything from specific assemblies + cache &= specificAssemblies == null; + + // if not caching, or not IDiscoverable, directly get types + if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false) + { + return GetTypesInternal( + typeof (T), typeof (TAttribute), + () => TypeFinder.FindClassesOfTypeWithAttribute(specificAssemblies ?? AssembliesToScan), + cache); + } + + // if caching and IDiscoverable + // filter the cached discovered types (and cache the result) + + var discovered = GetTypesInternal( + typeof (IDiscoverable), null, + () => TypeFinder.FindClassesOfType(AssembliesToScan), + true); + + return GetTypesInternal( + typeof (T), typeof (TAttribute), + () => discovered + .Where(x => typeof(T).IsAssignableFrom(x)) + .Where(x => x.GetCustomAttributes(false).Any()), + true); + } + + /// + /// Gets class types marked with the specified attribute. + /// + /// The type of the attribute. + /// Indicates whether to use cache for type resolution. + /// A set of assemblies for type resolution. + /// All class types marked with the specified attribute. + /// Caching is disabled when using specific assemblies. + public IEnumerable GetAttributedTypes(bool cache = true, IEnumerable specificAssemblies = null) + where TAttribute : Attribute + { + // do not cache anything from specific assemblies + cache &= specificAssemblies == null; + + return GetTypesInternal( + typeof (object), typeof (TAttribute), + () => TypeFinder.FindClassesWithAttribute(specificAssemblies ?? AssembliesToScan), + cache); + } + + private IEnumerable GetTypesInternal( + Type baseType, Type attributeType, + Func> finder, + bool cache) + { + // using an upgradeable lock makes little sense here as only one thread can enter the upgradeable + // lock at a time, and we don't have non-upgradeable readers, and quite probably the type + // loader is mostly not going to be used in any kind of massively multi-threaded scenario - so, + // a plain lock is enough + + var name = GetName(baseType, attributeType); + + lock (_typesLock) + using (_logger.TraceDuration( + "Getting " + name, + "Got " + name)) // cannot contain typesFound.Count as it's evaluated before the find + { + // get within a lock & timer + return GetTypesInternalLocked(baseType, attributeType, finder, cache); + } + } + + private static string GetName(Type baseType, Type attributeType) + { + var s = attributeType == null ? string.Empty : ("[" + attributeType + "]"); + s += baseType; + return s; + } + + private IEnumerable GetTypesInternalLocked( + Type baseType, Type attributeType, + Func> finder, + bool cache) + { + // check if the TypeList already exists, if so return it, if not we'll create it + var listKey = new TypeListKey(baseType, attributeType); + TypeList typeList = null; + if (cache) + _types.TryGetValue(listKey, out typeList); // else null + + // if caching and found, return + if (typeList != null) + { + // need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505 + _logger.Logger.Debug(() => $"Getting {GetName(baseType, attributeType)}: found a cached type list."); + return typeList.Types; + } + + // else proceed, + typeList = new TypeList(baseType, attributeType); + + var typesListFilePath = GetTypesListFilePath(); + var scan = RequiresRescanning || File.Exists(typesListFilePath) == false; + + if (scan) + { + // either we have to rescan, or we could not find the cache file: + // report (only once) and scan and update the cache file + if (_reportedChange == false) + { + _logger.Logger.Debug("Assemblies changes detected, need to rescan everything."); + _reportedChange = true; + } + } + + if (scan == false) + { + // if we don't have to scan, try the cache + var cacheResult = TryGetCached(baseType, attributeType); + + // here we need to identify if the CachedTypeNotFoundInFile was the exception, if it was then we need to re-scan + // in some cases the type will not have been scanned for on application startup, but the assemblies haven't changed + // so in this instance there will never be a result. + if (cacheResult.Exception is CachedTypeNotFoundInFileException || cacheResult.Success == false) + { + _logger.Logger.Debug(() => $"Getting {GetName(baseType, attributeType)}: failed to load from cache file, must scan assemblies."); + scan = true; + } + else + { + // successfully retrieved types from the file cache: load + foreach (var type in cacheResult.Result) + { + try + { + // we use the build manager to ensure we get all types loaded, this is slightly slower than + // Type.GetType but if the types in the assembly aren't loaded yet it would fail whereas + // BuildManager will load them - this is how eg MVC loads types, etc - no need to make it + // more complicated + typeList.Add(BuildManager.GetType(type, true)); + } + catch (Exception ex) + { + // in case of any exception, we have to exit, and revert to scanning + _logger.Logger.Error("Getting " + GetName(baseType, attributeType) + ": failed to load cache file type " + type + ", reverting to scanning assemblies.", ex); + scan = true; + break; + } + } + + if (scan == false) + { + _logger.Logger.Debug(() => $"Getting {GetName(baseType, attributeType)}: loaded types from cache file."); + } + } + } + + if (scan) + { + // either we had to scan, or we could not get the types from the cache file - scan now + _logger.Logger.Debug(() => $"Getting {GetName(baseType, attributeType)}: scanning assemblies."); + + foreach (var t in finder()) + typeList.Add(t); + } + + // if we are to cache the results, do so + if (cache) + { + var added = _types.ContainsKey(listKey) == false; + if (added) + { + _types[listKey] = typeList; + //if we are scanning then update the cache file + if (scan) + UpdateCache(); + } + + _logger.Logger.Debug(() => $"Got {GetName(baseType, attributeType)}, caching ({added.ToString().ToLowerInvariant()})."); + } + else + { + _logger.Logger.Debug(() => $"Got {GetName(baseType, attributeType)}."); + } + + return typeList.Types; + } + + #endregion + + #region Nested classes and stuff + + /// + /// Groups a type and a resolution kind into a key. + /// + private struct TypeListKey + { + // ReSharper disable MemberCanBePrivate.Local + public readonly Type BaseType; + public readonly Type AttributeType; + // ReSharper restore MemberCanBePrivate.Local + + public TypeListKey(Type baseType, Type attributeType) + { + BaseType = baseType ?? typeof (object); + AttributeType = attributeType; + } + + public override bool Equals(object obj) + { + if (obj == null || obj is TypeListKey == false) return false; + var o = (TypeListKey)obj; + return BaseType == o.BaseType && AttributeType == o.AttributeType; + } + + public override int GetHashCode() + { + // in case AttributeType is null we need something else, using typeof (TypeListKey) + // which does not really "mean" anything, it's just a value... + + var hash = 5381; + hash = ((hash << 5) + hash) ^ BaseType.GetHashCode(); + hash = ((hash << 5) + hash) ^ (AttributeType ?? typeof (TypeListKey)).GetHashCode(); + return hash; + } + } + + /// + /// Represents a list of types obtained by looking for types inheriting/implementing a + /// specified type, and/or marked with a specified attribute type. + /// + internal class TypeList + { + private readonly HashSet _types = new HashSet(); + + public TypeList(Type baseType, Type attributeType) + { + BaseType = baseType; + AttributeType = attributeType; + } + + public Type BaseType { get; } + public Type AttributeType { get; } + + /// + /// Adds a type. + /// + public void Add(Type type) + { + if (BaseType.IsAssignableFrom(type) == false) + throw new ArgumentException("Base type " + BaseType + " is not assignable from type " + type + ".", nameof(type)); + _types.Add(type); + } + + /// + /// Gets the types. + /// + public IEnumerable Types => _types; + } + + /// + /// Represents the error that occurs when a type was not found in the cache type + /// list with the specified TypeResolutionKind. + /// + internal class CachedTypeNotFoundInFileException : Exception + { } + + #endregion + } +} diff --git a/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs b/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs new file mode 100644 index 0000000000..6177151a00 --- /dev/null +++ b/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Cache; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core._Legacy.PackageActions; + +namespace Umbraco.Core.Composing +{ + internal static class TypeLoaderExtensions + { + /// + /// Gets all classes implementing . + /// + public static IEnumerable GetDataEditors(this TypeLoader mgr) + { + return mgr.GetTypes(); + } + + /// + /// Gets all classes implementing ICacheRefresher. + /// + public static IEnumerable GetCacheRefreshers(this TypeLoader mgr) + { + return mgr.GetTypes(); + } + + /// + /// Gets all classes implementing IPackageAction. + /// + public static IEnumerable GetPackageActions(this TypeLoader mgr) + { + return mgr.GetTypes(); + } + + /// + /// Gets all classes inheriting from BaseMapper and marked with the MapperForAttribute. + /// + public static IEnumerable GetAssignedMapperTypes(this TypeLoader mgr) + { + return mgr.GetTypesWithAttribute(); + } + + /// + /// Gets all classes implementing ISqlSyntaxProvider and marked with the SqlSyntaxProviderAttribute. + /// + public static IEnumerable GetSqlSyntaxProviders(this TypeLoader mgr) + { + return mgr.GetTypesWithAttribute(); + } + } +} diff --git a/src/Umbraco.Core/Composing/WeightAttribute.cs b/src/Umbraco.Core/Composing/WeightAttribute.cs new file mode 100644 index 0000000000..ec652ba9a3 --- /dev/null +++ b/src/Umbraco.Core/Composing/WeightAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Umbraco.Core.Composing +{ + /// + /// Specifies the weight of pretty much anything. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class WeightAttribute : Attribute + { + /// + /// Initializes a new instance of the class with a weight. + /// + /// + public WeightAttribute(int weight) + { + Weight = weight; + } + + /// + /// Gets the weight value. + /// + public int Weight { get; } + } +} diff --git a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs new file mode 100644 index 0000000000..99fa2d3eb9 --- /dev/null +++ b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LightInject; + +namespace Umbraco.Core.Composing +{ + /// + /// Implements a weighted collection builder. + /// + /// The type of the builder. + /// The type of the collection. + /// The type of the items. + public abstract class WeightedCollectionBuilderBase : CollectionBuilderBase + where TBuilder : WeightedCollectionBuilderBase + where TCollection : IBuilderCollection + { + /// + /// Initializes a new instance of the class. + /// + /// + protected WeightedCollectionBuilderBase(IServiceContainer container) + : base(container) + { } + + protected abstract TBuilder This { get; } + + /// + /// Clears all types in the collection. + /// + /// The buidler. + public TBuilder Clear() + { + Configure(types => types.Clear()); + return This; + } + + /// + /// Adds a type to the collection. + /// + /// The type to add. + /// The builder. + public TBuilder Add() + where T : TItem + { + Configure(types => + { + var type = typeof(T); + if (types.Contains(type) == false) types.Add(type); + }); + return This; + } + + /// + /// Adds a type to the collection. + /// + /// The type to add. + /// The builder. + public TBuilder Add(Type type) + { + Configure(types => + { + EnsureType(type, "register"); + if (types.Contains(type) == false) types.Add(type); + }); + return This; + } + + /// + /// Adds types to the collection. + /// + /// The types to add. + /// 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) == false) 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; + } + + protected override IEnumerable GetRegisteringTypes(IEnumerable types) + { + var list = types.ToList(); + list.Sort((t1, t2) => GetWeight(t1).CompareTo(GetWeight(t2))); + return list; + } + + protected virtual int DefaultWeight { get; set; } = 10; + + protected virtual int GetWeight(Type type) + { + var attr = type.GetCustomAttributes(typeof(WeightAttribute), false).OfType().SingleOrDefault(); + return attr?.Weight ?? DefaultWeight; + } + } +} diff --git a/src/Umbraco.Core/Configuration/BaseRest/BaseRestSection.cs b/src/Umbraco.Core/Configuration/BaseRest/BaseRestSection.cs deleted file mode 100644 index 271d07ddbb..0000000000 --- a/src/Umbraco.Core/Configuration/BaseRest/BaseRestSection.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.BaseRest -{ - - internal class BaseRestSection : UmbracoConfigurationSection, IBaseRestSection - { - private const string KeyEnabled = "enabled"; - - private bool? _enabled; - - [ConfigurationProperty("", IsKey = false, IsRequired = false, IsDefaultCollection = true)] - public ExtensionElementCollection Items - { - get { return (ExtensionElementCollection)base[""]; } - } - - /// - /// Gets or sets a value indicating whether base rest extensions are enabled. - /// - [ConfigurationProperty(KeyEnabled, DefaultValue = true, IsRequired = false)] - public bool Enabled - { - get - { - return _enabled ?? (IsPresent == false || (bool)this[KeyEnabled]); - } - internal set { _enabled = value; } - } - - IExtensionsCollection IBaseRestSection.Items - { - get { return Items; } - } - - } -} diff --git a/src/Umbraco.Core/Configuration/BaseRest/ExtensionElement.cs b/src/Umbraco.Core/Configuration/BaseRest/ExtensionElement.cs deleted file mode 100644 index df785efc2b..0000000000 --- a/src/Umbraco.Core/Configuration/BaseRest/ExtensionElement.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.BaseRest -{ - - [ConfigurationCollection(typeof(ExtensionElement), CollectionType = ConfigurationElementCollectionType.BasicMapAlternate)] - internal class ExtensionElement : ConfigurationElementCollection, IEnumerable, IExtension - { - const string KeyAlias = "alias"; - const string KeyType = "type"; - const string KeyMethod = "method"; - - [ConfigurationProperty(KeyAlias, IsKey = true, IsRequired = true)] - public string Alias - { - get { return (string)base[KeyAlias]; } - } - - [ConfigurationProperty(KeyType, IsKey = false, IsRequired = true)] - public string Type - { - get { return (string)base[KeyType]; } - } - - public override ConfigurationElementCollectionType CollectionType - { - get { return ConfigurationElementCollectionType.BasicMapAlternate; } - } - - protected override string ElementName - { - get { return KeyMethod; } - } - - protected override bool IsElementName(string elementName) - { - return elementName.Equals(KeyMethod, StringComparison.InvariantCultureIgnoreCase); - } - - protected override ConfigurationElement CreateNewElement() - { - return new MethodElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((MethodElement)element).Name; - } - - public override bool IsReadOnly() - { - return false; - } - - new public MethodElement this[string index] - { - get { return (MethodElement)BaseGet(index); } - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IMethodSection; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - IMethodSection IExtension.this[string index] - { - get { return this[index]; } - } - - } -} diff --git a/src/Umbraco.Core/Configuration/BaseRest/ExtensionElementCollection.cs b/src/Umbraco.Core/Configuration/BaseRest/ExtensionElementCollection.cs deleted file mode 100644 index e941f1aa6d..0000000000 --- a/src/Umbraco.Core/Configuration/BaseRest/ExtensionElementCollection.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.BaseRest -{ - public interface IExtensionsCollection : IEnumerable - { - IExtension this[string index] { get; } - } - - [ConfigurationCollection(typeof(ExtensionElement), CollectionType = ConfigurationElementCollectionType.BasicMapAlternate)] - internal class ExtensionElementCollection : ConfigurationElementCollection, IExtensionsCollection - { - const string KeyExtension = "extension"; - - public override ConfigurationElementCollectionType CollectionType - { - get { return ConfigurationElementCollectionType.BasicMapAlternate; } - } - - protected override string ElementName - { - get { return KeyExtension; } - } - - protected override bool IsElementName(string elementName) - { - return elementName.Equals(KeyExtension, StringComparison.InvariantCultureIgnoreCase); - } - - protected override ConfigurationElement CreateNewElement() - { - return new ExtensionElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((ExtensionElement)element).Alias; - } - - public override bool IsReadOnly() - { - return false; - } - - new public ExtensionElement this[string index] - { - get { return (ExtensionElement)BaseGet(index); } - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IExtension; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - IExtension IExtensionsCollection.this[string index] - { - get { return this[index]; } - } - } -} diff --git a/src/Umbraco.Core/Configuration/BaseRest/IBaseRestSection.cs b/src/Umbraco.Core/Configuration/BaseRest/IBaseRestSection.cs deleted file mode 100644 index 24808d6b24..0000000000 --- a/src/Umbraco.Core/Configuration/BaseRest/IBaseRestSection.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.BaseRest -{ - public interface IBaseRestSection - { - IExtensionsCollection Items { get; } - - /// - /// Gets a value indicating whether base rest extensions are enabled. - /// - bool Enabled { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/BaseRest/IExtension.cs b/src/Umbraco.Core/Configuration/BaseRest/IExtension.cs deleted file mode 100644 index 912898290b..0000000000 --- a/src/Umbraco.Core/Configuration/BaseRest/IExtension.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Umbraco.Core.Configuration.BaseRest -{ - public interface IExtension - { - string Alias { get; } - - string Type { get; } - - IMethodSection this[string index] { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/BaseRest/IMethodSection.cs b/src/Umbraco.Core/Configuration/BaseRest/IMethodSection.cs deleted file mode 100644 index 2c2f63928c..0000000000 --- a/src/Umbraco.Core/Configuration/BaseRest/IMethodSection.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Umbraco.Core.Configuration.BaseRest -{ - public interface IMethodSection - { - string Name { get; } - - bool AllowAll { get; } - - string AllowGroup { get; } - - string AllowType { get; } - - string AllowMember { get; } - - bool ReturnXml { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/BaseRest/MethodElement.cs b/src/Umbraco.Core/Configuration/BaseRest/MethodElement.cs deleted file mode 100644 index ab86fb2227..0000000000 --- a/src/Umbraco.Core/Configuration/BaseRest/MethodElement.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.BaseRest -{ - internal class MethodElement : ConfigurationElement, IMethodSection - { - const string KeyName = "name"; - const string KeyAllowAll = "allowAll"; - const string KeyAllowGroup = "allowGroup"; - const string KeyAllowType = "allowType"; - const string KeyAllowMember = "allowMember"; - const string KeyReturnXml = "returnXml"; - - [ConfigurationProperty(KeyName, IsKey = true, IsRequired = true)] - public string Name - { - get { return (string)base[KeyName]; } - } - - [ConfigurationProperty(KeyAllowAll, IsKey = false, IsRequired = false, DefaultValue = false)] - public bool AllowAll - { - get { return (bool)base[KeyAllowAll]; } - } - - [ConfigurationProperty(KeyAllowGroup, IsKey = false, IsRequired = false, DefaultValue = null)] - public string AllowGroup - { - get { return (string)base[KeyAllowGroup]; } - } - - [ConfigurationProperty(KeyAllowType, IsKey = false, IsRequired = false, DefaultValue = null)] - public string AllowType - { - get { return (string)base[KeyAllowType]; } - } - - [ConfigurationProperty(KeyAllowMember, IsKey = false, IsRequired = false, DefaultValue = null)] - public string AllowMember - { - get { return (string)base[KeyAllowMember]; } - } - - [ConfigurationProperty(KeyReturnXml, IsKey = false, IsRequired = false, DefaultValue = true)] - public bool ReturnXml - { - get { return (bool)base[KeyReturnXml]; } - } - } -} diff --git a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs b/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs index 62cd1ab59c..8693f2e6e8 100644 --- a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs +++ b/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs @@ -1,15 +1,19 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; using System.Web; using System.Xml.Linq; using ClientDependency.Core.CompositeFiles.Providers; using ClientDependency.Core.Config; +using Semver; using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Core.Configuration -{ +{ /// /// A utility class for working with CDF config and cache files - use sparingly! /// @@ -24,10 +28,74 @@ namespace Umbraco.Core.Configuration _logger = logger; _fileName = IOHelper.MapPath(string.Format("{0}/ClientDependency.config", SystemDirectories.Config)); } + + /// + /// Changes the version number in ClientDependency.config to a hashed value for the version and the DateTime.Day + /// + /// The version of Umbraco we're upgrading to + /// A date value to use in the hash to prevent this method from updating the version on each startup + /// Allows the developer to specify the date precision for the hash (i.e. "yyyyMMdd" would be a precision for the day) + /// Boolean to indicate succesful update of the ClientDependency.config file + public bool UpdateVersionNumber(SemVersion version, DateTime date, string dateFormat) + { + var byteContents = Encoding.Unicode.GetBytes(version + date.ToString(dateFormat)); + + //This is a way to convert a string to a long + //see https://www.codeproject.com/Articles/34309/Convert-String-to-bit-Integer + //We could much more easily use MD5 which would create us an INT but since that is not compliant with + //hashing standards we have to use SHA + int intHash; + using (var hash = SHA256.Create()) + { + var bytes = hash.ComputeHash(byteContents); + + var longResult = new[] { 0, 8, 16, 24 } + .Select(i => BitConverter.ToInt64(bytes, i)) + .Aggregate((x, y) => x ^ y); + + //CDF requires an INT, and although this isn't fail safe, it will work for our purposes. We are not hashing for crypto purposes + //so there could be some collisions with this conversion but it's not a problem for our purposes + //It's also important to note that the long.GetHashCode() implementation in .NET is this: return (int) this ^ (int) (this >> 32); + //which means that this value will not change per appdomain like some GetHashCode implementations. + intHash = longResult.GetHashCode(); + } + + try + { + var clientDependencyConfigXml = XDocument.Load(_fileName, LoadOptions.PreserveWhitespace); + if (clientDependencyConfigXml.Root != null) + { + + var versionAttribute = clientDependencyConfigXml.Root.Attribute("version"); + + //Set the new version to the hashcode of now + var oldVersion = versionAttribute.Value; + var newVersion = Math.Abs(intHash).ToString(); + + //don't update if it's the same version + if (oldVersion == newVersion) + return false; + + versionAttribute.SetValue(newVersion); + clientDependencyConfigXml.Save(_fileName, SaveOptions.DisableFormatting); + + _logger.Info(string.Format("Updated version number from {0} to {1}", oldVersion, newVersion)); + return true; + } + } + catch (Exception ex) + { + _logger.Error("Couldn't update ClientDependency version number", ex); + } + + return false; + } /// /// Changes the version number in ClientDependency.config to a random value to avoid stale caches /// + /// + [Obsolete("Use the UpdateVersionNumber method specifying the version, date and dateFormat instead")] public bool IncreaseVersionNumber() { try @@ -82,7 +150,7 @@ namespace Umbraco.Core.Configuration catch (Exception ex) { //invalid path format or something... try/catch to be safe - LogHelper.Error("Could not get path from ClientDependency.config", ex); + _logger.Error("Could not get path from ClientDependency.config", ex); } var success = true; @@ -99,7 +167,7 @@ namespace Umbraco.Core.Configuration catch (Exception ex) { // Something could be locking the directory or the was another error, making sure we don't break the upgrade installer - LogHelper.Error("Could not clear temp files", ex); + _logger.Error("Could not clear temp files", ex); success = false; } } @@ -107,4 +175,4 @@ namespace Umbraco.Core.Configuration return success; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/CommaDelimitedConfigurationElement.cs b/src/Umbraco.Core/Configuration/CommaDelimitedConfigurationElement.cs index d8be9c850b..5015e9d8a2 100644 --- a/src/Umbraco.Core/Configuration/CommaDelimitedConfigurationElement.cs +++ b/src/Umbraco.Core/Configuration/CommaDelimitedConfigurationElement.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Configuration; @@ -12,8 +12,8 @@ namespace Umbraco.Core.Configuration { public override CommaDelimitedStringCollection Value { - get - { + get + { var converter = new CommaDelimitedStringCollectionConverter(); return (CommaDelimitedStringCollection) converter.ConvertFrom(RawValue); } @@ -67,4 +67,4 @@ namespace Umbraco.Core.Configuration } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/CoreDebug.cs b/src/Umbraco.Core/Configuration/CoreDebug.cs index 74a28e7637..71d0f24941 100644 --- a/src/Umbraco.Core/Configuration/CoreDebug.cs +++ b/src/Umbraco.Core/Configuration/CoreDebug.cs @@ -23,9 +23,10 @@ namespace Umbraco.Core.Configuration // when true, Scope logs the stack trace for any scope that gets disposed without being completed. // this helps troubleshooting rogue scopes that we forget to complete - public bool LogUncompletedScopes { get; private set; } + public bool LogUncompletedScopes { get; } + // when true, the Logger creates a minidump of w3wp in ~/App_Data/MiniDump whenever it logs // an error due to a ThreadAbortException that is due to a timeout. - public bool DumpOnTimeoutThreadAbort { get; private set; } + public bool DumpOnTimeoutThreadAbort { get; } } } diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs index da7f7e3e49..d83fc3ccfc 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AccessElement.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Configuration.Dashboard { public AccessElement() { - + } public AccessElement(XElement rawXml) @@ -31,4 +31,4 @@ namespace Umbraco.Core.Configuration.Dashboard } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessItem.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessItem.cs index 0daacfb17c..ec270bada5 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessItem.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AccessItem.cs @@ -6,10 +6,10 @@ /// This can be grant, deny or grantBySection /// public AccessType Action { get; set; } - + /// /// The value of the action /// public string Value { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AccessType.cs b/src/Umbraco.Core/Configuration/Dashboard/AccessType.cs index afc74b671f..0d46b2e0e6 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AccessType.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AccessType.cs @@ -6,4 +6,4 @@ Deny, GrantBySection } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AreaCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/AreaCollection.cs index ac3706d0d4..488e9a625b 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AreaCollection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AreaCollection.cs @@ -29,4 +29,4 @@ namespace Umbraco.Core.Configuration.Dashboard return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AreaElement.cs b/src/Umbraco.Core/Configuration/Dashboard/AreaElement.cs index baea6ce2cd..a688b69008 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AreaElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AreaElement.cs @@ -9,4 +9,4 @@ namespace Umbraco.Core.Configuration.Dashboard get { return Value; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/AreasElement.cs b/src/Umbraco.Core/Configuration/Dashboard/AreasElement.cs index 037c394799..549096f6d9 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/AreasElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/AreasElement.cs @@ -12,4 +12,4 @@ namespace Umbraco.Core.Configuration.Dashboard set { base[""] = value; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/ControlCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/ControlCollection.cs index 1b39c206c6..a3fe9d9300 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/ControlCollection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/ControlCollection.cs @@ -34,4 +34,4 @@ namespace Umbraco.Core.Configuration.Dashboard return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs b/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs index 582998d1f2..680cebe2a6 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/ControlElement.cs @@ -5,7 +5,7 @@ using System.Xml.Linq; namespace Umbraco.Core.Configuration.Dashboard { - + internal class ControlElement : RawXmlConfigurationElement, IDashboardControl { public bool ShowOnce @@ -51,8 +51,8 @@ namespace Umbraco.Core.Configuration.Dashboard } } - public string ControlPath - { + public string ControlPath + { get { //we need to return the first (and only) text element of the children (wtf... who designed this configuration ! :P ) @@ -71,4 +71,4 @@ namespace Umbraco.Core.Configuration.Dashboard get { return Access; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs b/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs index 0c275e1373..477745ee1b 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IAccess.cs @@ -6,4 +6,4 @@ namespace Umbraco.Core.Configuration.Dashboard { IEnumerable Rules { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IAccessItem.cs b/src/Umbraco.Core/Configuration/Dashboard/IAccessItem.cs index a0ae9c2d5e..e343a39bfb 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IAccessItem.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IAccessItem.cs @@ -12,4 +12,4 @@ /// string Value { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IArea.cs b/src/Umbraco.Core/Configuration/Dashboard/IArea.cs index c562c915d7..71dbfdde75 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IArea.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IArea.cs @@ -4,4 +4,4 @@ { string AreaName { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs b/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs index c362d3ed78..ee766bef29 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IDashboardControl.cs @@ -12,4 +12,4 @@ IAccess AccessRights { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IDashboardSection.cs b/src/Umbraco.Core/Configuration/Dashboard/IDashboardSection.cs index 2b1da4a869..52b6e66d98 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IDashboardSection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IDashboardSection.cs @@ -6,4 +6,4 @@ namespace Umbraco.Core.Configuration.Dashboard { IEnumerable Sections { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/IDashboardTab.cs b/src/Umbraco.Core/Configuration/Dashboard/IDashboardTab.cs index 28cdc64021..ad49d084d6 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/IDashboardTab.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/IDashboardTab.cs @@ -10,4 +10,4 @@ namespace Umbraco.Core.Configuration.Dashboard IAccess AccessRights { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/ISection.cs b/src/Umbraco.Core/Configuration/Dashboard/ISection.cs index 48c53382c6..319dcacc12 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/ISection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/ISection.cs @@ -12,4 +12,4 @@ namespace Umbraco.Core.Configuration.Dashboard IAccess AccessRights { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/SectionCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/SectionCollection.cs index 2f47de2af6..c3ae6743a0 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/SectionCollection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/SectionCollection.cs @@ -34,4 +34,4 @@ namespace Umbraco.Core.Configuration.Dashboard return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/SectionElement.cs b/src/Umbraco.Core/Configuration/Dashboard/SectionElement.cs index 9adacd0b87..55498f98c5 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/SectionElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/SectionElement.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Configuration.Dashboard { get { return (AreasElement)this["areas"]; } } - + [ConfigurationProperty("access")] public AccessElement Access { @@ -34,7 +34,7 @@ namespace Umbraco.Core.Configuration.Dashboard IEnumerable ISection.Tabs { - get { return TabCollection; } + get { return TabCollection; } } IEnumerable ISection.Areas @@ -47,4 +47,4 @@ namespace Umbraco.Core.Configuration.Dashboard get { return Access; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/TabCollection.cs b/src/Umbraco.Core/Configuration/Dashboard/TabCollection.cs index 42aa912114..eed0f7af11 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/TabCollection.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/TabCollection.cs @@ -34,4 +34,4 @@ namespace Umbraco.Core.Configuration.Dashboard return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Dashboard/TabElement.cs b/src/Umbraco.Core/Configuration/Dashboard/TabElement.cs index abf3511db0..21dc953677 100644 --- a/src/Umbraco.Core/Configuration/Dashboard/TabElement.cs +++ b/src/Umbraco.Core/Configuration/Dashboard/TabElement.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Configuration.Dashboard { get { return (string)this["caption"]; } } - + [ConfigurationProperty("access")] public AccessElement Access { @@ -24,10 +24,10 @@ namespace Umbraco.Core.Configuration.Dashboard get { return (ControlCollection)base[""]; } set { base[""] = value; } } - + IEnumerable IDashboardTab.Controls { - get { return ControlCollection; } + get { return ControlCollection; } } IAccess IDashboardTab.AccessRights @@ -35,4 +35,4 @@ namespace Umbraco.Core.Configuration.Dashboard get { return Access; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs b/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs index a1221bc0d4..144ddf3195 100644 --- a/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs +++ b/src/Umbraco.Core/Configuration/FileSystemProviderElement.cs @@ -62,6 +62,6 @@ namespace Umbraco.Core.Configuration } return _params; } - } + } } } diff --git a/src/Umbraco.Core/Configuration/FileSystemProvidersSection.cs b/src/Umbraco.Core/Configuration/FileSystemProvidersSection.cs index fa32fe0885..6829ed0cd6 100644 --- a/src/Umbraco.Core/Configuration/FileSystemProvidersSection.cs +++ b/src/Umbraco.Core/Configuration/FileSystemProvidersSection.cs @@ -6,7 +6,7 @@ using System.Text; namespace Umbraco.Core.Configuration { - public class FileSystemProvidersSection : ConfigurationSection, IFileSystemProvidersSection + public class FileSystemProvidersSection : ConfigurationSection, IFileSystemProvidersSection { [ConfigurationProperty("", IsDefaultCollection = true, IsRequired = true)] @@ -15,16 +15,16 @@ namespace Umbraco.Core.Configuration get { return ((FileSystemProviderElementCollection)(base[""])); } } - private IDictionary _providers; + private IDictionary _providers; IDictionary IFileSystemProvidersSection.Providers { get { - if (_providers != null) return _providers; + if (_providers != null) return _providers; _providers = Providers.ToDictionary(x => x.Alias, x => x); return _providers; } - } + } } } diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 395502d1b2..5ce9dd0a35 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -1,48 +1,37 @@ -using System; -using System.Collections.Generic; +using System; using System.Configuration; using System.Linq; using System.Net.Configuration; using System.Web; using System.Web.Configuration; using System.Web.Hosting; -using System.Web.Routing; using System.Web.Security; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; +using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Security; namespace Umbraco.Core.Configuration { - //NOTE: Do not expose this class ever until we cleanup all configuration including removal of static classes, etc... - // we have this two tasks logged: - // http://issues.umbraco.org/issue/U4-58 - // http://issues.umbraco.org/issue/U4-115 - //TODO: Replace checking for if the app settings exist and returning an empty string, instead return the defaults! /// - /// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information from web.config appsettings + /// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information from web.config appsettings /// - internal class GlobalSettings + public class GlobalSettings : IGlobalSettings { #region Private static fields - private static Version _version; - private static readonly object Locker = new object(); - //make this volatile so that we can ensure thread safety with a double check lock - private static volatile string _reservedUrlsCache; - private static string _reservedPathsCache; - private static HashSet _reservedList = new HashSet(); + private static string _reservedPaths; private static string _reservedUrls; //ensure the built on (non-changeable) reserved paths are there at all times - private const string StaticReservedPaths = "~/app_plugins/,~/install/,"; - private const string StaticReservedUrls = "~/config/splashes/booting.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd,"; + internal const string StaticReservedPaths = "~/app_plugins/,~/install/,"; + internal const string StaticReservedUrls = "~/config/splashes/booting.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd,"; #endregion /// @@ -50,7 +39,7 @@ namespace Umbraco.Core.Configuration /// private static void ResetInternal() { - _reservedUrlsCache = null; + GlobalSettingsExtensions.Reset(); _reservedPaths = null; _reservedUrls = null; HasSmtpServer = null; @@ -64,10 +53,11 @@ namespace Umbraco.Core.Configuration { ResetInternal(); } - + + //fixme should this go on the interface or some other helper? public static bool HasSmtpServerConfigured(string appPath) { - if (HasSmtpServer.HasValue) return HasSmtpServer.Value; + if (HasSmtpServer.HasValue) return HasSmtpServer.Value; var config = WebConfigurationManager.OpenWebConfiguration(appPath); var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings"); @@ -77,29 +67,29 @@ namespace Umbraco.Core.Configuration if (settings.Smtp.Network != null && string.IsNullOrEmpty(settings.Smtp.Network.Host) == false) return true; return false; - } - + } + /// /// For testing only - /// - internal static bool? HasSmtpServer { get; set; } - + /// + internal static bool? HasSmtpServer { get; set; } + /// /// Gets the reserved urls from web.config. /// /// The reserved urls. - public static string ReservedUrls + public string ReservedUrls { get { if (_reservedUrls == null) { var urls = ConfigurationManager.AppSettings.ContainsKey("umbracoReservedUrls") - ? ConfigurationManager.AppSettings["umbracoReservedUrls"] - : string.Empty; + ? ConfigurationManager.AppSettings["umbracoReservedUrls"] + : string.Empty; //ensure the built on (non-changeable) reserved paths are there at all times - _reservedUrls = StaticReservedUrls + urls; + _reservedUrls = StaticReservedUrls + urls; } return _reservedUrls; } @@ -110,7 +100,7 @@ namespace Umbraco.Core.Configuration /// Gets the reserved paths from web.config /// /// The reserved paths. - public static string ReservedPaths + public string ReservedPaths { get { @@ -132,7 +122,6 @@ namespace Umbraco.Core.Configuration } return _reservedPaths; } - internal set { _reservedPaths = value; } } /// @@ -142,7 +131,7 @@ namespace Umbraco.Core.Configuration /// /// Defaults to ~/App_Data/umbraco.config /// - public static string ContentXmlFile + public string ContentXmlFile { get { @@ -151,26 +140,12 @@ namespace Umbraco.Core.Configuration : "~/App_Data/umbraco.config"; } } - - /// - /// Gets the path to the storage directory (/data by default). - /// - /// The storage directory. - public static string StorageDirectory - { - get - { - return ConfigurationManager.AppSettings.ContainsKey("umbracoStorageDirectory") - ? ConfigurationManager.AppSettings["umbracoStorageDirectory"] - : "~/App_Data"; - } - } - + /// /// Gets the path to umbraco's root directory (/umbraco by default). /// /// The path. - public static string Path + public string Path { get { @@ -178,96 +153,13 @@ namespace Umbraco.Core.Configuration ? IOHelper.ResolveUrl(ConfigurationManager.AppSettings["umbracoPath"]) : string.Empty; } - } - - /// - /// This returns the string of the MVC Area route. - /// - /// - /// THIS IS TEMPORARY AND SHOULD BE REMOVED WHEN WE MIGRATE/UPDATE THE CONFIG SETTINGS TO BE A REAL CONFIG SECTION - /// AND SHOULD PROBABLY BE HANDLED IN A MORE ROBUST WAY. - /// - /// This will return the MVC area that we will route all custom routes through like surface controllers, etc... - /// We will use the 'Path' (default ~/umbraco) to create it but since it cannot contain '/' and people may specify a path of ~/asdf/asdf/admin - /// we will convert the '/' to '-' and use that as the path. its a bit lame but will work. - /// - /// We also make sure that the virtual directory (SystemDirectories.Root) is stripped off first, otherwise we'd end up with something - /// like "MyVirtualDirectory-Umbraco" instead of just "Umbraco". - /// - public static string UmbracoMvcArea - { - get - { - if (Path.IsNullOrWhiteSpace()) - { - throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified"); - } - var path = Path; - if (path.StartsWith(SystemDirectories.Root)) // beware of TrimStart, see U4-2518 - path = path.Substring(SystemDirectories.Root.Length); - return path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower(); - } - } - - /// - /// Gets the path to umbraco's client directory (/umbraco_client by default). - /// This is a relative path to the Umbraco Path as it always must exist beside the 'umbraco' - /// folder since the CSS paths to images depend on it. - /// - /// The path. - public static string ClientPath - { - get - { - return Path + "/../umbraco_client"; - } - } - - /// - /// Gets the database connection string - /// - /// The database connection string. - [Obsolete("Use System.Configuration.ConfigurationManager.ConnectionStrings[\"umbracoDbDSN\"] instead")] - public static string DbDsn - { - get - { - var settings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; - var connectionString = string.Empty; - - if (settings != null) - { - connectionString = settings.ConnectionString; - - // The SqlCe connectionString is formatted slightly differently, so we need to update it - if (settings.ProviderName.Contains("SqlServerCe")) - connectionString = string.Format("datalayer=SQLCE4Umbraco.SqlCEHelper,SQLCE4Umbraco;{0}", connectionString); - } - - return connectionString; - } - set - { - if (DbDsn != value) - { - if (value.ToLower().Contains("SQLCE4Umbraco.SqlCEHelper".ToLower())) - { - ApplicationContext.Current.DatabaseContext.ConfigureEmbeddedDatabaseConnection(); - } - else - { - ApplicationContext.Current.DatabaseContext.ConfigureDatabaseConnection(value); - } - } - } - } - + } /// /// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance. /// /// The configuration status. - public static string ConfigurationStatus + public string ConfigurationStatus { get { @@ -281,38 +173,6 @@ namespace Umbraco.Core.Configuration } } - /// - /// Gets or sets the Umbraco members membership providers' useLegacyEncoding state. This will return a boolean - /// - /// The useLegacyEncoding status. - public static bool UmbracoMembershipProviderLegacyEncoding - { - get - { - return IsConfiguredMembershipProviderUsingLegacyEncoding(Constants.Conventions.Member.UmbracoMemberProviderName); - } - set - { - SetMembershipProvidersLegacyEncoding(Constants.Conventions.Member.UmbracoMemberProviderName, value); - } - } - - /// - /// Gets or sets the Umbraco users membership providers' useLegacyEncoding state. This will return a boolean - /// - /// The useLegacyEncoding status. - public static bool UmbracoUsersMembershipProviderLegacyEncoding - { - get - { - return IsConfiguredMembershipProviderUsingLegacyEncoding(UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider); - } - set - { - SetMembershipProvidersLegacyEncoding(UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider, value); - } - } - /// /// Saves a setting into the configuration file. /// @@ -353,66 +213,17 @@ namespace Umbraco.Core.Configuration setting.Remove(); xml.Save(fileName, SaveOptions.DisableFormatting); ConfigurationManager.RefreshSection("appSettings"); - } - } - - private static void SetMembershipProvidersLegacyEncoding(string providerName, bool useLegacyEncoding) - { - //check if this can even be configured. - var membershipProvider = Membership.Providers[providerName] as MembershipProviderBase; - if (membershipProvider == null) - { - return; } - if (membershipProvider.GetType().Namespace == "umbraco.providers.members") - { - //its the legacy one, this cannot be changed - return; - } - - var webConfigFilename = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root)); - var webConfigXml = XDocument.Load(webConfigFilename, LoadOptions.PreserveWhitespace); - - var membershipConfigs = webConfigXml.XPathSelectElements("configuration/system.web/membership/providers/add").ToList(); - - if (membershipConfigs.Any() == false) - return; - - var provider = membershipConfigs.SingleOrDefault(c => c.Attribute("name") != null && c.Attribute("name").Value == providerName); - - if (provider == null) - return; - - provider.SetAttributeValue("useLegacyEncoding", useLegacyEncoding); - - webConfigXml.Save(webConfigFilename, SaveOptions.DisableFormatting); - } - - private static bool IsConfiguredMembershipProviderUsingLegacyEncoding(string providerName) - { - //check if this can even be configured. - var membershipProvider = Membership.Providers[providerName] as MembershipProviderBase; - if (membershipProvider == null) - { - return false; - } - - return membershipProvider.UseLegacyEncoding; - } - - /// - /// Gets the full path to root. - /// - /// The fullpath to root. - public static string FullpathToRoot - { - get { return IOHelper.GetRootDirectorySafe(); } } + + [Obsolete("Use IOHelper.GetRootDirectorySafe() instead")] + public static string FullPathToRoot => IOHelper.GetRootDirectorySafe(); /// /// Gets a value indicating whether umbraco is running in [debug mode]. /// /// true if [debug mode]; otherwise, false. + //fixme surely thsi doesn't belong here and it's also a web request context thing public static bool DebugMode { get @@ -434,41 +245,11 @@ namespace Umbraco.Core.Configuration } } - /// - /// Gets a value indicating whether the current version of umbraco is configured. - /// - /// true if configured; otherwise, false. - [Obsolete("Do not use this, it is no longer in use and will be removed from the codebase in future versions")] - internal static bool Configured - { - get - { - try - { - string configStatus = ConfigurationStatus; - string currentVersion = UmbracoVersion.GetSemanticVersion().ToSemanticString(); - - - if (currentVersion != configStatus) - { - LogHelper.Debug("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'"); - } - - - return (configStatus == currentVersion); - } - catch - { - return false; - } - } - } - /// /// Gets the time out in minutes. /// /// The time out in minutes. - public static int TimeOutInMinutes + public int TimeOutInMinutes { get { @@ -487,7 +268,7 @@ namespace Umbraco.Core.Configuration /// Gets a value indicating whether umbraco uses directory urls. /// /// true if umbraco uses directory urls; otherwise, false. - public static bool UseDirectoryUrls + public bool UseDirectoryUrls { get { @@ -506,7 +287,7 @@ namespace Umbraco.Core.Configuration /// Returns a string value to determine if umbraco should skip version-checking. /// /// The version check period in days (0 = never). - public static int VersionCheckPeriod + public int VersionCheckPeriod { get { @@ -520,77 +301,31 @@ namespace Umbraco.Core.Configuration } } } - - /// - /// Returns a string value to determine if umbraco should disbable xslt extensions - /// - /// "true" if version xslt extensions are disabled, otherwise, "false" - [Obsolete("This is no longer used and will be removed from the codebase in future releases")] - public static string DisableXsltExtensions - { - get - { - return ConfigurationManager.AppSettings.ContainsKey("umbracoDisableXsltExtensions") - ? ConfigurationManager.AppSettings["umbracoDisableXsltExtensions"] - : "false"; - } - } - - internal static bool ContentCacheXmlStoredInCodeGen - { - get { return LocalTempStorageLocation == LocalTempStorage.AspNetTemp; } - } - + /// /// This is the location type to store temporary files such as cache files or other localized files for a given machine /// /// /// Currently used for the xml cache file and the plugin cache files /// - internal static LocalTempStorage LocalTempStorageLocation + public LocalTempStorage LocalTempStorageLocation { get - { - //there's a bunch of backwards compat config checks here.... - - //This is the current one - if (ConfigurationManager.AppSettings.ContainsKey("umbracoLocalTempStorage")) - { - return Enum.Parse(ConfigurationManager.AppSettings["umbracoLocalTempStorage"]); - } - - //This one is old - if (ConfigurationManager.AppSettings.ContainsKey("umbracoContentXMLStorage")) - { - return Enum.Parse(ConfigurationManager.AppSettings["umbracoContentXMLStorage"]); - } - - //This one is older - if (ConfigurationManager.AppSettings.ContainsKey("umbracoContentXMLUseLocalTemp")) - { - return bool.Parse(ConfigurationManager.AppSettings["umbracoContentXMLUseLocalTemp"]) - ? LocalTempStorage.AspNetTemp - : LocalTempStorage.Default; - } + { + var setting = ConfigurationManager.AppSettings["umbracoLocalTempStorage"]; + if (!string.IsNullOrWhiteSpace(setting)) + return Enum.Parse(setting); + return LocalTempStorage.Default; } } - /// - /// Returns a string value to determine if umbraco should use Xhtml editing mode in the wysiwyg editor - /// - /// "true" if Xhtml mode is enable, otherwise, "false" - [Obsolete("This is no longer used and will be removed from the codebase in future releases")] - public static string EditXhtmlMode - { - get { return "true"; } - } - /// /// Gets the default UI language. /// /// The default UI language. - public static string DefaultUILanguage + // ReSharper disable once InconsistentNaming + public string DefaultUILanguage { get { @@ -600,28 +335,13 @@ namespace Umbraco.Core.Configuration } } - /// - /// Gets the profile URL. - /// - /// The profile URL. - public static string ProfileUrl - { - get - { - //the default will be 'profiler' - return ConfigurationManager.AppSettings.ContainsKey("umbracoProfileUrl") - ? ConfigurationManager.AppSettings["umbracoProfileUrl"] - : "profiler"; - } - } - /// /// Gets a value indicating whether umbraco should hide top level nodes from generated urls. /// /// - /// true if umbraco hides top level nodes from urls; otherwise, false. + /// true if umbraco hides top level nodes from urls; otherwise, false. /// - public static bool HideTopLevelNodeFromPath + public bool HideTopLevelNodeFromPath { get { @@ -636,95 +356,16 @@ namespace Umbraco.Core.Configuration } } - /// - /// Gets the current version. - /// - /// The current version. - [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] - public static string CurrentVersion - { - get { return UmbracoVersion.GetSemanticVersion().ToSemanticString(); } - } - - /// - /// Gets the major version number. - /// - /// The major version number. - [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] - public static int VersionMajor - { - get - { - return UmbracoVersion.Current.Major; - } - } - - /// - /// Gets the minor version number. - /// - /// The minor version number. - [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] - public static int VersionMinor - { - get - { - return UmbracoVersion.Current.Minor; - } - } - - /// - /// Gets the patch version number. - /// - /// The patch version number. - [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] - public static int VersionPatch - { - get - { - return UmbracoVersion.Current.Build; - } - } - - /// - /// Gets the version comment (like beta or RC). - /// - /// The version comment. - [Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)] - public static string VersionComment - { - get - { - return Umbraco.Core.Configuration.UmbracoVersion.CurrentComment; - } - } - - - /// - /// Requests the is in umbraco application directory structure. - /// - /// The context. - /// - public static bool RequestIsInUmbracoApplication(HttpContext context) - { - return context.Request.Path.ToLower().IndexOf(IOHelper.ResolveUrl(SystemDirectories.Umbraco).ToLower()) > -1; - } - - public static bool RequestIsInUmbracoApplication(HttpContextBase context) - { - return context.Request.Path.ToLower().IndexOf(IOHelper.ResolveUrl(SystemDirectories.Umbraco).ToLower()) > -1; - } - /// /// Gets a value indicating whether umbraco should force a secure (https) connection to the backoffice. /// - /// true if [use SSL]; otherwise, false. - public static bool UseSSL + public bool UseHttps { get { try { - return bool.Parse(ConfigurationManager.AppSettings["umbracoUseSSL"]); + return bool.Parse(ConfigurationManager.AppSettings["umbracoUseHttps"]); } catch { @@ -733,131 +374,6 @@ namespace Umbraco.Core.Configuration } } - /// - /// Gets the umbraco license. - /// - /// The license. - public static string License - { - get - { - string license = - "the open source license MIT. The umbraco UI is freeware licensed under the umbraco license."; - - var versionDoc = new XmlDocument(); - var versionReader = new XmlTextReader(IOHelper.MapPath(SystemDirectories.Umbraco + "/version.xml")); - versionDoc.Load(versionReader); - versionReader.Close(); - - // check for license - try - { - string licenseUrl = - versionDoc.SelectSingleNode("/version/licensing/licenseUrl").FirstChild.Value; - string licenseValidation = - versionDoc.SelectSingleNode("/version/licensing/licenseValidation").FirstChild.Value; - string licensedTo = - versionDoc.SelectSingleNode("/version/licensing/licensedTo").FirstChild.Value; - - if (licensedTo != "" && licenseUrl != "") - { - license = "umbraco Commercial License
Registered to:
" + - licensedTo.Replace("\n", "
") + "
For use with domain:
" + - licenseUrl; - } - } - catch - { - } - - return license; - } - } - - /// - /// Determines whether the current request is reserved based on the route table and - /// whether the specified URL is reserved or is inside a reserved path. - /// - /// - /// - /// The route collection to lookup the request in - /// - public static bool IsReservedPathOrUrl(string url, HttpContextBase httpContext, RouteCollection routes) - { - if (httpContext == null) throw new ArgumentNullException("httpContext"); - if (routes == null) throw new ArgumentNullException("routes"); - - //check if the current request matches a route, if so then it is reserved. - var route = routes.GetRouteData(httpContext); - if (route != null) - return true; - - //continue with the standard ignore routine - return IsReservedPathOrUrl(url); - } - - /// - /// Determines whether the specified URL is reserved or is inside a reserved path. - /// - /// The URL to check. - /// - /// true if the specified URL is reserved; otherwise, false. - /// - public static bool IsReservedPathOrUrl(string url) - { - if (_reservedUrlsCache == null) - { - lock (Locker) - { - if (_reservedUrlsCache == null) - { - // store references to strings to determine changes - _reservedPathsCache = GlobalSettings.ReservedPaths; - _reservedUrlsCache = GlobalSettings.ReservedUrls; - - // add URLs and paths to a new list - var newReservedList = new HashSet(); - foreach (var reservedUrlTrimmed in _reservedUrlsCache - .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim().ToLowerInvariant()) - .Where(x => x.IsNullOrWhiteSpace() == false) - .Select(reservedUrl => IOHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/")) - .Where(reservedUrlTrimmed => reservedUrlTrimmed.IsNullOrWhiteSpace() == false)) - { - newReservedList.Add(reservedUrlTrimmed); - } - - foreach (var reservedPathTrimmed in _reservedPathsCache - .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim().ToLowerInvariant()) - .Where(x => x.IsNullOrWhiteSpace() == false) - .Select(reservedPath => IOHelper.ResolveUrl(reservedPath).Trim().EnsureStartsWith("/").EnsureEndsWith("/")) - .Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false)) - { - newReservedList.Add(reservedPathTrimmed); - } - - // use the new list from now on - _reservedList = newReservedList; - } - } - } - - //The url should be cleaned up before checking: - // * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/' - // * We shouldn't be comparing the query at all - var pathPart = url.Split(new[] {'?'}, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant(); - if (pathPart.Contains(".") == false) - { - pathPart = pathPart.EnsureEndsWith('/'); - } - - // return true if url starts with an element of the reserved list - return _reservedList.Any(x => pathPart.InvariantStartsWith(x)); - } - - - } diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs new file mode 100644 index 0000000000..1e37ae0668 --- /dev/null +++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Routing; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Configuration +{ + public static class GlobalSettingsExtensions + { + /// + /// Used in unit testing to reset all config items, this is automatically called by GlobalSettings.Reset() + /// + internal static void Reset() + { + _reservedUrlsCache = null; + _mvcArea = null; + } + + private static readonly object Locker = new object(); + //make this volatile so that we can ensure thread safety with a double check lock + private static volatile string _reservedUrlsCache; + private static string _reservedPathsCache; + private static HashSet _reservedList = new HashSet(); + private static string _mvcArea; + + /// + /// This returns the string of the MVC Area route. + /// + /// + /// This will return the MVC area that we will route all custom routes through like surface controllers, etc... + /// We will use the 'Path' (default ~/umbraco) to create it but since it cannot contain '/' and people may specify a path of ~/asdf/asdf/admin + /// we will convert the '/' to '-' and use that as the path. its a bit lame but will work. + /// + /// We also make sure that the virtual directory (SystemDirectories.Root) is stripped off first, otherwise we'd end up with something + /// like "MyVirtualDirectory-Umbraco" instead of just "Umbraco". + /// + public static string GetUmbracoMvcArea(this IGlobalSettings globalSettings) + { + if (_mvcArea != null) return _mvcArea; + + if (globalSettings.Path.IsNullOrWhiteSpace()) + { + throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified"); + } + + var path = globalSettings.Path; + if (path.StartsWith(SystemDirectories.Root)) // beware of TrimStart, see U4-2518 + path = path.Substring(SystemDirectories.Root.Length); + _mvcArea = path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower(); + return _mvcArea; + } + + /// + /// Determines whether the specified URL is reserved or is inside a reserved path. + /// + /// + /// The URL to check. + /// + /// true if the specified URL is reserved; otherwise, false. + /// + internal static bool IsReservedPathOrUrl(this IGlobalSettings globalSettings, string url) + { + if (_reservedUrlsCache == null) + { + lock (Locker) + { + if (_reservedUrlsCache == null) + { + // store references to strings to determine changes + _reservedPathsCache = globalSettings.ReservedPaths; + _reservedUrlsCache = globalSettings.ReservedUrls; + + // add URLs and paths to a new list + var newReservedList = new HashSet(); + foreach (var reservedUrlTrimmed in _reservedUrlsCache + .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim().ToLowerInvariant()) + .Where(x => x.IsNullOrWhiteSpace() == false) + .Select(reservedUrl => IOHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/")) + .Where(reservedUrlTrimmed => reservedUrlTrimmed.IsNullOrWhiteSpace() == false)) + { + newReservedList.Add(reservedUrlTrimmed); + } + + foreach (var reservedPathTrimmed in _reservedPathsCache + .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim().ToLowerInvariant()) + .Where(x => x.IsNullOrWhiteSpace() == false) + .Select(reservedPath => IOHelper.ResolveUrl(reservedPath).Trim().EnsureStartsWith("/").EnsureEndsWith("/")) + .Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false)) + { + newReservedList.Add(reservedPathTrimmed); + } + + // use the new list from now on + _reservedList = newReservedList; + } + } + } + + //The url should be cleaned up before checking: + // * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/' + // * We shouldn't be comparing the query at all + var pathPart = url.Split(new[] {'?'}, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant(); + if (pathPart.Contains(".") == false) + { + pathPart = pathPart.EnsureEndsWith('/'); + } + + // return true if url starts with an element of the reserved list + return _reservedList.Any(x => pathPart.InvariantStartsWith(x)); + } + + /// + /// Determines whether the current request is reserved based on the route table and + /// whether the specified URL is reserved or is inside a reserved path. + /// + /// + /// + /// + /// The route collection to lookup the request in + /// + internal static bool IsReservedPathOrUrl(this IGlobalSettings globalSettings, string url, HttpContextBase httpContext, RouteCollection routes) + { + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); + if (routes == null) throw new ArgumentNullException(nameof(routes)); + + //check if the current request matches a route, if so then it is reserved. + var route = routes.GetRouteData(httpContext); + if (route != null) + return true; + + //continue with the standard ignore routine + return globalSettings.IsReservedPathOrUrl(url); + } + + + } +} diff --git a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs index 334124a4e5..beade2d6d1 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using Umbraco.Core.Cache; using Umbraco.Core.Logging; @@ -13,4 +13,4 @@ namespace Umbraco.Core.Configuration.Grid public IGridEditorsConfig EditorsConfig { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index 3026064dec..a0ca2da4ff 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using Newtonsoft.Json.Linq; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; @@ -32,16 +33,16 @@ namespace Umbraco.Core.Configuration.Grid { Func> getResult = () => { + // fixme - should use the common one somehow! + ignoring _appPlugins here! + var parser = new ManifestParser(_runtimeCache, Current.ManifestValidators, _logger); + var editors = new List(); var gridConfig = Path.Combine(_configFolder.FullName, "grid.editors.config.js"); if (File.Exists(gridConfig)) { try { - var arr = JArray.Parse(File.ReadAllText(gridConfig)); - //ensure the contents parse correctly to objects - var parsed = ManifestParser.GetGridEditors(arr); - editors.AddRange(parsed); + editors.AddRange(parser.ParseGridEditors(File.ReadAllText(gridConfig))); } catch (Exception ex) { @@ -49,16 +50,13 @@ namespace Umbraco.Core.Configuration.Grid } } - var parser = new ManifestParser(_appPlugins, _runtimeCache); - var builder = new ManifestBuilder(_runtimeCache, parser); - foreach (var gridEditor in builder.GridEditors) + // add manifest editors, skip duplicates + foreach (var gridEditor in parser.Manifest.GridEditors) { - //no duplicates! (based on alias) if (editors.Contains(gridEditor) == false) - { editors.Add(gridEditor); - } } + return editors; }; @@ -72,7 +70,7 @@ namespace Umbraco.Core.Configuration.Grid return result; } - + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs b/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs index 0eedd5e035..e447f7f493 100644 --- a/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs @@ -12,4 +12,4 @@ namespace Umbraco.Core.Configuration.Grid string Icon { get; } IDictionary Config { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs index 64fb1a831f..418d9b9560 100644 --- a/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs @@ -6,4 +6,4 @@ namespace Umbraco.Core.Configuration.Grid { IEnumerable Editors { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/HealthChecks/DisabledHealthChecksElementCollection.cs b/src/Umbraco.Core/Configuration/HealthChecks/DisabledHealthChecksElementCollection.cs index cf57a3a91a..87600b8ae3 100644 --- a/src/Umbraco.Core/Configuration/HealthChecks/DisabledHealthChecksElementCollection.cs +++ b/src/Umbraco.Core/Configuration/HealthChecks/DisabledHealthChecksElementCollection.cs @@ -4,7 +4,7 @@ using System.Configuration; namespace Umbraco.Core.Configuration.HealthChecks { [ConfigurationCollection(typeof(DisabledHealthCheckElement), AddItemName = "check")] - public class DisabledHealthChecksElementCollection : ConfigurationElementCollection, IEnumerable + public class DisabledHealthChecksElementCollection : ConfigurationElementCollection, IEnumerable { protected override ConfigurationElement CreateNewElement() { diff --git a/src/Umbraco.Core/Configuration/HealthChecks/IHealthChecks.cs b/src/Umbraco.Core/Configuration/HealthChecks/IHealthChecks.cs index 7680836074..fa98e3b054 100644 --- a/src/Umbraco.Core/Configuration/HealthChecks/IHealthChecks.cs +++ b/src/Umbraco.Core/Configuration/HealthChecks/IHealthChecks.cs @@ -7,4 +7,4 @@ namespace Umbraco.Core.Configuration.HealthChecks IEnumerable DisabledChecks { get; } IHealthCheckNotificationSettings NotificationSettings { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/HealthChecks/INotificationMethodSettings.cs b/src/Umbraco.Core/Configuration/HealthChecks/INotificationMethodSettings.cs index 89d4799484..e41c82b393 100644 --- a/src/Umbraco.Core/Configuration/HealthChecks/INotificationMethodSettings.cs +++ b/src/Umbraco.Core/Configuration/HealthChecks/INotificationMethodSettings.cs @@ -5,4 +5,4 @@ string Key { get; } string Value { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodElement.cs b/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodElement.cs index 2646c97475..cbbe5e8b02 100644 --- a/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodElement.cs +++ b/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodElement.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Configuration.HealthChecks private const string SettingsKey = "settings"; [ConfigurationProperty(AliasKey, IsKey = true, IsRequired = true)] - public string Alias + public string Alias { get { diff --git a/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodSettingsElement.cs b/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodSettingsElement.cs index a861291427..ed42eb7221 100644 --- a/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodSettingsElement.cs +++ b/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodSettingsElement.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Configuration.HealthChecks private const string ValueKey = "value"; [ConfigurationProperty(KeyKey, IsKey = true, IsRequired = true)] - public string Key + public string Key { get { diff --git a/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodSettingsElementCollection.cs b/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodSettingsElementCollection.cs index 174727f722..226278ab61 100644 --- a/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodSettingsElementCollection.cs +++ b/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodSettingsElementCollection.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Configuration.HealthChecks protected override object GetElementKey(ConfigurationElement element) { return ((NotificationMethodSettingsElement)(element)).Key; - } + } IEnumerator> IEnumerable>.GetEnumerator() { diff --git a/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodsElementCollection.cs b/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodsElementCollection.cs index 9eebc47309..ee7e135961 100644 --- a/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodsElementCollection.cs +++ b/src/Umbraco.Core/Configuration/HealthChecks/NotificationMethodsElementCollection.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Configuration.HealthChecks { return ((NotificationMethodElement)(element)).Alias; } - + IEnumerator> IEnumerable>.GetEnumerator() { for (var i = 0; i < Count; i++) diff --git a/src/Umbraco.Core/Configuration/IFileSystemProviderElement.cs b/src/Umbraco.Core/Configuration/IFileSystemProviderElement.cs index 9427f42b68..438e6f2b81 100644 --- a/src/Umbraco.Core/Configuration/IFileSystemProviderElement.cs +++ b/src/Umbraco.Core/Configuration/IFileSystemProviderElement.cs @@ -8,4 +8,4 @@ namespace Umbraco.Core.Configuration string Type { get; } IDictionary Parameters { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/IFileSystemProvidersSection.cs b/src/Umbraco.Core/Configuration/IFileSystemProvidersSection.cs index 108d5a87de..f87ba383a9 100644 --- a/src/Umbraco.Core/Configuration/IFileSystemProvidersSection.cs +++ b/src/Umbraco.Core/Configuration/IFileSystemProvidersSection.cs @@ -6,4 +6,4 @@ namespace Umbraco.Core.Configuration { IDictionary Providers { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/IGlobalSettings.cs b/src/Umbraco.Core/Configuration/IGlobalSettings.cs new file mode 100644 index 0000000000..cf9478d30a --- /dev/null +++ b/src/Umbraco.Core/Configuration/IGlobalSettings.cs @@ -0,0 +1,84 @@ +namespace Umbraco.Core.Configuration +{ + /// + /// Contains general settings information for the entire Umbraco instance based on information from web.config appsettings + /// + public interface IGlobalSettings + { + /// + /// Gets the reserved urls from web.config. + /// + /// The reserved urls. + string ReservedUrls { get; } + + /// + /// Gets the reserved paths from web.config + /// + /// The reserved paths. + string ReservedPaths { get; } + + /// + /// Gets the name of the content XML file. + /// + /// The content XML. + /// + /// Defaults to ~/App_Data/umbraco.config + /// + string ContentXmlFile { get; } + + /// + /// Gets the path to umbraco's root directory (/umbraco by default). + /// + string Path { get; } + + /// + /// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance. + /// + string ConfigurationStatus { get; set; } + + /// + /// Gets the time out in minutes. + /// + int TimeOutInMinutes { get; } + + /// + /// Gets a value indicating whether umbraco uses directory urls. + /// + /// true if umbraco uses directory urls; otherwise, false. + bool UseDirectoryUrls { get; } + + /// + /// Gets the default UI language. + /// + /// The default UI language. + // ReSharper disable once InconsistentNaming + string DefaultUILanguage { get; } + + /// + /// Gets a value indicating whether umbraco should hide top level nodes from generated urls. + /// + /// + /// true if umbraco hides top level nodes from urls; otherwise, false. + /// + bool HideTopLevelNodeFromPath { get; } + + /// + /// Gets a value indicating whether umbraco should force a secure (https) connection to the backoffice. + /// + bool UseHttps { get; } + + /// + /// Returns a string value to determine if umbraco should skip version-checking. + /// + /// The version check period in days (0 = never). + int VersionCheckPeriod { get; } + + /// + /// This is the location type to store temporary files such as cache files or other localized files for a given machine + /// + /// + /// Used for some cache files and for specific environments such as Azure + /// + LocalTempStorage LocalTempStorageLocation { get; } + } +} diff --git a/src/Umbraco.Core/Configuration/InfrastructureSettings/Infrastructure.cs b/src/Umbraco.Core/Configuration/InfrastructureSettings/Infrastructure.cs deleted file mode 100644 index 8a8f4454bc..0000000000 --- a/src/Umbraco.Core/Configuration/InfrastructureSettings/Infrastructure.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.InfrastructureSettings -{ - public class Infrastructure : ConfigurationSection - { - private const string InfrastructureSectionName = "umbraco/infrastructure"; - - public static Infrastructure Instance - { - get { return (Infrastructure) ConfigurationManager.GetSection(InfrastructureSectionName); } - } - - #region RepositoriesSection Property - - internal const string RepositoriesPropertyName = "repositories"; - - [ConfigurationProperty(RepositoriesPropertyName, IsRequired = true, IsKey = false, IsDefaultCollection = false)] - public Repositories Repositories - { - get { return ((Repositories)base[RepositoriesPropertyName]); } - set { base[RepositoriesPropertyName] = value; } - } - - #endregion - - #region PublishingStrategy Property - - internal const string PublishingStrategyPropertyName = "publishingStrategy"; - - [ConfigurationProperty(PublishingStrategyPropertyName, IsRequired = true, IsKey = false, IsDefaultCollection = false)] - public PublishingProvider PublishingStrategy - { - get { return ((PublishingProvider)base[PublishingStrategyPropertyName]); } - set { base[PublishingStrategyPropertyName] = value; } - } - - #endregion - } - - public class Repositories : ConfigurationElement - { - [ConfigurationProperty("", IsDefaultCollection = true, IsRequired = true)] - public RepositoryElementCollection Repository - { - get { return ((RepositoryElementCollection)(base[""])); } - } - } - - [ConfigurationCollection(typeof(Repository), CollectionType = ConfigurationElementCollectionType.BasicMapAlternate, AddItemName = RepositoryPropertyName)] - public class RepositoryElementCollection : ConfigurationElementCollection - { - internal const string RepositoryPropertyName = "repository"; - - public override ConfigurationElementCollectionType CollectionType - { - get - { - return ConfigurationElementCollectionType.BasicMapAlternate; - } - } - - protected override string ElementName - { - get - { - return RepositoryPropertyName; - } - } - - protected override bool IsElementName(string elementName) - { - return elementName == RepositoryPropertyName; - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((Repository)element).InterfaceShortTypeName; - } - - protected override ConfigurationElement CreateNewElement() - { - return new Repository(); - } - - #region Indexer - - public Repository this[int index] - { - get { return (Repository)base.BaseGet(index); } - } - - public Repository this[string interfaceShortTypeName] - { - get { return (Repository)base.BaseGet(interfaceShortTypeName); } - } - - #endregion - - #region Add - - public void Add(Repository repository) - { - BaseAdd(repository); - } - - #endregion - - #region Remove - - public void Remove(Repository repository) - { - BaseRemove(repository); - } - - #endregion - - #region GetItem - - public Repository GetItemAt(int index) - { - return (Repository)BaseGet(index); - } - - public Repository GetItemByKey(string interfaceShortTypeName) - { - return (Repository)BaseGet(interfaceShortTypeName); - } - - #endregion - - public bool ContainsKey(string interfaceShortName) - { - bool result = false; - object[] keys = this.BaseGetAllKeys(); - foreach (object key in keys) - { - if ((string)key == interfaceShortName) - { - result = true; - break; - - } - } - return result; - } - } - - public class Repository : ConfigurationElement - { - internal const string InterfaceShortTypeNamePropertyName = "interfaceShortTypeName"; - - [ConfigurationPropertyAttribute(InterfaceShortTypeNamePropertyName, IsRequired = true, IsKey = true, IsDefaultCollection = false)] - public string InterfaceShortTypeName - { - get { return (string) base[InterfaceShortTypeNamePropertyName]; } - set { base[InterfaceShortTypeNamePropertyName] = value; } - } - - internal const string RepositoryFullTypeNamePropertyName = "repositoryFullTypeName"; - - [ConfigurationPropertyAttribute(RepositoryFullTypeNamePropertyName, IsRequired = true, IsKey = false, IsDefaultCollection = false)] - public string RepositoryFullTypeName - { - get { return (string)base[RepositoryFullTypeNamePropertyName]; } - set { base[RepositoryFullTypeNamePropertyName] = value; } - } - - internal const string CacheProviderFullTypeNamePropertyName = "cacheProviderFullTypeName"; - - [ConfigurationPropertyAttribute(CacheProviderFullTypeNamePropertyName, IsRequired = true, IsKey = false, IsDefaultCollection = false)] - public string CacheProviderFullTypeName - { - get { return (string)base[CacheProviderFullTypeNamePropertyName]; } - set { base[CacheProviderFullTypeNamePropertyName] = value; } - } - } - - public class PublishingProvider : ConfigurationElement - { - internal const string TypePropertyName = "type"; - - [ConfigurationPropertyAttribute(TypePropertyName, IsRequired = true, IsKey = false, IsDefaultCollection = false)] - public string Type - { - get { return (string)base[TypePropertyName]; } - set { base[TypePropertyName] = value; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/InnerTextConfigurationElement.cs b/src/Umbraco.Core/Configuration/InnerTextConfigurationElement.cs index fdbbc7889e..cfeec5ab1b 100644 --- a/src/Umbraco.Core/Configuration/InnerTextConfigurationElement.cs +++ b/src/Umbraco.Core/Configuration/InnerTextConfigurationElement.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Xml; using System.Xml.Linq; @@ -66,4 +66,4 @@ namespace Umbraco.Core.Configuration } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/LocalTempStorage.cs b/src/Umbraco.Core/Configuration/LocalTempStorage.cs index d41f7d1925..0013fb68e4 100644 --- a/src/Umbraco.Core/Configuration/LocalTempStorage.cs +++ b/src/Umbraco.Core/Configuration/LocalTempStorage.cs @@ -1,9 +1,10 @@ namespace Umbraco.Core.Configuration { - internal enum LocalTempStorage + public enum LocalTempStorage { + Unknown = 0, Default, AspNetTemp, EnvironmentTemp } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/OptionalCommaDelimitedConfigurationElement.cs b/src/Umbraco.Core/Configuration/OptionalCommaDelimitedConfigurationElement.cs index 59ad883e03..8b5b21762f 100644 --- a/src/Umbraco.Core/Configuration/OptionalCommaDelimitedConfigurationElement.cs +++ b/src/Umbraco.Core/Configuration/OptionalCommaDelimitedConfigurationElement.cs @@ -1,4 +1,4 @@ -using System.Configuration; +using System.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; namespace Umbraco.Core.Configuration @@ -12,7 +12,7 @@ namespace Umbraco.Core.Configuration private readonly string[] _defaultValue; public OptionalCommaDelimitedConfigurationElement() - { + { } public OptionalCommaDelimitedConfigurationElement(CommaDelimitedConfigurationElement wrapped, string[] defaultValue) @@ -40,4 +40,4 @@ namespace Umbraco.Core.Configuration } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/OptionalInnerTextConfigurationElement.cs b/src/Umbraco.Core/Configuration/OptionalInnerTextConfigurationElement.cs index 832a74fec2..8245811af0 100644 --- a/src/Umbraco.Core/Configuration/OptionalInnerTextConfigurationElement.cs +++ b/src/Umbraco.Core/Configuration/OptionalInnerTextConfigurationElement.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Configuration +namespace Umbraco.Core.Configuration { /// /// This is used to supply optional/default values when using InnerTextConfigurationElement @@ -20,4 +20,4 @@ namespace Umbraco.Core.Configuration get { return string.IsNullOrEmpty(_wrapped.RawValue) ? _defaultValue : _wrapped.Value; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/RawXmlConfigurationElement.cs b/src/Umbraco.Core/Configuration/RawXmlConfigurationElement.cs index f2d2d8a52f..1b09b80d68 100644 --- a/src/Umbraco.Core/Configuration/RawXmlConfigurationElement.cs +++ b/src/Umbraco.Core/Configuration/RawXmlConfigurationElement.cs @@ -1,4 +1,4 @@ -using System.Configuration; +using System.Configuration; using System.Xml; using System.Xml.Linq; @@ -12,7 +12,7 @@ namespace Umbraco.Core.Configuration { protected RawXmlConfigurationElement() { - + } protected RawXmlConfigurationElement(XElement rawXml) @@ -27,4 +27,4 @@ namespace Umbraco.Core.Configuration protected XElement RawXml { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/RazorDataTypeModelStaticMappingItem.cs b/src/Umbraco.Core/Configuration/RazorDataTypeModelStaticMappingItem.cs deleted file mode 100644 index 71aa7eaa0e..0000000000 --- a/src/Umbraco.Core/Configuration/RazorDataTypeModelStaticMappingItem.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; - -namespace Umbraco.Core.Configuration -{ - - //NOTE: This is used in the old DynamicNode for performing value conversions/mappings for certain data types. - // it has been obsoleted because we've replaced this with the PropertyEditorValueConvertersResolver which can - // have converters registered in code so we don't have to rely on even more config sections. - // These things probably won't need to be created all that often and in code is much easier to do. - - internal class RazorDataTypeModelStaticMappingItem - { - [Obsolete("This is not used whatsoever")] - public string Raw { get; set; } - //if all of the set (non null) properties match the property data currently being evaluated - public string PropertyTypeAlias { get; set; } - public string NodeTypeAlias { get; set; } - public Guid? DataTypeGuid { get; set; } - public string TypeName { get; set; } - - public bool Applies(Guid dataTypeGuid, string nodeTypeAlias, string propertyTypeAlias) - { - return - ( - (this.NodeTypeAlias != null || this.PropertyTypeAlias != null || this.DataTypeGuid != null) && - ((this.DataTypeGuid != null && this.DataTypeGuid == dataTypeGuid) || this.DataTypeGuid == null) && - ((this.PropertyTypeAlias != null && this.PropertyTypeAlias == propertyTypeAlias) || this.PropertyTypeAlias == null) && - ((this.NodeTypeAlias != null && this.NodeTypeAlias == nodeTypeAlias) || this.NodeTypeAlias == null) - ); - } - } -} diff --git a/src/Umbraco.Core/Configuration/UmbracoConfig.cs b/src/Umbraco.Core/Configuration/UmbracoConfig.cs index 82d90073e8..f16faab06e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoConfig.cs +++ b/src/Umbraco.Core/Configuration/UmbracoConfig.cs @@ -1,16 +1,12 @@ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Configuration; using System.IO; -using System.Linq; -using System.Threading; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.BaseRest; using Umbraco.Core.Configuration.Dashboard; using Umbraco.Core.Configuration.Grid; using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; namespace Umbraco.Core.Configuration @@ -24,33 +20,35 @@ namespace Umbraco.Core.Configuration private static readonly Lazy Lazy = new Lazy(() => new UmbracoConfig()); - public static UmbracoConfig For - { - get { return Lazy.Value; } - } + public static UmbracoConfig For => Lazy.Value; #endregion /// - /// Default constructor + /// Default constructor /// private UmbracoConfig() { + // note: need to use SafeCallContext here because ConfigurationManager.GetSection ends up getting AppDomain.Evidence + // which will want to serialize the call context including anything that is in there - what a mess! + if (_umbracoSettings == null) { - var umbracoSettings = ConfigurationManager.GetSection("umbracoConfiguration/settings") as IUmbracoSettingsSection; + IUmbracoSettingsSection umbracoSettings; + using (new SafeCallContext()) + { + umbracoSettings = ConfigurationManager.GetSection("umbracoConfiguration/settings") as IUmbracoSettingsSection; + } SetUmbracoSettings(umbracoSettings); } - if (_baseRestExtensions == null) - { - var baseRestExtensions = ConfigurationManager.GetSection("umbracoConfiguration/BaseRestExtensions") as IBaseRestSection; - SetBaseRestExtensions(baseRestExtensions); - } - if (_dashboardSection == null) { - var dashboardConfig = ConfigurationManager.GetSection("umbracoConfiguration/dashBoard") as IDashboardSection; + IDashboardSection dashboardConfig; + using (new SafeCallContext()) + { + dashboardConfig = ConfigurationManager.GetSection("umbracoConfiguration/dashBoard") as IDashboardSection; + } SetDashboardSettings(dashboardConfig); } @@ -65,22 +63,22 @@ namespace Umbraco.Core.Configuration /// Constructor - can be used for testing /// /// - /// /// /// - public UmbracoConfig(IUmbracoSettingsSection umbracoSettings, IBaseRestSection baseRestSettings, IDashboardSection dashboardSettings, IHealthChecks healthChecks) + /// + public UmbracoConfig(IUmbracoSettingsSection umbracoSettings, IDashboardSection dashboardSettings, IHealthChecks healthChecks, IGlobalSettings globalSettings) { SetHealthCheckSettings(healthChecks); SetUmbracoSettings(umbracoSettings); - SetBaseRestExtensions(baseRestSettings); SetDashboardSettings(dashboardSettings); + SetGlobalConfig(globalSettings); } private IHealthChecks _healthChecks; private IDashboardSection _dashboardSection; private IUmbracoSettingsSection _umbracoSettings; - private IBaseRestSection _baseRestExtensions; private IGridConfig _gridConfig; + private IGlobalSettings _globalSettings; /// /// Gets the IHealthCheck config @@ -90,13 +88,13 @@ namespace Umbraco.Core.Configuration if (_healthChecks == null) { var ex = new ConfigurationErrorsException("Could not load the " + typeof(IHealthChecks) + " from config file, ensure the web.config and healthchecks.config files are formatted correctly"); - LogHelper.Error("Config error", ex); + Current.Logger.Error("Config error", ex); throw ex; } return _healthChecks; - } - + } + /// /// Gets the IDashboardSection /// @@ -105,13 +103,13 @@ namespace Umbraco.Core.Configuration if (_dashboardSection == null) { var ex = new ConfigurationErrorsException("Could not load the " + typeof(IDashboardSection) + " from config file, ensure the web.config and Dashboard.config files are formatted correctly"); - LogHelper.Error("Config error", ex); + Current.Logger.Error("Config error", ex); throw ex; } return _dashboardSection; - } - + } + /// /// Only for testing /// @@ -139,6 +137,23 @@ namespace Umbraco.Core.Configuration _umbracoSettings = value; } + /// + /// Only for testing + /// + /// + public void SetGlobalConfig(IGlobalSettings value) + { + _globalSettings = value; + } + + /// + /// Gets the IGlobalSettings + /// + public IGlobalSettings GlobalSettings() + { + return _globalSettings ?? (_globalSettings = new GlobalSettings()); + } + /// /// Gets the IUmbracoSettings /// @@ -147,36 +162,12 @@ namespace Umbraco.Core.Configuration if (_umbracoSettings == null) { var ex = new ConfigurationErrorsException("Could not load the " + typeof (IUmbracoSettingsSection) + " from config file, ensure the web.config and umbracoSettings.config files are formatted correctly"); - LogHelper.Error("Config error", ex); + Current.Logger.Error("Config error", ex); throw ex; } return _umbracoSettings; } - - /// - /// Only for testing - /// - /// - public void SetBaseRestExtensions(IBaseRestSection value) - { - _baseRestExtensions = value; - } - - /// - /// Gets the IBaseRestSection - /// - public IBaseRestSection BaseRestExtensions() - { - if (_baseRestExtensions == null) - { - var ex = new ConfigurationErrorsException("Could not load the " + typeof(IBaseRestSection) + " from config file, ensure the web.config and BaseRestExtensions.config files are formatted correctly"); - LogHelper.Error("Config error", ex); - throw ex; - } - - return _baseRestExtensions; - } /// /// Only for testing @@ -202,4 +193,4 @@ namespace Umbraco.Core.Configuration //TODO: Add other configurations here ! } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs b/src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs index 2b7678a6d8..930d73c5f5 100644 --- a/src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Configuration /// public interface IUmbracoConfigurationSection { - + } /// diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsCollection.cs index 95510f094b..f602389f1f 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsCollection.cs @@ -33,4 +33,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsElement.cs index db260dbe66..3642cc576e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/AppCodeFileExtensionsElement.cs @@ -14,4 +14,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/CharCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/CharCollection.cs index f7ba4de35d..8ce65f95cb 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/CharCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/CharCollection.cs @@ -33,4 +33,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/CharElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/CharElement.cs index f785bf616c..6252ef7888 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/CharElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/CharElement.cs @@ -20,13 +20,13 @@ string IChar.Char { get { return Char; } - + } string IChar.Replacement { get { return Replacement; } - + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 64c9755f7d..b58df9b527 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -1,363 +1,146 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Configuration; +using Umbraco.Core.Macros; namespace Umbraco.Core.Configuration.UmbracoSettings { internal class ContentElement : UmbracoConfigurationElement, IContentSection { + private const string DefaultPreviewBadge = @"In Preview Mode - click to end"; + [ConfigurationProperty("imaging")] - internal ContentImagingElement Imaging - { - get { return (ContentImagingElement)this["imaging"]; } - } + internal ContentImagingElement Imaging => (ContentImagingElement) this["imaging"]; [ConfigurationProperty("scripteditor")] - internal ContentScriptEditorElement ScriptEditor - { - get { return (ContentScriptEditorElement)this["scripteditor"]; } - } + internal ContentScriptEditorElement ScriptEditor => (ContentScriptEditorElement) this["scripteditor"]; [ConfigurationProperty("ResolveUrlsFromTextString")] - internal InnerTextConfigurationElement ResolveUrlsFromTextString - { - get { return GetOptionalTextElement("ResolveUrlsFromTextString", false); } - } + internal InnerTextConfigurationElement ResolveUrlsFromTextString => GetOptionalTextElement("ResolveUrlsFromTextString", false); [ConfigurationProperty("UploadAllowDirectories")] - internal InnerTextConfigurationElement UploadAllowDirectories - { - get { return GetOptionalTextElement("UploadAllowDirectories", true); } - } + internal InnerTextConfigurationElement UploadAllowDirectories => GetOptionalTextElement("UploadAllowDirectories", true); - public IEnumerable Error404Collection - { - get { return Errors.Error404Collection; } - } + public IEnumerable Error404Collection => Errors.Error404Collection; [ConfigurationProperty("errors", IsRequired = true)] - internal ContentErrorsElement Errors - { - get { return (ContentErrorsElement) base["errors"]; } - } + internal ContentErrorsElement Errors => (ContentErrorsElement) base["errors"]; [ConfigurationProperty("notifications", IsRequired = true)] - internal NotificationsElement Notifications - { - get { return (NotificationsElement)base["notifications"]; } - } + internal NotificationsElement Notifications => (NotificationsElement) base["notifications"]; [ConfigurationProperty("ensureUniqueNaming")] - internal InnerTextConfigurationElement EnsureUniqueNaming - { - get { return GetOptionalTextElement("ensureUniqueNaming", true); } - } - - [ConfigurationProperty("TidyEditorContent")] - internal InnerTextConfigurationElement TidyEditorContent - { - get { return GetOptionalTextElement("TidyEditorContent", false); } - } - - [ConfigurationProperty("TidyCharEncoding")] - internal InnerTextConfigurationElement TidyCharEncoding - { - get { return GetOptionalTextElement("TidyCharEncoding", "UTF8"); } - } + internal InnerTextConfigurationElement EnsureUniqueNaming => GetOptionalTextElement("ensureUniqueNaming", true); [ConfigurationProperty("XmlCacheEnabled")] - internal InnerTextConfigurationElement XmlCacheEnabled - { - get { return GetOptionalTextElement("XmlCacheEnabled", true); } - } + internal InnerTextConfigurationElement XmlCacheEnabled => GetOptionalTextElement("XmlCacheEnabled", true); [ConfigurationProperty("ContinouslyUpdateXmlDiskCache")] - internal InnerTextConfigurationElement ContinouslyUpdateXmlDiskCache - { - get { return GetOptionalTextElement("ContinouslyUpdateXmlDiskCache", true); } - } + internal InnerTextConfigurationElement ContinouslyUpdateXmlDiskCache => GetOptionalTextElement("ContinouslyUpdateXmlDiskCache", true); [ConfigurationProperty("XmlContentCheckForDiskChanges")] - internal InnerTextConfigurationElement XmlContentCheckForDiskChanges - { - get { return GetOptionalTextElement("XmlContentCheckForDiskChanges", false); } - } + internal InnerTextConfigurationElement XmlContentCheckForDiskChanges => GetOptionalTextElement("XmlContentCheckForDiskChanges", false); [ConfigurationProperty("EnableSplashWhileLoading")] - internal InnerTextConfigurationElement EnableSplashWhileLoading - { - get { return GetOptionalTextElement("EnableSplashWhileLoading", false); } - } + internal InnerTextConfigurationElement EnableSplashWhileLoading => GetOptionalTextElement("EnableSplashWhileLoading", false); [ConfigurationProperty("PropertyContextHelpOption")] - internal InnerTextConfigurationElement PropertyContextHelpOption - { - get { return GetOptionalTextElement("PropertyContextHelpOption", "text"); } - } + internal InnerTextConfigurationElement PropertyContextHelpOption => GetOptionalTextElement("PropertyContextHelpOption", "text"); - [ConfigurationProperty("UseLegacyXmlSchema")] - internal InnerTextConfigurationElement UseLegacyXmlSchema - { - get { return GetOptionalTextElement("UseLegacyXmlSchema", false); } - } - [ConfigurationProperty("ForceSafeAliases")] - internal InnerTextConfigurationElement ForceSafeAliases - { - get { return GetOptionalTextElement("ForceSafeAliases", true); } - } + internal InnerTextConfigurationElement ForceSafeAliases => GetOptionalTextElement("ForceSafeAliases", true); [ConfigurationProperty("PreviewBadge")] - internal InnerTextConfigurationElement PreviewBadge - { - get - { - return GetOptionalTextElement("PreviewBadge", @"In Preview Mode - click to end"); - } - } + internal InnerTextConfigurationElement PreviewBadge => GetOptionalTextElement("PreviewBadge", DefaultPreviewBadge); [ConfigurationProperty("UmbracoLibraryCacheDuration")] - internal InnerTextConfigurationElement UmbracoLibraryCacheDuration - { - get { return GetOptionalTextElement("UmbracoLibraryCacheDuration", 1800); } - } + internal InnerTextConfigurationElement UmbracoLibraryCacheDuration => GetOptionalTextElement("UmbracoLibraryCacheDuration", 1800); [ConfigurationProperty("MacroErrors")] - internal InnerTextConfigurationElement MacroErrors - { - get { return GetOptionalTextElement("MacroErrors", MacroErrorBehaviour.Inline); } - } - - [Obsolete("This is here so that if this config element exists we won't get a YSOD, it is not used whatsoever and will be removed in future versions")] - [ConfigurationProperty("DocumentTypeIconList")] - internal InnerTextConfigurationElement DocumentTypeIconList - { - get { return GetOptionalTextElement("DocumentTypeIconList", IconPickerBehaviour.HideFileDuplicates); } - } + internal InnerTextConfigurationElement MacroErrors => GetOptionalTextElement("MacroErrors", MacroErrorBehaviour.Inline); [ConfigurationProperty("disallowedUploadFiles")] - internal CommaDelimitedConfigurationElement DisallowedUploadFiles - { - get { return GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd"}); } - } - + internal CommaDelimitedConfigurationElement DisallowedUploadFiles => GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd"}); + [ConfigurationProperty("allowedUploadFiles")] - internal CommaDelimitedConfigurationElement AllowedUploadFiles - { - get { return GetOptionalDelimitedElement("allowedUploadFiles", new string[0]); } - } + internal CommaDelimitedConfigurationElement AllowedUploadFiles => GetOptionalDelimitedElement("allowedUploadFiles", new string[0]); [ConfigurationProperty("cloneXmlContent")] - internal InnerTextConfigurationElement CloneXmlContent - { - get { return GetOptionalTextElement("cloneXmlContent", true); } - } + internal InnerTextConfigurationElement CloneXmlContent => GetOptionalTextElement("cloneXmlContent", true); [ConfigurationProperty("GlobalPreviewStorageEnabled")] - internal InnerTextConfigurationElement GlobalPreviewStorageEnabled - { - get { return GetOptionalTextElement("GlobalPreviewStorageEnabled", false); } - } + internal InnerTextConfigurationElement GlobalPreviewStorageEnabled => GetOptionalTextElement("GlobalPreviewStorageEnabled", false); [ConfigurationProperty("defaultDocumentTypeProperty")] - internal InnerTextConfigurationElement DefaultDocumentTypeProperty - { - get { return GetOptionalTextElement("defaultDocumentTypeProperty", "Textstring"); } - } + internal InnerTextConfigurationElement DefaultDocumentTypeProperty => GetOptionalTextElement("defaultDocumentTypeProperty", "Textstring"); [ConfigurationProperty("showDeprecatedPropertyEditors")] - internal InnerTextConfigurationElement ShowDeprecatedPropertyEditors - { - get { return GetOptionalTextElement("showDeprecatedPropertyEditors", false); } - } + internal InnerTextConfigurationElement ShowDeprecatedPropertyEditors => GetOptionalTextElement("showDeprecatedPropertyEditors", false); [ConfigurationProperty("EnableInheritedDocumentTypes")] - internal InnerTextConfigurationElement EnableInheritedDocumentTypes - { - get { return GetOptionalTextElement("EnableInheritedDocumentTypes", true); } - } + internal InnerTextConfigurationElement EnableInheritedDocumentTypes => GetOptionalTextElement("EnableInheritedDocumentTypes", true); [ConfigurationProperty("EnableInheritedMediaTypes")] - internal InnerTextConfigurationElement EnableInheritedMediaTypes - { - get { return GetOptionalTextElement("EnableInheritedMediaTypes", true); } - } - - [ConfigurationProperty("EnablePropertyValueConverters")] - internal InnerTextConfigurationElement EnablePropertyValueConverters - { - get { return GetOptionalTextElement("EnablePropertyValueConverters", false); } - } + internal InnerTextConfigurationElement EnableInheritedMediaTypes => GetOptionalTextElement("EnableInheritedMediaTypes", true); [ConfigurationProperty("loginBackgroundImage")] - internal InnerTextConfigurationElement LoginBackgroundImage - { - get { return GetOptionalTextElement("loginBackgroundImage", string.Empty); } - } + internal InnerTextConfigurationElement LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty); - string IContentSection.NotificationEmailAddress - { - get { return Notifications.NotificationEmailAddress; } - } + string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress; - bool IContentSection.DisableHtmlEmail - { - get { return Notifications.DisableHtmlEmail; } - } + bool IContentSection.DisableHtmlEmail => Notifications.DisableHtmlEmail; - IEnumerable IContentSection.ImageFileTypes - { - get { return Imaging.ImageFileTypes; } - } + IEnumerable IContentSection.ImageFileTypes => Imaging.ImageFileTypes; - IEnumerable IContentSection.ImageTagAllowedAttributes - { - get { return Imaging.ImageTagAllowedAttributes; } - } + IEnumerable IContentSection.ImageTagAllowedAttributes => Imaging.ImageTagAllowedAttributes; - IEnumerable IContentSection.ImageAutoFillProperties - { - get { return Imaging.ImageAutoFillProperties; } - } + IEnumerable IContentSection.ImageAutoFillProperties => Imaging.ImageAutoFillProperties; - bool IContentSection.ScriptEditorDisable - { - get { return ScriptEditor.ScriptEditorDisable; } - } + bool IContentSection.ScriptEditorDisable => ScriptEditor.ScriptEditorDisable; - string IContentSection.ScriptFolderPath - { - get { return ScriptEditor.ScriptFolderPath; } - } + string IContentSection.ScriptFolderPath => ScriptEditor.ScriptFolderPath; - IEnumerable IContentSection.ScriptFileTypes - { - get { return ScriptEditor.ScriptFileTypes; } - } + IEnumerable IContentSection.ScriptFileTypes => ScriptEditor.ScriptFileTypes; - bool IContentSection.ResolveUrlsFromTextString - { - get { return ResolveUrlsFromTextString; } - } + bool IContentSection.ResolveUrlsFromTextString => ResolveUrlsFromTextString; - bool IContentSection.UploadAllowDirectories - { - get { return UploadAllowDirectories; } - } + bool IContentSection.UploadAllowDirectories => UploadAllowDirectories; - bool IContentSection.EnsureUniqueNaming - { - get { return EnsureUniqueNaming; } - } + bool IContentSection.EnsureUniqueNaming => EnsureUniqueNaming; - bool IContentSection.TidyEditorContent - { - get { return TidyEditorContent; } - } + bool IContentSection.XmlCacheEnabled => XmlCacheEnabled; - string IContentSection.TidyCharEncoding - { - get { return TidyCharEncoding; } - } + bool IContentSection.ContinouslyUpdateXmlDiskCache => ContinouslyUpdateXmlDiskCache; - bool IContentSection.XmlCacheEnabled - { - get { return XmlCacheEnabled; } - } + bool IContentSection.XmlContentCheckForDiskChanges => XmlContentCheckForDiskChanges; - bool IContentSection.ContinouslyUpdateXmlDiskCache - { - get { return ContinouslyUpdateXmlDiskCache; } - } + bool IContentSection.EnableSplashWhileLoading => EnableSplashWhileLoading; - bool IContentSection.XmlContentCheckForDiskChanges - { - get { return XmlContentCheckForDiskChanges; } - } + string IContentSection.PropertyContextHelpOption => PropertyContextHelpOption; - bool IContentSection.EnableSplashWhileLoading - { - get { return EnableSplashWhileLoading; } - } + bool IContentSection.ForceSafeAliases => ForceSafeAliases; - string IContentSection.PropertyContextHelpOption - { - get { return PropertyContextHelpOption; } - } + string IContentSection.PreviewBadge => PreviewBadge; - bool IContentSection.UseLegacyXmlSchema - { - get { return UseLegacyXmlSchema; } - } + int IContentSection.UmbracoLibraryCacheDuration => UmbracoLibraryCacheDuration; - bool IContentSection.ForceSafeAliases - { - get { return ForceSafeAliases; } - } + MacroErrorBehaviour IContentSection.MacroErrorBehaviour => MacroErrors; - string IContentSection.PreviewBadge - { - get { return PreviewBadge; } - } + IEnumerable IContentSection.DisallowedUploadFiles => DisallowedUploadFiles; - int IContentSection.UmbracoLibraryCacheDuration - { - get { return UmbracoLibraryCacheDuration; } - } + IEnumerable IContentSection.AllowedUploadFiles => AllowedUploadFiles; - MacroErrorBehaviour IContentSection.MacroErrorBehaviour - { - get { return MacroErrors; } - } + bool IContentSection.CloneXmlContent => CloneXmlContent; - IEnumerable IContentSection.DisallowedUploadFiles - { - get { return DisallowedUploadFiles; } - } - - IEnumerable IContentSection.AllowedUploadFiles - { - get { return AllowedUploadFiles; } - } + bool IContentSection.GlobalPreviewStorageEnabled => GlobalPreviewStorageEnabled; - bool IContentSection.CloneXmlContent - { - get { return CloneXmlContent; } - } + string IContentSection.DefaultDocumentTypeProperty => DefaultDocumentTypeProperty; - bool IContentSection.GlobalPreviewStorageEnabled - { - get { return GlobalPreviewStorageEnabled; } - } + bool IContentSection.ShowDeprecatedPropertyEditors => ShowDeprecatedPropertyEditors; - string IContentSection.DefaultDocumentTypeProperty - { - get { return DefaultDocumentTypeProperty; } - } + bool IContentSection.EnableInheritedDocumentTypes => EnableInheritedDocumentTypes; - bool IContentSection.ShowDeprecatedPropertyEditors - { - get { return ShowDeprecatedPropertyEditors; } - } + bool IContentSection.EnableInheritedMediaTypes => EnableInheritedMediaTypes; - bool IContentSection.EnableInheritedDocumentTypes - { - get { return EnableInheritedDocumentTypes; } - } - - bool IContentSection.EnableInheritedMediaTypes - { - get { return EnableInheritedMediaTypes; } - } - bool IContentSection.EnablePropertyValueConverters - { - get { return EnablePropertyValueConverters; } - } - - string IContentSection.LoginBackgroundImage - { - get { return LoginBackgroundImage; } - } - + string IContentSection.LoginBackgroundImage => LoginBackgroundImage; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentError404Collection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentError404Collection.cs index 6691400cc7..cd1b9722de 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentError404Collection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentError404Collection.cs @@ -34,4 +34,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorPageElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorPageElement.cs index eed15b9a0e..5cedee59dc 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorPageElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorPageElement.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings public ContentErrorPageElement() { - + } public bool HasContentId @@ -62,4 +62,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings set { RawXml.Attribute("culture").Value = value; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorsElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorsElement.cs index c03444aa6c..8066306bf0 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorsElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentErrorsElement.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { internal class ContentErrorsElement : RawXmlConfigurationElement { - + public IEnumerable Error404Collection { get @@ -14,7 +14,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings if (RawXml != null) { var e404 = RawXml.Elements("error404").First(); - var ePages = e404.Elements("errorPage").ToArray(); + var ePages = e404.Elements("errorPage").ToArray(); if (ePages.Any()) { //there are multiple @@ -34,11 +34,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { RawValue = e404.Value }); - } + } } return result; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentImagingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentImagingElement.cs index 305463856b..61406ed7b9 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentImagingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentImagingElement.cs @@ -4,7 +4,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { internal class ContentImagingElement : ConfigurationElement { - + [ConfigurationProperty("imageFileTypes")] internal CommaDelimitedConfigurationElement ImageFileTypes { @@ -61,7 +61,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings }; return _defaultImageAutoFill; } - + return (ImagingAutoFillPropertiesCollection) base["autoFillImageProperties"]; } } @@ -78,4 +78,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs index f60e964a04..f0c6400799 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentScriptEditorElement.cs @@ -24,4 +24,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs index a4f182b373..519c0e6af1 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs @@ -16,4 +16,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings contentSection.DisallowedUploadFiles.Any(x => x.InvariantEquals(extension)) == false); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/CustomBooleanTypeConverter.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/CustomBooleanTypeConverter.cs deleted file mode 100644 index 4ad77f7bb6..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/CustomBooleanTypeConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.ComponentModel; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - /// - /// Allows for converting string representations of 0 and 1 to boolean - /// - internal class CustomBooleanTypeConverter : BooleanConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } - - public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) - { - if (value is string) - { - var str = (string)value; - if (str == "1") return true; - if (str == "0" || str == "") return false; - } - - return base.ConvertFrom(context, culture, value); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/DeveloperElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/DeveloperElement.cs index f2bf88db04..66531a1ab1 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/DeveloperElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/DeveloperElement.cs @@ -44,4 +44,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return AppCodeFileExtensions.AppCodeFileExtensionsCollection; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/DisabledLogTypesCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/DisabledLogTypesCollection.cs index 63841b3445..1674274990 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/DisabledLogTypesCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/DisabledLogTypesCollection.cs @@ -28,4 +28,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/DistributedCallElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/DistributedCallElement.cs index 9a9047f838..af035d2e0e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/DistributedCallElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/DistributedCallElement.cs @@ -45,4 +45,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return Servers; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ExternalLoggerElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ExternalLoggerElement.cs index 8d8d7b3f0d..cf6e785325 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ExternalLoggerElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ExternalLoggerElement.cs @@ -22,4 +22,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (bool)base["logAuditTrail"]; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/FileExtensionElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/FileExtensionElement.cs index 939b91cd0f..d0f566093f 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/FileExtensionElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/FileExtensionElement.cs @@ -12,10 +12,10 @@ namespace Umbraco.Core.Configuration.UmbracoSettings : base(rawXml) { } - + string IFileExtension.Extension { get { return Value; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/HelpElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/HelpElement.cs deleted file mode 100644 index eb1e452100..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/HelpElement.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This is no longer used and will be removed in future versions")] - internal class HelpElement : ConfigurationElement, IHelpSection - { - [ConfigurationProperty("defaultUrl", DefaultValue = "http://our.umbraco.org/wiki/umbraco-help/{0}/{1}")] - public string DefaultUrl - { - get { return (string) base["defaultUrl"]; } - } - - [ConfigurationCollection(typeof (LinksCollection), AddItemName = "link")] - [ConfigurationProperty("", IsDefaultCollection = true)] - public LinksCollection Links - { - get { return (LinksCollection) base[""]; } - } - - string IHelpSection.DefaultUrl - { - get { return DefaultUrl; } - } - - IEnumerable IHelpSection.Links - { - get { return Links; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs index 4ec15603c2..0b3f5cb404 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs @@ -5,4 +5,4 @@ string Char { get; } string Replacement { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentErrorPage.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentErrorPage.cs index 5f10e542bb..9761727be8 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentErrorPage.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentErrorPage.cs @@ -11,4 +11,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool HasContentKey { get; } string Culture { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index d6ee260fa9..879afd65bf 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Umbraco.Core.Macros; namespace Umbraco.Core.Configuration.UmbracoSettings { @@ -28,10 +29,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool EnsureUniqueNaming { get; } - bool TidyEditorContent { get; } - - string TidyCharEncoding { get; } - bool XmlCacheEnabled { get; } bool ContinouslyUpdateXmlDiskCache { get; } @@ -42,8 +39,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings string PropertyContextHelpOption { get; } - bool UseLegacyXmlSchema { get; } - bool ForceSafeAliases { get; } string PreviewBadge { get; } @@ -51,9 +46,9 @@ namespace Umbraco.Core.Configuration.UmbracoSettings int UmbracoLibraryCacheDuration { get; } MacroErrorBehaviour MacroErrorBehaviour { get; } - - IEnumerable DisallowedUploadFiles { get; } - + + IEnumerable DisallowedUploadFiles { get; } + IEnumerable AllowedUploadFiles { get; } bool CloneXmlContent { get; } @@ -63,8 +58,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings string DefaultDocumentTypeProperty { get; } /// - /// The default for this is false but if you would like deprecated property editors displayed - /// in the data type editor you can enable this + /// Gets a value indicating whether to show deprecated property editors in + /// a datatype list of available editors. /// bool ShowDeprecatedPropertyEditors { get; } @@ -72,9 +67,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool EnableInheritedMediaTypes { get; } - bool EnablePropertyValueConverters { get; } - - string LoginBackgroundImage { get; } - + string LoginBackgroundImage { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IDeveloperSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IDeveloperSection.cs index a1f1bffd3e..40aa6af0db 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IDeveloperSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IDeveloperSection.cs @@ -6,4 +6,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { IEnumerable AppCodeFileExtensions { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IDistributedCallSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IDistributedCallSection.cs index 5f8e51d60f..96f695c8c7 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IDistributedCallSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IDistributedCallSection.cs @@ -10,4 +10,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings IEnumerable Servers { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IFileExtension.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IFileExtension.cs index 2d9ee19fcd..08ae1d3699 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IFileExtension.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IFileExtension.cs @@ -4,4 +4,4 @@ { string Extension { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IHelpSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IHelpSection.cs deleted file mode 100644 index dc9b6af437..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IHelpSection.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This is no longer used and will be removed in future versions")] - public interface IHelpSection : IUmbracoConfigurationSection - { - string DefaultUrl { get; } - - IEnumerable Links { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs index 6f42e14668..320cf641be 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs @@ -15,4 +15,4 @@ string ExtensionFieldAlias { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ILink.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ILink.cs index 689878cf0d..c4b5781ca3 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ILink.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ILink.cs @@ -17,4 +17,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings string HelpUrl { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ILogType.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ILogType.cs index 559fb1fb13..bf1a9306da 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ILogType.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ILogType.cs @@ -4,4 +4,4 @@ { string LogTypeAlias { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSection.cs index 8a9a6ad29a..b5b1a0dc37 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ILoggingSection.cs @@ -8,20 +8,10 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool EnableLogging { get; } - bool EnableAsyncLogging { get; } - int CleaningMiliseconds { get; } int MaxLogAge { get; } IEnumerable DisabledLogTypes { get; } - - string ExternalLoggerAssembly { get; } - - string ExternalLoggerType { get; } - - bool ExternalLoggerEnableAuditTrail { get; } - - bool ExternalLoggerIsConfigured { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/INotDynamicXmlDocument.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/INotDynamicXmlDocument.cs deleted file mode 100644 index 3ab1a9a67b..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/INotDynamicXmlDocument.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface INotDynamicXmlDocument - { - string Element { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IProvidersSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IProvidersSection.cs index 8a61bfffb3..52e4405847 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IProvidersSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IProvidersSection.cs @@ -3,7 +3,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { public interface IProvidersSection : IUmbracoConfigurationSection - { + { string DefaultBackOfficeUserProvider { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IRazorStaticMapping.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IRazorStaticMapping.cs deleted file mode 100644 index a330fbcfac..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IRazorStaticMapping.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IRazorStaticMapping - { - Guid DataTypeGuid { get; } - string NodeTypeAlias { get; } - string PropertyTypeAlias { get; } - string MappingName { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IRepositoriesSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IRepositoriesSection.cs index 063acbe1cf..7dbb44ba25 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IRepositoriesSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IRepositoriesSection.cs @@ -7,4 +7,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { IEnumerable Repositories { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IRepository.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IRepository.cs index 7559f090c0..cff1d40145 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IRepository.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IRepository.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings Guid Id { get; } string RepositoryUrl { get; } string WebServiceUrl { get; } - bool HasCustomWebServiceUrl { get; } + bool HasCustomWebServiceUrl { get; } string RestApiUrl { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSection.cs index a8b8d17ad9..c1151b24d2 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IRequestHandlerSection.cs @@ -16,4 +16,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings IEnumerable CharCollection { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTask.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTask.cs index 46d4256170..fd448897ee 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTask.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTask.cs @@ -10,4 +10,4 @@ string Url { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTasksSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTasksSection.cs index 8b25e11e91..8da6446675 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTasksSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IScheduledTasksSection.cs @@ -8,4 +8,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings string BaseUrl { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IScriptingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IScriptingSection.cs deleted file mode 100644 index c28dce98b8..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IScriptingSection.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IScriptingSection : IUmbracoConfigurationSection - { - IEnumerable NotDynamicXmlDocumentElements { get; } - - IEnumerable DataTypeModelStaticMappings { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs index 31ee4611d0..11eab07ec2 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs @@ -5,7 +5,7 @@ bool KeepUserLoggedIn { get; } bool HideDisabledUsersInBackoffice { get; } - + /// /// Used to enable/disable the forgot password functionality on the back office login screen /// @@ -13,15 +13,15 @@ string AuthCookieName { get; } - string AuthCookieDomain { get; } - + string AuthCookieDomain { get; } + /// /// A boolean indicating that by default the email address will be the username /// /// /// Even if this is true and the username is different from the email in the database, the username field will still be shown. /// When this is false, the username and email fields will be shown in the user section. - /// + /// bool UsernameIsEmail { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IServer.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IServer.cs index 856c570277..26b1db056e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IServer.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IServer.cs @@ -9,4 +9,4 @@ string AppId { get; } string ServerName { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ITemplatesSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ITemplatesSection.cs index a80fc6d2c7..a08bae87fb 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ITemplatesSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ITemplatesSection.cs @@ -13,4 +13,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [Obsolete("This has no affect and will be removed in future versions")] bool EnableTemplateFolders { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs index 7174f7762f..085a826626 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs @@ -17,7 +17,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings IDeveloperSection Developer { get; } - IViewStateMoverModuleSection ViewStateMoverModule { get; } ILoggingSection Logging { get; } @@ -29,12 +28,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings IProvidersSection Providers { get; } - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This is no longer used and will be removed in future versions")] - IHelpSection Help { get; } - IWebRoutingSection WebRouting { get; } - IScriptingSection Scripting { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IViewStateMoverModuleSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IViewStateMoverModuleSection.cs deleted file mode 100644 index 150827dccb..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IViewStateMoverModuleSection.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - public interface IViewStateMoverModuleSection : IUmbracoConfigurationSection - { - bool Enable { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs index 9eb6d02aa7..c7b7a07206 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IWebRoutingSection.cs @@ -9,7 +9,7 @@ bool DisableAlternativeTemplates { get; } bool DisableFindContentByIdPath { get; } - + bool DisableRedirectUrlTracking { get; } string UrlProviderMode { get; } @@ -17,4 +17,4 @@ string UmbracoApplicationUrl { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillPropertiesCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillPropertiesCollection.cs index 28cca97c8e..60d68252e2 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillPropertiesCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillPropertiesCollection.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { return ((ImagingAutoFillUploadFieldElement)element).Alias; } - + internal void Add(ImagingAutoFillUploadFieldElement item) { BaseAdd(item); @@ -34,4 +34,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs index eafe43817d..77b9faec8e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ImagingAutoFillUploadFieldElement.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings string IImagingAutoFillUploadField.Alias { get { return Alias; } - + } string IImagingAutoFillUploadField.WidthFieldAlias @@ -64,4 +64,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return ExtensionFieldAlias; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/LinkElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/LinkElement.cs deleted file mode 100644 index ad1655dfb0..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/LinkElement.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.ComponentModel; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This is no longer used and will be removed in future versions")] - internal class LinkElement : ConfigurationElement, ILink - { - [ConfigurationProperty("application")] - internal string Application - { - get { return (string)base["application"]; } - } - - [ConfigurationProperty("applicationUrl")] - internal string ApplicationUrl - { - get { return (string)base["applicationUrl"]; } - } - - [ConfigurationProperty("language")] - internal string Language - { - get { return (string)base["language"]; } - } - - [ConfigurationProperty("userType")] - internal string UserType - { - get { return (string)base["userType"]; } - } - - [ConfigurationProperty("helpUrl")] - internal string HelpUrl - { - get { return (string)base["helpUrl"]; } - } - - string ILink.Application - { - get { return Application; } - } - - string ILink.ApplicationUrl - { - get { return ApplicationUrl; } - } - - string ILink.Language - { - get { return Language; } - } - - string ILink.UserType - { - get { return UserType; } - } - - string ILink.HelpUrl - { - get { return HelpUrl; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/LinksCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/LinksCollection.cs deleted file mode 100644 index 485c8e4bbd..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/LinksCollection.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This is no longer used and will be removed in future versions")] - internal class LinksCollection : ConfigurationElementCollection, IEnumerable - { - protected override ConfigurationElement CreateNewElement() - { - return new LinkElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((LinkElement)element).Application - + ((LinkElement)element).ApplicationUrl - + ((LinkElement)element).Language - + ((LinkElement)element).UserType; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as ILink; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/LogTypeElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/LogTypeElement.cs index 44ca547c8b..ff92d9c173 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/LogTypeElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/LogTypeElement.cs @@ -8,4 +8,4 @@ get { return Value; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs index 39e6327b3a..3f9e93ed86 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/LoggingElement.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { internal class LoggingElement : UmbracoConfigurationElement, ILoggingSection { - + [ConfigurationProperty("autoCleanLogs")] internal InnerTextConfigurationElement AutoCleanLogs { @@ -63,21 +63,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings } } - string ILoggingSection.ExternalLoggerAssembly - { - get { return ExternalLogger.Assembly; } - } - - string ILoggingSection.ExternalLoggerType - { - get { return ExternalLogger.Type; } - } - - bool ILoggingSection.ExternalLoggerEnableAuditTrail - { - get { return ExternalLogger.LogAuditTrail; } - } - bool ILoggingSection.AutoCleanLogs { get { return AutoCleanLogs; } @@ -88,11 +73,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return EnableLogging; } } - bool ILoggingSection.EnableAsyncLogging - { - get { return EnableAsyncLogging; } - } - int ILoggingSection.CleaningMiliseconds { get { return CleaningMiliseconds; } @@ -109,4 +89,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/NotDynamicXmlDocumentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/NotDynamicXmlDocumentElement.cs deleted file mode 100644 index f6d4d18936..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/NotDynamicXmlDocumentElement.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class NotDynamicXmlDocumentElement : InnerTextConfigurationElement, INotDynamicXmlDocument - { - public string Element - { - get { return Value; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/NotDynamicXmlDocumentElementCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/NotDynamicXmlDocumentElementCollection.cs deleted file mode 100644 index ffcd9c81c3..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/NotDynamicXmlDocumentElementCollection.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class NotDynamicXmlDocumentElementCollection : ConfigurationElementCollection, IEnumerable - { - internal void Add(NotDynamicXmlDocumentElement element) - { - BaseAdd(element); - } - - protected override ConfigurationElement CreateNewElement() - { - return new NotDynamicXmlDocumentElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((NotDynamicXmlDocumentElement) element).Value; - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as INotDynamicXmlDocument; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs index 89e3f447ee..0facef0f87 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/NotificationsElement.cs @@ -17,4 +17,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ProvidersElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ProvidersElement.cs index 5cc94a4eb2..2a02ceaa80 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ProvidersElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ProvidersElement.cs @@ -15,4 +15,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return Users.DefaultBackOfficeProvider; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/RazorElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/RazorElement.cs deleted file mode 100644 index 273977f298..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/RazorElement.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class RazorElement : ConfigurationElement - { - private NotDynamicXmlDocumentElementCollection _defaultCollection; - - [ConfigurationCollection(typeof (NotDynamicXmlDocumentElementCollection), AddItemName = "element")] - [ConfigurationProperty("notDynamicXmlDocumentElements", IsDefaultCollection = true)] - internal NotDynamicXmlDocumentElementCollection NotDynamicXmlDocumentElements - { - get - { - if (_defaultCollection != null) - { - return _defaultCollection; - } - - //here we need to check if this element is defined, if it is not then we'll setup the defaults - var prop = Properties["notDynamicXmlDocumentElements"]; - var autoFill = this[prop] as ConfigurationElementCollection; - if (autoFill != null && autoFill.ElementInformation.IsPresent == false) - { - _defaultCollection = GetDefaultNotDynamicXmlDocuments(); - - //must return the collection directly - return _defaultCollection; - } - - return (NotDynamicXmlDocumentElementCollection)base["notDynamicXmlDocumentElements"]; - } - } - - internal static NotDynamicXmlDocumentElementCollection GetDefaultNotDynamicXmlDocuments() - { - return new NotDynamicXmlDocumentElementCollection - { - new NotDynamicXmlDocumentElement {RawValue = "p"}, - new NotDynamicXmlDocumentElement {RawValue = "div"}, - new NotDynamicXmlDocumentElement {RawValue = "ul"}, - new NotDynamicXmlDocumentElement {RawValue = "span"} - }; - } - - [ConfigurationCollection(typeof (RazorStaticMappingCollection), AddItemName = "mapping")] - [ConfigurationProperty("dataTypeModelStaticMappings", IsDefaultCollection = true)] - internal RazorStaticMappingCollection DataTypeModelStaticMappings - { - get { return (RazorStaticMappingCollection) base["dataTypeModelStaticMappings"]; } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/RazorStaticMappingCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/RazorStaticMappingCollection.cs deleted file mode 100644 index d35d808f46..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/RazorStaticMappingCollection.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class RazorStaticMappingCollection : ConfigurationElementCollection, IEnumerable - { - protected override ConfigurationElement CreateNewElement() - { - return new RazorStaticMappingElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((RazorStaticMappingElement) element).DataTypeGuid - + ((RazorStaticMappingElement) element).NodeTypeAlias - + ((RazorStaticMappingElement) element).PropertyTypeAlias; - } - - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = 0; i < Count; i++) - { - yield return BaseGet(i) as IRazorStaticMapping; - } - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/RazorStaticMappingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/RazorStaticMappingElement.cs deleted file mode 100644 index 917ff5e63b..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/RazorStaticMappingElement.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class RazorStaticMappingElement : InnerTextConfigurationElement, IRazorStaticMapping - { - public Guid DataTypeGuid - { - get - { - return RawXml.Attribute("dataTypeGuid") == null - ? default(Guid) - : Guid.Parse(RawXml.Attribute("dataTypeGuid").Value); - } - } - - public string NodeTypeAlias - { - get - { - return RawXml.Attribute("nodeTypeAlias") == null - ? null - : RawXml.Attribute("nodeTypeAlias").Value; - } - } - - public string PropertyTypeAlias - { - get - { - return RawXml.Attribute("propertyTypeAlias") == null - ? null - : RawXml.Attribute("propertyTypeAlias").Value; - } - } - - string IRazorStaticMapping.MappingName - { - get { return Value; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoriesCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoriesCollection.cs index 994c808703..1a228d33f2 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoriesCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoriesCollection.cs @@ -33,4 +33,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoriesElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoriesElement.cs index e2c3ba3036..0f1063adb0 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoriesElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoriesElement.cs @@ -20,4 +20,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return Repositories; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoryConfigExtensions.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoryConfigExtensions.cs index e2c4283dc6..d311a4423b 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoryConfigExtensions.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoryConfigExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; namespace Umbraco.Core.Configuration.UmbracoSettings @@ -16,4 +16,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return found; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoryElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoryElement.cs index a249be2ee3..ae583ca7c3 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoryElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/RepositoryElement.cs @@ -38,7 +38,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { var prop = Properties["webserviceurl"]; - return (string) prop.DefaultValue != (string) this[prop]; + return (string) prop.DefaultValue != (string) this[prop]; } } @@ -48,6 +48,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (string)base["restapiurl"]; } set { base["restapiurl"] = value; } } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs index c040c83708..cf16a31889 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/RequestHandlerElement.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { return _defaultUrlReplacing; } - + //here we need to check if this element is defined, if it is not then we'll setup the defaults var prop = Properties["urlReplacing"]; var urls = this[prop] as ConfigurationElement; @@ -42,11 +42,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return _defaultUrlReplacing; } - + return (UrlReplacingElement)this["urlReplacing"]; } } - + internal static CharCollection GetDefaultCharReplacements() { var dictionary = new Dictionary() @@ -124,4 +124,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return UrlReplacing.CharCollection; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTaskElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTaskElement.cs index 46b25ddaa2..1ba844f6e1 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTaskElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTaskElement.cs @@ -28,4 +28,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (string)base["url"]; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksCollection.cs index ce6fbd2cc6..5ff35ccf63 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksCollection.cs @@ -28,4 +28,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksElement.cs index 0598aeec9e..7829259ab8 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ScheduledTasksElement.cs @@ -23,4 +23,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (string)base["baseUrl"]; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ScriptingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ScriptingElement.cs deleted file mode 100644 index 6721b71629..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ScriptingElement.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ScriptingElement : ConfigurationElement, IScriptingSection - { - [ConfigurationProperty("razor")] - internal RazorElement Razor - { - get { return (RazorElement) base["razor"]; } - } - - IEnumerable IScriptingSection.NotDynamicXmlDocumentElements - { - get { return Razor.NotDynamicXmlDocumentElements; } - } - - IEnumerable IScriptingSection.DataTypeModelStaticMappings - { - get { return Razor.DataTypeModelStaticMappings; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs index dc2ba7e983..8ae5816569 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs @@ -90,4 +90,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return AuthCookieDomain; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ServerCollection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ServerCollection.cs index 2333549faa..dfaa5c5247 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ServerCollection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ServerCollection.cs @@ -28,4 +28,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings return GetEnumerator(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ServerElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ServerElement.cs index 7c00c02277..30c836bc07 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ServerElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ServerElement.cs @@ -20,7 +20,7 @@ ? null : RawXml.Attribute("forceProtocol").Value; } - } + } string IServer.ServerAddress { @@ -46,4 +46,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs index 4e249df3a2..9eb843313c 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/TemplatesElement.cs @@ -51,4 +51,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return EnableTemplateFolders; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoConfigurationElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoConfigurationElement.cs index 063b5324d8..670f620b15 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoConfigurationElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoConfigurationElement.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Configuration; namespace Umbraco.Core.Configuration.UmbracoSettings @@ -33,4 +33,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings defaultVal)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs index 9ec1b36053..0cf97b2560 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/UmbracoSettingsSection.cs @@ -44,11 +44,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (DeveloperElement)this["developer"]; } } - [ConfigurationProperty("viewstateMoverModule")] - internal ViewstateMoverModuleElement ViewstateMoverModule - { - get { return (ViewstateMoverModuleElement)this["viewstateMoverModule"]; } - } [ConfigurationProperty("logging")] internal LoggingElement Logging @@ -91,7 +86,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings new RepositoryElement() {Name = "Umbraco package Repository", Id = new Guid("65194810-1f85-11dd-bd0b-0800200c9a66")} }; - + _defaultRepositories = new RepositoriesElement() { Repositories = collection @@ -104,7 +99,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings var reposElement = (RepositoriesElement)base["repositories"]; if (reposElement.Repositories.All(x => x.Id != new Guid("65194810-1f85-11dd-bd0b-0800200c9a66"))) { - reposElement.Repositories.Add(new RepositoryElement() { Name = "Umbraco package Repository", Id = new Guid("65194810-1f85-11dd-bd0b-0800200c9a66") }); + reposElement.Repositories.Add(new RepositoryElement() { Name = "Umbraco package Repository", Id = new Guid("65194810-1f85-11dd-bd0b-0800200c9a66") }); } return reposElement; @@ -117,24 +112,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (ProvidersElement)this["providers"]; } } - [ConfigurationProperty("help")] - internal HelpElement Help - { - get { return (HelpElement)this["help"]; } - } - [ConfigurationProperty("web.routing")] internal WebRoutingElement WebRouting { get { return (WebRoutingElement)this["web.routing"]; } } - [ConfigurationProperty("scripting")] - internal ScriptingElement Scripting - { - get { return (ScriptingElement)this["scripting"]; } - } - IContentSection IUmbracoSettingsSection.Content { get { return Content; } @@ -165,11 +148,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return Developer; } } - IViewStateMoverModuleSection IUmbracoSettingsSection.ViewStateMoverModule - { - get { return ViewstateMoverModule; } - } - ILoggingSection IUmbracoSettingsSection.Logging { get { return Logging; } @@ -195,21 +173,10 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return Providers; } } - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This is no longer used and will be removed in future versions")] - IHelpSection IUmbracoSettingsSection.Help - { - get { return Help; } - } - IWebRoutingSection IUmbracoSettingsSection.WebRouting { get { return WebRouting; } } - IScriptingSection IUmbracoSettingsSection.Scripting - { - get { return Scripting; } - } } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/UrlReplacingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/UrlReplacingElement.cs index 887528fb4a..6d76f45a8c 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/UrlReplacingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/UrlReplacingElement.cs @@ -24,6 +24,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (CharCollection)base[""]; } set { base[""] = value; } } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/UserProviderElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/UserProviderElement.cs index 53e85bc774..60da143b58 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/UserProviderElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/UserProviderElement.cs @@ -16,6 +16,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings } } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ViewstateMoverModuleElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ViewstateMoverModuleElement.cs deleted file mode 100644 index 869ca94841..0000000000 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ViewstateMoverModuleElement.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Configuration; - -namespace Umbraco.Core.Configuration.UmbracoSettings -{ - internal class ViewstateMoverModuleElement : ConfigurationElement, IViewStateMoverModuleSection - { - [ConfigurationProperty("enable", DefaultValue = false)] - public bool Enable - { - get { return (bool)base["enable"]; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs index 82f5d46b28..393aac4f50 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/WebRoutingElement.cs @@ -45,4 +45,4 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return (string)base["umbracoApplicationUrl"]; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 178429d479..9c5c38c916 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -1,43 +1,71 @@ using System; +using System.Configuration; using System.Reflection; using Semver; namespace Umbraco.Core.Configuration { - public class UmbracoVersion + /// + /// Represents the version of the executing code. + /// + public static class UmbracoVersion { - private static readonly Version Version = new Version("7.8.0"); + // BEWARE! + // This class is parsed and updated by the build scripts. + // Do NOT modify it unless you understand what you are doing. /// - /// Gets the current version of Umbraco. - /// Version class with the specified major, minor, build (Patch), and revision numbers. + /// Gets the version of the executing code. + /// + public static Version Current { get; } = new Version("8.0.0"); + + /// + /// Gets the version comment of the executing code (eg "beta"). + /// + public static string CurrentComment => "alpha.32"; + + /// + /// Gets the assembly version of Umbraco.Code.dll. + /// + /// Get it by looking at a class in that dll, due to medium trust issues, + /// see http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx, + /// however fixme we don't support medium trust anymore? + public static string AssemblyVersion => new AssemblyName(typeof(UmbracoVersion).Assembly.FullName).Version.ToString(); + + /// + /// Gets the semantic version of the executing code. + /// + public static SemVersion SemanticVersion { get; } = new SemVersion( + Current.Major, + Current.Minor, + Current.Build, + CurrentComment.IsNullOrWhiteSpace() ? null : CurrentComment, + Current.Revision > 0 ? Current.Revision.ToInvariantString() : null); + + /// + /// Gets the "local" version of the site. /// /// - /// CURRENT UMBRACO VERSION ID. + /// Three things have a version, really: the executing code, the database model, + /// and the site/files. The database model version is entirely managed via migrations, + /// and changes during an upgrade. The executing code version changes when new code is + /// deployed. The site/files version changes during an upgrade. /// - public static Version Current + public static SemVersion Local { - get { return Version; } - } - - /// - /// Gets the version comment (like beta or RC). - /// - /// The version comment. - public static string CurrentComment { get { return ""; } } - - // Get the version of the umbraco.dll by looking at a class in that dll - // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx - public static string AssemblyVersion { get { return new AssemblyName(typeof(ActionsResolver).Assembly.FullName).Version.ToString(); } } - - public static SemVersion GetSemanticVersion() - { - return new SemVersion( - Current.Major, - Current.Minor, - Current.Build, - CurrentComment.IsNullOrWhiteSpace() ? null : CurrentComment, - Current.Revision > 0 ? Current.Revision.ToInvariantString() : null); + get + { + try + { + // fixme - this should live in its own independent file! NOT web.config! + var value = ConfigurationManager.AppSettings["umbracoConfigurationStatus"]; + return SemVersion.TryParse(value, out var semver) ? semver : null; + } + catch + { + return null; + } + } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index a5c67fba93..4c859469fd 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -1,52 +1,52 @@ namespace Umbraco.Core { - public static partial class Constants - { - /// - /// Defines the alias identifiers for Umbraco's core application sections. - /// - public static class Applications - { - /// - /// Application alias for the content section. - /// - public const string Content = "content"; + public static partial class Constants + { + /// + /// Defines the alias identifiers for Umbraco's core application sections. + /// + public static class Applications + { + /// + /// Application alias for the content section. + /// + public const string Content = "content"; - /// - /// Application alias for the developer section. - /// - public const string Developer = "developer"; + /// + /// Application alias for the developer section. + /// + public const string Developer = "developer"; - /// - /// Application alias for the media section. - /// - public const string Media = "media"; + /// + /// Application alias for the media section. + /// + public const string Media = "media"; - /// - /// Application alias for the members section. - /// - public const string Members = "member"; + /// + /// Application alias for the members section. + /// + public const string Members = "member"; - /// - /// Application alias for the settings section. - /// - public const string Settings = "settings"; + /// + /// Application alias for the settings section. + /// + public const string Settings = "settings"; - /// - /// Application alias for the translation section. - /// - public const string Translation = "translation"; + /// + /// Application alias for the translation section. + /// + public const string Translation = "translation"; - /// - /// Application alias for the users section. - /// - public const string Users = "users"; + /// + /// Application alias for the users section. + /// + public const string Users = "users"; /// /// Application alias for the forms section. /// public const string Forms = "forms"; - } + } /// /// Defines the alias identifiers for Umbraco's core trees. @@ -56,8 +56,8 @@ /// /// alias for the content tree. /// - public const string Content = "content"; - + public const string Content = "content"; + /// /// alias for the content blueprint tree. /// @@ -81,17 +81,17 @@ /// /// alias for the datatype tree. /// - public const string DataTypes = "dataTypes"; + public const string DataTypes = "dataTypes"; /// /// alias for the packages tree /// public const string Packages = "packager"; - /// - /// alias for the dictionary tree. - /// - public const string Dictionary = "dictionary"; + /// + /// alias for the dictionary tree. + /// + public const string Dictionary = "dictionary"; public const string Stylesheets = "stylesheets"; @@ -105,12 +105,16 @@ /// public const string MediaTypes = "mediaTypes"; - /// /// alias for the member type tree. /// public const string MemberTypes = "memberTypes"; + /// + /// alias for the member group tree. + /// + public const string MemberGroups = "memberGroups"; + /// /// alias for the template tree. /// @@ -122,17 +126,28 @@ public const string Languages = "languages"; + /// + /// alias for the user types tree. + /// + public const string UserTypes = "userTypes"; + + /// + /// alias for the user permissions tree. + /// + public const string UserPermissions = "userPermissions"; + + /// + /// alias for the users tree. + /// + public const string Users = "users"; + + public const string Scripts = "scripts"; + public const string PartialViews = "partialViews"; public const string PartialViewMacros = "partialViewMacros"; - public const string Scripts = "scripts"; - - public const string Users = "users"; - //TODO: Fill in the rest! } } - - -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-Composing.cs b/src/Umbraco.Core/Constants-Composing.cs new file mode 100644 index 0000000000..734a715c2d --- /dev/null +++ b/src/Umbraco.Core/Constants-Composing.cs @@ -0,0 +1,29 @@ +namespace Umbraco.Core +{ + /// + /// Defines constants. + /// + public static partial class Constants + { + /// + /// Defines constants for composition. + /// + public static class Composing + { + /// + /// Defines file system names. + /// + public static class FileSystems + { + public const string ScriptFileSystem = "ScriptFileSystem"; + public const string PartialViewFileSystem = "PartialViewFileSystem"; + public const string PartialViewMacroFileSystem = "PartialViewMacroFileSystem"; + public const string StylesheetFileSystem = "StylesheetFileSystem"; + public const string MasterpageFileSystem = "MasterpageFileSystem"; + public const string ViewFileSystem = "ViewFileSystem"; + public const string XsltFileSystem = "XsltFileSystem"; + public const string JavascriptLibraryFileSystem = "JavascriptLibraryFileSystem"; + } + } + } +} diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index b59b7e487e..4e49933585 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -4,145 +4,144 @@ using Umbraco.Core.Models; namespace Umbraco.Core { - public static partial class Constants - { - /// - /// Defines the identifiers for property-type alias conventions that are used within the Umbraco core. - /// - public static class Conventions - { - internal static class PermissionCategories - { - public const string ContentCategory = "content"; - public const string AdministrationCategory = "administration"; - public const string StructureCategory = "structure"; - public const string OtherCategory = "other"; - } + { + /// + /// Defines the identifiers for property-type alias conventions that are used within the Umbraco core. + /// + public static class Conventions + { + internal static class PermissionCategories + { + public const string ContentCategory = "content"; + public const string AdministrationCategory = "administration"; + public const string StructureCategory = "structure"; + public const string OtherCategory = "other"; + } - public static class PublicAccess - { - public const string MemberUsernameRuleType = "MemberUsername"; + public static class PublicAccess + { + public const string MemberUsernameRuleType = "MemberUsername"; public const string MemberRoleRuleType = "MemberRole"; [Obsolete("No longer supported, this is here for backwards compatibility only")] public const string MemberIdRuleType = "MemberId"; [Obsolete("No longer supported, this is here for backwards compatibility only")] public const string MemberGroupIdRuleType = "MemberGroupId"; - } + } - public static class Localization - { + public static class Localization + { /// /// The root id for all top level dictionary items /// [Obsolete("There is no dictionary root item id anymore, it is simply null")] public const string DictionaryItemRootId = "41c7638d-f529-4bff-853e-59a0c2fb1bde"; - } + } - public static class DataTypes - { - public const string ListViewPrefix = "List View - "; - } + public static class DataTypes + { + public const string ListViewPrefix = "List View - "; + } - public static class PropertyGroups - { - public const string ListViewGroupName = "umbContainerView"; - } + public static class PropertyGroups + { + public const string ListViewGroupName = "umbContainerView"; + } - /// - /// Constants for Umbraco Content property aliases. - /// - public static class Content - { - /// - /// Property alias for the Content's Url (internal) redirect. - /// - public const string InternalRedirectId = "umbracoInternalRedirectId"; - - /// - /// Property alias for the Content's navigational hide, (not actually used in core code). - /// - public const string NaviHide = "umbracoNaviHide"; - - /// - /// Property alias for the Content's Url redirect. - /// - public const string Redirect = "umbracoRedirect"; - - /// - /// Property alias for the Content's Url alias. - /// - public const string UrlAlias = "umbracoUrlAlias"; - - /// - /// Property alias for the Content's Url name. - /// - public const string UrlName = "umbracoUrlName"; - } - - /// - /// Constants for Umbraco Media property aliases. - /// - public static class Media - { - /// - /// Property alias for the Media's file name. - /// - public const string File = "umbracoFile"; - - /// - /// Property alias for the Media's width. - /// - public const string Width = "umbracoWidth"; - - /// - /// Property alias for the Media's height. - /// - public const string Height = "umbracoHeight"; - - /// - /// Property alias for the Media's file size (in bytes). - /// - public const string Bytes = "umbracoBytes"; - - /// - /// Property alias for the Media's file extension. - /// - public const string Extension = "umbracoExtension"; - } - - /// - /// Defines the alias identifiers for Umbraco media types. - /// - public static class MediaTypes - { - /// - /// MediaType alias for a file. - /// - public const string File = "File"; - - /// - /// MediaType alias for a folder. + /// + /// Constants for Umbraco Content property aliases. + /// + public static class Content + { + /// + /// Property alias for the Content's Url (internal) redirect. /// - public const string Folder = "Folder"; + public const string InternalRedirectId = "umbracoInternalRedirectId"; - /// - /// MediaType alias for an image. - /// - public const string Image = "Image"; + /// + /// Property alias for the Content's navigational hide, (not actually used in core code). + /// + public const string NaviHide = "umbracoNaviHide"; + + /// + /// Property alias for the Content's Url redirect. + /// + public const string Redirect = "umbracoRedirect"; + + /// + /// Property alias for the Content's Url alias. + /// + public const string UrlAlias = "umbracoUrlAlias"; + + /// + /// Property alias for the Content's Url name. + /// + public const string UrlName = "umbracoUrlName"; + } + + /// + /// Constants for Umbraco Media property aliases. + /// + public static class Media + { + /// + /// Property alias for the Media's file name. + /// + public const string File = "umbracoFile"; + + /// + /// Property alias for the Media's width. + /// + public const string Width = "umbracoWidth"; + + /// + /// Property alias for the Media's height. + /// + public const string Height = "umbracoHeight"; + + /// + /// Property alias for the Media's file size (in bytes). + /// + public const string Bytes = "umbracoBytes"; + + /// + /// Property alias for the Media's file extension. + /// + public const string Extension = "umbracoExtension"; + } + + /// + /// Defines the alias identifiers for Umbraco media types. + /// + public static class MediaTypes + { + /// + /// MediaType alias for a file. + /// + public const string File = "File"; + + /// + /// MediaType alias for a folder. + /// + public const string Folder = "Folder"; + + /// + /// MediaType alias for an image. + /// + public const string Image = "Image"; /// /// MediaType alias indicating allowing auto-selection. /// - public const string AutoSelect = "umbracoAutoSelect"; - } - - /// - /// Constants for Umbraco Member property aliases. - /// - public static class Member - { + public const string AutoSelect = "umbracoAutoSelect"; + } + + /// + /// Constants for Umbraco Member property aliases. + /// + public static class Member + { /// /// if a role starts with __umbracoRole we won't show it as it's an internal role used for public access /// @@ -220,103 +219,103 @@ namespace Umbraco.Core /// internal const string StandardPropertiesGroupName = "Membership"; - public static Dictionary GetStandardPropertyTypeStubs() - { - return new Dictionary - { - { - Comments, - new PropertyType(PropertyEditors.TextboxMultipleAlias, DataTypeDatabaseType.Ntext, true, Comments) - { - Name = CommentsLabel - } - }, - { - FailedPasswordAttempts, - new PropertyType(PropertyEditors.NoEditAlias, DataTypeDatabaseType.Integer, true, FailedPasswordAttempts) - { - Name = FailedPasswordAttemptsLabel - } - }, - { - IsApproved, - new PropertyType(PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, true, IsApproved) - { - Name = IsApprovedLabel - } - }, - { - IsLockedOut, - new PropertyType(PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, true, IsLockedOut) - { - Name = IsLockedOutLabel - } - }, - { - LastLockoutDate, - new PropertyType(PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, true, LastLockoutDate) - { - Name = LastLockoutDateLabel - } - }, - { - LastLoginDate, - new PropertyType(PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, true, LastLoginDate) - { - Name = LastLoginDateLabel - } - }, - { - LastPasswordChangeDate, - new PropertyType(PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, true, LastPasswordChangeDate) - { - Name = LastPasswordChangeDateLabel - } - }, - { - PasswordAnswer, - new PropertyType(PropertyEditors.NoEditAlias, DataTypeDatabaseType.Nvarchar, true, PasswordAnswer) - { - Name = PasswordAnswerLabel - } - }, - { - PasswordQuestion, - new PropertyType(PropertyEditors.NoEditAlias, DataTypeDatabaseType.Nvarchar, true, PasswordQuestion) - { - Name = PasswordQuestionLabel - } - } - }; - } - } + public static Dictionary GetStandardPropertyTypeStubs() + { + return new Dictionary + { + { + Comments, + new PropertyType(PropertyEditors.Aliases.TextArea, ValueStorageType.Ntext, true, Comments) + { + Name = CommentsLabel + } + }, + { + FailedPasswordAttempts, + new PropertyType(PropertyEditors.Aliases.NoEdit, 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.NoEdit, ValueStorageType.Date, true, LastLockoutDate) + { + Name = LastLockoutDateLabel + } + }, + { + LastLoginDate, + new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, true, LastLoginDate) + { + Name = LastLoginDateLabel + } + }, + { + LastPasswordChangeDate, + new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Date, true, LastPasswordChangeDate) + { + Name = LastPasswordChangeDateLabel + } + }, + { + PasswordAnswer, + new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar, true, PasswordAnswer) + { + Name = PasswordAnswerLabel + } + }, + { + PasswordQuestion, + new PropertyType(PropertyEditors.Aliases.NoEdit, ValueStorageType.Nvarchar, true, PasswordQuestion) + { + Name = PasswordQuestionLabel + } + } + }; + } + } - /// - /// Defines the alias identifiers for Umbraco member types. - /// - public static class MemberTypes - { - /// - /// MemberType alias for default member type. - /// - public const string DefaultAlias = "Member"; + /// + /// Defines the alias identifiers for Umbraco member types. + /// + public static class MemberTypes + { + /// + /// MemberType alias for default member type. + /// + public const string DefaultAlias = "Member"; public const string SystemDefaultProtectType = "_umbracoSystemDefaultProtectType"; - public const string AllMembersListId = "all-members"; - } + public const string AllMembersListId = "all-members"; + } + + /// + /// Constants for Umbraco URLs/Querystrings. + /// + public static class Url + { + /// + /// Querystring parameter name used for Umbraco's alternative template functionality. + /// + public const string AltTemplate = "altTemplate"; + } - /// - /// Constants for Umbraco URLs/Querystrings. - /// - public static class Url - { - /// - /// Querystring parameter name used for Umbraco's alternative template functionality. - /// - public const string AltTemplate = "altTemplate"; - } - /// /// Defines the alias identifiers for built-in Umbraco relation types. /// @@ -326,7 +325,7 @@ namespace Umbraco.Core /// ContentType name for default relation type "Relate Document On Copy". /// public const string RelateDocumentOnCopyName = "Relate Document On Copy"; - + /// /// ContentType alias for default relation type "Relate Document On Copy". /// @@ -342,6 +341,6 @@ namespace Umbraco.Core /// public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete"; } - } - } -} \ No newline at end of file + } + } +} diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs new file mode 100644 index 0000000000..510bbbb82b --- /dev/null +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core +{ + public static partial class Constants + { + public static class DataTypes + { + public const int Textbox = -88; + public const int Boolean = -49; + public const int Datetime = -36; + + public const int DefaultContentListView = -95; + public const int DefaultMediaListView = -96; + public const int DefaultMembersListView = -97; + + public const int Tags = 1041; + } + } +} diff --git a/src/Umbraco.Core/Constants-DatabaseProviders.cs b/src/Umbraco.Core/Constants-DatabaseProviders.cs index 2a64ade081..c1430eb82d 100644 --- a/src/Umbraco.Core/Constants-DatabaseProviders.cs +++ b/src/Umbraco.Core/Constants-DatabaseProviders.cs @@ -9,4 +9,4 @@ public const string MySql = "MySql.Data.MySqlClient"; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-DeploySelector.cs b/src/Umbraco.Core/Constants-DeploySelector.cs index cd9c48a6f5..f6f3f5fbae 100644 --- a/src/Umbraco.Core/Constants-DeploySelector.cs +++ b/src/Umbraco.Core/Constants-DeploySelector.cs @@ -1,5 +1,5 @@ namespace Umbraco.Core -{ +{ public static partial class Constants { /// @@ -14,4 +14,4 @@ public const string DescendantsOfThis = "descendants"; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-Examine.cs b/src/Umbraco.Core/Constants-Examine.cs index 9b1de812f7..2f52e289c6 100644 --- a/src/Umbraco.Core/Constants-Examine.cs +++ b/src/Umbraco.Core/Constants-Examine.cs @@ -18,20 +18,7 @@ /// The alias of the external content indexer /// public const string ExternalIndexer = "ExternalIndexer"; - /// - /// The alias of the internal member searcher - /// - public const string InternalMemberSearcher = "InternalMemberSearcher"; - - /// - /// The alias of the internal content searcher - /// - public const string InternalSearcher = "InternalSearcher"; - - /// - /// The alias of the external content searcher - /// - public const string ExternalSearcher = "ExternalSearcher"; + } } } diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs index a0928919f0..f74b44b70b 100644 --- a/src/Umbraco.Core/Constants-Icons.cs +++ b/src/Umbraco.Core/Constants-Icons.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core { public static class Icons { - + /// /// System contenttype icon diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 059b1cbd3c..c303068a24 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -5,243 +5,133 @@ namespace Umbraco.Core public static partial class Constants { /// - /// Defines the identifiers for Umbraco object types as constants for easy centralized access/management. + /// Defines the Umbraco object type unique identifiers. /// public static class ObjectTypes { /// - /// Guid for a data type container + /// Defines the Umbraco object type unique identifiers as string. /// - public const string DataTypeContainer = "521231E3-8B37-469C-9F9D-51AFC91FEB7B"; + /// Should be used only when it's not possible to use the corresponding + /// readonly Guid value, e.g. in attributes (where only consts can be used). + public static class Strings + { + // ReSharper disable MemberHidesStaticFromOuterClass - /// - /// Guid for a data type container - /// - public static readonly Guid DataTypeContainerGuid = new Guid(DataTypeContainer); + public const string DataTypeContainer = "521231E3-8B37-469C-9F9D-51AFC91FEB7B"; - /// - /// Guid for a doc type container - /// - public const string DocumentTypeContainer = "2F7A2769-6B0B-4468-90DD-AF42D64F7F16"; + public const string DocumentTypeContainer = "2F7A2769-6B0B-4468-90DD-AF42D64F7F16"; - /// - /// Guid for a doc type container - /// - public static readonly Guid DocumentTypeContainerGuid = new Guid(DocumentTypeContainer); + public const string MediaTypeContainer = "42AEF799-B288-4744-9B10-BE144B73CDC4"; - /// - /// Guid for a doc type container - /// - public const string MediaTypeContainer = "42AEF799-B288-4744-9B10-BE144B73CDC4"; + public const string ContentItem = "10E2B09F-C28B-476D-B77A-AA686435E44A"; - /// - /// Guid for a doc type container - /// - public static readonly Guid MediaTypeContainerGuid = new Guid(MediaTypeContainer); + public const string ContentItemType = "7A333C54-6F43-40A4-86A2-18688DC7E532"; - /// - /// Guid for a Content Item object. - /// - public const string ContentItem = "10E2B09F-C28B-476D-B77A-AA686435E44A"; + public const string ContentRecycleBin = "01BB7FF2-24DC-4C0C-95A2-C24EF72BBAC8"; - /// - /// Guid for a Content Item Type object. - /// - public const string ContentItemType = "7A333C54-6F43-40A4-86A2-18688DC7E532"; + public const string DataType = "30A2A501-1978-4DDB-A57B-F7EFED43BA3C"; - /// - /// Guid for the Content Recycle Bin. - /// - public const string ContentRecycleBin = "01BB7FF2-24DC-4C0C-95A2-C24EF72BBAC8"; + public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972"; - /// - /// Guid for a DataType object. - /// - public const string DataType = "30A2A501-1978-4DDB-A57B-F7EFED43BA3C"; + public const string DocumentBlueprint = "6EBEF410-03AA-48CF-A792-E1C1CB087ACA"; - /// - /// Guid for a DataType object. - /// - public static readonly Guid DataTypeGuid = new Guid(DataType); + public const string DocumentType = "A2CB7800-F571-4787-9638-BC48539A0EFB"; - /// - /// Guid for a Document object. - /// - public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972"; - - /// - /// Guid for a Document object. - /// - public static readonly Guid DocumentGuid = new Guid(Document); + public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C"; - /// - /// Guid for a Document Blueprint object. - /// - public const string DocumentBlueprint = "6EBEF410-03AA-48CF-A792-E1C1CB087ACA"; + public const string MediaRecycleBin = "CF3D8E34-1C1C-41e9-AE56-878B57B32113"; - /// - /// Guid for a Document object. - /// - public static readonly Guid DocumentBlueprintGuid = new Guid(DocumentBlueprint); + public const string MediaType = "4EA4382B-2F5A-4C2B-9587-AE9B3CF3602E"; - /// - /// Guid for a Document Type object. - /// - public const string DocumentType = "A2CB7800-F571-4787-9638-BC48539A0EFB"; + public const string Member = "39EB0F98-B348-42A1-8662-E7EB18487560"; - /// - /// Guid for a Document Type object. - /// - public static readonly Guid DocumentTypeGuid = new Guid(DocumentType); + public const string MemberGroup = "366E63B9-880F-4E13-A61C-98069B029728"; - /// - /// Guid for a Media object. - /// - public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C"; + public const string MemberType = "9B5416FB-E72F-45A9-A07B-5A9A2709CE43"; - /// - /// Guid for a Document object. - /// - public static readonly Guid MediaGuid = new Guid(Media); + public const string SystemRoot = "EA7D8624-4CFE-4578-A871-24AA946BF34D"; - /// - /// Guid for the Media Recycle Bin. - /// - public const string MediaRecycleBin = "CF3D8E34-1C1C-41e9-AE56-878B57B32113"; + public const string Template = "6FBDE604-4178-42CE-A10B-8A2600A2F07D"; - /// - /// Guid for a Media Type object. - /// - public const string MediaType = "4EA4382B-2F5A-4C2B-9587-AE9B3CF3602E"; + public const string LockObject = "87A9F1FF-B1E4-4A25-BABB-465A4A47EC41"; - /// - /// Guid for a Media Type object. - /// - public static readonly Guid MediaTypeGuid = new Guid(MediaType); + public const string RelationType = "B1988FAD-8675-4F47-915A-B3A602BC5D8D"; - /// - /// Guid for a Member object. - /// - public const string Member = "39EB0F98-B348-42A1-8662-E7EB18487560"; + public const string FormsForm = "F5A9F787-6593-46F0-B8FF-BFD9BCA9F6BB"; - /// - /// Guid for a Media Type object. - /// - public static readonly Guid MemberGuid = new Guid(Member); + public const string FormsPreValue = "42D7BF9B-A362-4FEE-B45A-674D5C064B70"; - /// - /// Guid for a Member Group object. - /// - public const string MemberGroup = "366E63B9-880F-4E13-A61C-98069B029728"; + public const string FormsDataSource = "CFED6CE4-9359-443E-9977-9956FEB1D867"; - /// - /// Guid for a Member Group object. - /// - public static readonly Guid MemberGroupGuid = new Guid(MemberGroup); + public const string Language = "6B05D05B-EC78-49BE-A4E4-79E274F07A77"; - /// - /// Guid for a Member Type object. - /// - public const string MemberType = "9B5416FB-E72F-45A9-A07B-5A9A2709CE43"; + public const string IdReservation = "92849B1E-3904-4713-9356-F646F87C25F4"; - /// - /// Guid for a Member Type object. - /// - public static readonly Guid MemberTypeGuid = new Guid(MemberType); + [Obsolete("This no longer exists in the database")] + internal const string Stylesheet = "9F68DA4F-A3A8-44C2-8226-DCBD125E4840"; - /// - /// Guid for a Stylesheet object. - /// - [Obsolete("This no longer exists in the database")] - public const string Stylesheet = "9F68DA4F-A3A8-44C2-8226-DCBD125E4840"; + [Obsolete("This no longer exists in the database")] + internal const string StylesheetProperty = "5555da4f-a123-42b2-4488-dcdfb25e4111"; + + // ReSharper restore MemberHidesStaticFromOuterClass + } + + public static readonly Guid SystemRoot = new Guid(Strings.SystemRoot); + + public static readonly Guid ContentRecycleBin = new Guid(Strings.ContentRecycleBin); + + public static readonly Guid MediaRecycleBin = new Guid(Strings.MediaRecycleBin); + + public static readonly Guid DataTypeContainer = new Guid(Strings.DataTypeContainer); + + public static readonly Guid DocumentTypeContainer = new Guid(Strings.DocumentTypeContainer); + + public static readonly Guid MediaTypeContainer = new Guid(Strings.MediaTypeContainer); + + public static readonly Guid DataType = new Guid(Strings.DataType); + + public static readonly Guid Document = new Guid(Strings.Document); + + public static readonly Guid DocumentBlueprint = new Guid(Strings.DocumentBlueprint); + + public static readonly Guid DocumentType = new Guid(Strings.DocumentType); + + public static readonly Guid Media = new Guid(Strings.Media); + + public static readonly Guid MediaType = new Guid(Strings.MediaType); + + public static readonly Guid Member = new Guid(Strings.Member); + + public static readonly Guid MemberGroup = new Guid(Strings.MemberGroup); + + public static readonly Guid MemberType = new Guid(Strings.MemberType); + + public static readonly Guid TemplateType = new Guid(Strings.Template); + + public static readonly Guid LockObject = new Guid(Strings.LockObject); + + public static readonly Guid RelationType = new Guid(Strings.RelationType); + + public static readonly Guid FormsForm = new Guid(Strings.FormsForm); + + public static readonly Guid FormsPreValue = new Guid(Strings.FormsPreValue); + + public static readonly Guid FormsDataSource = new Guid(Strings.FormsDataSource); + + public static readonly Guid Language = new Guid(Strings.Language); + + public static readonly Guid IdReservation = new Guid(Strings.IdReservation); + + public static readonly Guid Template = new Guid(Strings.Template); + + public static readonly Guid ContentItem = new Guid(Strings.ContentItem); [Obsolete("This no longer exists in the database")] - internal const string StylesheetProperty = "5555da4f-a123-42b2-4488-dcdfb25e4111"; + internal static readonly Guid Stylesheet = new Guid(Strings.Stylesheet); - /// - /// Guid for the System Root. - /// - public const string SystemRoot = "EA7D8624-4CFE-4578-A871-24AA946BF34D"; - - /// - /// Guid for a Template object. - /// - public const string Template = "6FBDE604-4178-42CE-A10B-8A2600A2F07D"; - - /// - /// Guid for a Template object. - /// - public static readonly Guid TemplateTypeGuid = new Guid(Template); - - /// - /// Guid for a Lock object. - /// - public const string LockObject = "87A9F1FF-B1E4-4A25-BABB-465A4A47EC41"; - - /// - /// Guid for a Lock object. - /// - public static readonly Guid LockObjectGuid = new Guid(LockObject); - - /// - /// Guid for a relation type. - /// - public const string RelationType = "B1988FAD-8675-4F47-915A-B3A602BC5D8D"; - - /// - /// Guid for a relation type. - /// - public static readonly Guid RelationTypeGuid = new Guid(RelationType); - - /// - /// Guid for a Forms Form. - /// - public const string FormsForm = "F5A9F787-6593-46F0-B8FF-BFD9BCA9F6BB"; - - /// - /// Guid for a Forms Form. - /// - public static readonly Guid FormsFormGuid = new Guid(FormsForm); - - /// - /// Guid for a Forms PreValue Source. - /// - public const string FormsPreValue = "42D7BF9B-A362-4FEE-B45A-674D5C064B70"; - - /// - /// Guid for a Forms PreValue Source. - /// - public static readonly Guid FormsPreValueGuid = new Guid(FormsPreValue); - - /// - /// Guid for a Forms DataSource. - /// - public const string FormsDataSource = "CFED6CE4-9359-443E-9977-9956FEB1D867"; - - /// - /// Guid for a Forms DataSource. - /// - public static readonly Guid FormsDataSourceGuid = new Guid(FormsDataSource); - - /// - /// Guid for a Language. - /// - public const string Language = "6B05D05B-EC78-49BE-A4E4-79E274F07A77"; - - /// - /// Guid for a Forms DataSource. - /// - public static readonly Guid LanguageGuid = new Guid(Language); - - /// - /// Guid for an Identifier Reservation. - /// - public const string IdReservation = "92849B1E-3904-4713-9356-F646F87C25F4"; - - /// - /// Guid for an Identifier Reservation. - /// - public static readonly Guid IdReservationGuid = new Guid(IdReservation); + [Obsolete("This no longer exists in the database")] + internal static readonly Guid StylesheetProperty = new Guid(Strings.StylesheetProperty); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-Packaging.cs b/src/Umbraco.Core/Constants-Packaging.cs index 31cbad1246..e88639ebbd 100644 --- a/src/Umbraco.Core/Constants-Packaging.cs +++ b/src/Umbraco.Core/Constants-Packaging.cs @@ -52,4 +52,4 @@ public const string RunatNodeAttribute = "runat"; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 1f3986eeaf..43bd381571 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -1,11 +1,11 @@ -using System; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core { - public static partial class Constants + public static partial class Constants { /// - /// Defines the identifiers for Umbraco Property Editors as constants for easy centralized access/management. + /// Defines property editors constants. /// public static class PropertyEditors { @@ -15,441 +15,217 @@ namespace Umbraco.Core public const string InternalGenericPropertiesPrefix = "_umb_"; /// - /// Guid for the Checkbox list datatype. + /// Defines Umbraco built-in property editor aliases. /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string CheckBoxList = "B4471851-82B6-4C75-AFA4-39FA9C6A75E9"; - - /// - /// Alias for Checkbox list datatype. - /// - public const string CheckBoxListAlias = "Umbraco.CheckBoxList"; - - /// - /// Guid for the Color Picker datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string ColorPicker = "F8D60F68-EC59-4974-B43B-C46EB5677985"; - - /// - /// Alias for the Color Picker datatype. - /// - public const string ColorPickerAlias = "Umbraco.ColorPickerAlias"; - - /// - /// Guid for the Content Picker datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string ContentPicker = "158AA029-24ED-4948-939E-C3DA209E5FBA"; - - - [Obsolete("This is an obsoleted content picker, use ContentPicker2Alias instead")] - public const string ContentPickerAlias = "Umbraco.ContentPickerAlias"; - - /// - /// Alias for the Content Picker datatype. - /// - public const string ContentPicker2Alias = "Umbraco.ContentPicker2"; - - /// - /// Guid for the Date datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string Date = "23E93522-3200-44E2-9F29-E61A6FCBB79A"; - - /// - /// Alias for the Date datatype. - /// - public const string DateAlias = "Umbraco.Date"; - - /// - /// Guid for the Date/Time datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string DateTime = "B6FB1622-AFA5-4BBF-A3CC-D9672A442222"; - - /// - /// Alias for the Date/Time datatype. - /// - public const string DateTimeAlias = "Umbraco.DateTime"; - - /// - /// Guid for the Dictionary Picker datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string DictionaryPicker = "17B70066-F764-407D-AB05-3717F1E1C513"; - - /// - /// Guid for the Dropdown list datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string DropDownList = "A74EA9C9-8E18-4D2A-8CF6-73C6206C5DA6"; - - /// - /// Alias for the Dropdown list datatype. - /// - public const string DropDownListAlias = "Umbraco.DropDown"; - - /// - /// Guid for the Dropdown list multiple datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string DropDownListMultiple = "928639ED-9C73-4028-920C-1E55DBB68783"; - - /// - /// Alias for the Dropdown list multiple datatype. - /// - public const string DropDownListMultipleAlias = "Umbraco.DropDownMultiple"; - - /// - /// Guid for the Dropdown list multiple, publish keys datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string DropdownlistMultiplePublishKeys = "928639AA-9C73-4028-920C-1E55DBB68783"; - - /// - /// Alias for the Dropdown list multiple, publish keys datatype. - /// - public const string DropdownlistMultiplePublishKeysAlias = "Umbraco.DropdownlistMultiplePublishKeys"; - - /// - /// Guid for the Dropdown list, publishing keys datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string DropdownlistPublishingKeys = "A74EA9E1-8E18-4D2A-8CF6-73C6206C5DA6"; - - /// - /// Alias for the Dropdown list, publishing keys datatype. - /// - public const string DropdownlistPublishingKeysAlias = "Umbraco.DropdownlistPublishingKeys"; - - /// - /// Guid for the Folder browser datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string FolderBrowser = "CCCD4AE9-F399-4ED2-8038-2E88D19E810C"; - - /// - /// Alias for the Folder browser datatype. - /// - public const string FolderBrowserAlias = "Umbraco.FolderBrowser"; - - /// - /// Alias for the grid datatype. - /// - public const string GridAlias = "Umbraco.Grid"; - - - /// - /// Guid for the Image Cropper datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string ImageCropper = "7A2D436C-34C2-410F-898F-4A23B3D79F54"; - - ///// - ///// Alias for the Image Cropper datatype. - ///// - public const string ImageCropperAlias = "Umbraco.ImageCropper"; - - /// - /// Guid for the Integer datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string Integer = "1413AFCB-D19A-4173-8E9A-68288D2A73B8"; - - /// - /// Alias for the Integer datatype. - /// - public const string IntegerAlias = "Umbraco.Integer"; - - /// - /// Alias for the Decimal datatype. - /// - public const string DecimalAlias = "Umbraco.Decimal"; - - /// - /// Alias for the listview datatype. - /// - public const string ListViewAlias = "Umbraco.ListView"; - - /// - /// Guid for the list view datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string ListView = "474FCFF8-9D2D-12DE-ABC6-AD7A56D89593"; - - - /// - /// Guid for the Macro Container datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string MacroContainer = "474FCFF8-9D2D-11DE-ABC6-AD7A56D89593"; - - /// - /// Alias for the Macro Container datatype. - /// - public const string MacroContainerAlias = "Umbraco.MacroContainer"; - - /// - /// Guid for the Media Picker datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string MediaPicker = "EAD69342-F06D-4253-83AC-28000225583B"; - - [Obsolete("This is an obsoleted picker, use MediaPicker2Alias instead")] - public const string MediaPickerAlias = "Umbraco.MediaPicker"; - - /// - /// Alias for the Media Picker datatype. - /// - public const string MediaPicker2Alias = "Umbraco.MediaPicker2"; - - [Obsolete("This is an obsoleted picker, use MediaPicker2Alias instead")] - public const string MultipleMediaPickerAlias = "Umbraco.MultipleMediaPicker"; - - /// - /// Guid for the Member Picker datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string MemberPicker = "39F533E4-0551-4505-A64B-E0425C5CE775"; - - [Obsolete("This is an obsoleted picker, use MemberPicker2Alias instead")] - public const string MemberPickerAlias = "Umbraco.MemberPicker"; - - /// - /// Alias for the Member Picker datatype. - /// - public const string MemberPicker2Alias = "Umbraco.MemberPicker2"; - - /// - /// Alias for the Member Group Picker datatype. - /// - public const string MemberGroupPickerAlias = "Umbraco.MemberGroupPicker"; - - /// - /// Guid for the Multi-Node Tree Picker datatype - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string MultiNodeTreePicker = "7E062C13-7C41-4AD9-B389-41D88AEEF87C"; - - [Obsolete("This is an obsoleted picker, use MultiNodeTreePicker2Alias instead")] - public const string MultiNodeTreePickerAlias = "Umbraco.MultiNodeTreePicker"; - - /// - /// Alias for the Multi-Node Tree Picker datatype - /// - public const string MultiNodeTreePicker2Alias = "Umbraco.MultiNodeTreePicker2"; - - /// - /// Guid for the Multiple Textstring datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string MultipleTextstring = "5359AD0B-06CC-4182-92BD-0A9117448D3F"; - - /// - /// Alias for the Multiple Textstring datatype. - /// - public const string MultipleTextstringAlias = "Umbraco.MultipleTextstring"; - - /// - /// Guid for the No edit datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string NoEdit = "6C738306-4C17-4D88-B9BD-6546F3771597"; - - /// - /// Alias for the No edit datatype. - /// - public const string NoEditAlias = "Umbraco.NoEdit"; - - /// - /// Guid for the Picker Relations datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string PickerRelations = "83396FF2-2E39-4A90-9066-17F5F3989374"; - - /// - /// Alias for the Picker Relations datatype. - /// - public const string PickerRelationsAlias = "Umbraco.PickerRelations"; - - /// - /// Guid for the Radiobutton list datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string RadioButtonList = "A52C7C1C-C330-476E-8605-D63D3B84B6A6"; - - /// - /// Alias for the Radiobutton list datatype. - /// - public const string RadioButtonListAlias = "Umbraco.RadioButtonList"; - - /// - /// Guid for the Related Links datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string RelatedLinks = "71B8AD1A-8DC2-425C-B6B8-FAA158075E63"; - - [Obsolete("This is an obsoleted picker, use RelatedLinks2Alias instead")] - public const string RelatedLinksAlias = "Umbraco.RelatedLinks"; - - /// - /// Alias for the Related Links property editor. - /// - public const string RelatedLinks2Alias = "Umbraco.RelatedLinks2"; - - /// - /// Guid for the Slider datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string Slider = "29E790E6-26B3-438A-B21F-908663A0B19E"; - - /// - /// Alias for the Slider datatype. - /// - public const string SliderAlias = "Umbraco.Slider"; - - /// - /// Guid for the Tags datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string Tags = "4023E540-92F5-11DD-AD8B-0800200C9A66"; - - /// - /// Alias for the Tags datatype. - /// - public const string TagsAlias = "Umbraco.Tags"; - - /// - /// Guid for the Textbox datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string Textbox = "EC15C1E5-9D90-422A-AA52-4F7622C63BEA"; - - /// - /// Alias for the Textbox datatype. - /// - public const string TextboxAlias = "Umbraco.Textbox"; - - /// - /// Guid for the Textarea datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string TextboxMultiple = "67DB8357-EF57-493E-91AC-936D305E0F2A"; - - /// - /// Alias for the Textarea datatype. - /// - public const string TextboxMultipleAlias = "Umbraco.TextboxMultiple"; - - /// - /// Guid for the TinyMCE v3 wysiwyg datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string TinyMCEv3 = "5E9B75AE-FACE-41C8-B47E-5F4B0FD82F83"; - - /// - /// Alias for the TinyMCE wysiwyg datatype. - /// - public const string TinyMCEAlias = "Umbraco.TinyMCEv3"; - - /// - /// Guid for the True/False (Ja/Nej) datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string TrueFalse = "38B352C1-E9F8-4FD8-9324-9A2EAB06D97A"; - - /// - /// Alias for the True/False (Ja/Nej) datatype. - /// - public const string TrueFalseAlias = "Umbraco.TrueFalse"; - - /// - /// Guid for the Ultimate Picker datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string UltimatePicker = "CDBF0B5D-5CB2-445F-BC12-FCAAEC07CF2C"; - - /// - /// Guid for the UltraSimpleEditor datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string UltraSimpleEditor = "60B7DABF-99CD-41EB-B8E9-4D2E669BBDE9"; - - /// - /// Alias for the MarkdownEditor datatype. - /// - public const string MarkdownEditorAlias = "Umbraco.MarkdownEditor"; - - /// - /// Guid for the Umbraco Usercontrol Wrapper datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string UmbracoUserControlWrapper = "D15E1281-E456-4B24-AA86-1DDA3E4299D5"; - - /// - /// Guid for the Upload field datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string UploadField = "5032A6E6-69E3-491D-BB28-CD31CD11086C"; - - /// - /// Alias for the User picker datatype. - /// - public const string UserPickerAlias = "Umbraco.UserPicker"; - - /// - /// Guid for the User picker datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string UserPicker = "e66af4a0-e8b4-11de-8a39-0800200c9a66"; - - /// - /// Alias for the Upload field datatype. - /// - public const string UploadFieldAlias = "Umbraco.UploadField"; - - - /// - /// Guid for the XPath CheckBoxList datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string XPathCheckBoxList = "34451D92-D270-49BA-8C7F-EE55BFEEE1CB"; - - /// - /// Alias for the XPath CheckBoxList datatype. - /// - public const string XPathCheckBoxListAlias = "Umbraco.XPathCheckBoxList"; - - /// - /// Guid for the XPath DropDownList datatype. - /// - [Obsolete("GUIDs are no longer used to reference Property Editors, use the Alias constant instead. This will be removed in future versions")] - public const string XPathDropDownList = "173A96AE-00ED-4A7C-9F76-4B53D4A0A1B9"; - - /// - /// Alias for the XPath DropDownList datatype. - /// - public const string XPathDropDownListAlias = "Umbraco.XPathDropDownList"; - - /// - /// Alias for the email address property editor - /// - public const string EmailAddressAlias = "Umbraco.EmailAddress"; - - /// - /// Alias for the nested content property editor. - /// - public const string NestedContentAlias = "Umbraco.NestedContent"; - - public static class PreValueKeys + public static class Aliases { /// - /// Pre-value name used to indicate a field that can be used to override the database field to which data for the associated - /// property is saved + /// CheckBox List. /// + public const string CheckBoxList = "Umbraco.CheckBoxList"; + + /// + /// Color Picker. + /// + public const string ColorPicker = "Umbraco.ColorPicker"; + + /// + /// Content Picker. + /// + public const string ContentPicker = "Umbraco.ContentPicker"; + + /// + /// Date. + /// + public const string Date = "Umbraco.Date"; + + /// + /// DateTime. + /// + public const string DateTime = "Umbraco.DateTime"; + + /// + /// DropDown List. + /// + public const string DropDownList = "Umbraco.DropDown"; + + /// + /// DropDown List, Publish Keys. + /// + public const string DropdownlistPublishKeys = "Umbraco.DropdownlistPublishingKeys"; + + /// + /// DropDown List Multiple. + /// + public const string DropDownListMultiple = "Umbraco.DropDownMultiple"; + + /// + /// DropDown List Multiple, Publish Keys. + /// + public const string DropdownlistMultiplePublishKeys = "Umbraco.DropdownlistMultiplePublishKeys"; + + /// + /// DropDown List. + /// + public const string DropDownListFlexible = "Umbraco.DropDown.Flexible"; + + /// + /// Folder Browser. + /// + public const string FolderBrowser = "Umbraco.FolderBrowser"; + + /// + /// Grid. + /// + public const string Grid = "Umbraco.Grid"; + + /// + /// Image Cropper. + /// + public const string ImageCropper = "Umbraco.ImageCropper"; + + /// + /// Integer. + /// + public const string Integer = "Umbraco.Integer"; + + /// + /// Decimal. + /// + public const string Decimal = "Umbraco.Decimal"; + + /// + /// ListView. + /// + public const string ListView = "Umbraco.ListView"; + + /// + /// Macro Container. + /// + public const string MacroContainer = "Umbraco.MacroContainer"; + + /// + /// Media Picker. + /// + public const string MediaPicker = "Umbraco.MediaPicker"; + + /// + /// Member Picker. + /// + public const string MemberPicker = "Umbraco.MemberPicker"; + + /// + /// Member Group Picker. + /// + public const string MemberGroupPicker = "Umbraco.MemberGroupPicker"; + + /// + /// MultiNode Tree Picker. + /// + public const string MultiNodeTreePicker = "Umbraco.MultiNodeTreePicker"; + + /// + /// Multiple TextString. + /// + public const string MultipleTextstring = "Umbraco.MultipleTextstring"; + + /// + /// NoEdit. + /// + public const string NoEdit = "Umbraco.NoEdit"; + + /// + /// Picker Relations. + /// + public const string PickerRelations = "Umbraco.PickerRelations"; + + /// + /// RadioButton list. + /// + public const string RadioButtonList = "Umbraco.RadioButtonList"; + + /// + /// Related Links. + /// + public const string RelatedLinks = "Umbraco.RelatedLinks"; + + /// + /// Slider. + /// + public const string Slider = "Umbraco.Slider"; + + /// + /// Tags. + /// + public const string Tags = "Umbraco.Tags"; + + /// + /// Textbox. + /// + public const string TextBox = "Umbraco.TextBox"; + + /// + /// Textbox Multiple. + /// + public const string TextArea = "Umbraco.TextArea"; + + /// + /// TinyMCE + /// + public const string TinyMce = "Umbraco.TinyMCEv3"; + + /// + /// Boolean. + /// + public const string Boolean = "Umbraco.TrueFalse"; + + /// + /// Markdown Editor. + /// + public const string MarkdownEditor = "Umbraco.MarkdownEditor"; + + /// + /// User Picker. + /// + public const string UserPicker = "Umbraco.UserPicker"; + + /// + /// Upload Field. + /// + public const string UploadField = "Umbraco.UploadField"; + + /// + /// XPatch Checkbox List. + /// + public const string XPathCheckBoxList = "Umbraco.XPathCheckBoxList"; + + /// + /// XPath DropDown List. + /// + public const string XPathDropDownList = "Umbraco.XPathDropDownList"; + + /// + /// Email Address. + /// + public const string EmailAddress = "Umbraco.EmailAddress"; + + /// + /// Nested Content. + /// + public const string NestedContent = "Umbraco.NestedContent"; + } + + /// + /// Defines Umbraco build-in datatype configuration keys. + /// + public static class ConfigurationKeys + { + /// + /// The value type of property data (i.e., string, integer, etc) + /// + /// Must be a valid value. public const string DataValueType = "umbracoDataValueType"; } } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Core/Constants-PropertyTypeGroups.cs b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs index 5dabe90029..ef6951df1a 100644 --- a/src/Umbraco.Core/Constants-PropertyTypeGroups.cs +++ b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs @@ -28,4 +28,4 @@ public const string Membership = "0756729D-D665-46E3-B84A-37ACEAA614F8"; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 0ada375163..4c24febb22 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -7,8 +7,13 @@ namespace Umbraco.Core { public static class Security { + /// + /// Gets the identifier of the 'super' user. + /// + public const int SuperId = -1; public const string AdminGroupAlias = "admin"; + public const string SensitiveDataGroupAlias = "sensitiveData"; public const string TranslatorGroupAlias = "translator"; public const string BackOfficeAuthenticationType = "UmbracoBackOffice"; @@ -19,12 +24,12 @@ namespace Umbraco.Core internal const string EmptyPasswordPrefix = "___UIDEMPTYPWORD__"; internal const string ForceReAuthFlag = "umbraco-force-auth"; - + /// /// The prefix used for external identity providers for their authentication type /// /// - /// By default we don't want to interfere with front-end external providers and their default setup, for back office the + /// By default we don't want to interfere with front-end external providers and their default setup, for back office the /// providers need to be setup differently and each auth type for the back office will be prefixed with this value /// public const string BackOfficeExternalAuthenticationTypePrefix = "Umbraco."; @@ -36,4 +41,4 @@ namespace Umbraco.Core } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index d4deda826e..9fad62c347 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -11,7 +11,7 @@ /// The integer identifier for global system root node. /// public const int Root = -1; - + /// /// The string identifier for global system root node. /// @@ -22,13 +22,13 @@ /// The integer identifier for content's recycle bin. /// public const int RecycleBinContent = -20; - + /// /// The string identifier for content's recycle bin. /// /// Use this instead of re-creating the string everywhere. - public const string RecycleBinContentString = "-20"; - + public const string RecycleBinContentString = "-20"; + /// /// The string path prefix of the content's recycle bin. /// @@ -36,7 +36,7 @@ /// Everything that is in the content recycle bin, has a path that starts with the prefix. /// Use this instead of re-creating the string everywhere. /// - public const string RecycleBinContentPathPrefix = "-1,-20,"; + public const string RecycleBinContentPathPrefix = "-1,-20,"; /// /// The integer identifier for media's recycle bin. @@ -58,13 +58,8 @@ /// public const string RecycleBinMediaPathPrefix = "-1,-21,"; - public const int DefaultContentListViewDataTypeId = -95; - public const int DefaultMediaListViewDataTypeId = -96; - public const int DefaultMembersListViewDataTypeId = -97; - public const string UmbracoConnectionName = "umbracoDbDSN"; - public const string UmbracoMigrationName = "Umbraco"; + public const string UmbracoUpgradePlanName = "Umbraco.Core"; } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 67b5698610..a5f140ddba 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -3,8 +3,8 @@ using System.ComponentModel; namespace Umbraco.Core { - public static partial class Constants - { + public static partial class Constants + { /// /// Defines the identifiers for Umbraco system nodes. /// @@ -21,6 +21,8 @@ namespace Umbraco.Core /// public const string PreviewCookieName = "UMB_PREVIEW"; + public const string InstallerCookieName = "umb_installId"; + /// /// The auth cookie name /// @@ -29,6 +31,5 @@ namespace Umbraco.Core public const string AuthCookieName = "UMB_UCONTEXT"; } - - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Core/Constants.cs b/src/Umbraco.Core/Constants.cs index d7ef971d4c..a1e77bc919 100644 --- a/src/Umbraco.Core/Constants.cs +++ b/src/Umbraco.Core/Constants.cs @@ -1,10 +1,10 @@ namespace Umbraco.Core { - /// - /// Constants all the identifiers within the Umbraco core. - /// - public static partial class Constants - { - // generic constants can go here - } + /// + /// Constants all the identifiers within the Umbraco core. + /// + public static partial class Constants + { + // generic constants can go here + } } diff --git a/src/Umbraco.Core/ControlExtensions.cs b/src/Umbraco.Core/ControlExtensions.cs deleted file mode 100644 index 391c69159f..0000000000 --- a/src/Umbraco.Core/ControlExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.UI; - -namespace Umbraco.Core -{ - internal static class ControlExtensions - { - /// - /// Recursively finds a control with the specified identifier. - /// - /// - /// The type of control to be found. - /// - /// - /// The parent control from which the search will start. - /// - /// - /// The identifier of the control to be found. - /// - /// - /// The control with the specified identifier, otherwise if the control - /// is not found. - /// - public static T FindControlRecursive(this Control parent, string id) where T : Control - { - if ((parent is T) && (parent.ID == id)) - { - return (T)parent; - } - - foreach (Control control in parent.Controls) - { - var foundControl = FindControlRecursive(control, id); - if (foundControl != null) - { - return foundControl; - } - } - return default(T); - } - - } -} diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs deleted file mode 100644 index a0455d820a..0000000000 --- a/src/Umbraco.Core/CoreBootManager.cs +++ /dev/null @@ -1,558 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Web; -using AutoMapper; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Events; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix; -using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Profiling; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Core.Publishing; -using Umbraco.Core.Macros; -using Umbraco.Core.Manifest; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using Umbraco.Core.Sync; -using Umbraco.Core.Strings; -using IntegerValidator = Umbraco.Core.PropertyEditors.IntegerValidator; -using MigrationsVersionFourNineZero = Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero; - -namespace Umbraco.Core -{ - /// - /// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application - /// - /// - /// This does not provide any startup functionality relating to web objects - /// - public class CoreBootManager : IBootManager - { - protected ProfilingLogger ProfilingLogger { get; private set; } - private DisposableTimer _timer; - private bool _isInitialized = false; - private bool _isStarted = false; - private bool _isComplete = false; - private readonly IServiceProvider _serviceProvider = new ActivatorServiceProvider(); - private readonly UmbracoApplicationBase _umbracoApplication; - protected ApplicationContext ApplicationContext { get; private set; } - protected CacheHelper ApplicationCache { get; private set; } - protected PluginManager PluginManager { get; private set; } - - protected UmbracoApplicationBase UmbracoApplication - { - get { return _umbracoApplication; } - } - - protected IServiceProvider ServiceProvider - { - get { return _serviceProvider; } - } - - public CoreBootManager(UmbracoApplicationBase umbracoApplication) - { - if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); - _umbracoApplication = umbracoApplication; - } - - internal CoreBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger) - { - if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); - if (logger == null) throw new ArgumentNullException("logger"); - _umbracoApplication = umbracoApplication; - ProfilingLogger = logger; - } - - public virtual IBootManager Initialize() - { - if (_isInitialized) - throw new InvalidOperationException("The boot manager has already been initialized"); - - InitializeLoggerResolver(); - InitializeProfilerResolver(); - - ProfilingLogger = ProfilingLogger?? new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler); - - _timer = ProfilingLogger.TraceDuration( - string.Format("Umbraco {0} application starting on {1}", UmbracoVersion.GetSemanticVersion().ToSemanticString(), NetworkHelper.MachineName), - "Umbraco application startup complete"); - - ApplicationCache = CreateApplicationCache(); - - //create and set the plugin manager (I'd much prefer to not use this singleton anymore but many things are using it unfortunately and - // the way that it is setup, there must only ever be one per app so without IoC it would be hard to make this not a singleton) - PluginManager = new PluginManager(ServiceProvider, ApplicationCache.RuntimeCache, ProfilingLogger); - PluginManager.Current = PluginManager; - - //Create the legacy prop-eds mapping - LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); - LegacyParameterEditorAliasConverter.CreateMappingsForCoreEditors(); - - //create database and service contexts for the app context - var dbFactory = new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, ProfilingLogger.Logger); - Database.Mapper = new PetaPocoMapper(); - - var scopeProvider = new ScopeProvider(dbFactory); - dbFactory.ScopeProvider = scopeProvider; - - var dbContext = new DatabaseContext( - scopeProvider, - ProfilingLogger.Logger, - SqlSyntaxProviders.CreateDefault(ProfilingLogger.Logger)); - - //initialize the DatabaseContext - dbContext.Initialize(); - - //get the service context - var serviceContext = CreateServiceContext(dbContext, scopeProvider); - - //set property and singleton from response - ApplicationContext.Current = ApplicationContext = CreateApplicationContext(dbContext, serviceContext); - - InitializeApplicationEventsResolver(); - - InitializeResolvers(); - - InitializeModelMappers(); - - using (ProfilingLogger.DebugDuration( - string.Format("Executing {0} IApplicationEventHandler.OnApplicationInitialized", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()), - "Finished executing IApplicationEventHandler.OnApplicationInitialized")) - { - //now we need to call the initialize methods - ApplicationEventsResolver.Current.ApplicationEventHandlers - .ForEach(x => - { - try - { - using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationInitialized", x.GetType()))) - { - x.OnApplicationInitialized(UmbracoApplication, ApplicationContext); - } - } - catch (Exception ex) - { - ProfilingLogger.Logger.Error("An error occurred running OnApplicationInitialized for handler " + x.GetType(), ex); - throw; - } - }); - } - - _isInitialized = true; - - return this; - } - - /// - /// Creates and returns the service context for the app - /// - /// - /// - /// - protected virtual ServiceContext CreateServiceContext(DatabaseContext dbContext, IScopeProvider scopeProvider) - { - //default transient factory - var msgFactory = new TransientMessagesFactory(); - return new ServiceContext( - new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), - new PetaPocoUnitOfWorkProvider(scopeProvider), - ApplicationCache, - ProfilingLogger.Logger, - msgFactory); - } - - /// - /// Creates and returns the application context for the app - /// - /// - /// - protected virtual ApplicationContext CreateApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext) - { - //create the ApplicationContext - return new ApplicationContext(dbContext, serviceContext, ApplicationCache, ProfilingLogger); - } - - /// - /// Creates and returns the CacheHelper for the app - /// - protected virtual CacheHelper CreateApplicationCache() - { - var cacheHelper = new CacheHelper( - //we need to have the dep clone runtime cache provider to ensure - //all entities are cached properly (cloned in and cloned out) - new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()), - new StaticCacheProvider(), - //we have no request based cache when not running in web-based context - new NullCacheProvider(), - new IsolatedRuntimeCache(type => - //we need to have the dep clone runtime cache provider to ensure - //all entities are cached properly (cloned in and cloned out) - new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); - - return cacheHelper; - } - - /// - /// This method allows for configuration of model mappers - /// - /// - /// Model mappers MUST be defined on ApplicationEventHandler instances with the interface IMapperConfiguration. - /// This allows us to search for less types on startup. - /// - protected void InitializeModelMappers() - { - Mapper.Initialize(configuration => - { - foreach (var m in ApplicationEventsResolver.Current.ApplicationEventHandlers.OfType()) - { - m.ConfigureMappings(configuration, ApplicationContext); - } - }); - } - - /// - /// Special method to initialize the LoggerResolver - /// - protected virtual void InitializeLoggerResolver() - { - LoggerResolver.Current = new LoggerResolver(ProfilingLogger == null ? Logger.CreateWithDefaultLog4NetConfiguration() : ProfilingLogger.Logger) - { - //This is another special resolver that needs to be resolvable before resolution is frozen - //since it is used for profiling the application startup - CanResolveBeforeFrozen = true - }; - } - - /// - /// Special method to initialize the ProfilerResolver - /// - protected virtual void InitializeProfilerResolver() - { - //By default we'll initialize the Log profiler (in the web project, we'll override with the web profiler) - ProfilerResolver.Current = new ProfilerResolver(ProfilingLogger == null ? new LogProfiler(LoggerResolver.Current.Logger) : ProfilingLogger.Profiler) - { - //This is another special resolver that needs to be resolvable before resolution is frozen - //since it is used for profiling the application startup - CanResolveBeforeFrozen = true - }; - } - - /// - /// Special method to initialize the ApplicationEventsResolver and any modifications required for it such - /// as adding custom types to the resolver. - /// - protected virtual void InitializeApplicationEventsResolver() - { - //find and initialize the application startup handlers, we need to initialize this resolver here because - //it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to - //events and to call their events during bootup. - //ApplicationStartupHandler.RegisterHandlers(); - //... and set the special flag to let us resolve before frozen resolution - ApplicationEventsResolver.Current = new ApplicationEventsResolver( - ServiceProvider, - ProfilingLogger.Logger, - PluginManager.ResolveApplicationStartupHandlers()) - { - CanResolveBeforeFrozen = true - }; - } - - /// - /// Special method to extend the use of Umbraco by enabling the consumer to overwrite - /// the absolute path to the root of an Umbraco site/solution, which is used for stuff - /// like Umbraco.Core.IO.IOHelper.MapPath etc. - /// - /// Absolute - protected virtual void InitializeApplicationRootPath(string rootPath) - { - IOHelper.SetRootDirectory(rootPath); - } - - /// - /// Fires after initialization and calls the callback to allow for customizations to occur & - /// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called - /// - /// - /// - public virtual IBootManager Startup(Action afterStartup) - { - if (_isStarted) - throw new InvalidOperationException("The boot manager has already been initialized"); - - using (ProfilingLogger.DebugDuration( - string.Format("Executing {0} IApplicationEventHandler.OnApplicationStarting", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()), - "Finished executing IApplicationEventHandler.OnApplicationStarting")) - { - //call OnApplicationStarting of each application events handler - ApplicationEventsResolver.Current.ApplicationEventHandlers - .ForEach(x => - { - try - { - using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationStarting", x.GetType()))) - { - x.OnApplicationStarting(UmbracoApplication, ApplicationContext); - } - } - catch (Exception ex) - { - ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarting for handler " + x.GetType(), ex); - throw; - } - }); - } - - if (afterStartup != null) - { - afterStartup(ApplicationContext.Current); - } - - _isStarted = true; - - return this; - } - - /// - /// Fires after startup and calls the callback once customizations are locked - /// - /// - /// - public virtual IBootManager Complete(Action afterComplete) - { - if (_isComplete) - throw new InvalidOperationException("The boot manager has already been completed"); - - FreezeResolution(); - - //Here we need to make sure the db can be connected to - EnsureDatabaseConnection(); - - - //This is a special case for the user service, we need to tell it if it's an upgrade, if so we need to ensure that - // exceptions are bubbled up if a user is attempted to be persisted during an upgrade (i.e. when they auth to login) - ((UserService) ApplicationContext.Services.UserService).IsUpgrading = true; - - - using (ProfilingLogger.DebugDuration( - string.Format("Executing {0} IApplicationEventHandler.OnApplicationStarted", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()), - "Finished executing IApplicationEventHandler.OnApplicationStarted")) - { - //call OnApplicationStarting of each application events handler - ApplicationEventsResolver.Current.ApplicationEventHandlers - .ForEach(x => - { - try - { - using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationStarted", x.GetType()))) - { - x.OnApplicationStarted(UmbracoApplication, ApplicationContext); - } - } - catch (Exception ex) - { - ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex); - throw; - } - }); - } - - //Now, startup all of our legacy startup handler - ApplicationEventsResolver.Current.InstantiateLegacyStartupHandlers(); - - if (afterComplete != null) - { - afterComplete(ApplicationContext.Current); - } - - _isComplete = true; - - // we're ready to serve content! - ApplicationContext.IsReady = true; - - //stop the timer and log the output - _timer.Dispose(); - return this; - } - - /// - /// We cannot continue if the db cannot be connected to - /// - private void EnsureDatabaseConnection() - { - if (ApplicationContext.IsConfigured == false) return; - if (ApplicationContext.DatabaseContext.IsDatabaseConfigured == false) return; - - // deal with localdb - var databaseContext = ApplicationContext.DatabaseContext; - var localdbex = new Regex(@"\(localdb\)\\([a-zA-Z0-9-_]+)(;|$)"); - var m = localdbex.Match(databaseContext.ConnectionString); - if (m.Success) - { - var instanceName = m.Groups[1].Value; - ProfilingLogger.Logger.Info(string.Format("LocalDb instance \"{0}\"", instanceName)); - - var localDb = new LocalDb(); - if (localDb.IsAvailable == false) - throw new UmbracoStartupFailedException("Umbraco cannot start. LocalDb is not available."); - - if (localDb.InstanceExists(m.Groups[1].Value) == false) - { - if (localDb.CreateInstance(instanceName) == false) - throw new UmbracoStartupFailedException(string.Format("Umbraco cannot start. LocalDb cannot create instance \"{0}\".", instanceName)); - if (localDb.StartInstance(instanceName) == false) - throw new UmbracoStartupFailedException(string.Format("Umbraco cannot start. LocalDb cannot start instance \"{0}\".", instanceName)); - } - } - - //try now - if (ApplicationContext.DatabaseContext.CanConnect) - return; - - var currentTry = 0; - while (currentTry < 5) - { - //first wait, then retry - Thread.Sleep(1000); - - if (ApplicationContext.DatabaseContext.CanConnect) - break; - - currentTry++; - } - - if (currentTry == 5) - { - throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but Umbraco cannot connect to the database."); - } - } - - /// - /// Freeze resolution to not allow Resolvers to be modified - /// - protected virtual void FreezeResolution() - { - Resolution.Freeze(); - } - - /// - /// Create the resolvers - /// - protected virtual void InitializeResolvers() - { - var builder = new ManifestBuilder( - ApplicationCache.RuntimeCache, - new ManifestParser(new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), ApplicationCache.RuntimeCache)); - - PropertyEditorResolver.Current = new PropertyEditorResolver(ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolvePropertyEditors(), builder); - ParameterEditorResolver.Current = new ParameterEditorResolver(ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveParameterEditors(), builder); - - //setup the validators resolver with our predefined validators - ValidatorsResolver.Current = new ValidatorsResolver( - ServiceProvider, ProfilingLogger.Logger, new[] - { - new Lazy(() => typeof (RequiredManifestValueValidator)), - new Lazy(() => typeof (RegexValidator)), - new Lazy(() => typeof (DelimitedManifestValueValidator)), - new Lazy(() => typeof (EmailValidator)), - new Lazy(() => typeof (IntegerValidator)), - new Lazy(() => typeof (DecimalValidator)), - }); - - //by default we'll use the db server registrar unless the developer has the legacy - // dist calls enabled, in which case we'll use the config server registrar - if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) - { - ServerRegistrarResolver.Current = new ServerRegistrarResolver(new ConfigServerRegistrar()); - } - else if ("true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"])) - { - ServerRegistrarResolver.Current = new ServerRegistrarResolver(new SingleServerRegistrar()); - } - else - { - ServerRegistrarResolver.Current = new ServerRegistrarResolver( - new DatabaseServerRegistrar( - new Lazy(() => ApplicationContext.Services.ServerRegistrationService), - new DatabaseServerRegistrarOptions())); - } - - //by default we'll use the database server messenger with default options (no callbacks), - // this will be overridden in the web startup - ServerMessengerResolver.Current = new ServerMessengerResolver( - new DatabaseServerMessenger(ApplicationContext, true, new DatabaseServerMessengerOptions())); - - MappingResolver.Current = new MappingResolver( - ServiceProvider, ProfilingLogger.Logger, - () => PluginManager.ResolveAssignedMapperTypes()); - - - //RepositoryResolver.Current = new RepositoryResolver( - // new RepositoryFactory(ApplicationCache)); - - CacheRefreshersResolver.Current = new CacheRefreshersResolver( - ServiceProvider, ProfilingLogger.Logger, - () => PluginManager.ResolveCacheRefreshers()); - - DataTypesResolver.Current = new DataTypesResolver( - ServiceProvider, ProfilingLogger.Logger, - () => PluginManager.ResolveDataTypes()); - - MacroFieldEditorsResolver.Current = new MacroFieldEditorsResolver( - ServiceProvider, ProfilingLogger.Logger, - () => PluginManager.ResolveMacroRenderings()); - - PackageActionsResolver.Current = new PackageActionsResolver( - ServiceProvider, ProfilingLogger.Logger, - () => PluginManager.ResolvePackageActions()); - - ActionsResolver.Current = new ActionsResolver( - ServiceProvider, ProfilingLogger.Logger, - () => PluginManager.ResolveActions()); - - //the database migration objects - MigrationResolver.Current = new MigrationResolver( - ProfilingLogger.Logger, - () => PluginManager.ResolveTypes()); - - // todo: remove once we drop IPropertyEditorValueConverter support. - PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( - ServiceProvider, ProfilingLogger.Logger, - PluginManager.ResolvePropertyEditorValueConverters()); - - // need to filter out the ones we dont want!! - PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver( - ServiceProvider, ProfilingLogger.Logger, - PluginManager.ResolveTypes()); - - // use the new DefaultShortStringHelper - ShortStringHelperResolver.Current = new ShortStringHelperResolver( - //new LegacyShortStringHelper()); - new DefaultShortStringHelper(UmbracoConfig.For.UmbracoSettings()).WithDefaultConfig()); - - UrlSegmentProviderResolver.Current = new UrlSegmentProviderResolver( - ServiceProvider, ProfilingLogger.Logger, - typeof(DefaultUrlSegmentProvider)); - - // by default, no factory is activated - PublishedContentModelFactoryResolver.Current = new PublishedContentModelFactoryResolver(); - } - } -} diff --git a/src/Umbraco.Core/CustomBooleanTypeConverter.cs b/src/Umbraco.Core/CustomBooleanTypeConverter.cs index 002639e40d..f96f8ef460 100644 --- a/src/Umbraco.Core/CustomBooleanTypeConverter.cs +++ b/src/Umbraco.Core/CustomBooleanTypeConverter.cs @@ -1,32 +1,32 @@ -using System; +using System; using System.ComponentModel; namespace Umbraco.Core { - /// - /// Allows for converting string representations of 0 and 1 to boolean - /// - internal class CustomBooleanTypeConverter : BooleanConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); - } + /// + /// Allows for converting string representations of 0 and 1 to boolean + /// + internal class CustomBooleanTypeConverter : BooleanConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + { + return true; + } + return base.CanConvertFrom(context, sourceType); + } - public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) - { - if (value is string) - { - var str = (string)value; - if (str == null || str.Length == 0 || str == "0") return false; - if (str == "1") return true; - } + public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + { + if (value is string) + { + var str = (string)value; + if (str == null || str.Length == 0 || str == "0") return false; + if (str == "1") return true; + } - return base.ConvertFrom(context, culture, value); - } - } -} \ No newline at end of file + return base.ConvertFrom(context, culture, value); + } + } +} diff --git a/src/Umbraco.Core/DataTableExtensions.cs b/src/Umbraco.Core/DataTableExtensions.cs index bf6967da4b..272fc064c4 100644 --- a/src/Umbraco.Core/DataTableExtensions.cs +++ b/src/Umbraco.Core/DataTableExtensions.cs @@ -6,115 +6,114 @@ using System.Data; using System.Linq; using System.Text; using System.Threading; -using umbraco.interfaces; namespace Umbraco.Core { - - /// - /// Static and extension methods for the DataTable object - /// - internal static class DataTableExtensions - { - - /// - /// Creates a DataTable with the specified alias and columns and uses a callback to populate the headers. - /// - /// - /// - /// - /// - /// - /// This has been migrated from the Node class and uses proper locking now. It is now used by the Node class and the - /// DynamicPublishedContent extensions for legacy reasons. - /// - public static DataTable GenerateDataTable( - string tableAlias, - Func>> getHeaders, - Func>, IEnumerable>>>> rowData) - { - var dt = new DataTable(tableAlias); - - //get all row data - var tableData = rowData().ToArray(); - - //get all headers - var propertyHeaders = GetPropertyHeaders(tableAlias, getHeaders); - foreach(var h in propertyHeaders) - { - dt.Columns.Add(new DataColumn(h.Value)); - } - - //add row data - foreach(var r in tableData) - { - dt.PopulateRow( - propertyHeaders, - r.Item1, - r.Item2); - } + /// + /// Static and extension methods for the DataTable object + /// + internal static class DataTableExtensions + { - return dt; - } - /// - /// Helper method to return this ugly object - /// - /// - /// - /// This is for legacy code, I didn't want to go creating custom classes for these - /// - internal static List>, IEnumerable>>> CreateTableData() - { - return new List>, IEnumerable>>>(); - } + /// + /// Creates a DataTable with the specified alias and columns and uses a callback to populate the headers. + /// + /// + /// + /// + /// + /// + /// This has been migrated from the Node class and uses proper locking now. It is now used by the Node class and the + /// DynamicPublishedContent extensions for legacy reasons. + /// + public static DataTable GenerateDataTable( + string tableAlias, + Func>> getHeaders, + Func>, IEnumerable>>>> rowData) + { + var dt = new DataTable(tableAlias); - /// - /// Helper method to deal with these ugly objects - /// - /// - /// - /// - /// - /// This is for legacy code, I didn't want to go creating custom classes for these - /// - internal static void AddRowData( - List>, IEnumerable>>> rowData, - IEnumerable> standardVals, - IEnumerable> userVals) - { - rowData.Add(new System.Tuple>, IEnumerable>>( - standardVals, - userVals - )); - } + //get all row data + var tableData = rowData().ToArray(); - private static IDictionary GetPropertyHeaders(string alias, Func>> getHeaders) - { - var headers = getHeaders(alias); - var def = headers.ToDictionary(pt => pt.Key, pt => pt.Value); - return def; - } + //get all headers + var propertyHeaders = GetPropertyHeaders(tableAlias, getHeaders); + foreach(var h in propertyHeaders) + { + dt.Columns.Add(new DataColumn(h.Value)); + } - private static void PopulateRow( - this DataTable dt, - IDictionary aliasesToNames, - IEnumerable> standardVals, - IEnumerable> userPropertyVals) - { - var dr = dt.NewRow(); - foreach (var r in standardVals) - { - dr[r.Key] = r.Value; - } - foreach (var p in userPropertyVals.Where(p => p.Value != null)) - { - dr[aliasesToNames[p.Key]] = p.Value; - } - dt.Rows.Add(dr); - } + //add row data + foreach(var r in tableData) + { + dt.PopulateRow( + propertyHeaders, + r.Item1, + r.Item2); + } - } + return dt; + } + + /// + /// Helper method to return this ugly object + /// + /// + /// + /// This is for legacy code, I didn't want to go creating custom classes for these + /// + internal static List>, IEnumerable>>> CreateTableData() + { + return new List>, IEnumerable>>>(); + } + + /// + /// Helper method to deal with these ugly objects + /// + /// + /// + /// + /// + /// This is for legacy code, I didn't want to go creating custom classes for these + /// + internal static void AddRowData( + List>, IEnumerable>>> rowData, + IEnumerable> standardVals, + IEnumerable> userVals) + { + rowData.Add(new System.Tuple>, IEnumerable>>( + standardVals, + userVals + )); + } + + private static IDictionary GetPropertyHeaders(string alias, Func>> getHeaders) + { + var headers = getHeaders(alias); + var def = headers.ToDictionary(pt => pt.Key, pt => pt.Value); + return def; + } + + private static void PopulateRow( + this DataTable dt, + IDictionary aliasesToNames, + IEnumerable> standardVals, + IEnumerable> userPropertyVals) + { + var dr = dt.NewRow(); + foreach (var r in standardVals) + { + dr[r.Key] = r.Value; + } + foreach (var p in userPropertyVals.Where(p => p.Value != null)) + { + dr[aliasesToNames[p.Key]] = p.Value; + } + dt.Rows.Add(dr); + } + + } } diff --git a/src/Umbraco.Core/DataTypesResolver.cs b/src/Umbraco.Core/DataTypesResolver.cs deleted file mode 100644 index cf4074fc7e..0000000000 --- a/src/Umbraco.Core/DataTypesResolver.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using umbraco.interfaces; - -namespace Umbraco.Core -{ - /// - /// A resolver to return all IDataType objects - /// - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - internal sealed class DataTypesResolver : LegacyTransientObjectsResolver - { - /// - /// Constructor - /// - /// - /// - /// - internal DataTypesResolver(IServiceProvider serviceProvider, ILogger logger, Func> dataTypes) - : base(serviceProvider, logger, dataTypes) - { - - } - - /// - /// Gets the implementations. - /// - public IEnumerable DataTypes - { - get - { - EnsureIsInitialized(); - return Values; - } - } - - protected override Guid GetUniqueIdentifier(IDataType obj) - { - return obj.Id; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs deleted file mode 100644 index 1409016da6..0000000000 --- a/src/Umbraco.Core/DatabaseContext.cs +++ /dev/null @@ -1,901 +0,0 @@ -using System; -using System.Configuration; -using System.Data.SqlServerCe; -using System.IO; -using System.Linq; -using System.Web; -using System.Xml.Linq; -using Semver; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.Migrations.Initial; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; - -namespace Umbraco.Core -{ - /// - /// The Umbraco Database context - /// - /// - /// One per AppDomain, represents the Umbraco database - /// - public class DatabaseContext - { - internal readonly IScopeProviderInternal ScopeProvider; - private readonly ILogger _logger; - private readonly SqlSyntaxProviders _syntaxProviders; - private bool _configured; - private string _connectionString; - private string _providerName; - private DatabaseSchemaResult _result; - private DateTime? _connectionLastChecked; - - /// - /// The number of minutes to throttle the checks to CanConnect - /// - private const int ConnectionCheckMinutes = 1; - - #region Compatibility with 7.5 - - // note: the ctors accepting IDatabaseFactory are here only for backward compatibility purpose - // - // problem: IDatabaseFactory2 adds the CreateNewDatabase() method which creates a new database - // 'cos IDatabaseFactory CreateDatabase() is supposed to also manage the ambient thing. We - // want to keep these ctors for backward compatibility reasons (in case ppl use them in tests) - // so we need to create a scope provider (else nothing would work) and so we need a IDatabaseFactory2, - // so...? - // solution: wrap IDatabaseFactory and pretend we have a IDatabaseFactory2, it *should* work in most - // cases but really, it depends on what ppl are doing in their tests... yet, cannot really see any - // other way to do it? - - [Obsolete("Use the constructor specifying all dependencies instead")] - public DatabaseContext(IDatabaseFactory factory) - : this(factory, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[] - { - new MySqlSyntaxProvider(LoggerResolver.Current.Logger), - new SqlCeSyntaxProvider(), - new SqlServerSyntaxProvider() - })) - { } - - /// - /// Default constructor - /// - /// - /// - /// - public DatabaseContext(IDatabaseFactory factory, ILogger logger, SqlSyntaxProviders syntaxProviders) - { - if (factory == null) throw new ArgumentNullException("factory"); - if (logger == null) throw new ArgumentNullException("logger"); - if (syntaxProviders == null) throw new ArgumentNullException("syntaxProviders"); - - var asDbFactory2 = factory as IDatabaseFactory2; - ScopeProvider = asDbFactory2 == null - ? new ScopeProvider(new DatabaseFactoryWrapper(factory)) - : new ScopeProvider(asDbFactory2); - - _logger = logger; - _syntaxProviders = syntaxProviders; - } - - /// - /// Create a configured DatabaseContext - /// - /// - /// - /// - /// - public DatabaseContext(IDatabaseFactory factory, ILogger logger, ISqlSyntaxProvider sqlSyntax, string providerName) - { - _providerName = providerName; - SqlSyntax = sqlSyntax; - SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax; - - var asDbFactory2 = factory as IDatabaseFactory2; - ScopeProvider = asDbFactory2 == null - ? new ScopeProvider(new DatabaseFactoryWrapper(factory)) - : new ScopeProvider(asDbFactory2); - - _logger = logger; - _configured = true; - } - - private class DatabaseFactoryWrapper : IDatabaseFactory2 - { - private readonly IDatabaseFactory _factory; - - public DatabaseFactoryWrapper(IDatabaseFactory factory) - { - _factory = factory; - } - - public UmbracoDatabase CreateDatabase() - { - return _factory.CreateDatabase(); - } - - public UmbracoDatabase CreateNewDatabase() - { - return CreateDatabase(); - } - - public void Dispose() - { - _factory.Dispose(); - } - } - - #endregion - - [Obsolete("Use the constructor specifying all dependencies instead")] - internal DatabaseContext(IScopeProviderInternal scopeProvider) - : this(scopeProvider, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[] - { - new MySqlSyntaxProvider(LoggerResolver.Current.Logger), - new SqlCeSyntaxProvider(), - new SqlServerSyntaxProvider() - })) - { } - - /// - /// Default constructor - /// - /// - /// - /// - internal DatabaseContext(IScopeProviderInternal scopeProvider, ILogger logger, SqlSyntaxProviders syntaxProviders) - { - if (scopeProvider == null) throw new ArgumentNullException("scopeProvider"); - if (logger == null) throw new ArgumentNullException("logger"); - if (syntaxProviders == null) throw new ArgumentNullException("syntaxProviders"); - - ScopeProvider = scopeProvider; - _logger = logger; - _syntaxProviders = syntaxProviders; - } - - /// - /// Create a configured DatabaseContext - /// - /// - /// - /// - /// - internal DatabaseContext(IScopeProviderInternal scopeProvider, ILogger logger, ISqlSyntaxProvider sqlSyntax, string providerName) - { - _providerName = providerName; - SqlSyntax = sqlSyntax; - SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax; - ScopeProvider = scopeProvider; - _logger = logger; - _configured = true; - } - - public ISqlSyntaxProvider SqlSyntax { get; private set; } - - /// - /// Gets an "ambient" database for doing CRUD operations against custom tables that resides in the Umbraco database. - /// - /// - /// Should not be used for operation against standard Umbraco tables; as services should be used instead. - /// Gets or creates an "ambient" database that is either stored in http context + available for the whole - /// request + auto-disposed at the end of the request, or stored in call context if there is no http context - in which - /// case it *must* be explicitely disposed (which will remove it from call context). - /// - public virtual UmbracoDatabase Database - { - get - { - if (IsDatabaseConfigured == false) - { - throw new InvalidOperationException("Cannot create a database instance, there is no available connection string"); - } - - return ScopeProvider.GetAmbientOrNoScope().Database; - //var scope = ScopeProvider.AmbientScope; - //return scope != null ? scope.Database : ScopeProvider.CreateNoScope().Database; - } - } - - /// - /// Boolean indicating whether the database has been configured - /// - public virtual bool IsDatabaseConfigured - { - get { return _configured; } - } - - /// - /// Determines if the db can be connected to - /// - public virtual bool CanConnect - { - get - { - if (IsDatabaseConfigured == false) - return false; - - //Don't check again if the timeout period hasn't elapsed - //this ensures we don't keep checking the connection too many times in a row like during startup. - //Do check if the _connectionLastChecked is null which means we're just initializing or it could - //not connect last time it was checked. - if ((_connectionLastChecked.HasValue && (DateTime.Now - _connectionLastChecked.Value).TotalMinutes > ConnectionCheckMinutes) - || _connectionLastChecked.HasValue == false) - { - var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider); - LogHelper.Info("CanConnect = " + canConnect); - - _connectionLastChecked = canConnect == false ? null : (DateTime?) DateTime.Now; - return canConnect; - } - - return _connectionLastChecked.HasValue; - } - } - - - /// - /// Gets the configured umbraco db connection string. - /// - public virtual string ConnectionString - { - get { return _connectionString; } - } - - /// - /// Returns the name of the dataprovider from the connectionstring setting in config - /// - internal string ProviderName - { - get - { - if (string.IsNullOrEmpty(_providerName) == false) - return _providerName; - - _providerName = Constants.DatabaseProviders.SqlServer; - if (ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName] != null) - { - if (string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName) == false) - _providerName = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName; - } - else - { - throw new NullReferenceException("Can't find a connection string with the name '" + Constants.System.UmbracoConnectionName + "'"); - } - return _providerName; - } - } - - /// - /// Returns the Type of DatabaseProvider used - /// - public virtual DatabaseProviders DatabaseProvider - { - get - { - string dbtype = Database.Connection == null ? ProviderName : Database.Connection.GetType().Name; - - if (dbtype.StartsWith("MySql")) return DatabaseProviders.MySql; - if (dbtype.StartsWith("SqlCe") || dbtype.Contains("SqlServerCe")) return DatabaseProviders.SqlServerCE; - if (dbtype.StartsWith("Npgsql")) return DatabaseProviders.PostgreSQL; - if (dbtype.StartsWith("Oracle") || dbtype.Contains("OracleClient")) return DatabaseProviders.Oracle; - if (dbtype.StartsWith("SQLite")) return DatabaseProviders.SQLite; - if (dbtype.Contains("Azure")) return DatabaseProviders.SqlAzure; - - return DatabaseProviders.SqlServer; - } - } - - /// - /// Configure a ConnectionString for the embedded database. - /// - public void ConfigureEmbeddedDatabaseConnection() - { - const string providerName = Constants.DatabaseProviders.SqlCe; - - var connectionString = GetEmbeddedDatabaseConnectionString(); - SaveConnectionString(connectionString, providerName); - - var path = Path.Combine(GlobalSettings.FullpathToRoot, "App_Data", "Umbraco.sdf"); - if (File.Exists(path) == false) - { - using (var engine = new SqlCeEngine(connectionString)) - { - engine.CreateDatabase(); - } - } - - Initialize(providerName); - } - - public string GetEmbeddedDatabaseConnectionString() - { - return @"Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1;"; - } - - /// - /// Configure a ConnectionString that has been entered manually. - /// - /// - /// Please note that we currently assume that the 'System.Data.SqlClient' provider can be used. - /// - /// - public void ConfigureDatabaseConnection(string connectionString) - { - var provider = DbConnectionExtensions.DetectProviderFromConnectionString(connectionString); - var databaseProvider = provider.ToString(); - var providerName = Constants.DatabaseProviders.SqlServer; - if (databaseProvider.ToLower().Contains("mysql")) - { - providerName = Constants.DatabaseProviders.MySql; - } - SaveConnectionString(connectionString, providerName); - Initialize(string.Empty); - } - - /// - /// Configures a ConnectionString for the Umbraco database based on the passed in properties from the installer. - /// - /// Name or address of the database server - /// Name of the database - /// Database Username - /// Database Password - /// Type of the provider to be used (Sql, Sql Azure, Sql Ce, MySql) - public void ConfigureDatabaseConnection(string server, string databaseName, string user, string password, string databaseProvider) - { - string providerName; - var connectionString = GetDatabaseConnectionString(server, databaseName, user, password, databaseProvider, out providerName); - - SaveConnectionString(connectionString, providerName); - Initialize(providerName); - } - - public string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName) - { - providerName = Constants.DatabaseProviders.SqlServer; - if (databaseProvider.ToLower().Contains("mysql")) - { - providerName = Constants.DatabaseProviders.MySql; - return string.Format("Server={0}; Database={1};Uid={2};Pwd={3}", server, databaseName, user, password); - } - if (databaseProvider.ToLower().Contains("azure")) - { - return BuildAzureConnectionString(server, databaseName, user, password); - } - return string.Format("server={0};database={1};user id={2};password={3}", server, databaseName, user, password); - } - - /// - /// Configures a ConnectionString for the Umbraco database that uses Microsoft SQL Server integrated security. - /// - /// Name or address of the database server - /// Name of the database - public void ConfigureIntegratedSecurityDatabaseConnection(string server, string databaseName) - { - const string providerName = Constants.DatabaseProviders.SqlServer; - var connectionString = GetIntegratedSecurityDatabaseConnectionString(server, databaseName); - SaveConnectionString(connectionString, providerName); - Initialize(providerName); - } - - public string GetIntegratedSecurityDatabaseConnectionString(string server, string databaseName) - { - return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName); - } - - internal string BuildAzureConnectionString(string server, string databaseName, string user, string password) - { - if (server.Contains(".") && ServerStartsWithTcp(server) == false) - server = string.Format("tcp:{0}", server); - - if (server.Contains(".") == false && ServerStartsWithTcp(server)) - { - string serverName = server.Contains(",") - ? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal)) - : server; - - var portAddition = string.Empty; - - if (server.Contains(",")) - portAddition = server.Substring(server.IndexOf(",", StringComparison.Ordinal)); - - server = string.Format("{0}.database.windows.net{1}", serverName, portAddition); - } - - if (ServerStartsWithTcp(server) == false) - server = string.Format("tcp:{0}.database.windows.net", server); - - if (server.Contains(",") == false) - server = string.Format("{0},1433", server); - - if (user.Contains("@") == false) - { - var userDomain = server; - - if (ServerStartsWithTcp(server)) - userDomain = userDomain.Substring(userDomain.IndexOf(":", StringComparison.Ordinal) + 1); - - if (userDomain.Contains(".")) - userDomain = userDomain.Substring(0, userDomain.IndexOf(".", StringComparison.Ordinal)); - - user = string.Format("{0}@{1}", user, userDomain); - } - - return string.Format("Server={0};Database={1};User ID={2};Password={3}", server, databaseName, user, password); - } - - private static bool ServerStartsWithTcp(string server) - { - return server.ToLower().StartsWith("tcp:".ToLower()); - } - - /// - /// Saves the connection string as a proper .net ConnectionString and the legacy AppSettings key/value. - /// - /// - /// Saves the ConnectionString in the very nasty 'medium trust'-supportive way. - /// - /// - /// - private void SaveConnectionString(string connectionString, string providerName) - { - //Set the connection string for the new datalayer - var connectionStringSettings = string.IsNullOrEmpty(providerName) - ? new ConnectionStringSettings(Constants.System.UmbracoConnectionName, - connectionString) - : new ConnectionStringSettings(Constants.System.UmbracoConnectionName, - connectionString, providerName); - - _connectionString = connectionString; - _providerName = providerName; - - var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root)); - var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); - var connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single(); - - // Update connectionString if it exists, or else create a new appSetting for the given key and value - var setting = connectionstrings.Descendants("add").FirstOrDefault(s => s.Attribute("name").Value == Constants.System.UmbracoConnectionName); - if (setting == null) - connectionstrings.Add(new XElement("add", - new XAttribute("name", Constants.System.UmbracoConnectionName), - new XAttribute("connectionString", connectionStringSettings), - new XAttribute("providerName", providerName))); - else - { - setting.Attribute("connectionString").Value = connectionString; - setting.Attribute("providerName").Value = providerName; - } - - xml.Save(fileName, SaveOptions.DisableFormatting); - - _logger.Info("Configured a new ConnectionString using the '" + providerName + "' provider."); - } - - /// - /// Internal method to initialize the database configuration. - /// - /// - /// If an Umbraco connectionstring exists the database can be configured on app startup, - /// but if its a new install the entry doesn't exist and the db cannot be configured. - /// So for new installs the Initialize() method should be called after the connectionstring - /// has been added to the web.config. - /// - internal void Initialize() - { - var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; - if (databaseSettings != null && string.IsNullOrWhiteSpace(databaseSettings.ConnectionString) == false && string.IsNullOrWhiteSpace(databaseSettings.ProviderName) == false) - { - var providerName = Constants.DatabaseProviders.SqlServer; - string connString = null; - if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName)) - { - providerName = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName; - connString = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ConnectionString; - } - Initialize(providerName, connString); - - } - else if (ConfigurationManager.AppSettings.ContainsKey(Constants.System.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[Constants.System.UmbracoConnectionName]) == false) - { - //A valid connectionstring does not exist, but the legacy appSettings key was found, so we'll reconfigure the conn.string. - var legacyConnString = ConfigurationManager.AppSettings[Constants.System.UmbracoConnectionName]; - if (legacyConnString.ToLowerInvariant().Contains("sqlce4umbraco")) - { - ConfigureEmbeddedDatabaseConnection(); - } - else if (legacyConnString.ToLowerInvariant().Contains("tcp:")) - { - //Must be sql azure - SaveConnectionString(legacyConnString, Constants.DatabaseProviders.SqlServer); - Initialize(Constants.DatabaseProviders.SqlServer); - } - else if (legacyConnString.ToLowerInvariant().Contains("datalayer=mysql")) - { - //Must be MySql - - //Need to strip the datalayer part off - var connectionStringWithoutDatalayer = string.Empty; - foreach (var variable in legacyConnString.Split(';').Where(x => x.ToLowerInvariant().StartsWith("datalayer") == false)) - connectionStringWithoutDatalayer = string.Format("{0}{1};", connectionStringWithoutDatalayer, variable); - - SaveConnectionString(connectionStringWithoutDatalayer, Constants.DatabaseProviders.MySql); - Initialize(Constants.DatabaseProviders.MySql); - } - else - { - //Must be sql - SaveConnectionString(legacyConnString, Constants.DatabaseProviders.SqlServer); - Initialize(Constants.DatabaseProviders.SqlServer); - } - - //Remove the legacy connection string, so we don't end up in a loop if something goes wrong. - GlobalSettings.RemoveSetting(Constants.System.UmbracoConnectionName); - - } - else - { - _configured = false; - } - } - - internal void Initialize(string providerName) - { - //only configure once! - if (_configured == true) return; - - _providerName = providerName; - - try - { - if (_syntaxProviders != null) - { - SqlSyntax = _syntaxProviders.GetByProviderNameOrDefault(providerName); - } - else if (SqlSyntax == null) - { - throw new InvalidOperationException("No " + typeof(ISqlSyntaxProvider) + " specified or no " + typeof(SqlSyntaxProviders) + " instance specified"); - } - - SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax; - - _configured = true; - } - catch (Exception e) - { - _configured = false; - - _logger.Info("Initialization of the DatabaseContext failed with following error: " + e.Message); - _logger.Info(e.StackTrace); - } - } - - internal void Initialize(string providerName, string connectionString) - { - _connectionString = connectionString; - Initialize(providerName); - } - - internal DatabaseSchemaResult ValidateDatabaseSchema() - { - if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName))) - return new DatabaseSchemaResult(); - - if (_result == null) - { - - if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted - && ProviderName == Constants.DatabaseProviders.MySql) - { - throw new InvalidOperationException("Cannot use MySql in Medium Trust configuration"); - } - - var database = new UmbracoDatabase(_connectionString, ProviderName, _logger); - var dbSchema = new DatabaseSchemaCreation(database, _logger, SqlSyntax); - _result = dbSchema.ValidateSchema(); - } - return _result; - } - - internal Result CreateDatabaseSchemaAndData(ApplicationContext applicationContext) - { - try - { - var readyForInstall = CheckReadyForInstall(); - if (readyForInstall.Success == false) - { - return readyForInstall.Result; - } - - _logger.Info("Database configuration status: Started"); - - string message; - - var database = new UmbracoDatabase(_connectionString, ProviderName, _logger); - - // If MySQL, we're going to ensure that database calls are maintaining proper casing as to remove the necessity for checks - // for case insensitive queries. In an ideal situation (which is what we're striving for), all calls would be case sensitive. - - /* - var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database); - if (supportsCaseInsensitiveQueries == false) - { - message = "

 

The database you're trying to use does not support case insensitive queries.
We currently do not support these types of databases.

" + - "

You can fix this by changing the following setting in your my.ini file in your MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

Note: Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - - return new Result { Message = message, Success = false, Percentage = "15" }; - } - */ - - message = GetResultMessageForMySql(); - - var schemaResult = ValidateDatabaseSchema(); - - var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); - - //If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing - if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedSchemaVersion.Equals(new Version(0, 0, 0))) - { - var helper = new DatabaseSchemaHelper(database, _logger, SqlSyntax); - helper.CreateDatabaseSchema(true, applicationContext); - - message = message + "

Installation completed!

"; - - //now that everything is done, we need to determine the version of SQL server that is executing - _logger.Info("Database configuration status: " + message); - return new Result { Message = message, Success = true, Percentage = "100" }; - } - - //we need to do an upgrade so return a new status message and it will need to be done during the next step - _logger.Info("Database requires upgrade"); - message = "

Upgrading database, this may take some time...

"; - return new Result - { - RequiresUpgrade = true, - Message = message, - Success = true, - Percentage = "30" - }; - } - catch (Exception ex) - { - return HandleInstallException(ex); - } - } - - /// - /// This assumes all of the previous checks are done! - /// - /// - internal Result UpgradeSchemaAndData(IMigrationEntryService migrationEntryService) - { - try - { - - var readyForInstall = CheckReadyForInstall(); - if (readyForInstall.Success == false) - { - return readyForInstall.Result; - } - - _logger.Info("Database upgrade started"); - - var database = new UmbracoDatabase(_connectionString, ProviderName, _logger); - //var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database); - - var message = GetResultMessageForMySql(); - - var schemaResult = ValidateDatabaseSchema(); - - var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion()); - - var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService); - - var targetVersion = UmbracoVersion.Current; - - //In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will - // return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the - // migrations are run for the target version - if (installedMigrationVersion == new SemVersion(new Version(0, 0, 0)) && installedSchemaVersion > new SemVersion(new Version(0, 0, 0))) - { - //set the installedMigrationVersion to be one less than the target so the latest migrations are guaranteed to execute - installedMigrationVersion = new SemVersion(targetVersion.SubtractRevision()); - } - - //Figure out what our current installed version is. If the web.config doesn't have a version listed, then we'll use the minimum - // version detected between the schema installed and the migrations listed in the migration table. - // If there is a version in the web.config, we'll take the minimum between the listed migration in the db and what - // is declared in the web.config. - - var currentInstalledVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) - //Take the minimum version between the detected schema version and the installed migration version - ? new[] {installedSchemaVersion, installedMigrationVersion}.Min() - //Take the minimum version between the installed migration version and the version specified in the config - : new[] { SemVersion.Parse(GlobalSettings.ConfigurationStatus), installedMigrationVersion }.Min(); - - //Ok, another edge case here. If the current version is a pre-release, - // then we want to ensure all migrations for the current release are executed. - if (currentInstalledVersion.Prerelease.IsNullOrWhiteSpace() == false) - { - currentInstalledVersion = new SemVersion(currentInstalledVersion.GetVersion().SubtractRevision()); - } - - //DO the upgrade! - - var runner = new MigrationRunner(migrationEntryService, _logger, currentInstalledVersion, UmbracoVersion.GetSemanticVersion(), Constants.System.UmbracoMigrationName); - - var upgraded = runner.Execute(database, true); - - if (upgraded == false) - { - throw new ApplicationException("Upgrading failed, either an error occurred during the upgrade process or an event canceled the upgrade process, see log for full details"); - } - - message = message + "

Upgrade completed!

"; - - //now that everything is done, we need to determine the version of SQL server that is executing - - _logger.Info("Database configuration status: " + message); - - return new Result { Message = message, Success = true, Percentage = "100" }; - } - catch (Exception ex) - { - return HandleInstallException(ex); - } - } - - private string GetResultMessageForMySql() - { - if (SqlSyntax.GetType() == typeof(MySqlSyntaxProvider)) - { - return "

 

Congratulations, the database step ran successfully!

" + - "

Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.

" + - "

However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries

" + - "

Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - } - return string.Empty; - } - - /* - private string GetResultMessageForMySql(bool? supportsCaseInsensitiveQueries) - { - if (supportsCaseInsensitiveQueries == null) - { - return "

 

Warning! Could not check if your database type supports case insensitive queries.
We currently do not support these databases that do not support case insensitive queries.

" + - "

You can check this by looking for the following setting in your my.ini file in your MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

Note: Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - } - if (SqlSyntax.GetType() == typeof(MySqlSyntaxProvider)) - { - return "

 

Congratulations, the database step ran successfully!

" + - "

Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.

" + - "

However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries

" + - "

Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - } - return string.Empty; - }*/ - - private Attempt CheckReadyForInstall() - { - if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted - && ProviderName == Constants.DatabaseProviders.MySql) - { - throw new InvalidOperationException("Cannot use MySql in Medium Trust configuration"); - } - - if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName))) - { - return Attempt.Fail(new Result - { - Message = - "Database configuration is invalid. Please check that the entered database exists and that the provided username and password has write access to the database.", - Success = false, - Percentage = "10" - }); - } - return Attempt.Succeed(); - } - - private Result HandleInstallException(Exception ex) - { - _logger.Error("Database configuration failed", ex); - - if (_result != null) - { - _logger.Info("The database schema validation produced the following summary: \n" + _result.GetSummary()); - } - - return new Result - { - Message = - "The database configuration failed with the following message: " + ex.Message + - "\n Please check log file for additional information (can be found in '/App_Data/Logs/UmbracoTraceLog.txt')", - Success = false, - Percentage = "90" - }; - } - - internal class Result - { - public bool RequiresUpgrade { get; set; } - public string Message { get; set; } - public bool Success { get; set; } - public string Percentage { get; set; } - } - - internal bool IsConnectionStringConfigured(ConnectionStringSettings databaseSettings) - { - var dbIsSqlCe = false; - if (databaseSettings != null && databaseSettings.ProviderName != null) - dbIsSqlCe = databaseSettings.ProviderName == Constants.DatabaseProviders.SqlCe; - var sqlCeDatabaseExists = false; - if (dbIsSqlCe) - { - var parts = databaseSettings.ConnectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - var dataSourcePart = parts.FirstOrDefault(x => x.InvariantStartsWith("Data Source=")); - if (dataSourcePart != null) - { - var datasource = dataSourcePart.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString()); - var filePath = datasource.Replace("Data Source=", string.Empty); - sqlCeDatabaseExists = File.Exists(filePath); - } - } - - // Either the connection details are not fully specified or it's a SQL CE database that doesn't exist yet - if (databaseSettings == null - || string.IsNullOrWhiteSpace(databaseSettings.ConnectionString) || string.IsNullOrWhiteSpace(databaseSettings.ProviderName) - || (dbIsSqlCe && sqlCeDatabaseExists == false)) - { - return false; - } - - return true; - } - - /* - private class UsingDatabase : IDisposable - { - private readonly UmbracoDatabase _orig; - private readonly UmbracoDatabase _temp; - - public UsingDatabase(UmbracoDatabase orig, UmbracoDatabase temp) - { - _orig = orig; - _temp = temp; - } - - public void Dispose() - { - if (_temp != null) - { - _temp.Dispose(); - if (_orig != null) - DefaultDatabaseFactory.AttachAmbientDatabase(_orig); - } - GC.SuppressFinalize(this); - } - } - */ - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/DateTimeExtensions.cs b/src/Umbraco.Core/DateTimeExtensions.cs index 8332fb9356..b3babd2d07 100644 --- a/src/Umbraco.Core/DateTimeExtensions.cs +++ b/src/Umbraco.Core/DateTimeExtensions.cs @@ -44,7 +44,7 @@ namespace Umbraco.Core } /// - /// Calculates the number of minutes from a date time, on a rolling daily basis (so if + /// Calculates the number of minutes from a date time, on a rolling daily basis (so if /// date time is before the time, calculate onto next day) /// /// Date to start from diff --git a/src/Umbraco.Core/DecimalExtensions.cs b/src/Umbraco.Core/DecimalExtensions.cs index b4d74fdb2e..24afb9f7a9 100644 --- a/src/Umbraco.Core/DecimalExtensions.cs +++ b/src/Umbraco.Core/DecimalExtensions.cs @@ -3,7 +3,7 @@ /// /// Provides extension methods for System.Decimal. /// - /// See System.Decimal on MSDN and also + /// See System.Decimal on MSDN and also /// http://stackoverflow.com/questions/4298719/parse-decimal-and-filter-extra-0-on-the-right/4298787#4298787. /// public static class DecimalExtensions diff --git a/src/Umbraco.Core/DelegateEqualityComparer.cs b/src/Umbraco.Core/DelegateEqualityComparer.cs index 140ea016d1..d9c4f14435 100644 --- a/src/Umbraco.Core/DelegateEqualityComparer.cs +++ b/src/Umbraco.Core/DelegateEqualityComparer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace Umbraco.Core @@ -57,4 +57,4 @@ namespace Umbraco.Core #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/DelegateExtensions.cs b/src/Umbraco.Core/DelegateExtensions.cs index 78450448e2..c9b2e681c5 100644 --- a/src/Umbraco.Core/DelegateExtensions.cs +++ b/src/Umbraco.Core/DelegateExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading; @@ -41,4 +41,4 @@ namespace Umbraco.Core return Attempt.Fail(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs index a3fd160a49..f645c50824 100644 --- a/src/Umbraco.Core/Deploy/ArtifactBase.cs +++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; @@ -53,4 +53,4 @@ namespace Umbraco.Core.Deploy public string Name { get; set; } public string Alias { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/ArtifactDependency.cs b/src/Umbraco.Core/Deploy/ArtifactDependency.cs index 41e349d636..374772210c 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependency.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependency.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Deploy +namespace Umbraco.Core.Deploy { /// /// Represents an artifact dependency. @@ -37,4 +37,4 @@ namespace Umbraco.Core.Deploy /// public ArtifactDependencyMode Mode { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs index fd036f4628..c447dbcde2 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; @@ -66,4 +66,4 @@ namespace Umbraco.Core.Deploy get { return false; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs index 7ee5c2f220..c285a5f9fb 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Deploy +namespace Umbraco.Core.Deploy { /// /// Indicates the mode of the dependency. @@ -15,4 +15,4 @@ namespace Umbraco.Core.Deploy /// Exist } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs index 3723e483cb..ede6801953 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Deploy +namespace Umbraco.Core.Deploy { /// /// Represent the state of an artifact being deployed. @@ -47,4 +47,4 @@ namespace Umbraco.Core.Deploy /// public int NextPass { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs b/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs index b4d2be23cd..6d7a51b383 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs @@ -45,4 +45,4 @@ return Artifact; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/ArtifactSignature.cs b/src/Umbraco.Core/Deploy/ArtifactSignature.cs index 4bea6a5ce8..219e27140e 100644 --- a/src/Umbraco.Core/Deploy/ArtifactSignature.cs +++ b/src/Umbraco.Core/Deploy/ArtifactSignature.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace Umbraco.Core.Deploy @@ -18,4 +18,4 @@ namespace Umbraco.Core.Deploy public IEnumerable Dependencies { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/Difference.cs b/src/Umbraco.Core/Deploy/Difference.cs index 90329afdd6..05d7c66013 100644 --- a/src/Umbraco.Core/Deploy/Difference.cs +++ b/src/Umbraco.Core/Deploy/Difference.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Deploy +namespace Umbraco.Core.Deploy { public class Difference { @@ -25,4 +25,4 @@ namespace Umbraco.Core.Deploy return s; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/Direction.cs b/src/Umbraco.Core/Deploy/Direction.cs index b0e1c1dc0a..4d87afa3a0 100644 --- a/src/Umbraco.Core/Deploy/Direction.cs +++ b/src/Umbraco.Core/Deploy/Direction.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Core.Deploy +namespace Umbraco.Core.Deploy { public enum Direction { ToArtifact, FromArtifact } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/IArtifact.cs b/src/Umbraco.Core/Deploy/IArtifact.cs index 653e6386bb..0668047fea 100644 --- a/src/Umbraco.Core/Deploy/IArtifact.cs +++ b/src/Umbraco.Core/Deploy/IArtifact.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Deploy +namespace Umbraco.Core.Deploy { /// /// Represents an artifact ie an object that can be transfered between environments. @@ -8,4 +8,4 @@ namespace Umbraco.Core.Deploy string Name { get; } string Alias { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/IArtifactSignature.cs b/src/Umbraco.Core/Deploy/IArtifactSignature.cs index 83b112586b..287b70a8f4 100644 --- a/src/Umbraco.Core/Deploy/IArtifactSignature.cs +++ b/src/Umbraco.Core/Deploy/IArtifactSignature.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Deploy { @@ -38,4 +38,4 @@ namespace Umbraco.Core.Deploy /// IEnumerable Dependencies { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/IDeployContext.cs b/src/Umbraco.Core/Deploy/IDeployContext.cs index 531ed9dae4..01d1a8e98c 100644 --- a/src/Umbraco.Core/Deploy/IDeployContext.cs +++ b/src/Umbraco.Core/Deploy/IDeployContext.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; @@ -45,4 +45,4 @@ namespace Umbraco.Core.Deploy ///// //CancellationToken CancellationToken { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/IFileSource.cs b/src/Umbraco.Core/Deploy/IFileSource.cs index 7d13e0fbde..e789e0bf24 100644 --- a/src/Umbraco.Core/Deploy/IFileSource.cs +++ b/src/Umbraco.Core/Deploy/IFileSource.cs @@ -88,4 +88,4 @@ namespace Umbraco.Core.Deploy ///// A byte array containing the file content. //byte[] GetFileBytes(StringUdi Udi); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs b/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs index a4c5e7aeab..827a902b89 100644 --- a/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs +++ b/src/Umbraco.Core/Deploy/IGridCellValueConnector.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Deploy @@ -38,4 +38,4 @@ namespace Umbraco.Core.Deploy /// The should only be used to assist with context data relevant when setting the value. void SetValue(GridValue.GridControl gridControl, Property property); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/IImageSourceParser.cs b/src/Umbraco.Core/Deploy/IImageSourceParser.cs index d8e8d860ac..061125955c 100644 --- a/src/Umbraco.Core/Deploy/IImageSourceParser.cs +++ b/src/Umbraco.Core/Deploy/IImageSourceParser.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Deploy { @@ -24,4 +24,4 @@ namespace Umbraco.Core.Deploy /// Turns umb://media/... into /media/.... string FromArtifact(string value); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs index c5906c2060..d6d68ed81d 100644 --- a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs +++ b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Deploy { @@ -24,4 +24,4 @@ namespace Umbraco.Core.Deploy /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. string FromArtifact(string value); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/IMacroParser.cs b/src/Umbraco.Core/Deploy/IMacroParser.cs index 9cde8ef8b6..725cd6c88e 100644 --- a/src/Umbraco.Core/Deploy/IMacroParser.cs +++ b/src/Umbraco.Core/Deploy/IMacroParser.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Deploy { @@ -29,4 +29,4 @@ namespace Umbraco.Core.Deploy /// Value with converted identifiers string ReplaceAttributeValue(string value, string editorAlias, ICollection dependencies, Direction direction); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/IPreValueConnector.cs b/src/Umbraco.Core/Deploy/IPreValueConnector.cs index 4ef898cc74..15f9a9e619 100644 --- a/src/Umbraco.Core/Deploy/IPreValueConnector.cs +++ b/src/Umbraco.Core/Deploy/IPreValueConnector.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Deploy { @@ -7,7 +7,7 @@ namespace Umbraco.Core.Deploy /// /// PreValues may contain values such as content identifiers, that would be local /// to one environment, and need to be converted in order to be deployed. - public interface IPreValueConnector + public interface IPreValueConnector // fixme this needs to change really { /// /// Gets the property editor aliases that the value converter supports by default. @@ -29,4 +29,4 @@ namespace Umbraco.Core.Deploy /// IDictionary ConvertToLocalEnvironment(IDictionary preValues); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs index 65553b991d..a0fc274236 100644 --- a/src/Umbraco.Core/Deploy/IServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs @@ -1,8 +1,5 @@ using System.Collections.Generic; -using System.Linq; -using System.Text; -using umbraco.interfaces; -using Umbraco.Core; +using Umbraco.Core.Composing; namespace Umbraco.Core.Deploy { diff --git a/src/Umbraco.Core/Deploy/IValueConnector.cs b/src/Umbraco.Core/Deploy/IValueConnector.cs index a93e5a05a4..35304e3fde 100644 --- a/src/Umbraco.Core/Deploy/IValueConnector.cs +++ b/src/Umbraco.Core/Deploy/IValueConnector.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Deploy @@ -32,4 +32,4 @@ namespace Umbraco.Core.Deploy /// The deploy property value. void SetValue(IContentBase content, string alias, string value); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Dictionary/CultureDictionaryFactoryResolver.cs b/src/Umbraco.Core/Dictionary/CultureDictionaryFactoryResolver.cs deleted file mode 100644 index 334c2fbe4b..0000000000 --- a/src/Umbraco.Core/Dictionary/CultureDictionaryFactoryResolver.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.ComponentModel; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Core.Dictionary -{ - /// - /// Resolves the current CultureDictionaryFactory - /// - public sealed class CultureDictionaryFactoryResolver : SingleObjectResolverBase - { - internal CultureDictionaryFactoryResolver(ICultureDictionaryFactory factory) - : base(factory) - { - } - - [Obsolete("Use SetDictionaryFactory instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void SetContentStore(ICultureDictionaryFactory factory) - { - Value = factory; - } - - /// - /// Can be used by developers at runtime to set their ICultureDictionaryFactory at app startup - /// - /// - public void SetDictionaryFactory(ICultureDictionaryFactory factory) - { - Value = factory; - } - - /// - /// Returns the ICultureDictionaryFactory - /// - public ICultureDictionaryFactory Factory - { - get { return Value; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dictionary/ICultureDictionary.cs b/src/Umbraco.Core/Dictionary/ICultureDictionary.cs index 8617d7440e..f8a6e49973 100644 --- a/src/Umbraco.Core/Dictionary/ICultureDictionary.cs +++ b/src/Umbraco.Core/Dictionary/ICultureDictionary.cs @@ -1,31 +1,31 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; namespace Umbraco.Core.Dictionary { - /// - /// Represents a dictionary based on a specific culture - /// - public interface ICultureDictionary - { - /// - /// Returns the dictionary value based on the key supplied - /// - /// - /// - string this[string key] { get; } + /// + /// Represents a dictionary based on a specific culture + /// + public interface ICultureDictionary + { + /// + /// Returns the dictionary value based on the key supplied + /// + /// + /// + string this[string key] { get; } - /// - /// Returns the current culture - /// - CultureInfo Culture { get; } + /// + /// Returns the current culture + /// + CultureInfo Culture { get; } /// /// Returns the child dictionary entries for a given key /// /// /// - IDictionary GetChildren(string key); - } -} \ No newline at end of file + IDictionary GetChildren(string key); + } +} diff --git a/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs b/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs index 798c3c47c6..ce896a5ef7 100644 --- a/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs +++ b/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Core.Dictionary +namespace Umbraco.Core.Dictionary { - public interface ICultureDictionaryFactory - { - ICultureDictionary CreateDictionary(); - } -} \ No newline at end of file + public interface ICultureDictionaryFactory + { + ICultureDictionary CreateDictionary(); + } +} diff --git a/src/Umbraco.Core/DictionaryExtensions.cs b/src/Umbraco.Core/DictionaryExtensions.cs index c488650496..8c618a06ac 100644 --- a/src/Umbraco.Core/DictionaryExtensions.cs +++ b/src/Umbraco.Core/DictionaryExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; @@ -9,11 +9,11 @@ using System.Web; namespace Umbraco.Core { - /// - /// Extension methods for dictionary & concurrentdictionary - /// - internal static class DictionaryExtensions - { + /// + /// Extension methods for dictionary & concurrentdictionary + /// + internal static class DictionaryExtensions + { /// /// Method to Get a value by the key. If the key doesn't exist it will create a new TVal object for the key and return it. @@ -23,15 +23,15 @@ namespace Umbraco.Core /// /// /// - public static TVal GetOrCreate(this IDictionary dict, TKey key) + public static TVal GetOrCreate(this IDictionary dict, TKey key) where TVal : class, new() - { - if (dict.ContainsKey(key) == false) - { + { + if (dict.ContainsKey(key) == false) + { dict.Add(key, new TVal()); - } + } return dict[key]; - } + } /// /// Updates an item with the specified key with the specified value @@ -44,7 +44,7 @@ namespace Umbraco.Core /// /// /// Taken from: http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression - /// + /// /// If there is an item in the dictionary with the key, it will keep trying to update it until it can /// public static bool TryUpdate(this ConcurrentDictionary dict, TKey key, Func updateFactory) @@ -71,7 +71,7 @@ namespace Umbraco.Core /// /// /// Taken from: http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression - /// + /// /// WARNING: If the value changes after we've retreived it, then the item will not be updated /// public static bool TryUpdateOptimisitic(this ConcurrentDictionary dict, TKey key, Func updateFactory) @@ -83,80 +83,58 @@ namespace Umbraco.Core return true;//note we return true whether we succeed or not, see explanation below. } - /// - /// Converts a dictionary to another type by only using direct casting - /// - /// - /// - /// - /// - public static IDictionary ConvertTo(this IDictionary d) - { - var result = new Dictionary(); - foreach (DictionaryEntry v in d) - { - result.Add((TKeyOut)v.Key, (TValOut)v.Value); - } - return result; - } + /// + /// Converts a dictionary to another type by only using direct casting + /// + /// + /// + /// + /// + public static IDictionary ConvertTo(this IDictionary d) + { + var result = new Dictionary(); + foreach (DictionaryEntry v in d) + { + result.Add((TKeyOut)v.Key, (TValOut)v.Value); + } + return result; + } - /// - /// Converts a dictionary to another type using the specified converters - /// - /// - /// - /// - /// - /// - /// - public static IDictionary ConvertTo(this IDictionary d, Func keyConverter, Func valConverter) - { - var result = new Dictionary(); - foreach (DictionaryEntry v in d) - { - result.Add(keyConverter(v.Key), valConverter(v.Value)); - } - return result; - } + /// + /// Converts a dictionary to another type using the specified converters + /// + /// + /// + /// + /// + /// + /// + public static IDictionary ConvertTo(this IDictionary d, Func keyConverter, Func valConverter) + { + var result = new Dictionary(); + foreach (DictionaryEntry v in d) + { + result.Add(keyConverter(v.Key), valConverter(v.Value)); + } + return result; + } - /// - /// Converts a dictionary to a NameValueCollection - /// - /// - /// - public static NameValueCollection ToNameValueCollection(this IDictionary d) - { - var n = new NameValueCollection(); - foreach (var i in d) - { - n.Add(i.Key, i.Value); - } - return n; - } + /// + /// Converts a dictionary to a NameValueCollection + /// + /// + /// + public static NameValueCollection ToNameValueCollection(this IDictionary d) + { + var n = new NameValueCollection(); + foreach (var i in d) + { + n.Add(i.Key, i.Value); + } + return n; + } - /// - /// Merges all key/values from the sources dictionaries into the destination dictionary - /// - /// - /// - /// - /// The source dictionary to merge other dictionaries into - /// - /// By default all values will be retained in the destination if the same keys exist in the sources but - /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the destination. Note that - /// it will just use the last found key/value if this is true. - /// - /// The other dictionaries to merge values from - public static void MergeLeft(this T destination, IEnumerable> sources, bool overwrite = false) - where T : IDictionary - { - foreach (var p in sources.SelectMany(src => src).Where(p => overwrite || destination.ContainsKey(p.Key) == false)) - { - destination[p.Key] = p.Value; - } - } - /// /// Merges all key/values from the sources dictionaries into the destination dictionary /// @@ -165,7 +143,29 @@ namespace Umbraco.Core /// /// The source dictionary to merge other dictionaries into /// - /// By default all values will be retained in the destination if the same keys exist in the sources but + /// By default all values will be retained in the destination if the same keys exist in the sources but + /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the destination. Note that + /// it will just use the last found key/value if this is true. + /// + /// The other dictionaries to merge values from + public static void MergeLeft(this T destination, IEnumerable> sources, bool overwrite = false) + where T : IDictionary + { + foreach (var p in sources.SelectMany(src => src).Where(p => overwrite || destination.ContainsKey(p.Key) == false)) + { + destination[p.Key] = p.Value; + } + } + + /// + /// Merges all key/values from the sources dictionaries into the destination dictionary + /// + /// + /// + /// + /// The source dictionary to merge other dictionaries into + /// + /// By default all values will be retained in the destination if the same keys exist in the sources but /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the destination. Note that /// it will just use the last found key/value if this is true. /// @@ -176,109 +176,109 @@ namespace Umbraco.Core destination.MergeLeft(new[] {source}, overwrite); } - /// - /// Returns the value of the key value based on the key, if the key is not found, a null value is returned - /// - /// The type of the key. - /// The type of the val. - /// The d. - /// The key. - /// The default value. - /// - public static TVal GetValue(this IDictionary d, TKey key, TVal defaultValue = default(TVal)) - { - if (d.ContainsKey(key)) - { - return d[key]; - } - return defaultValue; - } - - /// - /// Returns the value of the key value based on the key as it's string value, if the key is not found, then an empty string is returned - /// - /// - /// - /// - public static string GetValueAsString(this IDictionary d, TKey key) - { - if (d.ContainsKey(key)) - { - return d[key].ToString(); - } - return String.Empty; - } - - /// - /// Returns the value of the key value based on the key as it's string value, if the key is not found or is an empty string, then the provided default value is returned - /// - /// - /// - /// - /// - public static string GetValueAsString(this IDictionary d, TKey key, string defaultValue) - { - if (d.ContainsKey(key)) - { - var value = d[key].ToString(); - if (value != string.Empty) - return value; - } - + /// + /// Returns the value of the key value based on the key, if the key is not found, a null value is returned + /// + /// The type of the key. + /// The type of the val. + /// The d. + /// The key. + /// The default value. + /// + public static TVal GetValue(this IDictionary d, TKey key, TVal defaultValue = default(TVal)) + { + if (d.ContainsKey(key)) + { + return d[key]; + } return defaultValue; - } + } - /// contains key ignore case. - /// The dictionary. - /// The key. - /// Value Type - /// The contains key ignore case. - public static bool ContainsKeyIgnoreCase(this IDictionary dictionary, string key) - { - return dictionary.Keys.InvariantContains(key); - } + /// + /// Returns the value of the key value based on the key as it's string value, if the key is not found, then an empty string is returned + /// + /// + /// + /// + public static string GetValueAsString(this IDictionary d, TKey key) + { + if (d.ContainsKey(key)) + { + return d[key].ToString(); + } + return String.Empty; + } - /// - /// Converts a dictionary object to a query string representation such as: - /// firstname=shannon&lastname=deminick - /// - /// - /// - public static string ToQueryString(this IDictionary d) - { - if (!d.Any()) return ""; + /// + /// Returns the value of the key value based on the key as it's string value, if the key is not found or is an empty string, then the provided default value is returned + /// + /// + /// + /// + /// + public static string GetValueAsString(this IDictionary d, TKey key, string defaultValue) + { + if (d.ContainsKey(key)) + { + var value = d[key].ToString(); + if (value != string.Empty) + return value; + } - var builder = new StringBuilder(); - foreach (var i in d) - { - builder.Append(String.Format("{0}={1}&", HttpUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : HttpUtility.UrlEncode(i.Value.ToString()))); - } - return builder.ToString().TrimEnd('&'); - } + return defaultValue; + } - /// The get entry ignore case. - /// The dictionary. - /// The key. - /// The type - /// The entry - public static TValue GetValueIgnoreCase(this IDictionary dictionary, string key) - { - return dictionary.GetValueIgnoreCase(key, default(TValue)); - } + /// contains key ignore case. + /// The dictionary. + /// The key. + /// Value Type + /// The contains key ignore case. + public static bool ContainsKeyIgnoreCase(this IDictionary dictionary, string key) + { + return dictionary.Keys.InvariantContains(key); + } - /// The get entry ignore case. - /// The dictionary. - /// The key. - /// The default value. - /// The type - /// The entry - public static TValue GetValueIgnoreCase(this IDictionary dictionary, string key, TValue defaultValue) - { - key = dictionary.Keys.FirstOrDefault(i => i.InvariantEquals(key)); + /// + /// Converts a dictionary object to a query string representation such as: + /// firstname=shannon&lastname=deminick + /// + /// + /// + public static string ToQueryString(this IDictionary d) + { + if (!d.Any()) return ""; - return key.IsNullOrWhiteSpace() == false - ? dictionary[key] - : defaultValue; - } - } -} \ No newline at end of file + var builder = new StringBuilder(); + foreach (var i in d) + { + builder.Append(String.Format("{0}={1}&", HttpUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : HttpUtility.UrlEncode(i.Value.ToString()))); + } + return builder.ToString().TrimEnd('&'); + } + + /// The get entry ignore case. + /// The dictionary. + /// The key. + /// The type + /// The entry + public static TValue GetValueIgnoreCase(this IDictionary dictionary, string key) + { + return dictionary.GetValueIgnoreCase(key, default(TValue)); + } + + /// The get entry ignore case. + /// The dictionary. + /// The key. + /// The default value. + /// The type + /// The entry + public static TValue GetValueIgnoreCase(this IDictionary dictionary, string key, TValue defaultValue) + { + key = dictionary.Keys.FirstOrDefault(i => i.InvariantEquals(key)); + + return key.IsNullOrWhiteSpace() == false + ? dictionary[key] + : defaultValue; + } + } +} diff --git a/src/Umbraco.Core/DisposableObject.cs b/src/Umbraco.Core/DisposableObject.cs index 0a3ae1c782..ecdc149f6e 100644 --- a/src/Umbraco.Core/DisposableObject.cs +++ b/src/Umbraco.Core/DisposableObject.cs @@ -1,61 +1,64 @@ -using System; -using System.Threading; +using System; namespace Umbraco.Core -{ +{ /// /// Abstract implementation of IDisposable. /// /// + /// This is for objects that DO have unmanaged resources. Use + /// for objects that do NOT have unmanaged resources, and avoid creating a finalizer. + /// /// Can also be used as a pattern for when inheriting is not possible. - /// + /// /// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx /// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/ - /// + /// /// Note: if an object's ctor throws, it will never be disposed, and so if that ctor /// has allocated disposable objects, it should take care of disposing them. /// - public abstract class DisposableObject : IDisposable - { - private bool _disposed; - private readonly object _locko = new object(); - - // gets a value indicating whether this instance is disposed. + public abstract class DisposableObject : IDisposable + { + private readonly object _locko = new object(); + + // gets a value indicating whether this instance is disposed. // for internal tests only (not thread safe) - //TODO make this internal + rename "Disposed" when we can break compatibility - public bool IsDisposed { get { return _disposed; } } - - // implements IDisposable - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - // finalizer + public bool Disposed { get; private set; } + + // implements IDisposable + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + // finalizer ~DisposableObject() { Dispose(false); - } + } - //TODO make this private, non-virtual when we can break compatibility - protected virtual void Dispose(bool disposing) - { - lock (_locko) - { - if (_disposed) return; - _disposed = true; + private void Dispose(bool disposing) + { + // can happen if the object construction failed + if (_locko == null) + return; + + lock (_locko) + { + if (Disposed) return; + Disposed = true; } DisposeUnmanagedResources(); if (disposing) - DisposeResources(); - } - - protected abstract void DisposeResources(); - - protected virtual void DisposeUnmanagedResources() - { } + DisposeResources(); + } + + protected abstract void DisposeResources(); + + protected virtual void DisposeUnmanagedResources() + { } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/DisposableObjectSlim.cs b/src/Umbraco.Core/DisposableObjectSlim.cs index 1ecb325100..4992f8bc0f 100644 --- a/src/Umbraco.Core/DisposableObjectSlim.cs +++ b/src/Umbraco.Core/DisposableObjectSlim.cs @@ -3,16 +3,27 @@ namespace Umbraco.Core { /// - /// This should not be use if there are ubmanaged resources to be disposed, use DisposableObject instead + /// Abstract implementation of managed IDisposable. /// + /// + /// This is for objects that do NOT have unmanaged resources. Use + /// for objects that DO have unmanaged resources and need to deal with them when disposing. + /// + /// Can also be used as a pattern for when inheriting is not possible. + /// + /// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx + /// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/ + /// + /// Note: if an object's ctor throws, it will never be disposed, and so if that ctor + /// has allocated disposable objects, it should take care of disposing them. + /// public abstract class DisposableObjectSlim : IDisposable { - private bool _disposed; private readonly object _locko = new object(); // gets a value indicating whether this instance is disposed. // for internal tests only (not thread safe) - internal bool Disposed { get { return _disposed; } } + public bool Disposed { get; private set; } // implements IDisposable public void Dispose() @@ -23,16 +34,20 @@ namespace Umbraco.Core private void Dispose(bool disposing) { + // can happen if the object construction failed + if (_locko == null) + return; + lock (_locko) { - if (_disposed) return; - _disposed = true; + if (Disposed) return; + Disposed = true; } if (disposing) DisposeResources(); } - protected abstract void DisposeResources(); + protected virtual void DisposeResources() { } } } diff --git a/src/Umbraco.Core/DisposableTimer.cs b/src/Umbraco.Core/DisposableTimer.cs index dab99584f3..6ded588be6 100644 --- a/src/Umbraco.Core/DisposableTimer.cs +++ b/src/Umbraco.Core/DisposableTimer.cs @@ -1,253 +1,111 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System; using System.Diagnostics; -using System.Web; using Umbraco.Core.Logging; -using Umbraco.Core.Profiling; namespace Umbraco.Core { - /// - /// Starts the timer and invokes a callback upon disposal. Provides a simple way of timing an operation by wrapping it in a using (C#) statement. - /// - public class DisposableTimer : DisposableObjectSlim + /// + /// Starts the timer and invokes a callback upon disposal. Provides a simple way of timing an operation by wrapping it in a using (C#) statement. + /// + public class DisposableTimer : DisposableObjectSlim { - private readonly ILogger _logger; - private readonly LogType? _logType; - private readonly IProfiler _profiler; - private readonly Type _loggerType; - private readonly string _endMessage; - private readonly IDisposable _profilerStep; - private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); - private readonly Action _callback; + private readonly ILogger _logger; + private readonly LogType? _logType; + private readonly Type _loggerType; + private readonly int _thresholdMilliseconds; + private readonly IDisposable _profilerStep; + private readonly string _endMessage; + private string _failMessage; + private Exception _failException; + private bool _failed; - internal enum LogType - { - Debug, Info - } - - internal DisposableTimer(ILogger logger, LogType logType, IProfiler profiler, Type loggerType, string startMessage, string endMessage) + internal enum LogType { - if (logger == null) throw new ArgumentNullException("logger"); - if (loggerType == null) throw new ArgumentNullException("loggerType"); + Debug, Info + } + + // internal - created by profiling logger + internal DisposableTimer(ILogger logger, LogType logType, IProfiler profiler, Type loggerType, + string startMessage, string endMessage, string failMessage = null, + int thresholdMilliseconds = 0) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (loggerType == null) throw new ArgumentNullException(nameof(loggerType)); _logger = logger; _logType = logType; - _profiler = profiler; _loggerType = loggerType; _endMessage = endMessage; - - switch (logType) + _failMessage = failMessage; + _thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds; + + if (thresholdMilliseconds == 0) { - case LogType.Debug: - logger.Debug(loggerType, startMessage); - break; - case LogType.Info: - logger.Info(loggerType, startMessage); - break; - default: - throw new ArgumentOutOfRangeException("logType"); - } - - if (profiler != null) - { - _profilerStep = profiler.Step(loggerType, startMessage); - } - } - - protected internal DisposableTimer(Action callback) - { - if (callback == null) throw new ArgumentNullException("callback"); - _callback = callback; - } - - public Stopwatch Stopwatch - { - get { return _stopwatch; } - } - - /// - /// Starts the timer and invokes the specified callback upon disposal. - /// - /// The callback. - /// - [Obsolete("Use either TraceDuration or DebugDuration instead of using Start")] - public static DisposableTimer Start(Action callback) - { - return new DisposableTimer(callback); - } - - #region TraceDuration - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use the other methods that specify strings instead of Func")] - public static DisposableTimer TraceDuration(Func startMessage, Func completeMessage) - { - return TraceDuration(typeof(T), startMessage, completeMessage); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use the other methods that specify strings instead of Func")] - public static DisposableTimer TraceDuration(Type loggerType, Func startMessage, Func completeMessage) - { - return new DisposableTimer( - LoggerResolver.Current.Logger, - LogType.Info, - ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : null, - loggerType, - startMessage(), - completeMessage()); - } - - /// - /// Adds a start and end log entry as Info and tracks how long it takes until disposed. - /// - /// - /// - /// - /// - [Obsolete("Use the Umbraco.Core.Logging.ProfilingLogger to create instances of DisposableTimer")] - public static DisposableTimer TraceDuration(string startMessage, string completeMessage) - { - return TraceDuration(typeof(T), startMessage, completeMessage); - } - - /// - /// Adds a start and end log entry as Info and tracks how long it takes until disposed. - /// - /// - /// - /// - [Obsolete("Use the Umbraco.Core.Logging.ProfilingLogger to create instances of DisposableTimer")] - public static DisposableTimer TraceDuration(string startMessage) - { - return TraceDuration(typeof(T), startMessage, "Complete"); - } - - /// - /// Adds a start and end log entry as Info and tracks how long it takes until disposed. - /// - /// - /// - /// - /// - [Obsolete("Use the Umbraco.Core.Logging.ProfilingLogger to create instances of DisposableTimer")] - public static DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage) - { - return new DisposableTimer( - LoggerResolver.Current.Logger, - LogType.Info, - ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : null, - loggerType, - startMessage, - completeMessage); - } - - #endregion - - #region DebugDuration - /// - /// Adds a start and end log entry as Debug and tracks how long it takes until disposed. - /// - /// - /// - /// - /// - [Obsolete("Use the Umbraco.Core.Logging.ProfilingLogger to create instances of DisposableTimer")] - public static DisposableTimer DebugDuration(string startMessage, string completeMessage) - { - return DebugDuration(typeof(T), startMessage, completeMessage); - } - - /// - /// Adds a start and end log entry as Debug and tracks how long it takes until disposed. - /// - /// - /// - /// - [Obsolete("Use the Umbraco.Core.Logging.ProfilingLogger to create instances of DisposableTimer")] - public static DisposableTimer DebugDuration(string startMessage) - { - return DebugDuration(typeof(T), startMessage, "Complete"); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use the other methods that specify strings instead of Func")] - public static DisposableTimer DebugDuration(Func startMessage, Func completeMessage) - { - return DebugDuration(typeof(T), startMessage, completeMessage); - } - - /// - /// Adds a start and end log entry as Debug and tracks how long it takes until disposed. - /// - /// - /// - /// - /// - [Obsolete("Use the Umbraco.Core.Logging.ProfilingLogger to create instances of DisposableTimer")] - public static DisposableTimer DebugDuration(Type loggerType, string startMessage, string completeMessage) - { - return new DisposableTimer( - LoggerResolver.Current.Logger, - LogType.Debug, - ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : null, - loggerType, - startMessage, - completeMessage); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use the other methods that specify strings instead of Func")] - public static DisposableTimer DebugDuration(Type loggerType, Func startMessage, Func completeMessage) - { - return new DisposableTimer( - LoggerResolver.Current.Logger, - LogType.Debug, - ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : null, - loggerType, - startMessage(), - completeMessage()); - } - #endregion - - /// - /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic. - /// - protected override void DisposeResources() - { - if (_profiler != null) - { - _profiler.DisposeIfDisposable(); - } - - if (_profilerStep != null) - { - _profilerStep.Dispose(); - } - - if (_logType.HasValue && _endMessage.IsNullOrWhiteSpace() == false && _loggerType != null && _logger != null) - { - switch (_logType) + switch (logType) { case LogType.Debug: - _logger.Debug(_loggerType, () => _endMessage + " (took " + Stopwatch.ElapsedMilliseconds + "ms)"); + logger.Debug(loggerType, startMessage); break; case LogType.Info: - _logger.Info(_loggerType, () => _endMessage + " (took " + Stopwatch.ElapsedMilliseconds + "ms)"); + logger.Info(loggerType, startMessage); break; default: - throw new ArgumentOutOfRangeException("logType"); + throw new ArgumentOutOfRangeException(nameof(logType)); } } - if (_callback != null) - { - _callback.Invoke(Stopwatch.ElapsedMilliseconds); - } - - } + // else aren't logging the start message, this is output to the profiler but not the log, + // we just want the log to contain the result if it's more than the minimum ms threshold. - } -} \ No newline at end of file + _profilerStep = profiler?.Step(loggerType, startMessage); + } + + /// + /// Reports a failure. + /// + /// The fail message. + /// The exception. + /// Completion of the timer will be reported as an error, with the specified message and exception. + public void Fail(string failMessage = null, Exception exception = null) + { + _failed = true; + _failMessage = failMessage ?? _failMessage ?? "Failed."; + _failException = exception; + } + + public Stopwatch Stopwatch { get; } = Stopwatch.StartNew(); + + /// + ///Disposes resources. + /// + /// Overrides abstract class which handles required locking. + protected override void DisposeResources() + { + Stopwatch.Stop(); + + _profilerStep?.Dispose(); + + if ((Stopwatch.ElapsedMilliseconds >= _thresholdMilliseconds || _failed) + && _logType.HasValue && _loggerType != null && _logger != null + && (_endMessage.IsNullOrWhiteSpace() == false || _failed)) + { + if (_failed) + { + _logger.Error(_loggerType, $"{_failMessage} ({Stopwatch.ElapsedMilliseconds}ms)", _failException); + } + else switch (_logType) + { + case LogType.Debug: + _logger.Debug(_loggerType, () => $"{_endMessage} ({Stopwatch.ElapsedMilliseconds}ms)"); + break; + case LogType.Info: + _logger.Info(_loggerType, () => $"{_endMessage} ({Stopwatch.ElapsedMilliseconds}ms)"); + break; + // filtered in the ctor + //default: + // throw new Exception(); + } + } + } + } +} diff --git a/src/Umbraco.Core/Dynamics/CaseInsensitiveDynamicObject.cs b/src/Umbraco.Core/Dynamics/CaseInsensitiveDynamicObject.cs deleted file mode 100644 index 43d7adc647..0000000000 --- a/src/Umbraco.Core/Dynamics/CaseInsensitiveDynamicObject.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Dynamic; -using System.Linq; -using System.Reflection; - -namespace Umbraco.Core.Dynamics -{ - /// - /// This will check enable dynamic access to properties and methods in a case insensitive manner - /// - /// - /// - /// This works by using reflection on the type - the reflection lookup is lazy so it will not execute unless a dynamic method needs to be accessed - /// - public abstract class CaseInsensitiveDynamicObject : DynamicObject - where T: class - { - /// - /// Used for dynamic access for case insensitive property access - /// ` - private static readonly Lazy>> CaseInsensitivePropertyAccess = new Lazy>>(() => - { - var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public) - .DistinctBy(x => x.Name); - return props.Select(propInfo => - { - var name = propInfo.Name.ToLowerInvariant(); - Func getVal = propInfo.GetValue; - return new KeyValuePair>(name, getVal); - - }).ToDictionary(x => x.Key, x => x.Value); - }); - - /// - /// Used for dynamic access for case insensitive property access - /// - private static readonly Lazy>>> CaseInsensitiveMethodAccess - = new Lazy>>>(() => - { - var props = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public) - .Where(x => x.IsSpecialName == false && x.IsVirtual == false) - .DistinctBy(x => x.Name); - return props.Select(methodInfo => - { - var name = methodInfo.Name.ToLowerInvariant(); - Func getVal = methodInfo.Invoke; - var val = new Tuple>(methodInfo.GetParameters(), getVal); - return new KeyValuePair>>(name, val); - - }).ToDictionary(x => x.Key, x => x.Value); - }); - - public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) - { - var name = binder.Name.ToLowerInvariant(); - if (CaseInsensitiveMethodAccess.Value.ContainsKey(name) == false) - return base.TryInvokeMember(binder, args, out result); - - var val = CaseInsensitiveMethodAccess.Value[name]; - var parameters = val.Item1; - var callback = val.Item2; - var fullArgs = new List(args); - if (args.Length <= parameters.Length) - { - //need to fill them up if they're optional - for (var i = args.Length; i < parameters.Length; i++) - { - if (parameters[i].IsOptional) - { - fullArgs.Add(parameters[i].DefaultValue); - } - } - if (fullArgs.Count == parameters.Length) - { - result = callback((T)(object)this, fullArgs.ToArray()); - return true; - } - } - return base.TryInvokeMember(binder, args, out result); - } - - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - var name = binder.Name.ToLowerInvariant(); - if (CaseInsensitivePropertyAccess.Value.ContainsKey(name) == false) - return base.TryGetMember(binder, out result); - - result = CaseInsensitivePropertyAccess.Value[name]((T)(object)this); - return true; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/ClassFactory.cs b/src/Umbraco.Core/Dynamics/ClassFactory.cs deleted file mode 100644 index 87f4315e76..0000000000 --- a/src/Umbraco.Core/Dynamics/ClassFactory.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; -using System.Threading; - -namespace Umbraco.Core.Dynamics -{ - internal class ClassFactory - { - public static readonly ClassFactory Instance = new ClassFactory(); - - static ClassFactory() { } // Trigger lazy initialization of static fields - - ModuleBuilder module; - Dictionary classes; - int classCount; - ReaderWriterLock rwLock; - - protected ClassFactory() - { - AssemblyName name = new AssemblyName("DynamicClasses"); - AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run); -#if ENABLE_LINQ_PARTIAL_TRUST - new ReflectionPermission(PermissionState.Unrestricted).Assert(); -#endif - try - { - module = assembly.DefineDynamicModule("Module"); - } - finally - { -#if ENABLE_LINQ_PARTIAL_TRUST - PermissionSet.RevertAssert(); -#endif - } - classes = new Dictionary(); - rwLock = new ReaderWriterLock(); - } - - public Type GetDynamicClass(IEnumerable properties) - { - rwLock.AcquireReaderLock(Timeout.Infinite); - try - { - Signature signature = new Signature(properties); - Type type; - if (!classes.TryGetValue(signature, out type)) - { - type = CreateDynamicClass(signature.Properties); - classes.Add(signature, type); - } - return type; - } - finally - { - rwLock.ReleaseReaderLock(); - } - } - - Type CreateDynamicClass(DynamicProperty[] properties) - { - LockCookie cookie = rwLock.UpgradeToWriterLock(Timeout.Infinite); - try - { - string typeName = "DynamicClass" + (classCount + 1); -#if ENABLE_LINQ_PARTIAL_TRUST - new ReflectionPermission(PermissionState.Unrestricted).Assert(); -#endif - try - { - TypeBuilder tb = this.module.DefineType(typeName, TypeAttributes.Class | - TypeAttributes.Public, typeof(DynamicClass)); - FieldInfo[] fields = GenerateProperties(tb, properties); - GenerateEquals(tb, fields); - GenerateGetHashCode(tb, fields); - Type result = tb.CreateType(); - classCount++; - return result; - } - finally - { -#if ENABLE_LINQ_PARTIAL_TRUST - PermissionSet.RevertAssert(); -#endif - } - } - finally - { - rwLock.DowngradeFromWriterLock(ref cookie); - } - } - - FieldInfo[] GenerateProperties(TypeBuilder tb, DynamicProperty[] properties) - { - FieldInfo[] fields = new FieldBuilder[properties.Length]; - for (int i = 0; i < properties.Length; i++) - { - DynamicProperty dp = properties[i]; - FieldBuilder fb = tb.DefineField("_" + dp.Name, dp.Type, FieldAttributes.Private); - PropertyBuilder pb = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, null); - MethodBuilder mbGet = tb.DefineMethod("get_" + dp.Name, - MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, - dp.Type, Type.EmptyTypes); - ILGenerator genGet = mbGet.GetILGenerator(); - genGet.Emit(OpCodes.Ldarg_0); - genGet.Emit(OpCodes.Ldfld, fb); - genGet.Emit(OpCodes.Ret); - MethodBuilder mbSet = tb.DefineMethod("set_" + dp.Name, - MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, - null, new Type[] { dp.Type }); - ILGenerator genSet = mbSet.GetILGenerator(); - genSet.Emit(OpCodes.Ldarg_0); - genSet.Emit(OpCodes.Ldarg_1); - genSet.Emit(OpCodes.Stfld, fb); - genSet.Emit(OpCodes.Ret); - pb.SetGetMethod(mbGet); - pb.SetSetMethod(mbSet); - fields[i] = fb; - } - return fields; - } - - void GenerateEquals(TypeBuilder tb, FieldInfo[] fields) - { - MethodBuilder mb = tb.DefineMethod("Equals", - MethodAttributes.Public | MethodAttributes.ReuseSlot | - MethodAttributes.Virtual | MethodAttributes.HideBySig, - typeof(bool), new Type[] { typeof(object) }); - ILGenerator gen = mb.GetILGenerator(); - LocalBuilder other = gen.DeclareLocal(tb); - Label next = gen.DefineLabel(); - gen.Emit(OpCodes.Ldarg_1); - gen.Emit(OpCodes.Isinst, tb); - gen.Emit(OpCodes.Stloc, other); - gen.Emit(OpCodes.Ldloc, other); - gen.Emit(OpCodes.Brtrue_S, next); - gen.Emit(OpCodes.Ldc_I4_0); - gen.Emit(OpCodes.Ret); - gen.MarkLabel(next); - foreach (FieldInfo field in fields) - { - Type ft = field.FieldType; - Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); - next = gen.DefineLabel(); - gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null); - gen.Emit(OpCodes.Ldarg_0); - gen.Emit(OpCodes.Ldfld, field); - gen.Emit(OpCodes.Ldloc, other); - gen.Emit(OpCodes.Ldfld, field); - gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("Equals", new Type[] { ft, ft }), null); - gen.Emit(OpCodes.Brtrue_S, next); - gen.Emit(OpCodes.Ldc_I4_0); - gen.Emit(OpCodes.Ret); - gen.MarkLabel(next); - } - gen.Emit(OpCodes.Ldc_I4_1); - gen.Emit(OpCodes.Ret); - } - - void GenerateGetHashCode(TypeBuilder tb, FieldInfo[] fields) - { - MethodBuilder mb = tb.DefineMethod("GetHashCode", - MethodAttributes.Public | MethodAttributes.ReuseSlot | - MethodAttributes.Virtual | MethodAttributes.HideBySig, - typeof(int), Type.EmptyTypes); - ILGenerator gen = mb.GetILGenerator(); - gen.Emit(OpCodes.Ldc_I4_0); - foreach (FieldInfo field in fields) - { - Type ft = field.FieldType; - Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); - gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null); - gen.Emit(OpCodes.Ldarg_0); - gen.Emit(OpCodes.Ldfld, field); - gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("GetHashCode", new Type[] { ft }), null); - gen.Emit(OpCodes.Xor); - } - gen.Emit(OpCodes.Ret); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicClass.cs b/src/Umbraco.Core/Dynamics/DynamicClass.cs deleted file mode 100644 index 13914cad42..0000000000 --- a/src/Umbraco.Core/Dynamics/DynamicClass.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Reflection; -using System.Text; - -namespace Umbraco.Core.Dynamics -{ - public abstract class DynamicClass - { - public override string ToString() - { - PropertyInfo[] props = this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); - StringBuilder sb = new StringBuilder(); - sb.Append("{"); - for (int i = 0; i < props.Length; i++) - { - if (i > 0) sb.Append(", "); - sb.Append(props[i].Name); - sb.Append("="); - sb.Append(props[i].GetValue(this, null)); - } - sb.Append("}"); - return sb.ToString(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicDictionary.cs b/src/Umbraco.Core/Dynamics/DynamicDictionary.cs deleted file mode 100644 index f3ab75413f..0000000000 --- a/src/Umbraco.Core/Dynamics/DynamicDictionary.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; -using System.Dynamic; - -namespace Umbraco.Core.Dynamics -{ - public class DynamicDictionary : DynamicObject - { - internal readonly Dictionary SourceItems; - - public DynamicDictionary(Dictionary sourceItems) - { - SourceItems = sourceItems; - } - public override bool TrySetMember(SetMemberBinder binder, object value) - { - if (SourceItems.ContainsKey(binder.Name)) - { - SourceItems[binder.Name.ToLower()] = value; - } - else - { - SourceItems.Add(binder.Name.ToLower(), value); - } - return true; - } - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - if (SourceItems != null) - { - if (SourceItems.TryGetValue(binder.Name.ToLower(), out result)) - { - return true; - } - } - result = null; - return true; - } - } -} diff --git a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs deleted file mode 100644 index 59f149ef09..0000000000 --- a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs +++ /dev/null @@ -1,194 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Dynamic; -using System.Linq; -using System.Reflection; -using System.Text; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Dynamics -{ - /// - /// A helper class to try invoke members, find properties, etc... - /// - internal class DynamicInstanceHelper - { - - internal class TryInvokeMemberResult - { - public object ObjectResult { get; private set; } - public TryInvokeMemberSuccessReason Reason { get; private set; } - - public TryInvokeMemberResult(object result, TryInvokeMemberSuccessReason reason) - { - ObjectResult = result; - Reason = reason; - } - } - - internal enum TryInvokeMemberSuccessReason - { - FoundProperty, - FoundMethod, - FoundExtensionMethod - } - - /// - /// Attempts to invoke a member based on the dynamic instance - /// - /// - /// - /// The object instance to invoke the extension method for - /// - /// - /// - /// - /// First tries to find a property with the binder name, if that fails it will try to find a static or instance method - /// on the object that matches the binder name - /// - public static Attempt TryInvokeMember(IRuntimeCacheProvider runtimeCache, T thisObject, InvokeMemberBinder binder, object[] args) - { - return TryInvokeMember(runtimeCache, thisObject, binder, args, null); - } - - /// - /// Attempts to invoke a member based on the dynamic instance - /// - /// - /// - /// The object instance to invoke the extension method for - /// - /// - /// The types to scan for extension methods - /// - /// - /// First tries to find a property with the binder name, if that fails it will try to find a static or instance method - /// on the object that matches the binder name, if that fails it will then attempt to invoke an extension method - /// based on the binder name and the extension method types to scan. - /// - public static Attempt TryInvokeMember( - IRuntimeCacheProvider runtimeCache, - T thisObject, - InvokeMemberBinder binder, - object[] args, - IEnumerable findExtensionMethodsOnTypes) - { - //TODO: We MUST cache the result here, it is very expensive to keep finding extension methods! - object result; - try - { - //Property? - result = typeof(T).InvokeMember(binder.Name, - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.GetProperty, - null, - thisObject, - args); - return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundProperty)); - } - catch (MissingMethodException) - { - try - { - //Static or Instance Method? - result = typeof(T).InvokeMember(binder.Name, - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.Static | - BindingFlags.InvokeMethod, - null, - thisObject, - args); - return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundMethod)); - } - catch (MissingMethodException) - { - if (findExtensionMethodsOnTypes != null) - { - try - { - result = FindAndExecuteExtensionMethod(runtimeCache, thisObject, args, binder.Name, findExtensionMethodsOnTypes); - return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundExtensionMethod)); - } - catch (TargetInvocationException ext) - { - //don't log here, we return this exception because the caller may need to do something specific when - //this exception occurs. - var mresult = new TryInvokeMemberResult(null, TryInvokeMemberSuccessReason.FoundExtensionMethod); - return Attempt.Fail(mresult, ext); - } - catch (Exception ex) - { - var sb = new StringBuilder(); - sb.AppendFormat("An error occurred finding and executing extension method \"{0}\" ", binder.Name); - sb.AppendFormat("for type \"{0}\". ", typeof (T)); - sb.Append("Types searched for extension methods were "); - sb.Append(string.Join(", ", findExtensionMethodsOnTypes)); - sb.Append("."); - LogHelper.Error(sb.ToString(), ex); - var mresult = new TryInvokeMemberResult(null, TryInvokeMemberSuccessReason.FoundExtensionMethod); - return Attempt.Fail(mresult, ex); - } - } - return Attempt.Fail(); - } - } - catch (Exception ex) - { - LogHelper.Error("An unhandled exception occurred in method TryInvokeMember", ex); - return Attempt.Fail(ex); - } - } - - /// - /// Attempts to find an extension method that matches the name and arguments based on scanning the Type's passed in - /// to the findMethodsOnTypes parameter - /// - /// - /// The instance object to execute the extension method for - /// - /// - /// - /// - internal static object FindAndExecuteExtensionMethod( - IRuntimeCacheProvider runtimeCache, - T thisObject, - object[] args, - string name, - IEnumerable findMethodsOnTypes) - { - object result = null; - - //find known extension methods that match the first type in the list - MethodInfo toExecute = null; - foreach (var t in findMethodsOnTypes) - { - toExecute = ExtensionMethodFinder.FindExtensionMethod(runtimeCache, t, args, name, false); - if (toExecute != null) - break; - } - - if (toExecute != null) - { - var genericArgs = (new[] { (object)thisObject }).Concat(args); - - // else we'd get an exception w/ message "Late bound operations cannot - // be performed on types or methods for which ContainsGenericParameters is true." - // because MakeGenericMethod must be used to obtain an actual method that can run - if (toExecute.ContainsGenericParameters) - throw new InvalidOperationException("Method contains generic parameters, something's wrong."); - - result = toExecute.Invoke(null, genericArgs.ToArray()); - } - else - { - throw new MissingMethodException(typeof(T).FullName, name); - } - return result; - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicNull.cs b/src/Umbraco.Core/Dynamics/DynamicNull.cs deleted file mode 100644 index 593808e867..0000000000 --- a/src/Umbraco.Core/Dynamics/DynamicNull.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections; -using System.Dynamic; -using System.Web; - -namespace Umbraco.Core.Dynamics -{ - //This type is used as a return type when TryGetMember fails on a DynamicNode - //.Where explicitly checks for this type, to indicate that nothing was returned - //Because it's IEnumerable, if the user is actually trying @Model.TextPages or similar - //it will still return an enumerable object (assuming the call actually failed because there were no children of that type) - //but in .Where, if they use a property that doesn't exist, the lambda will bypass this and return false - - // returned when TryGetMember fails on a DynamicPublishedContent - // - // so if user does @CurrentPage.TextPages it will get something that is enumerable (but empty) - // note - not sure I understand the stuff about .Where, though - - public class DynamicNull : DynamicObject, IEnumerable, IHtmlString - { - public static readonly DynamicNull Null = new DynamicNull(); - - private DynamicNull() {} - - public IEnumerator GetEnumerator() - { - return (new List()).GetEnumerator(); - } - - public DynamicNull Where(string predicate, params object[] values) - { - return this; - } - - public DynamicNull OrderBy(string orderBy) - { - return this; - } - - public DynamicNull ToContentSet() - { - return this; - } - - public int Count() - { - return 0; - } - - public override string ToString() - { - return string.Empty; - } - - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - result = this; - return true; - } - - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) - { - result = this; - return true; - } - - public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) - { - result = this; - return true; - } - - public bool IsNull() - { - return true; - } - - public bool HasValue() - { - return false; - } - - public string Name - { - get { return string.Empty; } - } - - public int Id - { - get { return 0; } - } - - public static implicit operator bool(DynamicNull n) - { - return false; - } - - public static implicit operator DateTime(DynamicNull n) - { - return DateTime.MinValue; - } - - public static implicit operator int(DynamicNull n) - { - return 0; - } - - public static implicit operator string(DynamicNull n) - { - return string.Empty; - } - - public string ToHtmlString() - { - return string.Empty; - } - } -} diff --git a/src/Umbraco.Core/Dynamics/DynamicOrdering.cs b/src/Umbraco.Core/Dynamics/DynamicOrdering.cs deleted file mode 100644 index b4f0bf0d3a..0000000000 --- a/src/Umbraco.Core/Dynamics/DynamicOrdering.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Linq.Expressions; - -namespace Umbraco.Core.Dynamics -{ - internal class DynamicOrdering - { - public Expression Selector; - public bool Ascending; - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicProperty.cs b/src/Umbraco.Core/Dynamics/DynamicProperty.cs deleted file mode 100644 index acb29bf387..0000000000 --- a/src/Umbraco.Core/Dynamics/DynamicProperty.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace Umbraco.Core.Dynamics -{ - internal class DynamicProperty - { - readonly string _name; - readonly Type _type; - - public DynamicProperty(string name, Type type) - { - if (name == null) throw new ArgumentNullException("name"); - if (type == null) throw new ArgumentNullException("type"); - this._name = name; - this._type = type; - } - - public string Name - { - get { return _name; } - } - - public Type Type - { - get { return _type; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicQueryableGetMemberBinder.cs b/src/Umbraco.Core/Dynamics/DynamicQueryableGetMemberBinder.cs deleted file mode 100644 index 92d7cfcdc9..0000000000 --- a/src/Umbraco.Core/Dynamics/DynamicQueryableGetMemberBinder.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Dynamic; - -namespace Umbraco.Core.Dynamics -{ - internal class DynamicQueryableGetMemberBinder : GetMemberBinder - { - public DynamicQueryableGetMemberBinder(string name, bool ignoreCase) : base(name, ignoreCase) { } - - public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Umbraco.Core/Dynamics/DynamicXml.cs b/src/Umbraco.Core/Dynamics/DynamicXml.cs deleted file mode 100644 index 5fd97fc778..0000000000 --- a/src/Umbraco.Core/Dynamics/DynamicXml.cs +++ /dev/null @@ -1,848 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Dynamic; -using System.Reflection; -using System.Xml.Linq; -using System.Xml.XPath; -using System.Collections; -using System.IO; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.Cache; - -namespace Umbraco.Core.Dynamics -{ - [TypeConverter(typeof(DynamicXmlConverter))] - public class DynamicXml : DynamicObject, IEnumerable, IEnumerable - { - /// - /// Returns the XElement used to create the DynamicXml structure - /// - public XElement BaseElement { get; set; } - - /// - /// Returns the raw XElement used to create the DynamicXml structure if one was specified otherwise returns the - /// same value as BaseElement. - /// - /// - /// This is purely used for when an instance of DynamicXml is created with the overload that supports - /// passing in both a raw xml version and a dash-stripped xml version. Otherwise this value is exactly the - /// same as BaseElement. - /// - public XElement RawXmlElement { get; internal set; } - - /// - /// Constructor - /// - /// - public DynamicXml(XElement baseElement) - { - if (baseElement == null) return; - - //same - RawXmlElement = baseElement; - BaseElement = baseElement; - } - - /// - /// When this constructor is used the BaseElement becomes equivalent to the strippedXml structure - /// - /// - /// - internal DynamicXml(XElement strippedXml, XElement rawXml) - { - if (rawXml == null) return; - if (strippedXml == null) return; - - RawXmlElement = rawXml; - BaseElement = strippedXml; - } - - /// - /// When this constructor is used the BaseElement becomes equivalent to the strippedXml structure - /// - /// - /// - internal DynamicXml(string strippedXml, string rawXml) - { - if (rawXml == null) return; - if (strippedXml == null) return; - - RawXmlElement = XElement.Parse(rawXml); - BaseElement = XElement.Parse(strippedXml); - } - - /// - /// Constructor - /// - /// - public DynamicXml(string xml) - { - if (xml.IsNullOrWhiteSpace()) return; - - var baseElement = XElement.Parse(xml); - - //same - RawXmlElement = baseElement; - BaseElement = baseElement; - } - - /// - /// Constructor - /// - /// - public DynamicXml(XPathNodeIterator xpni) - { - if (xpni == null) return; - if (xpni.Current == null) return; - - //TODO: OuterXml is really bad for performance! Should actually use the XPathNodeIterator api - var xml = xpni.Current.OuterXml; - var baseElement = XElement.Parse(xml); - - //same - RawXmlElement = baseElement; - BaseElement = baseElement; - } - - /// - /// Returns the InnertText based on the BaseElement object - /// - public string InnerText - { - get - { - return BaseElement.Value; - } - } - - /// - /// Returns the string representation of the BaseElement object - /// - /// - public string ToXml() - { - return BaseElement.ToString(SaveOptions.DisableFormatting); - } - - /// - /// Returns the string representation of the RawXmlElement object - /// - /// - public string ToRawXml() - { - return RawXmlElement.ToString(SaveOptions.DisableFormatting); - } - - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) - { - int index = 0; - if (indexes.Length > 0) - { - index = (int)indexes[0]; - result = new DynamicXml( - RawXmlElement.Elements().ElementAt(index), - BaseElement.Elements().ElementAt(index)); - return true; - } - return base.TryGetIndex(binder, indexes, out result); - } - public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) - { - if (args.Length == 0 && binder.Name == "ToXml") - { - result = BaseElement.ToString(); - return true; - } - if (args.Length == 1 && binder.Name == "XPath") - { - var elements = BaseElement.XPathSelectElements(args[0].ToString()); - HandleIEnumerableXElement(elements, out result); - return true; //anyway - } - - var runtimeCache = ApplicationContext.Current != null ? ApplicationContext.Current.ApplicationCache.RuntimeCache : new NullCacheProvider(); - - //ok, now lets try to match by member, property, extensino method - var attempt = DynamicInstanceHelper.TryInvokeMember(runtimeCache, this, binder, args, new[] - { - typeof (IEnumerable), - typeof (IEnumerable), - typeof (DynamicXml) - }); - - if (attempt.Success) - { - result = attempt.Result.ObjectResult; - - //need to check the return type and possibly cast if result is from an extension method found - if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod) - { - //if the result is already a DynamicXml instance, then we do not need to cast - if (attempt.Result.ObjectResult != null && !(attempt.Result.ObjectResult is DynamicXml)) - { - if (attempt.Result.ObjectResult is XElement) - { - result = new DynamicXml((XElement)attempt.Result.ObjectResult); - } - else if (attempt.Result.ObjectResult is IEnumerable) - { - result = ((IEnumerable)attempt.Result.ObjectResult).Select(x => new DynamicXml(x)); - } - else if (attempt.Result.ObjectResult is IEnumerable) - { - result = ((IEnumerable) attempt.Result.ObjectResult).Select(x => new DynamicXml( - x.RawXmlElement, - x.BaseElement)); - } - } - } - return true; - } - - //this is the result of an extension method execution gone wrong so we return dynamic null - if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod - && attempt.Exception != null && attempt.Exception is TargetInvocationException) - { - result = DynamicNull.Null; - return true; - } - - result = null; - return false; - - } - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - if (RawXmlElement == null || binder == null) - { - result = null; - return false; - } - - //Check if the name matches a node based on the BaseElement (which if the correct ctor is used, will be dash stripped) - var elementByNameAttempt = CheckNodeNameMatch(binder.Name, BaseElement); - if (elementByNameAttempt.Success) - { - if (HandleIEnumerableXElement(elementByNameAttempt.Result, out result)) - { - return true; - } - } - - //Check if the name matches a node based on the BaseElement (which if the correct ctor is used, will be dash stripped) - var attributeByNameAttempt = CheckAttributeNameMatch(binder.Name, BaseElement); - if (attributeByNameAttempt.Success) - { - if (attributeByNameAttempt.Result.Count() > 1) - { - //more than one attribute matched, lets return the collection - result = attributeByNameAttempt.Result; - } - else - { - //only one attribute matched, lets just return it - result = attributeByNameAttempt.Result.FirstOrDefault(); - } - return true; - } - - - return base.TryGetMember(binder, out result); - } - - /// - /// Checks if the 'name' matches any attributes of xmlElement - /// - /// The name to match - /// The xml element to check against - /// - private static Attempt> CheckAttributeNameMatch(string name, XElement xmlElement) - { - var attributes = xmlElement.Attributes(name).Select(attr => attr.Value).ToArray(); - if (attributes.Any()) - { - return Attempt>.Succeed(attributes); - } - - if (!attributes.Any() && xmlElement.Name == "root" && xmlElement.Elements().Count() == 1) - { - //no elements matched and this node is called 'root' and only has one child... lets see if it matches. - var childElements = xmlElement.Elements().ElementAt(0).Attributes(name).Select(attr => attr.Value).ToArray(); - if (childElements.Any()) - { - //we've found a match by the first child of an element called 'root' (strange, but sure) - return Attempt>.Succeed(childElements); - } - } - - //no deal - return Attempt>.Fail(); - } - - /// - /// Checks if the 'name' matches any elements of xmlElement - /// - /// The name to match - /// The xml element to check against - /// - private Attempt> CheckNodeNameMatch(string name, XElement xmlElement) - { - //Go ahead and try to fetch all of the elements matching the member name, and wrap them - var elements = xmlElement.Elements(name).ToArray(); - - //Check if we've got any matches, if so then return true - if (elements.Any()) - { - return Attempt>.Succeed(elements); - } - - if (!elements.Any() && xmlElement.Name == "root" && xmlElement.Elements().Count() == 1) - { - //no elements matched and this node is called 'root' and only has one child... lets see if it matches. - var childElements = xmlElement.Elements().ElementAt(0).Elements(name).ToArray(); - if (childElements.Any()) - { - //we've found a match by the first child of an element called 'root' (strange, but sure) - return Attempt>.Succeed(childElements); - } - } - - //no deal - return Attempt>.Fail(); - } - - private bool HandleIEnumerableXElement(IEnumerable elements, out object result) - { - //Get the count now, so we don't have to call it twice - int count = elements.Count(); - if (count > 0) - { - var firstElement = elements.FirstOrDefault(); - //we have a single element, does it have any children? - if (firstElement != null && firstElement.Elements().Count() == 0 && !firstElement.HasAttributes) - { - //no, return the text - result = firstElement.Value; - return true; - } - else - { - //We have more than one matching element, so let's return the collection - //elements is IEnumerable - //but we want to be able to re-enter this code - var root = new XElement(XName.Get("root")); - root.Add(elements); - result = new DynamicXml(root); - - //From here, you'll either end up back here (because you have ) - //or you use [] indexing and you end up with a single element - return true; - } - } - result = null; - return false; - } - - /// - /// Executes an XPath expression over the BaseElement object - /// - /// - /// - public DynamicXml XPath(string expression) - { - var matched = BaseElement.XPathSelectElements(expression); - var root = new DynamicXml(""); - foreach (var element in matched) - { - root.BaseElement.Add(element); - } - return root; - } - - /// - /// Return the string version of the BaseElement object - /// - /// - public override string ToString() - { - return ToXml(); - } - - public IHtmlString ToHtml() - { - return new HtmlString(this.ToXml()); - } - public DynamicXml Find(string expression) - { - return new DynamicXml(BaseElement.XPathSelectElements(expression).FirstOrDefault()); - } - - public DynamicXml Find(string attributeName, object value) - { - string expression = string.Format("//*[{0}='{1}']", attributeName, value); - return new DynamicXml(BaseElement.XPathSelectElements(expression).FirstOrDefault()); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return BaseElement.Elements().GetEnumerator(); - } - - public IEnumerator GetEnumerator() - { - return BaseElement.Elements().Select(e => new DynamicXml(e)).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public int Count() - { - return ((IEnumerable)this).Count(); - } - - public bool Any() - { - return ((IEnumerable)this).Any(); - } - - public IEnumerable Take(int count) - { - return ((IEnumerable)this).Take(count); - } - - public IEnumerable Skip(int count) - { - return ((IEnumerable)this).Skip(count); - } - - public bool IsNull() - { - return false; - } - public bool HasValue() - { - return true; - } - - public bool IsFirst() - { - return IsHelper(n => n.Index() == 0); - } - public HtmlString IsFirst(string valueIfTrue) - { - return IsHelper(n => n.Index() == 0, valueIfTrue); - } - public HtmlString IsFirst(string valueIfTrue, string valueIfFalse) - { - return IsHelper(n => n.Index() == 0, valueIfTrue, valueIfFalse); - } - public bool IsNotFirst() - { - return !IsHelper(n => n.Index() == 0); - } - public HtmlString IsNotFirst(string valueIfTrue) - { - return IsHelper(n => n.Index() != 0, valueIfTrue); - } - public HtmlString IsNotFirst(string valueIfTrue, string valueIfFalse) - { - return IsHelper(n => n.Index() != 0, valueIfTrue, valueIfFalse); - } - public bool IsPosition(int index) - { - if (BaseElement == null || BaseElement.Parent == null) - { - return false; - } - return IsHelper(n => n.Index() == index); - } - public HtmlString IsPosition(int index, string valueIfTrue) - { - if (BaseElement == null || BaseElement.Parent == null) - { - return new HtmlString(string.Empty); - } - return IsHelper(n => n.Index() == index, valueIfTrue); - } - public HtmlString IsPosition(int index, string valueIfTrue, string valueIfFalse) - { - if (BaseElement == null || BaseElement.Parent == null) - { - return new HtmlString(valueIfFalse); - } - return IsHelper(n => n.Index() == index, valueIfTrue, valueIfFalse); - } - public bool IsModZero(int modulus) - { - if (BaseElement == null || BaseElement.Parent == null) - { - return false; - } - return IsHelper(n => n.Index() % modulus == 0); - } - public HtmlString IsModZero(int modulus, string valueIfTrue) - { - if (BaseElement == null || BaseElement.Parent == null) - { - return new HtmlString(string.Empty); - } - return IsHelper(n => n.Index() % modulus == 0, valueIfTrue); - } - public HtmlString IsModZero(int modulus, string valueIfTrue, string valueIfFalse) - { - if (BaseElement == null || BaseElement.Parent == null) - { - return new HtmlString(valueIfFalse); - } - return IsHelper(n => n.Index() % modulus == 0, valueIfTrue, valueIfFalse); - } - - public bool IsNotModZero(int modulus) - { - if (BaseElement == null || BaseElement.Parent == null) - { - return false; - } - return IsHelper(n => n.Index() % modulus != 0); - } - public HtmlString IsNotModZero(int modulus, string valueIfTrue) - { - if (BaseElement == null || this.BaseElement.Parent == null) - { - return new HtmlString(string.Empty); - } - return IsHelper(n => n.Index() % modulus != 0, valueIfTrue); - } - public HtmlString IsNotModZero(int modulus, string valueIfTrue, string valueIfFalse) - { - if (this.BaseElement == null || this.BaseElement.Parent == null) - { - return new HtmlString(valueIfFalse); - } - return IsHelper(n => n.Index() % modulus != 0, valueIfTrue, valueIfFalse); - } - public bool IsNotPosition(int index) - { - if (this.BaseElement == null || this.BaseElement.Parent == null) - { - return false; - } - return !IsHelper(n => n.Index() == index); - } - public HtmlString IsNotPosition(int index, string valueIfTrue) - { - if (this.BaseElement == null || this.BaseElement.Parent == null) - { - return new HtmlString(string.Empty); - } - return IsHelper(n => n.Index() != index, valueIfTrue); - } - public HtmlString IsNotPosition(int index, string valueIfTrue, string valueIfFalse) - { - if (this.BaseElement == null || this.BaseElement.Parent == null) - { - return new HtmlString(valueIfFalse); - } - return IsHelper(n => n.Index() != index, valueIfTrue, valueIfFalse); - } - public bool IsLast() - { - if (this.BaseElement == null || this.BaseElement.Parent == null) - { - return false; - } - int count = this.BaseElement.Parent.Elements().Count(); - return IsHelper(n => n.Index() == count - 1); - } - public HtmlString IsLast(string valueIfTrue) - { - if (this.BaseElement == null || this.BaseElement.Parent == null) - { - return new HtmlString(string.Empty); - } - int count = this.BaseElement.Parent.Elements().Count(); - return IsHelper(n => n.Index() == count - 1, valueIfTrue); - } - public HtmlString IsLast(string valueIfTrue, string valueIfFalse) - { - if (this.BaseElement == null || this.BaseElement.Parent == null) - { - return new HtmlString(valueIfFalse); - } - int count = this.BaseElement.Parent.Elements().Count(); - return IsHelper(n => n.Index() == count - 1, valueIfTrue, valueIfFalse); - } - public bool IsNotLast() - { - if (this.BaseElement == null || this.BaseElement.Parent == null) - { - return false; - } - int count = this.BaseElement.Parent.Elements().Count(); - return !IsHelper(n => n.Index() == count - 1); - } - public HtmlString IsNotLast(string valueIfTrue) - { - if (this.BaseElement == null || this.BaseElement.Parent == null) - { - return new HtmlString(string.Empty); - } - int count = this.BaseElement.Parent.Elements().Count(); - return IsHelper(n => n.Index() != count - 1, valueIfTrue); - } - public HtmlString IsNotLast(string valueIfTrue, string valueIfFalse) - { - if (this.BaseElement == null || this.BaseElement.Parent == null) - { - return new HtmlString(valueIfFalse); - } - int count = this.BaseElement.Parent.Elements().Count(); - return IsHelper(n => n.Index() != count - 1, valueIfTrue, valueIfFalse); - } - public bool IsEven() - { - return IsHelper(n => n.Index() % 2 == 0); - } - public HtmlString IsEven(string valueIfTrue) - { - return IsHelper(n => n.Index() % 2 == 0, valueIfTrue); - } - public HtmlString IsEven(string valueIfTrue, string valueIfFalse) - { - return IsHelper(n => n.Index() % 2 == 0, valueIfTrue, valueIfFalse); - } - public bool IsOdd() - { - return IsHelper(n => n.Index() % 2 == 1); - } - public HtmlString IsOdd(string valueIfTrue) - { - return IsHelper(n => n.Index() % 2 == 1, valueIfTrue); - } - public HtmlString IsOdd(string valueIfTrue, string valueIfFalse) - { - return IsHelper(n => n.Index() % 2 == 1, valueIfTrue, valueIfFalse); - } - public bool IsEqual(DynamicXml other) - { - return IsHelper(n => n.BaseElement == other.BaseElement); - } - public HtmlString IsEqual(DynamicXml other, string valueIfTrue) - { - return IsHelper(n => n.BaseElement == other.BaseElement, valueIfTrue); - } - public HtmlString IsEqual(DynamicXml other, string valueIfTrue, string valueIfFalse) - { - return IsHelper(n => n.BaseElement == other.BaseElement, valueIfTrue, valueIfFalse); - } - public bool IsNotEqual(DynamicXml other) - { - return IsHelper(n => n.BaseElement != other.BaseElement); - } - public HtmlString IsNotEqual(DynamicXml other, string valueIfTrue) - { - return IsHelper(n => n.BaseElement != other.BaseElement, valueIfTrue); - } - public HtmlString IsNotEqual(DynamicXml other, string valueIfTrue, string valueIfFalse) - { - return IsHelper(n => n.BaseElement != other.BaseElement, valueIfTrue, valueIfFalse); - } - public bool IsDescendant(DynamicXml other) - { - var ancestors = this.Ancestors(); - return IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.BaseElement == other.BaseElement) != null); - } - public HtmlString IsDescendant(DynamicXml other, string valueIfTrue) - { - var ancestors = this.Ancestors(); - return IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.BaseElement == other.BaseElement) != null, valueIfTrue); - } - public HtmlString IsDescendant(DynamicXml other, string valueIfTrue, string valueIfFalse) - { - var ancestors = this.Ancestors(); - return IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.BaseElement == other.BaseElement) != null, valueIfTrue, valueIfFalse); - } - public bool IsDescendantOrSelf(DynamicXml other) - { - var ancestors = this.AncestorsOrSelf(); - return IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.BaseElement == other.BaseElement) != null); - } - public HtmlString IsDescendantOrSelf(DynamicXml other, string valueIfTrue) - { - var ancestors = this.AncestorsOrSelf(); - return IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.BaseElement == other.BaseElement) != null, valueIfTrue); - } - public HtmlString IsDescendantOrSelf(DynamicXml other, string valueIfTrue, string valueIfFalse) - { - var ancestors = this.AncestorsOrSelf(); - return IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.BaseElement == other.BaseElement) != null, valueIfTrue, valueIfFalse); - } - public bool IsAncestor(DynamicXml other) - { - var descendants = this.Descendants(); - return IsHelper(n => descendants.FirstOrDefault(descendant => descendant.BaseElement == other.BaseElement) != null); - } - public HtmlString IsAncestor(DynamicXml other, string valueIfTrue) - { - var descendants = this.Descendants(); - return IsHelper(n => descendants.FirstOrDefault(descendant => descendant.BaseElement == other.BaseElement) != null, valueIfTrue); - } - public HtmlString IsAncestor(DynamicXml other, string valueIfTrue, string valueIfFalse) - { - var descendants = this.Descendants(); - return IsHelper(n => descendants.FirstOrDefault(descendant => descendant.BaseElement == other.BaseElement) != null, valueIfTrue, valueIfFalse); - } - public bool IsAncestorOrSelf(DynamicXml other) - { - var descendants = this.DescendantsOrSelf(); - return IsHelper(n => descendants.FirstOrDefault(descendant => descendant.BaseElement == other.BaseElement) != null); - } - public HtmlString IsAncestorOrSelf(DynamicXml other, string valueIfTrue) - { - var descendants = this.DescendantsOrSelf(); - return IsHelper(n => descendants.FirstOrDefault(descendant => descendant.BaseElement == other.BaseElement) != null, valueIfTrue); - } - public HtmlString IsAncestorOrSelf(DynamicXml other, string valueIfTrue, string valueIfFalse) - { - var descendants = this.DescendantsOrSelf(); - return IsHelper(n => descendants.FirstOrDefault(descendant => descendant.BaseElement == other.BaseElement) != null, valueIfTrue, valueIfFalse); - } - public IEnumerable Descendants() - { - return Descendants(n => true); - } - public IEnumerable Descendants(Func func) - { - //var flattenedNodes = this.BaseElement.Elements().Map(func, n => n.Elements()); - var flattenedNodes = this.BaseElement.Elements().SelectMany(n => n.Elements()).Where(func); - return flattenedNodes.ToList().ConvertAll(n => new DynamicXml(n)); - } - public IEnumerable DescendantsOrSelf() - { - return DescendantsOrSelf(n => true); - } - public IEnumerable DescendantsOrSelf(Func func) - { - //var flattenedNodes = this.BaseElement.Elements().Map(func, n => n.Elements()); - var flattenedNodes = this.BaseElement.Elements().SelectMany(n => n.Elements()).Where(func); - var list = new List(); - list.Add(this); - list.AddRange(flattenedNodes.ToList().ConvertAll(n => new DynamicXml(n))); - return list; - } - public IEnumerable Ancestors() - { - return Ancestors(item => true); - } - public IEnumerable Ancestors(Func func) - { - var ancestorList = new List(); - var node = this.BaseElement; - while (node != null) - { - if (node.Parent == null) break; - XElement parent = node.Parent; - if (parent != null) - { - if (this.BaseElement != parent) - { - node = parent; - if (func(node)) - { - ancestorList.Add(node); - } - } - else - { - break; - } - } - else - { - break; - } - } - ancestorList.Reverse(); - return ancestorList.ConvertAll(item => new DynamicXml(item)); - } - public IEnumerable AncestorsOrSelf() - { - return AncestorsOrSelf(item => true); - } - public IEnumerable AncestorsOrSelf(Func func) - { - List ancestorList = new List(); - var node = this.BaseElement; - ancestorList.Add(node); - while (node != null) - { - if (node.Parent == null) break; - XElement parent = node.Parent; - if (parent != null) - { - if (this.BaseElement != parent) - { - node = parent; - if (func(node)) - { - ancestorList.Add(node); - } - } - else - { - break; - } - } - else - { - break; - } - } - ancestorList.Reverse(); - return ancestorList.ConvertAll(item => new DynamicXml(item)); - } - - public int Index() - { - if (this.BaseElement != null && this.BaseElement.Parent != null) - { - var elements = this.BaseElement.Parent.Elements(); - int index = 0; - foreach (var element in elements) - { - if (element == this.BaseElement) break; - index++; - } - return index; - } - return 0; - } - - public bool IsHelper(Func test) - { - return test(this); - } - public HtmlString IsHelper(Func test, string valueIfTrue) - { - return IsHelper(test, valueIfTrue, string.Empty); - } - public HtmlString IsHelper(Func test, string valueIfTrue, string valueIfFalse) - { - return test(this) ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); - } - - [Obsolete("Use XmlHelper.StripDashesInElementOrAttributeNames instead")] - public static string StripDashesInElementOrAttributeNames(string xml) - { - return XmlHelper.StripDashesInElementOrAttributeNames(xml); - } - - - } -} diff --git a/src/Umbraco.Core/Dynamics/DynamicXmlConverter.cs b/src/Umbraco.Core/Dynamics/DynamicXmlConverter.cs deleted file mode 100644 index 6af13d6887..0000000000 --- a/src/Umbraco.Core/Dynamics/DynamicXmlConverter.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using System.Xml; -using System.Xml.Linq; - -namespace Umbraco.Core.Dynamics -{ - /// - /// Used to return the raw xml string value from DynamicXml when using type converters - /// - public class RawXmlString - { - public string Value { get; private set; } - - public RawXmlString(string value) - { - Value = value; - } - } - - /// - /// Used to return the raw xml XElement value from DynamicXml when using type converters - /// - public class RawXElement - { - public XElement Value { get; private set; } - - public RawXElement(XElement value) - { - Value = value; - } - } - - /// - /// Used to return the raw xml XElement value from DynamicXml when using type converters - /// - public class RawXmlElement - { - public XmlElement Value { get; private set; } - - public RawXmlElement(XmlElement value) - { - Value = value; - } - } - - /// - /// Used to return the raw xml XmlDocument value from DynamicXml when using type converters - /// - public class RawXmlDocument - { - public XmlDocument Value { get; private set; } - - public RawXmlDocument(XmlDocument value) - { - Value = value; - } - } - - /// - /// A custom type converter for DynamicXml - /// - public class DynamicXmlConverter : TypeConverter - { - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - var convertableTypes = new[] - { - typeof(string), - typeof(XElement), - typeof(XmlElement), - typeof(XmlDocument), - typeof(RawXmlString), - typeof(RawXElement), - typeof(RawXmlElement), - typeof(RawXmlDocument) - }; - - return convertableTypes.Any(x => TypeHelper.IsTypeAssignableFrom(x, destinationType)) - || base.CanConvertFrom(context, destinationType); - } - - public override object ConvertTo( - ITypeDescriptorContext context, - CultureInfo culture, - object value, - Type destinationType) - { - var dxml = value as DynamicXml; - if (dxml == null) - return null; - //string - if (TypeHelper.IsTypeAssignableFrom(destinationType)) - { - return value.ToString(); - } - - //XElement - if (TypeHelper.IsTypeAssignableFrom(destinationType)) - { - return dxml.BaseElement; - } - //XmlElement - if (TypeHelper.IsTypeAssignableFrom(destinationType)) - { - var xDoc = new XmlDocument(); - xDoc.LoadXml(dxml.ToString()); - return xDoc.DocumentElement; - } - //XmlDocument - if (TypeHelper.IsTypeAssignableFrom(destinationType)) - { - var xDoc = new XmlDocument(); - xDoc.LoadXml(dxml.ToString()); - return xDoc; - } - - //RAW values: - //string - if (TypeHelper.IsTypeAssignableFrom(destinationType)) - { - return new RawXmlString(dxml.ToRawXml()); - } - //XElement - if (TypeHelper.IsTypeAssignableFrom(destinationType)) - { - return new RawXElement(dxml.RawXmlElement); - } - //XmlElement - if (TypeHelper.IsTypeAssignableFrom(destinationType)) - { - var xDoc = new XmlDocument(); - xDoc.LoadXml(dxml.ToRawXml()); - return new RawXmlElement(xDoc.DocumentElement); - } - //XmlDocument - if (TypeHelper.IsTypeAssignableFrom(destinationType)) - { - var xDoc = new XmlDocument(); - xDoc.LoadXml(dxml.ToRawXml()); - return new RawXmlDocument(xDoc); - } - - - return base.ConvertTo(context, culture, value, destinationType); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs deleted file mode 100644 index 9772aaf5bd..0000000000 --- a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Linq.Expressions; -using System.Text; -using System.Web.Services.Description; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Dynamics -{ - /// - /// Utility class for finding extension methods on a type to execute - /// - internal static class ExtensionMethodFinder - { - /// - /// The static cache for extension methods found that match the criteria that we are looking for - /// - private static readonly ConcurrentDictionary, MethodInfo[]> MethodCache = new ConcurrentDictionary, MethodInfo[]>(); - - private static IEnumerable GetTypes(Assembly a) - { - try - { - return TypeFinder.GetTypesWithFormattedException(a); - } - catch (ReflectionTypeLoadException ex) - { - // is this going to flood the log? - LogHelper.Error(typeof (ExtensionMethodFinder), "Failed to get types.", ex); - return Enumerable.Empty(); - } - } - - /// - /// Returns the enumerable of all extension method info's in the app domain = USE SPARINGLY!!! - /// - /// - /// - /// We cache this as a sliding 5 minute exiration, in unit tests there's over 1100 methods found, surely that will eat up a bit of memory so we want - /// to make sure we give it back. - /// - private static IEnumerable GetAllExtensionMethodsInAppDomain(IRuntimeCacheProvider runtimeCacheProvider) - { - if (runtimeCacheProvider == null) throw new ArgumentNullException("runtimeCacheProvider"); - - return runtimeCacheProvider.GetCacheItem(typeof (ExtensionMethodFinder).Name, () => TypeFinder.GetAssembliesWithKnownExclusions() - // assemblies that contain extension methods - .Where(a => a.IsDefined(typeof (ExtensionAttribute), false)) - // types that contain extension methods - .SelectMany(a => GetTypes(a) - .Where(t => t.IsDefined(typeof (ExtensionAttribute), false) && t.IsSealed && t.IsGenericType == false && t.IsNested == false)) - // actual extension methods - .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public) - .Where(m => m.IsDefined(typeof (ExtensionAttribute), false))) - // and also IEnumerable extension methods - because the assembly is excluded - .Concat(typeof (Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)) - //If we don't do this then we'll be scanning all assemblies each time! - .ToArray(), - - //only cache for 5 minutes - timeout: TimeSpan.FromMinutes(5), - - //each time this is accessed it will be for 5 minutes longer - isSliding:true); - } - - /// - /// Returns all extension methods found matching the definition - /// - /// - /// The runtime cache is used to temporarily cache all extension methods found in the app domain so that - /// while we search for individual extension methods, the process will be reasonably 'quick'. We then statically - /// cache the MethodInfo's that we are looking for and then the runtime cache will expire and give back all that memory. - /// - /// - /// - /// - /// The arguments EXCLUDING the 'this' argument in an extension method - /// - /// - /// - /// NOTE: This will be an intensive method to call! Results will be cached based on the key (args) of this method - /// - internal static IEnumerable GetAllExtensionMethods(IRuntimeCacheProvider runtimeCache, Type thisType, string name, int argumentCount) - { - var key = new Tuple(thisType, name, argumentCount); - - return MethodCache.GetOrAdd(key, tuple => - { - var candidates = GetAllExtensionMethodsInAppDomain(runtimeCache); - - // filter by name - var filtr1 = candidates.Where(m => m.Name == name); - - // filter by args count - // ensure we add + 1 to the arg count because the 'this' arg is not included in the count above! - var filtr2 = filtr1.Where(m => m.GetParameters().Length == argumentCount + 1); - - // filter by first parameter type (target of the extension method) - // ie find the right overload that can take genericParameterType - // (which will be either DynamicNodeList or List which is IEnumerable) - var filtr3 = filtr2.Select(x => - { - var t = x.GetParameters()[0].ParameterType; // exists because of +1 above - var bindings = new Dictionary(); - if (TypeHelper.MatchType(thisType, t, bindings) == false) return null; - - // create the generic method if necessary - if (x.ContainsGenericParameters == false) return x; - var targs = t.GetGenericArguments().Select(y => bindings[y.Name]).ToArray(); - return x.MakeGenericMethod(targs); - }).Where(x => x != null); - - return filtr3.ToArray(); - }); - - } - - private static MethodInfo DetermineMethodFromParams(IEnumerable methods, Type genericType, IEnumerable args) - { - MethodInfo methodToExecute = null; - - //Given the args, lets get the types and compare the type sequence to try and find the correct overload - var argTypes = args.Select(o => - { - var oe = (o as Expression); - return oe != null ? oe.Type : o.GetType(); - }); - - var methodsWithArgTypes = methods.Select(method => new - { - method, - //skip the first arg because that is the extension method type ('this') that we are looking for - types = method.GetParameters().Select(pi => pi.ParameterType).Skip(1) - }); - - //This type comparer will check - var typeComparer = new DelegateEqualityComparer( - //Checks if the argument type passed in can be assigned from the parameter type in the method. For - // example, if the argument type is HtmlHelper but the method parameter type is HtmlHelper then - // it will match because the argument is assignable to that parameter type and will be able to execute - TypeHelper.IsTypeAssignableFrom, - //This will not ever execute but if it does we need to get the hash code of the string because the hash - // code of a type is random - type => type.FullName.GetHashCode()); - - var firstMatchingOverload = methodsWithArgTypes - .FirstOrDefault(m => m.types.SequenceEqual(argTypes, typeComparer)); - - if (firstMatchingOverload != null) - { - methodToExecute = firstMatchingOverload.method; - } - - return methodToExecute; - } - - public static MethodInfo FindExtensionMethod(IRuntimeCacheProvider runtimeCache, Type thisType, object[] args, string name, bool argsContainsThis) - { - Type genericType = null; - if (thisType.IsGenericType) - { - genericType = thisType.GetGenericArguments()[0]; - } - - args = args - //if the args contains 'this', remove the first one since that is 'this' and we don't want to use - //that in the method searching - .Skip(argsContainsThis ? 1 : 0) - .ToArray(); - - var methods = GetAllExtensionMethods(runtimeCache, thisType, name, args.Length).ToArray(); - - return DetermineMethodFromParams(methods, genericType, args); - } - } -} diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethods.cs b/src/Umbraco.Core/Dynamics/ExtensionMethods.cs deleted file mode 100644 index 8439278b2e..0000000000 --- a/src/Umbraco.Core/Dynamics/ExtensionMethods.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Dynamics -{ - [Obsolete("This class should not be used, it is just referenced by already obsoleted code and will be removed in the future")] - internal static class ExtensionMethods - { - //public static IEnumerable Map( - // this IEnumerable source, - // Func selectorFunction, - // Func> getChildrenFunction) - //{ - // if (!source.Any()) - // { - // return source; - // } - // // Add what we have to the stack - // var flattenedList = source.Where(selectorFunction); - // // Go through the input enumerable looking for children, - // // and add those if we have them - // foreach (TSource element in source) - // { - // var secondInner = getChildrenFunction(element); - // if (secondInner.Any()) - // { - // secondInner = secondInner.Map(selectorFunction, getChildrenFunction); - // } - // flattenedList = flattenedList.Concat(secondInner); - // } - // return flattenedList; - //} - - [Obsolete("This method should not be used and will be removed in the future")] - public static bool ContainsAny(this string haystack, IEnumerable needles) - { - return StringExtensions.ContainsAny(haystack, needles); - } - [Obsolete("This method should not be used and will be removed in the future")] - public static bool ContainsAny(this string haystack, params string[] needles) - { - return StringExtensions.ContainsAny(haystack, needles); - } - [Obsolete("This method should not be used and will be removed in the future")] - public static bool ContainsAny(this string haystack, StringComparison comparison, IEnumerable needles) - { - return StringExtensions.ContainsAny(haystack, needles, comparison); - } - [Obsolete("This method should not be used and will be removed in the future")] - public static bool ContainsAny(this string haystack, StringComparison comparison, params string[] needles) - { - return StringExtensions.ContainsAny(haystack, needles, comparison); - } - [Obsolete("This method should not be used and will be removed in the future")] - public static bool ContainsInsensitive(this string haystack, string needle) - { - if (haystack == null) throw new ArgumentNullException("haystack"); - return haystack.IndexOf(needle, StringComparison.CurrentCultureIgnoreCase) >= 0; - } - [Obsolete("This method should not be used and will be removed in the future")] - public static bool HasValue(this string s) - { - return !string.IsNullOrWhiteSpace(s); - } - - } -} diff --git a/src/Umbraco.Core/Dynamics/ParseException.cs b/src/Umbraco.Core/Dynamics/ParseException.cs deleted file mode 100644 index 5208644306..0000000000 --- a/src/Umbraco.Core/Dynamics/ParseException.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Umbraco.Core.Dynamics -{ - internal sealed class ParseException : Exception - { - readonly int _position; - - public ParseException(string message, int position) - : base(message) - { - this._position = position; - } - - public int Position - { - get { return _position; } - } - - public override string ToString() - { - return string.Format(Res.ParseExceptionFormat, Message, _position); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/PropertyResult.cs b/src/Umbraco.Core/Dynamics/PropertyResult.cs deleted file mode 100644 index e2b1d5b541..0000000000 --- a/src/Umbraco.Core/Dynamics/PropertyResult.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Umbraco.Core.Models; -using System.Web; - -namespace Umbraco.Core.Dynamics -{ - internal class PropertyResult : IPublishedProperty, IHtmlString - { - private readonly IPublishedProperty _source; - private readonly string _alias; - private readonly object _value; - private readonly PropertyResultType _type; - - internal PropertyResult(IPublishedProperty source, PropertyResultType type) - { - if (source == null) throw new ArgumentNullException("source"); - - _type = type; - _source = source; - } - - internal PropertyResult(string alias, object value, PropertyResultType type) - { - if (alias == null) throw new ArgumentNullException("alias"); - if (value == null) throw new ArgumentNullException("value"); - - _type = type; - _alias = alias; - _value = value; - } - - internal PropertyResultType PropertyType { get { return _type; } } - - public string PropertyTypeAlias { get { return _source == null ? _alias : _source.PropertyTypeAlias; } } - public object DataValue { get { return _source == null ? _value : _source.DataValue; } } - public bool HasValue { get { return _source == null || _source.HasValue; } } - public object Value { get { return _source == null ? _value : _source.Value; } } - public object XPathValue { get { return Value == null ? null : Value.ToString(); } } - - public string ToHtmlString() - { - var value = Value; - return value == null ? string.Empty : value.ToString(); - } - } -} diff --git a/src/Umbraco.Core/Dynamics/PropertyResultType.cs b/src/Umbraco.Core/Dynamics/PropertyResultType.cs deleted file mode 100644 index d1ab0904f5..0000000000 --- a/src/Umbraco.Core/Dynamics/PropertyResultType.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Umbraco.Core.Dynamics -{ - /// - /// Currently just used for informational purposes as to where a PropertyResult object was created from. - /// - internal enum PropertyResultType - { - /// - /// The property resolved was a normal document property - /// - UserProperty, - - /// - /// The property resolved was a property defined as a member on the document object (IPublishedContent) itself - /// - ReflectedProperty, - - /// - /// The property was created manually for a custom purpose - /// - CustomProperty - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/QueryableExtensions.cs b/src/Umbraco.Core/Dynamics/QueryableExtensions.cs deleted file mode 100644 index 25c260d7ca..0000000000 --- a/src/Umbraco.Core/Dynamics/QueryableExtensions.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; - -namespace Umbraco.Core.Dynamics -{ - /// - /// Extension methods for IQueryable - /// - /// - /// NOTE: Ordering code taken from: http://stackoverflow.com/questions/41244/dynamic-linq-orderby-on-ienumerablet - /// - /// ANOTHER NOTE: We have a bastardized version of Dynamic Linq existing at Umbraco.Web.Dynamics however it's been hacked so not - /// sure we can use it for anything other than DynamicNode. - /// - internal static class QueryableExtensions - { - public static IOrderedQueryable OrderBy(this IQueryable source, string property) - { - return ApplyOrder(source, property, "OrderBy"); - } - public static IOrderedQueryable OrderByDescending(this IQueryable source, string property) - { - return ApplyOrder(source, property, "OrderByDescending"); - } - public static IOrderedQueryable ThenBy(this IOrderedQueryable source, string property) - { - return ApplyOrder(source, property, "ThenBy"); - } - public static IOrderedQueryable ThenByDescending(this IOrderedQueryable source, string property) - { - return ApplyOrder(source, property, "ThenByDescending"); - } - static IOrderedQueryable ApplyOrder(IQueryable source, string property, string methodName) - { - string[] props = property.Split('.'); - Type type = typeof(T); - ParameterExpression arg = Expression.Parameter(type, "x"); - Expression expr = arg; - foreach (string prop in props) - { - // use reflection (not ComponentModel) to mirror LINQ - PropertyInfo pi = type.GetProperty(prop, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); - expr = Expression.Property(expr, pi); - type = pi.PropertyType; - } - Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type); - LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg); - - object result = typeof(Queryable).GetMethods().Single( - method => method.Name == methodName - && method.IsGenericMethodDefinition - && method.GetGenericArguments().Length == 2 - && method.GetParameters().Length == 2) - .MakeGenericMethod(typeof(T), type) - .Invoke(null, new object[] { source, lambda }); - return (IOrderedQueryable)result; - } - - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/Res.cs b/src/Umbraco.Core/Dynamics/Res.cs deleted file mode 100644 index 4ea5193d68..0000000000 --- a/src/Umbraco.Core/Dynamics/Res.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace Umbraco.Core.Dynamics -{ - internal static class Res - { - public const string DuplicateIdentifier = "The identifier '{0}' was defined more than once"; - public const string ExpressionTypeMismatch = "Expression of type '{0}' expected"; - public const string ExpressionExpected = "Expression expected"; - public const string InvalidCharacterLiteral = "Character literal must contain exactly one character"; - public const string InvalidIntegerLiteral = "Invalid integer literal '{0}'"; - public const string InvalidRealLiteral = "Invalid real literal '{0}'"; - public const string UnknownIdentifier = "Unknown identifier '{0}'"; - public const string NoItInScope = "No 'it' is in scope"; - public const string IifRequiresThreeArgs = "The 'iif' function requires three arguments"; - public const string FirstExprMustBeBool = "The first expression must be of type 'Boolean'"; - public const string BothTypesConvertToOther = "Both of the types '{0}' and '{1}' convert to the other"; - public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other"; - public const string MissingAsClause = "Expression is missing an 'as' clause"; - public const string ArgsIncompatibleWithLambda = "Argument list incompatible with lambda expression"; - public const string TypeHasNoNullableForm = "Type '{0}' has no nullable form"; - public const string NoMatchingConstructor = "No matching constructor in type '{0}'"; - public const string AmbiguousConstructorInvocation = "Ambiguous invocation of '{0}' constructor"; - public const string CannotConvertValue = "A value of type '{0}' cannot be converted to type '{1}'"; - public const string NoApplicableMethod = "No applicable method '{0}' exists in type '{1}'"; - public const string MethodsAreInaccessible = "Methods on type '{0}' are not accessible"; - public const string MethodIsVoid = "Method '{0}' in type '{1}' does not return a value"; - public const string AmbiguousMethodInvocation = "Ambiguous invocation of method '{0}' in type '{1}'"; - public const string UnknownPropertyOrField = "No property or field '{0}' exists in type '{1}'"; - public const string NoApplicableAggregate = "No applicable aggregate method '{0}' exists"; - public const string CannotIndexMultiDimArray = "Indexing of multi-dimensional arrays is not supported"; - public const string InvalidIndex = "Array index must be an integer expression"; - public const string NoApplicableIndexer = "No applicable indexer exists in type '{0}'"; - public const string AmbiguousIndexerInvocation = "Ambiguous invocation of indexer in type '{0}'"; - public const string IncompatibleOperand = "Operator '{0}' incompatible with operand type '{1}'"; - public const string IncompatibleOperands = "Operator '{0}' incompatible with operand types '{1}' and '{2}'"; - public const string UnterminatedStringLiteral = "Unterminated string literal"; - public const string InvalidCharacter = "Syntax error '{0}'"; - public const string DigitExpected = "Digit expected"; - public const string SyntaxError = "Syntax error"; - public const string TokenExpected = "{0} expected"; - public const string ParseExceptionFormat = "{0} (at index {1})"; - public const string ColonExpected = "':' expected"; - public const string OpenParenExpected = "'(' expected"; - public const string CloseParenOrOperatorExpected = "')' or operator expected"; - public const string CloseParenOrCommaExpected = "')' or ',' expected"; - public const string DotOrOpenParenExpected = "'.' or '(' expected"; - public const string OpenBracketExpected = "'[' expected"; - public const string CloseBracketOrCommaExpected = "']' or ',' expected"; - public const string IdentifierExpected = "Identifier expected"; - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/Signature.cs b/src/Umbraco.Core/Dynamics/Signature.cs deleted file mode 100644 index cececc502d..0000000000 --- a/src/Umbraco.Core/Dynamics/Signature.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Core.Dynamics -{ - internal class Signature : IEquatable - { - public DynamicProperty[] Properties { get; set; } - public int HashCode { get; set; } - - public Signature(IEnumerable properties) - { - this.Properties = properties.ToArray(); - HashCode = 0; - foreach (DynamicProperty p in this.Properties) - { - HashCode ^= p.Name.GetHashCode() ^ p.Type.GetHashCode(); - } - } - - public override int GetHashCode() - { - return HashCode; - } - - public override bool Equals(object obj) - { - return obj is Signature && Equals((Signature)obj); - } - - public bool Equals(Signature other) - { - if (Properties.Length != other.Properties.Length) return false; - for (int i = 0; i < Properties.Length; i++) - { - if (Properties[i].Name != other.Properties[i].Name || - Properties[i].Type != other.Properties[i].Type) return false; - } - return true; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/EmailSender.cs b/src/Umbraco.Core/EmailSender.cs index 6f381f693c..1f2c6d8653 100644 --- a/src/Umbraco.Core/EmailSender.cs +++ b/src/Umbraco.Core/EmailSender.cs @@ -78,7 +78,7 @@ namespace Umbraco.Core client.Send(message); } } - } + } } /// @@ -99,7 +99,7 @@ namespace Umbraco.Core { get { return SendEmail != null; } } - + /// /// An event that is raised when no smtp server is configured if events are enabled /// diff --git a/src/Umbraco.Core/Enum.cs b/src/Umbraco.Core/Enum.cs index 9d7b96f695..5bc70c637d 100644 --- a/src/Umbraco.Core/Enum.cs +++ b/src/Umbraco.Core/Enum.cs @@ -1,108 +1,109 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; namespace Umbraco.Core { - /// - /// A very useful class for parsing, enumerating and querying Enum objects - /// - /// - /// - /// Taken from http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net-3-5 - /// - public static class Enum where T : struct - { - private static readonly IEnumerable All = Enum.GetValues(typeof(T)).Cast(); - private static readonly Dictionary InsensitiveNames = All.ToDictionary(k => Enum.GetName(typeof(T), k).ToLowerInvariant()); - private static readonly Dictionary SensitiveNames = All.ToDictionary(k => Enum.GetName(typeof(T), k)); - private static readonly Dictionary Values = All.ToDictionary(k => Convert.ToInt32(k)); - private static readonly Dictionary Names = All.ToDictionary(k => k, v => v.ToString()); + /// + /// Provides utility methods for handling enumerations. + /// + /// + /// Taken from http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net-3-5 + /// + public static class Enum + where T : struct + { + private static readonly List Values; + private static readonly Dictionary InsensitiveNameToValue; + private static readonly Dictionary SensitiveNameToValue; + private static readonly Dictionary IntToValue; + private static readonly Dictionary ValueToName; - public static bool IsDefined(T value) - { - return Names.Keys.Contains(value); - } + static Enum() + { + Values = Enum.GetValues(typeof(T)).Cast().ToList(); - public static bool IsDefined(string value) - { - return SensitiveNames.Keys.Contains(value); - } + IntToValue = new Dictionary(); + ValueToName = new Dictionary(); + SensitiveNameToValue = new Dictionary(); + InsensitiveNameToValue = new Dictionary(); - public static bool IsDefined(int value) - { - return Values.Keys.Contains(value); - } + foreach (var value in Values) + { + var name = value.ToString(); - public static IEnumerable GetValues() - { - return All; - } + IntToValue[Convert.ToInt32(value)] = value; + ValueToName[value] = name; + SensitiveNameToValue[name] = value; + InsensitiveNameToValue[name.ToLowerInvariant()] = value; + } + } - public static string[] GetNames() - { - return Names.Values.ToArray(); - } + public static bool IsDefined(T value) + { + return ValueToName.Keys.Contains(value); + } - public static string GetName(T value) - { - string name; - Names.TryGetValue(value, out name); - return name; - } + public static bool IsDefined(string value) + { + return SensitiveNameToValue.Keys.Contains(value); + } - public static T Parse(string value) - { - T parsed = default(T); - if (!SensitiveNames.TryGetValue(value, out parsed)) - throw new ArgumentException("Value is not one of the named constants defined for the enumeration", "value"); - return parsed; - } + public static bool IsDefined(int value) + { + return IntToValue.Keys.Contains(value); + } - public static T Parse(string value, bool ignoreCase) - { - if (!ignoreCase) - return Parse(value); + public static IEnumerable GetValues() + { + return Values; + } - T parsed = default(T); - if (!InsensitiveNames.TryGetValue(value.ToLowerInvariant(), out parsed)) - throw new ArgumentException("Value is not one of the named constants defined for the enumeration", "value"); - return parsed; - } + public static string[] GetNames() + { + return ValueToName.Values.ToArray(); + } - public static bool TryParse(string value, out T returnValue) - { - return SensitiveNames.TryGetValue(value, out returnValue); - } + public static string GetName(T value) + { + return ValueToName.TryGetValue(value, out var name) ? name : null; + } - public static bool TryParse(string value, bool ignoreCase, out T returnValue) - { - if (!ignoreCase) - return TryParse(value, out returnValue); + public static T Parse(string value, bool ignoreCase = false) + { + var names = ignoreCase ? InsensitiveNameToValue : SensitiveNameToValue; + if (ignoreCase) value = value.ToLowerInvariant(); - return InsensitiveNames.TryGetValue(value.ToLowerInvariant(), out returnValue); - } + if (names.TryGetValue(value, out var parsed)) + return parsed; - public static T? ParseOrNull(string value) - { - if (String.IsNullOrEmpty(value)) - return null; + throw new ArgumentException($"Value \"{value}\"is not a valid {typeof(T).Name} enumeration value.", nameof(value)); + } - T foundValue; - if (InsensitiveNames.TryGetValue(value.ToLowerInvariant(), out foundValue)) - return foundValue; + public static bool TryParse(string value, out T returnValue, bool ignoreCase = false) + { + var names = ignoreCase ? InsensitiveNameToValue : SensitiveNameToValue; + if (ignoreCase) value = value.ToLowerInvariant(); + return names.TryGetValue(value, out returnValue); + } - return null; - } + public static T? ParseOrNull(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return null; - public static T? CastOrNull(int value) - { - T foundValue; - if (Values.TryGetValue(value, out foundValue)) - return foundValue; + if (InsensitiveNameToValue.TryGetValue(value.ToLowerInvariant(), out var parsed)) + return parsed; - return null; - } - } + return null; + } + + public static T? CastOrNull(int value) + { + if (IntToValue.TryGetValue(value, out var foundValue)) + return foundValue; + + return null; + } + } } diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index 81c0df793e..d5d4a66122 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -21,9 +21,33 @@ namespace Umbraco.Core if (groupSize <= 0) throw new ArgumentException("Must be greater than zero.", "groupSize"); - return source - .Select((x, i) => Tuple.Create(i / groupSize, x)) - .GroupBy(t => t.Item1, t => t.Item2); + + // following code derived from MoreLinq and does not allocate bazillions of tuples + + T[] temp = null; + var count = 0; + + foreach (var item in source) + { + if (temp == null) temp = new T[groupSize]; + temp[count++] = item; + if (count != groupSize) continue; + yield return temp/*.Select(x => x)*/; + temp = null; + count = 0; + } + + if (temp != null && count > 0) + yield return temp.Take(count); + } + + public static IEnumerable SelectByGroups(this IEnumerable source, Func, IEnumerable> selector, int groupSize) + { + // don't want to use a SelectMany(x => x) here - isn't this better? + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var resultGroup in source.InGroupsOf(groupSize).Select(selector)) + foreach (var result in resultGroup) + yield return result; } /// The distinct by. @@ -68,13 +92,6 @@ namespace Umbraco.Core } } - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use a normal foreach loop instead, this adds more allocations than necessary")] - public static TResult[] ForEach(this IEnumerable items, Func func) - { - return items.Select(func).ToArray(); - } - [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use a normal foreach loop instead, this adds more allocations than necessary")] public static IEnumerable ForEach(this IEnumerable items, Action action) @@ -112,7 +129,7 @@ namespace Umbraco.Core public static bool ContainsAll(this IEnumerable source, IEnumerable other) { if (source == null) throw new ArgumentNullException("source"); - if (other == null) throw new ArgumentNullException("other"); + if (other == null) throw new ArgumentNullException("other"); return other.Except(source).Any() == false; } @@ -285,7 +302,7 @@ namespace Umbraco.Core /// /// The logic for this is taken from: /// http://stackoverflow.com/questions/4576723/test-whether-two-ienumerablet-have-the-same-values-with-the-same-frequencies - /// + /// /// There's a few answers, this one seems the best for it's simplicity and based on the comment of Eamon /// public static bool UnsortedSequenceEqual(this IEnumerable source, IEnumerable other) @@ -299,6 +316,36 @@ namespace Umbraco.Core && list1Groups.All(g => g.Count() == list2Groups[g.Key].Count()); } + /// + /// Transforms an enumerable. + /// + /// + /// + /// + /// + public static IEnumerable Transform(this IEnumerable source, Func, IEnumerable> transform) + { + return transform(source); + } + + /// + /// Gets a null IEnumerable as an empty IEnumerable. + /// + /// + /// + /// + public static IEnumerable EmptyNull(this IEnumerable items) + { + return items ?? Enumerable.Empty(); + } + + // the .OfType() filter is nice when there's only one type + // this is to support filtering with multiple types + public static IEnumerable OfTypes(this IEnumerable contents, params Type[] types) + { + return contents.Where(x => types.Contains(x.GetType())); + } + public static IEnumerable SkipLast(this IEnumerable source) { using (var e = source.GetEnumerator()) diff --git a/src/Umbraco.Core/Events/CancellableEventArgs.cs b/src/Umbraco.Core/Events/CancellableEventArgs.cs index 7a10623427..6f88fdcce4 100644 --- a/src/Umbraco.Core/Events/CancellableEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Security.Permissions; @@ -6,12 +6,12 @@ using System.Security.Permissions; namespace Umbraco.Core.Events { /// - /// Event args for that can support cancellation - /// - [HostProtection(SecurityAction.LinkDemand, SharedState = true)] - public class CancellableEventArgs : EventArgs, IEquatable + /// Event args for that can support cancellation + /// + [HostProtection(SecurityAction.LinkDemand, SharedState = true)] + public class CancellableEventArgs : EventArgs, IEquatable { - private bool _cancel; + private bool _cancel; private Dictionary _eventState; private static readonly ReadOnlyDictionary EmptyAdditionalData = new ReadOnlyDictionary(new Dictionary()); @@ -32,11 +32,11 @@ namespace Umbraco.Core.Events } public CancellableEventArgs(bool canCancel) - { - CanCancel = canCancel; + { + CanCancel = canCancel; //create a standalone messages Messages = new EventMessages(); - AdditionalData = EmptyAdditionalData; + AdditionalData = EmptyAdditionalData; } public CancellableEventArgs(EventMessages eventMessages) @@ -44,36 +44,36 @@ namespace Umbraco.Core.Events { } public CancellableEventArgs() - : this(true) - { } + : this(true) + { } - /// - /// Flag to determine if this instance will support being cancellable - /// - public bool CanCancel { get; set; } + /// + /// Flag to determine if this instance will support being cancellable + /// + public bool CanCancel { get; set; } - /// - /// If this instance supports cancellation, this gets/sets the cancel value - /// - public bool Cancel - { - get - { - if (CanCancel == false) - { - throw new InvalidOperationException("This event argument class does not support cancelling."); - } - return _cancel; - } - set - { - if (CanCancel == false) - { - throw new InvalidOperationException("This event argument class does not support cancelling."); - } - _cancel = value; - } - } + /// + /// If this instance supports cancellation, this gets/sets the cancel value + /// + public bool Cancel + { + get + { + if (CanCancel == false) + { + throw new InvalidOperationException("This event argument class does not support cancelling."); + } + return _cancel; + } + set + { + if (CanCancel == false) + { + throw new InvalidOperationException("This event argument class does not support cancelling."); + } + _cancel = value; + } + } /// /// if this instance supports cancellation, this will set Cancel to true with an affiliated cancellation message @@ -99,7 +99,7 @@ namespace Umbraco.Core.Events /// so we cannot change the strongly typed nature for some events. /// public ReadOnlyDictionary AdditionalData { get; private set; } - + /// /// This can be used by event subscribers to store state in the event args so they easily deal with custom state data between a starting ("ing") /// event and an ending ("ed") event @@ -107,36 +107,36 @@ namespace Umbraco.Core.Events public IDictionary EventState { get { return _eventState ?? (_eventState = new Dictionary()); } - } + } - public bool Equals(CancellableEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(AdditionalData, other.AdditionalData); - } + public bool Equals(CancellableEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(AdditionalData, other.AdditionalData); + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((CancellableEventArgs) obj); - } + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((CancellableEventArgs) obj); + } - public override int GetHashCode() - { - return AdditionalData != null ? AdditionalData.GetHashCode() : 0; - } + public override int GetHashCode() + { + return AdditionalData != null ? AdditionalData.GetHashCode() : 0; + } - public static bool operator ==(CancellableEventArgs left, CancellableEventArgs right) - { - return Equals(left, right); - } + public static bool operator ==(CancellableEventArgs left, CancellableEventArgs right) + { + return Equals(left, right); + } - public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right) - { - return Equals(left, right) == false; - } - } -} \ No newline at end of file + public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right) + { + return Equals(left, right) == false; + } + } +} diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs index 63562eb53e..748ecb31ed 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs @@ -1,10 +1,9 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Security.Permissions; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Events { @@ -174,4 +173,4 @@ namespace Umbraco.Core.Events return HashCodeHelper.GetHashCode(EventObject); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/ContentCacheEventArgs.cs b/src/Umbraco.Core/Events/ContentCacheEventArgs.cs index d9b627309e..b177f25674 100644 --- a/src/Umbraco.Core/Events/ContentCacheEventArgs.cs +++ b/src/Umbraco.Core/Events/ContentCacheEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Events +namespace Umbraco.Core.Events { - public class ContentCacheEventArgs : System.ComponentModel.CancelEventArgs { } -} \ No newline at end of file + public class ContentCacheEventArgs : System.ComponentModel.CancelEventArgs { } +} diff --git a/src/Umbraco.Core/Events/CopyEventArgs.cs b/src/Umbraco.Core/Events/CopyEventArgs.cs index 65dc54cc88..c697dcbc06 100644 --- a/src/Umbraco.Core/Events/CopyEventArgs.cs +++ b/src/Umbraco.Core/Events/CopyEventArgs.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace Umbraco.Core.Events @@ -84,4 +84,4 @@ namespace Umbraco.Core.Events return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/DatabaseCreationEventArgs.cs b/src/Umbraco.Core/Events/DatabaseCreationEventArgs.cs index 5fb9cc0d97..5736a08890 100644 --- a/src/Umbraco.Core/Events/DatabaseCreationEventArgs.cs +++ b/src/Umbraco.Core/Events/DatabaseCreationEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Events +namespace Umbraco.Core.Events { internal class DatabaseCreationEventArgs : System.ComponentModel.CancelEventArgs{} -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index 188ce70bd1..9b0d7a4e76 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -9,46 +9,46 @@ namespace Umbraco.Core.Events [SupersedeEvent(typeof(MoveEventArgs<>))] [SupersedeEvent(typeof(CopyEventArgs<>))] public class DeleteEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>, IDeletingMediaFilesEventArgs - { - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages) + { + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages) { MediaFilesToDelete = new List(); } - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, eventMessages) + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, eventMessages) { MediaFilesToDelete = new List(); } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages) + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages) : base(new List { eventObject }, eventMessages) { MediaFilesToDelete = new List(); } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) : base(new List { eventObject }, canCancel, eventMessages) { MediaFilesToDelete = new List(); @@ -60,90 +60,90 @@ namespace Umbraco.Core.Events /// /// public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) - { + { MediaFilesToDelete = new List(); - } + } - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - public DeleteEventArgs(IEnumerable eventObject) : base(eventObject) - { + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + public DeleteEventArgs(IEnumerable eventObject) : base(eventObject) + { MediaFilesToDelete = new List(); - } + } - /// - /// Constructor accepting a single entity instance - /// - /// - public DeleteEventArgs(TEntity eventObject) - : base(new List { eventObject }) - { + /// + /// Constructor accepting a single entity instance + /// + /// + public DeleteEventArgs(TEntity eventObject) + : base(new List { eventObject }) + { MediaFilesToDelete = new List(); - } + } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, bool canCancel) - : base(new List { eventObject }, canCancel) - { + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, bool canCancel) + : base(new List { eventObject }, canCancel) + { MediaFilesToDelete = new List(); - } + } - /// - /// Returns all entities that were deleted during the operation - /// - public IEnumerable DeletedEntities - { - get { return EventObject; } - internal set { EventObject = value; } - } + /// + /// Returns all entities that were deleted during the operation + /// + public IEnumerable DeletedEntities + { + get { return EventObject; } + internal set { EventObject = value; } // fixme ouch! + } /// /// A list of media files that can be added to during a deleted operation for which Umbraco will ensure are removed /// public List MediaFilesToDelete { get; private set; } - public bool Equals(DeleteEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && MediaFilesToDelete.SequenceEqual(other.MediaFilesToDelete); - } + public bool Equals(DeleteEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && MediaFilesToDelete.SequenceEqual(other.MediaFilesToDelete); + } - 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((DeleteEventArgs) obj); - } + 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((DeleteEventArgs) obj); + } - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ MediaFilesToDelete.GetHashCode(); - } - } + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ MediaFilesToDelete.GetHashCode(); + } + } - public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) - { - return Equals(left, right); - } + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) + { + return Equals(left, right); + } - public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) - { - return !Equals(left, right); - } - } + public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) + { + return !Equals(left, right); + } + } - public class DeleteEventArgs : CancellableEventArgs, IEquatable - { + public class DeleteEventArgs : CancellableEventArgs, IEquatable + { public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages) : base(canCancel, eventMessages) { @@ -151,52 +151,52 @@ namespace Umbraco.Core.Events } public DeleteEventArgs(int id, bool canCancel) - : base(canCancel) - { - Id = id; - } + : base(canCancel) + { + Id = id; + } - public DeleteEventArgs(int id) - { - Id = id; - } + public DeleteEventArgs(int id) + { + Id = id; + } - /// - /// Gets the Id of the object being deleted. - /// - public int Id { get; private set; } + /// + /// Gets the Id of the object being deleted. + /// + public int Id { get; private set; } - public bool Equals(DeleteEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && Id == other.Id; - } + public bool Equals(DeleteEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && Id == other.Id; + } - 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((DeleteEventArgs) obj); - } + 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((DeleteEventArgs) obj); + } - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ Id; - } - } + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ Id; + } + } - public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) - { - return Equals(left, right); - } + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) + { + return Equals(left, right); + } - public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) - { - return !Equals(left, right); - } - } -} \ No newline at end of file + public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs b/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs index 1db1296640..a93f08ebb8 100644 --- a/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs @@ -1,72 +1,69 @@ -using System; +using System; namespace Umbraco.Core.Events { - public class DeleteRevisionsEventArgs : DeleteEventArgs, IEquatable - { - public DeleteRevisionsEventArgs(int id, bool canCancel, Guid specificVersion = default(Guid), bool deletePriorVersions = false, DateTime dateToRetain = default(DateTime)) - : base(id, canCancel) - { - DeletePriorVersions = deletePriorVersions; - SpecificVersion = specificVersion; - DateToRetain = dateToRetain; - } + public class DeleteRevisionsEventArgs : DeleteEventArgs, IEquatable + { + public DeleteRevisionsEventArgs(int id, bool canCancel, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id, canCancel) + { + DeletePriorVersions = deletePriorVersions; + SpecificVersion = specificVersion; + DateToRetain = dateToRetain; + } - public DeleteRevisionsEventArgs(int id, Guid specificVersion = default(Guid), bool deletePriorVersions = false, DateTime dateToRetain = default(DateTime)) - : base(id) - { - DeletePriorVersions = deletePriorVersions; - SpecificVersion = specificVersion; - DateToRetain = dateToRetain; - } + public DeleteRevisionsEventArgs(int id, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id) + { + DeletePriorVersions = deletePriorVersions; + SpecificVersion = specificVersion; + DateToRetain = dateToRetain; + } - public bool DeletePriorVersions { get; private set; } - public Guid SpecificVersion { get; private set; } - public DateTime DateToRetain { get; private set; } - - /// - /// Returns true if we are deleting a specific revision - /// - public bool IsDeletingSpecificRevision - { - get { return SpecificVersion != default(Guid); } - } + public bool DeletePriorVersions { get; } + public int SpecificVersion { get; } + public DateTime DateToRetain { get; } - public bool Equals(DeleteRevisionsEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && DateToRetain.Equals(other.DateToRetain) && DeletePriorVersions == other.DeletePriorVersions && SpecificVersion.Equals(other.SpecificVersion); - } + /// + /// Returns true if we are deleting a specific revision + /// + public bool IsDeletingSpecificRevision => SpecificVersion != default; - 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((DeleteRevisionsEventArgs) obj); - } + public bool Equals(DeleteRevisionsEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && DateToRetain.Equals(other.DateToRetain) && DeletePriorVersions == other.DeletePriorVersions && SpecificVersion.Equals(other.SpecificVersion); + } - public override int GetHashCode() - { - unchecked - { - int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ DateToRetain.GetHashCode(); - hashCode = (hashCode * 397) ^ DeletePriorVersions.GetHashCode(); - hashCode = (hashCode * 397) ^ SpecificVersion.GetHashCode(); - return hashCode; - } - } + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((DeleteRevisionsEventArgs) obj); + } - public static bool operator ==(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) - { - return Equals(left, right); - } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ DateToRetain.GetHashCode(); + hashCode = (hashCode * 397) ^ DeletePriorVersions.GetHashCode(); + hashCode = (hashCode * 397) ^ SpecificVersion.GetHashCode(); + return hashCode; + } + } - public static bool operator !=(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) - { - return !Equals(left, right); - } - } -} \ No newline at end of file + public static bool operator ==(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) + { + return Equals(left, right); + } + + public static bool operator !=(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/EventDefinition.cs b/src/Umbraco.Core/Events/EventDefinition.cs index e861e9496e..17030bad54 100644 --- a/src/Umbraco.Core/Events/EventDefinition.cs +++ b/src/Umbraco.Core/Events/EventDefinition.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Core.Events { @@ -54,7 +54,7 @@ namespace Umbraco.Core.Events private readonly TSender _sender; private readonly TEventArgs _args; - public EventDefinition(TypedEventHandler trackedEvent, TSender sender, TEventArgs args, string eventName = null) + public EventDefinition(TypedEventHandler trackedEvent, TSender sender, TEventArgs args, string eventName = null) : base(sender, args, eventName) { _trackedEvent = trackedEvent; @@ -70,4 +70,4 @@ namespace Umbraco.Core.Events } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/EventDefinitionBase.cs b/src/Umbraco.Core/Events/EventDefinitionBase.cs index c0a061f80f..b61d00d75f 100644 --- a/src/Umbraco.Core/Events/EventDefinitionBase.cs +++ b/src/Umbraco.Core/Events/EventDefinitionBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; namespace Umbraco.Core.Events @@ -7,28 +7,27 @@ namespace Umbraco.Core.Events { protected EventDefinitionBase(object sender, object args, string eventName = null) { - if (sender == null) throw new ArgumentNullException("sender"); - if (args == null) throw new ArgumentNullException("args"); - Sender = sender; - Args = args; + Sender = sender ?? throw new ArgumentNullException(nameof(sender)); + Args = args ?? throw new ArgumentNullException(nameof(args)); EventName = eventName; if (EventName.IsNullOrWhiteSpace()) { - var findResult = EventNameExtractor.FindEvent(sender, args, - //don't match "Ing" suffixed names - exclude:EventNameExtractor.MatchIngNames); + // don't match "Ing" suffixed names + var findResult = EventNameExtractor.FindEvent(sender, args, exclude:EventNameExtractor.MatchIngNames); if (findResult.Success == false) - throw new AmbiguousMatchException("Could not automatically find the event name, the event name will need to be explicitly registered for this event definition. Error: " + findResult.Result.Error); + throw new AmbiguousMatchException("Could not automatically find the event name, the event name will need to be explicitly registered for this event definition. " + + $"Sender: {sender.GetType()} Args: {args.GetType()}" + + " Error: " + findResult.Result.Error); EventName = findResult.Result.Name; } } - public object Sender { get; private set; } - public object Args { get; private set; } - public string EventName { get; private set; } - + public object Sender { get; } + public object Args { get; } + public string EventName { get; } + public abstract void RaiseEvent(); public bool Equals(EventDefinitionBase other) @@ -67,4 +66,4 @@ namespace Umbraco.Core.Events return Equals(left, right) == false; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/EventDefinitionFilter.cs b/src/Umbraco.Core/Events/EventDefinitionFilter.cs index 4bbe75d10b..36838a1e3c 100644 --- a/src/Umbraco.Core/Events/EventDefinitionFilter.cs +++ b/src/Umbraco.Core/Events/EventDefinitionFilter.cs @@ -21,4 +21,4 @@ /// LastIn } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/EventExtensions.cs b/src/Umbraco.Core/Events/EventExtensions.cs index a366ad0cce..8843bcb1d6 100644 --- a/src/Umbraco.Core/Events/EventExtensions.cs +++ b/src/Umbraco.Core/Events/EventExtensions.cs @@ -1,17 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using Umbraco.Core.Persistence.UnitOfWork; +using System; namespace Umbraco.Core.Events { - /// - /// Extension methods for cancellable event operations - /// - public static class EventExtensions - { + /// + /// Extension methods for cancellable event operations + /// + public static class EventExtensions + { // keep these two for backward compatibility reasons but understand that // they are *not* part of any scope / event dispatcher / anything... @@ -26,12 +21,12 @@ namespace Umbraco.Core.Events /// A value indicating whether the cancelable event should be cancelled /// A cancelable event is raised by a component when it is about to perform an action that can be canceled. public static bool IsRaisedEventCancelled(this TypedEventHandler eventHandler, TArgs args, TSender sender) - where TArgs : CancellableEventArgs + where TArgs : CancellableEventArgs { if (eventHandler == null) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } + eventHandler(sender, args); + return args.Cancel; + } /// /// Raises an event. @@ -48,4 +43,4 @@ namespace Umbraco.Core.Events eventHandler(sender, args); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/EventMessage.cs b/src/Umbraco.Core/Events/EventMessage.cs index 473f955d45..c66753aeca 100644 --- a/src/Umbraco.Core/Events/EventMessage.cs +++ b/src/Umbraco.Core/Events/EventMessage.cs @@ -1,10 +1,10 @@ -namespace Umbraco.Core.Events +namespace Umbraco.Core.Events { /// /// An event message /// public sealed class EventMessage - { + { /// /// Initializes a new instance of the class. /// @@ -24,4 +24,4 @@ namespace Umbraco.Core.Events /// internal bool IsDefaultEventMessage { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/EventMessageType.cs b/src/Umbraco.Core/Events/EventMessageType.cs index 6299a0e7d7..c112312ee6 100644 --- a/src/Umbraco.Core/Events/EventMessageType.cs +++ b/src/Umbraco.Core/Events/EventMessageType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Events +namespace Umbraco.Core.Events { /// /// The type of event message @@ -8,7 +8,7 @@ namespace Umbraco.Core.Events Default = 0, Info = 1, Error = 2, - Success = 3, + Success = 3, Warning = 4 } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/EventMessages.cs b/src/Umbraco.Core/Events/EventMessages.cs index 6348fc0444..2df1911249 100644 --- a/src/Umbraco.Core/Events/EventMessages.cs +++ b/src/Umbraco.Core/Events/EventMessages.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Events { @@ -29,4 +29,4 @@ namespace Umbraco.Core.Events _msgs.Clear(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/EventNameExtractor.cs b/src/Umbraco.Core/Events/EventNameExtractor.cs index 33137770d4..5cb9ca64ef 100644 --- a/src/Umbraco.Core/Events/EventNameExtractor.cs +++ b/src/Umbraco.Core/Events/EventNameExtractor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Linq; using System.Reflection; @@ -7,17 +7,17 @@ using System.Text.RegularExpressions; namespace Umbraco.Core.Events { /// - /// There is actually no way to discover an event name in c# at the time of raising the event. It is possible - /// to get the event name from the handler that is being executed based on the event being raised, however that is not + /// There is actually no way to discover an event name in c# at the time of raising the event. It is possible + /// to get the event name from the handler that is being executed based on the event being raised, however that is not /// what we want in this case. We need to find the event name before it is being raised - you would think that it's possible /// with reflection or anything but that is not the case, the delegate that defines an event has no info attached to it, it /// is literally just an event. - /// + /// /// So what this does is take the sender and event args objects, looks up all public/static events on the sender that have /// a generic event handler with generic arguments (but only) one, then we match the type of event arguments with the ones /// being passed in. As it turns out, in our services this will work for the majority of our events! In some cases it may not - /// work and we'll have to supply a string but hopefully this saves a bit of magic strings. - /// + /// work and we'll have to supply a string but hopefully this saves a bit of magic strings. + /// /// We can also write tests to validate these are all working correctly for all services. /// internal class EventNameExtractor @@ -35,12 +35,12 @@ namespace Umbraco.Core.Events /// null if not found or an ambiguous match /// public static Attempt FindEvent(Type senderType, Type argsType, Func exclude) - { + { var found = MatchedEventNames.GetOrAdd(new Tuple(senderType, argsType), tuple => { var events = CandidateEvents.GetOrAdd(senderType, t => { - return t.GetEvents(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + return t.GetEvents(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy) //we can only look for events handlers with generic types because that is the only // way that we can try to find a matching event based on the arg type passed in .Where(x => x.EventHandlerType.IsGenericType) @@ -156,4 +156,4 @@ namespace Umbraco.Core.Events /// private static readonly ConcurrentDictionary, string[]> MatchedEventNames = new ConcurrentDictionary, string[]>(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/EventNameExtractorError.cs b/src/Umbraco.Core/Events/EventNameExtractorError.cs index 3b27db2a1a..a4aa47d611 100644 --- a/src/Umbraco.Core/Events/EventNameExtractorError.cs +++ b/src/Umbraco.Core/Events/EventNameExtractorError.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Core.Events +namespace Umbraco.Core.Events { internal enum EventNameExtractorError { NoneFound, Ambiguous } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/EventNameExtractorResult.cs b/src/Umbraco.Core/Events/EventNameExtractorResult.cs index 6213882d1f..7c2a295e38 100644 --- a/src/Umbraco.Core/Events/EventNameExtractorResult.cs +++ b/src/Umbraco.Core/Events/EventNameExtractorResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Events +namespace Umbraco.Core.Events { internal class EventNameExtractorResult { @@ -15,4 +15,4 @@ namespace Umbraco.Core.Events Error = error; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/ExportEventArgs.cs b/src/Umbraco.Core/Events/ExportEventArgs.cs index acbf920636..f46cccf05c 100644 --- a/src/Umbraco.Core/Events/ExportEventArgs.cs +++ b/src/Umbraco.Core/Events/ExportEventArgs.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Events /// /// public ExportEventArgs(TEntity eventObject, XElement xml, bool canCancel) - : base(new List { eventObject }, canCancel) + : base(new List { eventObject }, canCancel) { Xml = xml; } @@ -83,4 +83,4 @@ namespace Umbraco.Core.Events return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/ExportedMemberEventArgs.cs b/src/Umbraco.Core/Events/ExportedMemberEventArgs.cs new file mode 100644 index 0000000000..9c91f3e5bd --- /dev/null +++ b/src/Umbraco.Core/Events/ExportedMemberEventArgs.cs @@ -0,0 +1,18 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Events +{ + internal class ExportedMemberEventArgs : EventArgs + { + public IMember Member { get; } + public MemberExportModel Exported { get; } + + public ExportedMemberEventArgs(IMember member, MemberExportModel exported) + { + Member = member; + Exported = exported; + } + } +} diff --git a/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs index 45681042ba..19580b4106 100644 --- a/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs +++ b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Events { @@ -6,4 +6,4 @@ namespace Umbraco.Core.Events { List MediaFilesToDelete { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/IEventDefinition.cs b/src/Umbraco.Core/Events/IEventDefinition.cs index eb4fe65eb8..2afab1ee0f 100644 --- a/src/Umbraco.Core/Events/IEventDefinition.cs +++ b/src/Umbraco.Core/Events/IEventDefinition.cs @@ -10,4 +10,4 @@ namespace Umbraco.Core.Events void RaiseEvent(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/IEventDispatcher.cs b/src/Umbraco.Core/Events/IEventDispatcher.cs index 78f8ed3a4a..9d2c12ceac 100644 --- a/src/Umbraco.Core/Events/IEventDispatcher.cs +++ b/src/Umbraco.Core/Events/IEventDispatcher.cs @@ -95,4 +95,4 @@ namespace Umbraco.Core.Events /// The collected events. IEnumerable GetEvents(EventDefinitionFilter filter); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/IEventMessagesAccessor.cs b/src/Umbraco.Core/Events/IEventMessagesAccessor.cs new file mode 100644 index 0000000000..be4603a285 --- /dev/null +++ b/src/Umbraco.Core/Events/IEventMessagesAccessor.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Events +{ + public interface IEventMessagesAccessor + { + EventMessages EventMessages { get; set; } + } +} diff --git a/src/Umbraco.Core/Events/IEventMessagesFactory.cs b/src/Umbraco.Core/Events/IEventMessagesFactory.cs index cb2391186d..c35539b658 100644 --- a/src/Umbraco.Core/Events/IEventMessagesFactory.cs +++ b/src/Umbraco.Core/Events/IEventMessagesFactory.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Events { @@ -8,5 +8,7 @@ namespace Umbraco.Core.Events public interface IEventMessagesFactory { EventMessages Get(); + + EventMessages GetOrDefault(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/ImportEventArgs.cs b/src/Umbraco.Core/Events/ImportEventArgs.cs index 892149c0a2..fb8f7d4936 100644 --- a/src/Umbraco.Core/Events/ImportEventArgs.cs +++ b/src/Umbraco.Core/Events/ImportEventArgs.cs @@ -81,4 +81,4 @@ namespace Umbraco.Core.Events return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs index e4351b2064..7536b43e93 100644 --- a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs +++ b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs @@ -1,15 +1,15 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using Umbraco.Core.Packaging.Models; +using Umbraco.Core.Models.Packaging; namespace Umbraco.Core.Events { public class ImportPackageEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> { private readonly MetaData _packageMetaData; - - [EditorBrowsable(EditorBrowsableState.Never)] + + [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use the overload specifying packageMetaData instead")] public ImportPackageEventArgs(TEntity eventObject, bool canCancel) : base(new[] { eventObject }, canCancel) @@ -33,8 +33,8 @@ namespace Umbraco.Core.Events { get { return _packageMetaData; } } - - public IEnumerable InstallationSummary + + public IEnumerable InstallationSummary { get { return EventObject; } } @@ -73,4 +73,4 @@ namespace Umbraco.Core.Events return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs index 52ac19aae2..bdda23ba3a 100644 --- a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs +++ b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs @@ -1,20 +1,18 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using Umbraco.Core.Macros; namespace Umbraco.Core.Events { // Provides information on the macro that caused an error - public class MacroErrorEventArgs : System.EventArgs + public class MacroErrorEventArgs : EventArgs { /// - /// Name of the faulting macro. + /// Name of the faulting macro. /// public string Name { get; set; } /// - /// Alias of the faulting macro. + /// Alias of the faulting macro. /// public string Alias { get; set; } @@ -35,5 +33,10 @@ namespace Umbraco.Core.Events /// /// Macro error behaviour enum. public MacroErrorBehaviour Behaviour { get; set; } + + /// + /// The html code to display when Behavior is Content. + /// + public string Html { get; set; } } } diff --git a/src/Umbraco.Core/Events/MigrationEventArgs.cs b/src/Umbraco.Core/Events/MigrationEventArgs.cs index 8b8898e7d4..b4b9a53736 100644 --- a/src/Umbraco.Core/Events/MigrationEventArgs.cs +++ b/src/Umbraco.Core/Events/MigrationEventArgs.cs @@ -1,56 +1,18 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using Semver; -using Umbraco.Core.Configuration; -using Umbraco.Core.Persistence.Migrations; +using Umbraco.Core.Migrations; namespace Umbraco.Core.Events { - public class MigrationEventArgs : CancellableObjectEventArgs>, IEquatable + public class MigrationEventArgs : CancellableObjectEventArgs>, IEquatable { - /// - /// Constructor accepting multiple migrations that are used in the migration runner - /// - /// - /// - /// - /// - /// - public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel) - : this(eventObject, null, configuredVersion, targetVersion, productName, canCancel) + public MigrationEventArgs(IList migrationTypes, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel) + : this(migrationTypes, null, configuredVersion, targetVersion, productName, canCancel) { } - /// - /// Constructor accepting multiple migrations that are used in the migration runner - /// - /// - /// - /// - /// - [Obsolete("Use constructor accepting a product name instead.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel) - : this(eventObject, null, configuredVersion, targetVersion, Constants.System.UmbracoMigrationName, canCancel) - { } - - [Obsolete("Use constructor accepting SemVersion instances and a product name instead.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public MigrationEventArgs(IList eventObject, Version configuredVersion, Version targetVersion, bool canCancel) - : this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), Constants.System.UmbracoMigrationName, canCancel) - { } - - /// - /// Constructor accepting multiple migrations that are used in the migration runner - /// - /// - /// - /// - /// - /// - /// - internal MigrationEventArgs(IList eventObject, MigrationContext migrationContext, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel) - : base(eventObject, canCancel) + internal MigrationEventArgs(IList migrationTypes, IMigrationContext migrationContext, SemVersion configuredVersion, SemVersion targetVersion, string productName, bool canCancel) + : base(migrationTypes, canCancel) { MigrationContext = migrationContext; ConfiguredSemVersion = configuredVersion; @@ -58,89 +20,35 @@ namespace Umbraco.Core.Events ProductName = productName; } - /// - /// Constructor accepting multiple migrations that are used in the migration runner - /// - /// - /// - /// - /// - /// - [Obsolete("Use constructor accepting a product name instead.")] - [EditorBrowsable(EditorBrowsableState.Never)] - internal MigrationEventArgs(IList eventObject, MigrationContext migrationContext, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel) - : base(eventObject, canCancel) - { - MigrationContext = migrationContext; - ConfiguredSemVersion = configuredVersion; - TargetSemVersion = targetVersion; - ProductName = Constants.System.UmbracoMigrationName; - } - - /// - /// Constructor accepting multiple migrations that are used in the migration runner - /// - /// - /// - /// - /// - public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion, string productName) - : this(eventObject, null, configuredVersion, targetVersion, productName, false) + public MigrationEventArgs(IList migrationTypes, SemVersion configuredVersion, SemVersion targetVersion, string productName) + : this(migrationTypes, null, configuredVersion, targetVersion, productName, false) { } /// - /// Constructor accepting multiple migrations that are used in the migration runner + /// Returns all migrations that were used in the migration runner /// - /// - /// - /// - [Obsolete("Use constructor accepting a product name instead.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public MigrationEventArgs(IList eventObject, SemVersion configuredVersion, SemVersion targetVersion) - : this(eventObject, null, configuredVersion, targetVersion, Constants.System.UmbracoMigrationName, false) - { } - - [Obsolete("Use constructor accepting SemVersion instances and a product name instead.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public MigrationEventArgs(IList eventObject, Version configuredVersion, Version targetVersion) - : this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), Constants.System.UmbracoMigrationName, false) - { } - - /// - /// Returns all migrations that were used in the migration runner - /// - public IList Migrations - { - get { return EventObject; } - } - - [Obsolete("Use ConfiguredSemVersion instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public Version ConfiguredVersion - { - get { return ConfiguredSemVersion.GetVersion(); } - } - - [Obsolete("Use TargetUmbracoVersion instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public Version TargetVersion - { - get { return TargetSemVersion.GetVersion(); } - } + public IList MigrationsTypes => EventObject; /// /// Gets the origin version of the migration, i.e. the one that is currently installed. /// - public SemVersion ConfiguredSemVersion { get; private set; } + public SemVersion ConfiguredSemVersion { get; } /// /// Gets the target version of the migration. /// - public SemVersion TargetSemVersion { get; private set; } + public SemVersion TargetSemVersion { get; } - public string ProductName { get; private set; } + /// + /// Gets the product name. + /// + public string ProductName { get; } - internal MigrationContext MigrationContext { get; private set; } + /// + /// Gets the migration context. + /// + /// Is only available after migrations have run, for post-migrations. + internal IMigrationContext MigrationContext { get; } public bool Equals(MigrationEventArgs other) { @@ -180,4 +88,4 @@ namespace Umbraco.Core.Events return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/MoveEventArgs.cs b/src/Umbraco.Core/Events/MoveEventArgs.cs index 18c02ff46a..afc1f57111 100644 --- a/src/Umbraco.Core/Events/MoveEventArgs.cs +++ b/src/Umbraco.Core/Events/MoveEventArgs.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -118,13 +118,13 @@ namespace Umbraco.Core.Events if (first == null) { throw new InvalidOperationException("MoveInfoCollection must have at least one item"); - } + } - _moveInfoCollection = value; - - //assign the legacy props + _moveInfoCollection = value; + + //assign the legacy props EventObject = first.Entity; - ParentId = first.NewParentId; + ParentId = first.NewParentId; } } @@ -176,4 +176,4 @@ namespace Umbraco.Core.Events return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/MoveEventInfo.cs b/src/Umbraco.Core/Events/MoveEventInfo.cs index 9e77971837..0b6702611b 100644 --- a/src/Umbraco.Core/Events/MoveEventInfo.cs +++ b/src/Umbraco.Core/Events/MoveEventInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace Umbraco.Core.Events @@ -52,4 +52,4 @@ namespace Umbraco.Core.Events return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/NewEventArgs.cs b/src/Umbraco.Core/Events/NewEventArgs.cs index 415d82b90e..280a628fa9 100644 --- a/src/Umbraco.Core/Events/NewEventArgs.cs +++ b/src/Umbraco.Core/Events/NewEventArgs.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Events { - public class NewEventArgs : CancellableObjectEventArgs, IEquatable> - { + public class NewEventArgs : CancellableObjectEventArgs, IEquatable> + { public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId, EventMessages eventMessages) @@ -22,7 +22,7 @@ namespace Umbraco.Core.Events Parent = parent; } - public NewEventArgs(TEntity eventObject, string @alias, int parentId, EventMessages eventMessages) + public NewEventArgs(TEntity eventObject, string @alias, int parentId, EventMessages eventMessages) : base(eventObject, eventMessages) { Alias = alias; @@ -39,10 +39,10 @@ namespace Umbraco.Core.Events public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId) : base(eventObject, canCancel) - { - Alias = alias; - ParentId = parentId; - } + { + Alias = alias; + ParentId = parentId; + } public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, TEntity parent) : base(eventObject, canCancel) @@ -51,11 +51,11 @@ namespace Umbraco.Core.Events Parent = parent; } - public NewEventArgs(TEntity eventObject, string @alias, int parentId) : base(eventObject) - { - Alias = alias; - ParentId = parentId; - } + public NewEventArgs(TEntity eventObject, string @alias, int parentId) : base(eventObject) + { + Alias = alias; + ParentId = parentId; + } public NewEventArgs(TEntity eventObject, string @alias, TEntity parent) : base(eventObject) @@ -64,64 +64,64 @@ namespace Umbraco.Core.Events Parent = parent; } - /// - /// The entity being created - /// - public TEntity Entity - { - get { return EventObject; } - } + /// + /// The entity being created + /// + public TEntity Entity + { + get { return EventObject; } + } - /// - /// Gets or Sets the Alias. - /// - public string Alias { get; private set; } + /// + /// Gets or Sets the Alias. + /// + public string Alias { get; private set; } - /// - /// Gets or Sets the Id of the parent. - /// - public int ParentId { get; private set; } + /// + /// Gets or Sets the Id of the parent. + /// + public int ParentId { get; private set; } /// /// Gets or Sets the parent IContent object. /// public TEntity Parent { get; private set; } - public bool Equals(NewEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && string.Equals(Alias, other.Alias) && EqualityComparer.Default.Equals(Parent, other.Parent) && ParentId == other.ParentId; - } + public bool Equals(NewEventArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && string.Equals(Alias, other.Alias) && EqualityComparer.Default.Equals(Parent, other.Parent) && ParentId == other.ParentId; + } - 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((NewEventArgs) obj); - } + 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((NewEventArgs) obj); + } - public override int GetHashCode() - { - unchecked - { - int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ Alias.GetHashCode(); - hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Parent); - hashCode = (hashCode * 397) ^ ParentId; - return hashCode; - } - } + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ Alias.GetHashCode(); + hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Parent); + hashCode = (hashCode * 397) ^ ParentId; + return hashCode; + } + } - public static bool operator ==(NewEventArgs left, NewEventArgs right) - { - return Equals(left, right); - } + public static bool operator ==(NewEventArgs left, NewEventArgs right) + { + return Equals(left, right); + } - public static bool operator !=(NewEventArgs left, NewEventArgs right) - { - return !Equals(left, right); - } - } -} \ No newline at end of file + public static bool operator !=(NewEventArgs left, NewEventArgs right) + { + return !Equals(left, right); + } + } +} diff --git a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs index bc3709ea2a..dec3a62a88 100644 --- a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs +++ b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -36,20 +36,17 @@ namespace Umbraco.Core.Events public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null) { - if (eventHandler == null) return; - eventHandler(sender, args); + eventHandler?.Invoke(sender, args); } public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string eventName = null) { - if (eventHandler == null) return; - eventHandler(sender, args); + eventHandler?.Invoke(sender, args); } public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) { - if (eventHandler == null) return; - eventHandler(sender, args); + eventHandler?.Invoke(sender, args); } public IEnumerable GetEvents(EventDefinitionFilter filter) @@ -60,4 +57,4 @@ namespace Umbraco.Core.Events public void ScopeExit(bool completed) { } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs index 10bf94146c..3226162980 100644 --- a/src/Umbraco.Core/Events/PublishEventArgs.cs +++ b/src/Umbraco.Core/Events/PublishEventArgs.cs @@ -58,28 +58,28 @@ namespace Umbraco.Core.Events /// /// public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished) - : base(eventObject, canCancel) - { + : base(eventObject, canCancel) + { IsAllRepublished = isAllPublished; - } + } /// /// Constructor accepting multiple entities that are used in the publish operation /// /// public PublishEventArgs(IEnumerable eventObject) - : base(eventObject) - { - } + : base(eventObject) + { + } /// /// Constructor accepting a single entity instance /// /// public PublishEventArgs(TEntity eventObject) - : base(new List { eventObject }) - { - } + : base(new List { eventObject }) + { + } /// /// Constructor accepting a single entity instance @@ -88,18 +88,18 @@ namespace Umbraco.Core.Events /// /// public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished) - : base(new List { eventObject }, canCancel) - { + : base(new List { eventObject }, canCancel) + { IsAllRepublished = isAllPublished; - } + } - /// - /// Returns all entities that were published during the operation - /// - public IEnumerable PublishedEntities - { - get { return EventObject; } - } + /// + /// Returns all entities that were published during the operation + /// + public IEnumerable PublishedEntities + { + get { return EventObject; } + } public bool IsAllRepublished { get; private set; } @@ -136,4 +136,4 @@ namespace Umbraco.Core.Events return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs new file mode 100644 index 0000000000..e0d3847c17 --- /dev/null +++ b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs @@ -0,0 +1,40 @@ +using Umbraco.Core.Composing; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Events +{ + /// + /// An IEventDispatcher that queues events, and raise them when the scope + /// exits and has been completed. + /// + internal class QueuingEventDispatcher : QueuingEventDispatcherBase + { + public QueuingEventDispatcher() + : base(true) + { } + + protected override void ScopeExitCompleted() + { + // processing only the last instance of each event... + // this is probably far from perfect, because if eg a content is saved in a list + // and then as a single content, the two events will probably not be de-duplicated, + // but it's better than nothing + + foreach (var e in GetEvents(EventDefinitionFilter.LastIn)) + { + e.RaiseEvent(); + + // separating concerns means that this should probably not be here, + // but then where should it be (without making things too complicated)? + var delete = e.Args as IDeletingMediaFilesEventArgs; + if (delete != null && delete.MediaFilesToDelete.Count > 0) + MediaFileSystem.DeleteMediaFiles(delete.MediaFilesToDelete); + } + } + + private MediaFileSystem _mediaFileSystem; + + // fixme inject + private MediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem); + } +} diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs b/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs new file mode 100644 index 0000000000..0283ac372e --- /dev/null +++ b/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs @@ -0,0 +1,345 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Collections; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Events +{ + /// + /// An IEventDispatcher that queues events. + /// + /// + /// Can raise, or ignore, cancelable events, depending on option. + /// Implementations must override ScopeExitCompleted to define what + /// to do with the events when the scope exits and has been completed. + /// If the scope exits without being completed, events are ignored. + /// + public abstract class QueuingEventDispatcherBase : IEventDispatcher + { + //events will be enlisted in the order they are raised + private List _events; + private readonly bool _raiseCancelable; + + protected QueuingEventDispatcherBase(bool raiseCancelable) + { + _raiseCancelable = raiseCancelable; + } + + private List Events => _events ?? (_events = new List()); + + public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null) + { + if (eventHandler == null) return args.Cancel; + if (_raiseCancelable == false) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) return args.Cancel; + if (_raiseCancelable == false) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) return args.Cancel; + if (_raiseCancelable == false) return args.Cancel; + eventHandler(sender, args); + return args.Cancel; + } + + public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null) + { + if (eventHandler == null) return; + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } + + public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string eventName = null) + { + if (eventHandler == null) return; + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } + + public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) + { + if (eventHandler == null) return; + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } + + public IEnumerable GetEvents(EventDefinitionFilter filter) + { + if (_events == null) + return Enumerable.Empty(); + + IReadOnlyList events; + switch (filter) + { + case EventDefinitionFilter.All: + events = _events; + break; + case EventDefinitionFilter.FirstIn: + var l1 = new OrderedHashSet(); + foreach (var e in _events) + l1.Add(e); + events = l1; + break; + case EventDefinitionFilter.LastIn: + var l2 = new OrderedHashSet(keepOldest: false); + foreach (var e in _events) + l2.Add(e); + events = l2; + break; + default: + throw new ArgumentOutOfRangeException("filter", filter, null); + } + + return FilterSupersededAndUpdateToLatestEntity(events); + } + + private class EventDefinitionInfos + { + public IEventDefinition EventDefinition { get; set; } + public Type[] SupersedeTypes { get; set; } + } + + // fixme + // this is way too convoluted, the superceede attribute is used only on DeleteEventargs to specify + // that it superceeds save, publish, move and copy - BUT - publish event args is also used for + // unpublishing and should NOT be superceeded - so really it should not be managed at event args + // level but at event level + // + // what we want is: + // if an entity is deleted, then all Saved, Moved, Copied, Published events prior to this should + // not trigger for the entity - and even though, does it make any sense? making a copy of an entity + // should ... trigger? + // + // not going to refactor it all - we probably want to *always* trigger event but tell people that + // due to scopes, they should not expected eg a saved entity to still be around - however, now, + // going to write a ugly condition to deal with U4-10764 + + // iterates over the events (latest first) and filter out any events or entities in event args that are included + // in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want + // to raise the Saved event (well actually we just don't want to include it in the args for that saved event) + internal static IEnumerable FilterSupersededAndUpdateToLatestEntity(IReadOnlyList events) + { + // keeps the 'latest' entity and associated event data + var entities = new List>(); + + // collects the event definitions + // collects the arguments in result, that require their entities to be updated + var result = new List(); + var resultArgs = new List(); + + // eagerly fetch superceeded arg types for each arg type + var argTypeSuperceeding = events.Select(x => x.Args.GetType()) + .Distinct() + .ToDictionary(x => x, x => x.GetCustomAttributes(false).Select(y => y.SupersededEventArgsType).ToArray()); + + // iterate over all events and filter + // + // process the list in reverse, because events are added in the order they are raised and we want to keep + // the latest (most recent) entities and filter out what is not relevant anymore (too old), eg if an entity + // is Deleted after being Saved, we want to filter out the Saved event + for (var index = events.Count - 1; index >= 0; index--) + { + var def = events[index]; + + var infos = new EventDefinitionInfos + { + EventDefinition = def, + SupersedeTypes = argTypeSuperceeding[def.Args.GetType()] + }; + + var args = def.Args as CancellableObjectEventArgs; + if (args == null) + { + // not a cancellable event arg, include event definition in result + result.Add(def); + } + else + { + // event object can either be a single object or an enumerable of objects + // try to get as an enumerable, get null if it's not + var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject); + if (eventObjects == null) + { + // single object, cast as an IEntity + // if cannot cast, cannot filter, nothing - just include event definition in result + var eventEntity = args.EventObject as IEntity; + if (eventEntity == null) + { + result.Add(def); + continue; + } + + // look for this entity in superceding event args + // found = must be removed (ie not added), else track + if (IsSuperceeded(eventEntity, infos, entities) == false) + { + // track + entities.Add(Tuple.Create(eventEntity, infos)); + + // track result arguments + // include event definition in result + resultArgs.Add(args); + result.Add(def); + } + } + else + { + // enumerable of objects + var toRemove = new List(); + foreach (var eventObject in eventObjects) + { + // extract the event object, cast as an IEntity + // if cannot cast, cannot filter, nothing to do - just leave it in the list & continue + var eventEntity = eventObject as IEntity; + if (eventEntity == null) + continue; + + // look for this entity in superceding event args + // found = must be removed, else track + if (IsSuperceeded(eventEntity, infos, entities)) + toRemove.Add(eventEntity); + else + entities.Add(Tuple.Create(eventEntity, infos)); + } + + // remove superceded entities + foreach (var entity in toRemove) + eventObjects.Remove(entity); + + // if there are still entities in the list, keep the event definition + if (eventObjects.Count > 0) + { + if (toRemove.Count > 0) + { + // re-assign if changed + args.EventObject = eventObjects; + } + + // track result arguments + // include event definition in result + resultArgs.Add(args); + result.Add(def); + } + } + } + } + + // go over all args in result, and update them with the latest instanceof each entity + UpdateToLatestEntities(entities, resultArgs); + + // reverse, since we processed the list in reverse + result.Reverse(); + + return result; + } + + // edits event args to use the latest instance of each entity + private static void UpdateToLatestEntities(IEnumerable> entities, IEnumerable args) + { + // get the latest entities + // ordered hash set + keepOldest will keep the latest inserted entity (in case of duplicates) + var latestEntities = new OrderedHashSet(keepOldest: true); + foreach (var entity in entities.OrderByDescending(entity => entity.Item1.UpdateDate)) + latestEntities.Add(entity.Item1); + + foreach (var arg in args) + { + // event object can either be a single object or an enumerable of objects + // try to get as an enumerable, get null if it's not + var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(arg.EventObject); + if (eventObjects == null) + { + // single object + // look for a more recent entity for that object, and replace if any + // works by "equalling" entities ie the more recent one "equals" this one (though different object) + var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, arg.EventObject)); + if (foundEntity != null) + arg.EventObject = foundEntity; + } + else + { + // enumerable of objects + // same as above but for each object + var updated = false; + for (var i = 0; i < eventObjects.Count; i++) + { + var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, eventObjects[i])); + if (foundEntity == null) continue; + eventObjects[i] = foundEntity; + updated = true; + } + + if (updated) + arg.EventObject = eventObjects; + } + } + } + + // determines if a given entity, appearing in a given event definition, should be filtered out, + // considering the entities that have already been visited - an entity is filtered out if it + // appears in another even definition, which superceedes this event definition. + private static bool IsSuperceeded(IEntity entity, EventDefinitionInfos infos, List> entities) + { + //var argType = meta.EventArgsType; + var argType = infos.EventDefinition.Args.GetType(); + + // look for other instances of the same entity, coming from an event args that supercedes other event args, + // ie is marked with the attribute, and is not this event args (cannot supersede itself) + var superceeding = entities + .Where(x => x.Item2.SupersedeTypes.Length > 0 // has the attribute + && x.Item2.EventDefinition.Args.GetType() != argType // is not the same + && Equals(x.Item1, entity)) // same entity + .ToArray(); + + // first time we see this entity = not filtered + if (superceeding.Length == 0) + return false; + + // fixme see notes above + // delete event args does NOT superceedes 'unpublished' event + if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "UnPublished") + return false; + + // found occurences, need to determine if this event args is superceded + if (argType.IsGenericType) + { + // generic, must compare type arguments + var supercededBy = superceeding.FirstOrDefault(x => + x.Item2.SupersedeTypes.Any(y => + // superceeding a generic type which has the same generic type definition + // fixme no matter the generic type parameters? could be different? + y.IsGenericTypeDefinition && y == argType.GetGenericTypeDefinition() + // or superceeding a non-generic type which is ... fixme how is this ever possible? argType *is* generic? + || y.IsGenericTypeDefinition == false && y == argType)); + return supercededBy != null; + } + else + { + // non-generic, can compare types 1:1 + var supercededBy = superceeding.FirstOrDefault(x => + x.Item2.SupersedeTypes.Any(y => y == argType)); + return supercededBy != null; + } + } + + public void ScopeExit(bool completed) + { + if (_events == null) return; + if (completed) + ScopeExitCompleted(); + _events.Clear(); + } + + protected abstract void ScopeExitCompleted(); + } +} diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs index 62c00d7c5b..4a51e76108 100644 --- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs +++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs @@ -1,109 +1,32 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Umbraco.Core.Models; namespace Umbraco.Core.Events { - public class RecycleBinEventArgs : CancellableEventArgs, IEquatable, IDeletingMediaFilesEventArgs + public class RecycleBinEventArgs : CancellableEventArgs, IEquatable { - public RecycleBinEventArgs(Guid nodeObjectType, Dictionary> allPropertyData, bool emptiedSuccessfully) - : base(false) - { - AllPropertyData = allPropertyData; - NodeObjectType = nodeObjectType; - Ids = AllPropertyData.Select(x => x.Key); - RecycleBinEmptiedSuccessfully = emptiedSuccessfully; - Files = new List(); - } - - public RecycleBinEventArgs(Guid nodeObjectType, Dictionary> allPropertyData) - : base(true) - { - AllPropertyData = allPropertyData; - NodeObjectType = nodeObjectType; - Ids = AllPropertyData.Select(x => x.Key); - Files = new List(); - } - - /// - /// Backwards compatibility constructor - /// - /// - /// - /// - /// - internal RecycleBinEventArgs(Guid nodeObjectType, Dictionary> allPropertyData, List files, bool emptiedSuccessfully) - : base(false) - { - AllPropertyData = allPropertyData; - NodeObjectType = nodeObjectType; - Ids = AllPropertyData.Select(x => x.Key); - RecycleBinEmptiedSuccessfully = emptiedSuccessfully; - Files = files; - } - - /// - /// Backwards compatibility constructor - /// - /// - /// - /// - internal RecycleBinEventArgs(Guid nodeObjectType, Dictionary> allPropertyData, List files) - : base(true) - { - AllPropertyData = allPropertyData; - NodeObjectType = nodeObjectType; - Ids = AllPropertyData.Select(x => x.Key); - Files = files; - } - - [Obsolete("Use the other ctor that specifies all property data instead")] - public RecycleBinEventArgs(Guid nodeObjectType, IEnumerable ids, List files, bool emptiedSuccessfully) - : base(false) + public RecycleBinEventArgs(Guid nodeObjectType, EventMessages eventMessages) + : base(true, eventMessages) { NodeObjectType = nodeObjectType; - Ids = ids; - Files = files; - RecycleBinEmptiedSuccessfully = emptiedSuccessfully; } - [Obsolete("Use the other ctor that specifies all property data instead")] - public RecycleBinEventArgs(Guid nodeObjectType, IEnumerable ids, List files) + public RecycleBinEventArgs(Guid nodeObjectType) : base(true) { NodeObjectType = nodeObjectType; - Ids = ids; - Files = files; - } + } + /// - /// Gets the Id of the node object type of the items + /// Gets the Id of the node object type of the items /// being deleted from the Recycle Bin. /// - public Guid NodeObjectType { get; private set; } + public Guid NodeObjectType { get; } - /// - /// Gets the list of Ids for each of the items being deleted from the Recycle Bin. - /// - public IEnumerable Ids { get; private set; } - - /// - /// Gets the list of Files that should be deleted as part - /// of emptying the Recycle Bin. - /// - /// - /// This list can be appended to during an event handling operation, generally this is done based on the property data contained in these event args - /// - public List Files { get; private set; } - - public List MediaFilesToDelete { get { return Files; } } - - /// - /// Gets the list of all property data associated with a content id - /// - public Dictionary> AllPropertyData { get; private set; } - /// /// Boolean indicating whether the Recycle Bin was emptied successfully /// @@ -112,24 +35,18 @@ namespace Umbraco.Core.Events /// /// Boolean indicating whether this event was fired for the Content's Recycle Bin. /// - public bool IsContentRecycleBin - { - get { return NodeObjectType == new Guid(Constants.ObjectTypes.Document); } - } + public bool IsContentRecycleBin => NodeObjectType == Constants.ObjectTypes.Document; /// /// Boolean indicating whether this event was fired for the Media's Recycle Bin. /// - public bool IsMediaRecycleBin - { - get { return NodeObjectType == new Guid(Constants.ObjectTypes.Media); } - } + public bool IsMediaRecycleBin => NodeObjectType == Constants.ObjectTypes.Media; public bool Equals(RecycleBinEventArgs other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && AllPropertyData.Equals(other.AllPropertyData) && Files.Equals(other.Files) && Ids.Equals(other.Ids) && NodeObjectType.Equals(other.NodeObjectType) && RecycleBinEmptiedSuccessfully == other.RecycleBinEmptiedSuccessfully; + return base.Equals(other) && NodeObjectType.Equals(other.NodeObjectType) && RecycleBinEmptiedSuccessfully == other.RecycleBinEmptiedSuccessfully; } public override bool Equals(object obj) @@ -145,9 +62,6 @@ namespace Umbraco.Core.Events unchecked { int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ AllPropertyData.GetHashCode(); - hashCode = (hashCode * 397) ^ Files.GetHashCode(); - hashCode = (hashCode * 397) ^ Ids.GetHashCode(); hashCode = (hashCode * 397) ^ NodeObjectType.GetHashCode(); hashCode = (hashCode * 397) ^ RecycleBinEmptiedSuccessfully.GetHashCode(); return hashCode; @@ -164,4 +78,4 @@ namespace Umbraco.Core.Events return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/RefreshContentEventArgs.cs b/src/Umbraco.Core/Events/RefreshContentEventArgs.cs index a295b239c9..b54c286668 100644 --- a/src/Umbraco.Core/Events/RefreshContentEventArgs.cs +++ b/src/Umbraco.Core/Events/RefreshContentEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Events +namespace Umbraco.Core.Events { - //public class RefreshContentEventArgs : System.ComponentModel.CancelEventArgs { } -} \ No newline at end of file + //public class RefreshContentEventArgs : System.ComponentModel.CancelEventArgs { } +} diff --git a/src/Umbraco.Core/Events/RolesEventArgs.cs b/src/Umbraco.Core/Events/RolesEventArgs.cs new file mode 100644 index 0000000000..3104412f99 --- /dev/null +++ b/src/Umbraco.Core/Events/RolesEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace Umbraco.Core.Events +{ + public class RolesEventArgs : EventArgs + { + public RolesEventArgs(int[] memberIds, string[] roles) + { + MemberIds = memberIds; + Roles = roles; + } + + public int[] MemberIds { get; set; } + public string[] Roles { get; set; } + } +} diff --git a/src/Umbraco.Core/Events/RollbackEventArgs.cs b/src/Umbraco.Core/Events/RollbackEventArgs.cs index cf2189e962..279edef67c 100644 --- a/src/Umbraco.Core/Events/RollbackEventArgs.cs +++ b/src/Umbraco.Core/Events/RollbackEventArgs.cs @@ -1,23 +1,21 @@ -namespace Umbraco.Core.Events +namespace Umbraco.Core.Events { - public class RollbackEventArgs : CancellableObjectEventArgs - { - public RollbackEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) - { - } + public class RollbackEventArgs : CancellableObjectEventArgs + { + public RollbackEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) + { + } - public RollbackEventArgs(TEntity eventObject) : base(eventObject) - { - } + public RollbackEventArgs(TEntity eventObject) : base(eventObject) + { + } - /// - /// The entity being rolledback - /// - public TEntity Entity - { - get { return EventObject; } - } - - - } -} \ No newline at end of file + /// + /// The entity being rolledback + /// + public TEntity Entity + { + get { return EventObject; } + } + } +} diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs index cd19038d8e..a37cb357ca 100644 --- a/src/Umbraco.Core/Events/SaveEventArgs.cs +++ b/src/Umbraco.Core/Events/SaveEventArgs.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace Umbraco.Core.Events { - public class SaveEventArgs : CancellableEnumerableObjectEventArgs - { + public class SaveEventArgs : CancellableEnumerableObjectEventArgs + { /// /// Constructor accepting multiple entities that are used in the saving operation /// @@ -12,28 +12,28 @@ namespace Umbraco.Core.Events /// /// /// - public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) : base(eventObject, canCancel, messages, additionalData) - { - } + { + } - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages) { } - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, EventMessages eventMessages) + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, eventMessages) { } @@ -60,13 +60,13 @@ namespace Umbraco.Core.Events { } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) : base(new List { eventObject }, canCancel, eventMessages) { } @@ -78,44 +78,44 @@ namespace Umbraco.Core.Events /// /// public SaveEventArgs(IEnumerable eventObject, bool canCancel) - : base(eventObject, canCancel) - { - } + : base(eventObject, canCancel) + { + } - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - public SaveEventArgs(IEnumerable eventObject) - : base(eventObject) - { - } + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + public SaveEventArgs(IEnumerable eventObject) + : base(eventObject) + { + } - /// - /// Constructor accepting a single entity instance - /// - /// - public SaveEventArgs(TEntity eventObject) - : base(new List { eventObject }) - { - } + /// + /// Constructor accepting a single entity instance + /// + /// + public SaveEventArgs(TEntity eventObject) + : base(new List { eventObject }) + { + } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public SaveEventArgs(TEntity eventObject, bool canCancel) - : base(new List { eventObject }, canCancel) - { - } + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public SaveEventArgs(TEntity eventObject, bool canCancel) + : base(new List { eventObject }, canCancel) + { + } - /// - /// Returns all entities that were saved during the operation - /// - public IEnumerable SavedEntities - { - get { return EventObject; } - } - } -} \ No newline at end of file + /// + /// Returns all entities that were saved during the operation + /// + public IEnumerable SavedEntities + { + get { return EventObject; } + } + } +} diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs deleted file mode 100644 index eb4f8ec34e..0000000000 --- a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Umbraco.Core.IO; - -namespace Umbraco.Core.Events -{ - /// - /// An IEventDispatcher that queues events, and raise them when the scope - /// exits and has been completed. - /// - internal class ScopeEventDispatcher : ScopeEventDispatcherBase - { - public ScopeEventDispatcher() - : base(true) - { } - - protected override void ScopeExitCompleted() - { - // processing only the last instance of each event... - // this is probably far from perfect, because if eg a content is saved in a list - // and then as a single content, the two events will probably not be de-duplicated, - // but it's better than nothing - - foreach (var e in GetEvents(EventDefinitionFilter.LastIn)) - { - e.RaiseEvent(); - - // separating concerns means that this should probably not be here, - // but then where should it be (without making things too complicated)? - var delete = e.Args as IDeletingMediaFilesEventArgs; - if (delete != null && delete.MediaFilesToDelete.Count > 0) - MediaFileSystem.DeleteMediaFiles(delete.MediaFilesToDelete); - } - } - - private MediaFileSystem _mediaFileSystem; - - private MediaFileSystem MediaFileSystem - { - get - { - return _mediaFileSystem ?? (_mediaFileSystem = FileSystemProviderManager.Current.MediaFileSystem); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs b/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs deleted file mode 100644 index 7115f83eb6..0000000000 --- a/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs +++ /dev/null @@ -1,344 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Events -{ - /// - /// An IEventDispatcher that queues events. - /// - /// - /// Can raise, or ignore, cancelable events, depending on option. - /// Implementations must override ScopeExitCompleted to define what - /// to do with the events when the scope exits and has been completed. - /// If the scope exits without being completed, events are ignored. - /// - public abstract class ScopeEventDispatcherBase : IEventDispatcher - { - //events will be enlisted in the order they are raised - private List _events; - private readonly bool _raiseCancelable; - - protected ScopeEventDispatcherBase(bool raiseCancelable) - { - _raiseCancelable = raiseCancelable; - } - - private List Events { get { return _events ?? (_events = new List()); } } - - public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null) - { - if (eventHandler == null) return args.Cancel; - if (_raiseCancelable == false) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string eventName = null) - where TArgs : CancellableEventArgs - { - if (eventHandler == null) return args.Cancel; - if (_raiseCancelable == false) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) - where TArgs : CancellableEventArgs - { - if (eventHandler == null) return args.Cancel; - if (_raiseCancelable == false) return args.Cancel; - eventHandler(sender, args); - return args.Cancel; - } - - public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null) - { - if (eventHandler == null) return; - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); - } - - public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string eventName = null) - { - if (eventHandler == null) return; - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); - } - - public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string eventName = null) - { - if (eventHandler == null) return; - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); - } - - public IEnumerable GetEvents(EventDefinitionFilter filter) - { - if (_events == null) - return Enumerable.Empty(); - - IReadOnlyList events; - switch (filter) - { - case EventDefinitionFilter.All: - events = _events; - break; - case EventDefinitionFilter.FirstIn: - var l1 = new OrderedHashSet(); - foreach (var e in _events) - l1.Add(e); - events = l1; - break; - case EventDefinitionFilter.LastIn: - var l2 = new OrderedHashSet(keepOldest: false); - foreach (var e in _events) - l2.Add(e); - events = l2; - break; - default: - throw new ArgumentOutOfRangeException("filter", filter, null); - } - - return FilterSupersededAndUpdateToLatestEntity(events); - } - - private class EventDefinitionInfos - { - public IEventDefinition EventDefinition { get; set; } - public Type[] SupersedeTypes { get; set; } - } - - // fixme - // this is way too convoluted, the superceede attribute is used only on DeleteEventargs to specify - // that it superceeds save, publish, move and copy - BUT - publish event args is also used for - // unpublishing and should NOT be superceeded - so really it should not be managed at event args - // level but at event level - // - // what we want is: - // if an entity is deleted, then all Saved, Moved, Copied, Published events prior to this should - // not trigger for the entity - and even though, does it make any sense? making a copy of an entity - // should ... trigger? - // - // not going to refactor it all - we probably want to *always* trigger event but tell people that - // due to scopes, they should not expected eg a saved entity to still be around - however, now, - // going to write a ugly condition to deal with U4-10764 - - // iterates over the events (latest first) and filter out any events or entities in event args that are included - // in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want - // to raise the Saved event (well actually we just don't want to include it in the args for that saved event) - internal static IEnumerable FilterSupersededAndUpdateToLatestEntity(IReadOnlyList events) - { - // keeps the 'latest' entity and associated event data - var entities = new List>(); - - // collects the event definitions - // collects the arguments in result, that require their entities to be updated - var result = new List(); - var resultArgs = new List(); - - // eagerly fetch superceeded arg types for each arg type - var argTypeSuperceeding = events.Select(x => x.Args.GetType()) - .Distinct() - .ToDictionary(x => x, x => x.GetCustomAttributes(false).Select(y => y.SupersededEventArgsType).ToArray()); - - // iterate over all events and filter - // - // process the list in reverse, because events are added in the order they are raised and we want to keep - // the latest (most recent) entities and filter out what is not relevant anymore (too old), eg if an entity - // is Deleted after being Saved, we want to filter out the Saved event - for (var index = events.Count - 1; index >= 0; index--) - { - var def = events[index]; - - var infos = new EventDefinitionInfos - { - EventDefinition = def, - SupersedeTypes = argTypeSuperceeding[def.Args.GetType()] - }; - - var args = def.Args as CancellableObjectEventArgs; - if (args == null) - { - // not a cancellable event arg, include event definition in result - result.Add(def); - } - else - { - // event object can either be a single object or an enumerable of objects - // try to get as an enumerable, get null if it's not - var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject); - if (eventObjects == null) - { - // single object, cast as an IEntity - // if cannot cast, cannot filter, nothing - just include event definition in result - var eventEntity = args.EventObject as IEntity; - if (eventEntity == null) - { - result.Add(def); - continue; - } - - // look for this entity in superceding event args - // found = must be removed (ie not added), else track - if (IsSuperceeded(eventEntity, infos, entities) == false) - { - // track - entities.Add(Tuple.Create(eventEntity, infos)); - - // track result arguments - // include event definition in result - resultArgs.Add(args); - result.Add(def); - } - } - else - { - // enumerable of objects - var toRemove = new List(); - foreach (var eventObject in eventObjects) - { - // extract the event object, cast as an IEntity - // if cannot cast, cannot filter, nothing to do - just leave it in the list & continue - var eventEntity = eventObject as IEntity; - if (eventEntity == null) - continue; - - // look for this entity in superceding event args - // found = must be removed, else track - if (IsSuperceeded(eventEntity, infos, entities)) - toRemove.Add(eventEntity); - else - entities.Add(Tuple.Create(eventEntity, infos)); - } - - // remove superceded entities - foreach (var entity in toRemove) - eventObjects.Remove(entity); - - // if there are still entities in the list, keep the event definition - if (eventObjects.Count > 0) - { - if (toRemove.Count > 0) - { - // re-assign if changed - args.EventObject = eventObjects; - } - - // track result arguments - // include event definition in result - resultArgs.Add(args); - result.Add(def); - } - } - } - } - - // go over all args in result, and update them with the latest instanceof each entity - UpdateToLatestEntities(entities, resultArgs); - - // reverse, since we processed the list in reverse - result.Reverse(); - - return result; - } - - // edits event args to use the latest instance of each entity - private static void UpdateToLatestEntities(IEnumerable> entities, IEnumerable args) - { - // get the latest entities - // ordered hash set + keepOldest will keep the latest inserted entity (in case of duplicates) - var latestEntities = new OrderedHashSet(keepOldest: true); - foreach (var entity in entities.OrderByDescending(entity => entity.Item1.UpdateDate)) - latestEntities.Add(entity.Item1); - - foreach (var arg in args) - { - // event object can either be a single object or an enumerable of objects - // try to get as an enumerable, get null if it's not - var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(arg.EventObject); - if (eventObjects == null) - { - // single object - // look for a more recent entity for that object, and replace if any - // works by "equalling" entities ie the more recent one "equals" this one (though different object) - var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, arg.EventObject)); - if (foundEntity != null) - arg.EventObject = foundEntity; - } - else - { - // enumerable of objects - // same as above but for each object - var updated = false; - for (var i = 0; i < eventObjects.Count; i++) - { - var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, eventObjects[i])); - if (foundEntity == null) continue; - eventObjects[i] = foundEntity; - updated = true; - } - - if (updated) - arg.EventObject = eventObjects; - } - } - } - - // determines if a given entity, appearing in a given event definition, should be filtered out, - // considering the entities that have already been visited - an entity is filtered out if it - // appears in another even definition, which superceedes this event definition. - private static bool IsSuperceeded(IEntity entity, EventDefinitionInfos infos, List> entities) - { - //var argType = meta.EventArgsType; - var argType = infos.EventDefinition.Args.GetType(); - - // look for other instances of the same entity, coming from an event args that supercedes other event args, - // ie is marked with the attribute, and is not this event args (cannot supersede itself) - var superceeding = entities - .Where(x => x.Item2.SupersedeTypes.Length > 0 // has the attribute - && x.Item2.EventDefinition.Args.GetType() != argType // is not the same - && Equals(x.Item1, entity)) // same entity - .ToArray(); - - // first time we see this entity = not filtered - if (superceeding.Length == 0) - return false; - - // fixme see notes above - // delete event args does NOT superceedes 'unpublished' event - if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "UnPublished") - return false; - - // found occurences, need to determine if this event args is superceded - if (argType.IsGenericType) - { - // generic, must compare type arguments - var supercededBy = superceeding.FirstOrDefault(x => - x.Item2.SupersedeTypes.Any(y => - // superceeding a generic type which has the same generic type definition - // fixme no matter the generic type parameters? could be different? - y.IsGenericTypeDefinition && y == argType.GetGenericTypeDefinition() - // or superceeding a non-generic type which is ... fixme how is this ever possible? argType *is* generic? - || y.IsGenericTypeDefinition == false && y == argType)); - return supercededBy != null; - } - else - { - // non-generic, can compare types 1:1 - var supercededBy = superceeding.FirstOrDefault(x => - x.Item2.SupersedeTypes.Any(y => y == argType)); - return supercededBy != null; - } - } - - public void ScopeExit(bool completed) - { - if (_events == null) return; - if (completed) - ScopeExitCompleted(); - _events.Clear(); - } - - protected abstract void ScopeExitCompleted(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/ScopeLifespanMessagesFactory.cs b/src/Umbraco.Core/Events/ScopeLifespanMessagesFactory.cs deleted file mode 100644 index 710ee8a129..0000000000 --- a/src/Umbraco.Core/Events/ScopeLifespanMessagesFactory.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.Events -{ - /// - /// Stores the instance of EventMessages in the current scope. - /// - internal class ScopeLifespanMessagesFactory : IEventMessagesFactory - { - public const string ContextKey = "Umbraco.Core.Events.ScopeLifespanMessagesFactory"; - - private readonly IHttpContextAccessor _contextAccessor; - private readonly IScopeProviderInternal _scopeProvider; - - public static ScopeLifespanMessagesFactory Current { get; private set; } - - public ScopeLifespanMessagesFactory(IHttpContextAccessor contextAccesor, IScopeProvider scopeProvider) - { - if (contextAccesor == null) throw new ArgumentNullException("contextAccesor"); - if (scopeProvider == null) throw new ArgumentNullException("scopeProvider"); - if (scopeProvider is IScopeProviderInternal == false) throw new ArgumentException("Not IScopeProviderInternal.", "scopeProvider"); - _contextAccessor = contextAccesor; - _scopeProvider = (IScopeProviderInternal) scopeProvider; - Current = this; - } - - public EventMessages Get() - { - var messages = GetFromHttpContext(); - if (messages != null) return messages; - - var scope = _scopeProvider.GetAmbientOrNoScope(); - return scope.Messages; - } - - public EventMessages GetFromHttpContext() - { - if (_contextAccessor == null || _contextAccessor.Value == null) return null; - return (EventMessages)_contextAccessor.Value.Items[ContextKey]; - } - - public EventMessages TryGet() - { - var messages = GetFromHttpContext(); - if (messages != null) return messages; - - var scope = _scopeProvider.AmbientScope; - return scope == null ? null : scope.MessagesOrNull; - } - - public void Set(EventMessages messages) - { - if (_contextAccessor.Value == null) return; - _contextAccessor.Value.Items[ContextKey] = messages; - } - } -} diff --git a/src/Umbraco.Core/Events/ScopeLifetimeMessagesFactory.cs b/src/Umbraco.Core/Events/ScopeLifetimeMessagesFactory.cs new file mode 100644 index 0000000000..476789a30e --- /dev/null +++ b/src/Umbraco.Core/Events/ScopeLifetimeMessagesFactory.cs @@ -0,0 +1,60 @@ +// fixme - remove this file +//using System; +//using Umbraco.Core.Scoping; + +//namespace Umbraco.Core.Events +//{ +// /// +// /// Stores the instance of EventMessages in the current scope. +// /// +// internal class ScopeLifetimeMessagesFactory : IEventMessagesFactory +// { +// public const string ContextKey = "Umbraco.Core.Events.ScopeLifetimeMessagesFactory"; + +// // fixme for v8 that one will need to be entirely and massively refactored + +// private readonly IHttpContextAccessor _contextAccessor; +// private readonly IScopeProviderInternal _scopeProvider; + +// public static ScopeLifetimeMessagesFactory Current { get; private set; } + +// public ScopeLifetimeMessagesFactory(IHttpContextAccessor contextAccesor, IScopeProvider scopeProvider) +// { +// if (scopeProvider == null) throw new ArgumentNullException(nameof(scopeProvider)); +// if (scopeProvider is IScopeProviderInternal == false) throw new ArgumentException("Not IScopeProviderInternal.", nameof(scopeProvider)); +// _contextAccessor = contextAccesor ?? throw new ArgumentNullException(nameof(contextAccesor)); +// _scopeProvider = (IScopeProviderInternal) scopeProvider; +// Current = this; +// } + +// public EventMessages Get() +// { +// var messages = GetFromHttpContext(); +// if (messages != null) return messages; + +// var scope = _scopeProvider.GetAmbientOrNoScope(); +// return scope.Messages; +// } + +// public EventMessages GetFromHttpContext() +// { +// if (_contextAccessor == null || _contextAccessor.Value == null) return null; +// return (EventMessages)_contextAccessor.Value.Items[ContextKey]; +// } + +// public EventMessages TryGet() +// { +// var messages = GetFromHttpContext(); +// if (messages != null) return messages; + +// var scope = _scopeProvider.AmbientScope; +// return scope?.MessagesOrNull; +// } + +// public void Set(EventMessages messages) +// { +// if (_contextAccessor.Value == null) return; +// _contextAccessor.Value.Items[ContextKey] = messages; +// } +// } +//} diff --git a/src/Umbraco.Core/Events/SendEmailEventArgs.cs b/src/Umbraco.Core/Events/SendEmailEventArgs.cs index 8c6a2138d5..7b6cfceaaf 100644 --- a/src/Umbraco.Core/Events/SendEmailEventArgs.cs +++ b/src/Umbraco.Core/Events/SendEmailEventArgs.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Mail; namespace Umbraco.Core.Events @@ -10,6 +10,6 @@ namespace Umbraco.Core.Events public SendEmailEventArgs(MailMessage message) { Message = message; - } + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/SendToPublishEventArgs.cs b/src/Umbraco.Core/Events/SendToPublishEventArgs.cs index b246395a92..6895572c78 100644 --- a/src/Umbraco.Core/Events/SendToPublishEventArgs.cs +++ b/src/Umbraco.Core/Events/SendToPublishEventArgs.cs @@ -1,21 +1,21 @@ -namespace Umbraco.Core.Events +namespace Umbraco.Core.Events { - public class SendToPublishEventArgs : CancellableObjectEventArgs - { - public SendToPublishEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) - { - } + public class SendToPublishEventArgs : CancellableObjectEventArgs + { + public SendToPublishEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) + { + } - public SendToPublishEventArgs(TEntity eventObject) : base(eventObject) - { - } + public SendToPublishEventArgs(TEntity eventObject) : base(eventObject) + { + } - /// - /// The entity being sent to publish - /// - public TEntity Entity - { - get { return EventObject; } - } - } -} \ No newline at end of file + /// + /// The entity being sent to publish + /// + public TEntity Entity + { + get { return EventObject; } + } + } +} diff --git a/src/Umbraco.Core/Events/SupersedeEventAttribute.cs b/src/Umbraco.Core/Events/SupersedeEventAttribute.cs index c7a14ea158..30a218a677 100644 --- a/src/Umbraco.Core/Events/SupersedeEventAttribute.cs +++ b/src/Umbraco.Core/Events/SupersedeEventAttribute.cs @@ -1,9 +1,9 @@ -using System; +using System; namespace Umbraco.Core.Events { /// - /// This is used to know if the event arg attributed should supersede another event arg type when + /// This is used to know if the event arg attributed should supersede another event arg type when /// tracking events for the same entity. If one event args supercedes another then the event args that have been superseded /// will mean that the event will not be dispatched or the args will be filtered to exclude the entity. /// @@ -17,4 +17,4 @@ namespace Umbraco.Core.Events SupersededEventArgsType = supersededEventArgsType; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs b/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs new file mode 100644 index 0000000000..bd2e12dc17 --- /dev/null +++ b/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Events +{ + /// + /// A simple/default transient messages factory + /// + internal class TransientEventMessagesFactory : IEventMessagesFactory + { + public EventMessages Get() + { + return new EventMessages(); + } + + public EventMessages GetOrDefault() + { + return null; + } + } +} diff --git a/src/Umbraco.Core/Events/TransientMessagesFactory.cs b/src/Umbraco.Core/Events/TransientMessagesFactory.cs deleted file mode 100644 index 5cd291a37f..0000000000 --- a/src/Umbraco.Core/Events/TransientMessagesFactory.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Core.Events -{ - /// - /// A simple/default transient messages factory - /// - internal class TransientMessagesFactory : IEventMessagesFactory - { - public EventMessages Get() - { - return new EventMessages(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Events/TypedEventHandler.cs b/src/Umbraco.Core/Events/TypedEventHandler.cs index 113bb82f7e..abe75e6da9 100644 --- a/src/Umbraco.Core/Events/TypedEventHandler.cs +++ b/src/Umbraco.Core/Events/TypedEventHandler.cs @@ -1,7 +1,7 @@ -using System; +using System; namespace Umbraco.Core.Events { [Serializable] - public delegate void TypedEventHandler(TSender sender, TEventArgs e); -} \ No newline at end of file + public delegate void TypedEventHandler(TSender sender, TEventArgs e); +} diff --git a/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs b/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs index 301d98c595..13c260bf3e 100644 --- a/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs +++ b/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs @@ -1,31 +1,22 @@ using System.Collections.Generic; -using Umbraco.Core.Packaging.Models; +using Umbraco.Core.Models.Packaging; namespace Umbraco.Core.Events { public class UninstallPackageEventArgs : CancellableObjectEventArgs> { - private readonly MetaData _packageMetaData; - public UninstallPackageEventArgs(TEntity eventObject, bool canCancel) : base(new[] { eventObject }, canCancel) - { - } + { } public UninstallPackageEventArgs(TEntity eventObject, MetaData packageMetaData) : base(new[] { eventObject }) { - _packageMetaData = packageMetaData; + PackageMetaData = packageMetaData; } - public MetaData PackageMetaData - { - get { return _packageMetaData; } - } + public MetaData PackageMetaData { get; } - public IEnumerable UninstallationSummary - { - get { return EventObject; } - } + public IEnumerable UninstallationSummary => EventObject; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Events/UserGroupWithUsers.cs b/src/Umbraco.Core/Events/UserGroupWithUsers.cs new file mode 100644 index 0000000000..b69650d33f --- /dev/null +++ b/src/Umbraco.Core/Events/UserGroupWithUsers.cs @@ -0,0 +1,19 @@ +using System; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Events +{ + internal class UserGroupWithUsers + { + public UserGroupWithUsers(IUserGroup userGroup, IUser[] addedUsers, IUser[] removedUsers) + { + UserGroup = userGroup; + AddedUsers = addedUsers; + RemovedUsers = removedUsers; + } + + public IUserGroup UserGroup { get; } + public IUser[] AddedUsers { get; } + public IUser[] RemovedUsers { get; } + } +} diff --git a/src/Umbraco.Core/Exceptions/ArgumentNullOrEmptyException.cs b/src/Umbraco.Core/Exceptions/ArgumentNullOrEmptyException.cs new file mode 100644 index 0000000000..90cc20c404 --- /dev/null +++ b/src/Umbraco.Core/Exceptions/ArgumentNullOrEmptyException.cs @@ -0,0 +1,30 @@ +using System; + +namespace Umbraco.Core.Exceptions +{ + /// + /// The exception that is thrown when a null reference, or an empty argument, + /// is passed to a method that does not accept it as a valid argument. + /// + public class ArgumentNullOrEmptyException : ArgumentNullException + { + /// + /// Initializes a new instance of the class + /// with the name of the parameter that caused this exception. + /// + /// The named of the parameter that caused the exception. + public ArgumentNullOrEmptyException(string paramName) + : base(paramName) + { } + + /// + /// Initializes a new instance of the class + /// with a specified error message and the name of the parameter that caused this exception. + /// + /// The named of the parameter that caused the exception. + /// A message that describes the error. + public ArgumentNullOrEmptyException(string paramName, string message) + : base(paramName, message) + { } + } +} diff --git a/src/Umbraco.Core/Exceptions/AuthorizationException.cs b/src/Umbraco.Core/Exceptions/AuthorizationException.cs new file mode 100644 index 0000000000..955fec270b --- /dev/null +++ b/src/Umbraco.Core/Exceptions/AuthorizationException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Umbraco.Core.Exceptions +{ + public class AuthorizationException : Exception + { + public AuthorizationException() + { } + + public AuthorizationException(string message) + : base(message) + { } + } +} diff --git a/src/Umbraco.Core/Exceptions/BootFailedException.cs b/src/Umbraco.Core/Exceptions/BootFailedException.cs new file mode 100644 index 0000000000..ec07389d37 --- /dev/null +++ b/src/Umbraco.Core/Exceptions/BootFailedException.cs @@ -0,0 +1,33 @@ +using System; + +namespace Umbraco.Core.Exceptions +{ + /// + /// An exception that is thrown if the Umbraco application cannnot boot. + /// + public class BootFailedException : Exception + { + /// + /// Defines the default boot failed exception message. + /// + public const string DefaultMessage = "Boot failed: Umbraco cannot run. Sad. See Umbraco's log file for more details."; + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public BootFailedException(string message) + : base(message) + { } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception which is the cause of this exception. + /// + /// The message that describes the error. + /// The inner exception, or null. + public BootFailedException(string message, Exception inner) + : base(message, inner) + { } + } +} diff --git a/src/Umbraco.Core/Exceptions/ConnectionException.cs b/src/Umbraco.Core/Exceptions/ConnectionException.cs index 3535dd52bf..64fdbeee52 100644 --- a/src/Umbraco.Core/Exceptions/ConnectionException.cs +++ b/src/Umbraco.Core/Exceptions/ConnectionException.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Core.Exceptions { @@ -6,7 +6,7 @@ namespace Umbraco.Core.Exceptions { public ConnectionException(string message, Exception innerException) : base(message, innerException) { - + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Exceptions/DataOperationException.cs b/src/Umbraco.Core/Exceptions/DataOperationException.cs index 60a3ccd4eb..14fefcf9d3 100644 --- a/src/Umbraco.Core/Exceptions/DataOperationException.cs +++ b/src/Umbraco.Core/Exceptions/DataOperationException.cs @@ -18,4 +18,4 @@ namespace Umbraco.Core.Exceptions Operation = operation; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs index bb9becf058..9d154c6a6f 100644 --- a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs +++ b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs @@ -41,4 +41,4 @@ namespace Umbraco.Core.Exceptions } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Exceptions/LightInjectException.cs b/src/Umbraco.Core/Exceptions/LightInjectException.cs new file mode 100644 index 0000000000..03fd9f2f9f --- /dev/null +++ b/src/Umbraco.Core/Exceptions/LightInjectException.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.Exceptions +{ + /// + /// Represents errors that occur due to LightInject. + /// + public class LightInjectException : Exception + { + public LightInjectException(string message) + : base(message) + { } + + public LightInjectException(string message, Exception innerException) + : base(message, innerException) + { } + + private const string LightInjectUnableToResolveType = "Unable to resolve type:"; + private const string LightInjectUnresolvedDependency = "Unresolved dependency "; + private const string LightInjectRequestedDependency = "[Requested dependency:"; + + public static void TryThrow(Exception e) + { + var ex = e as InvalidOperationException; + if (ex == null || ex.Message.StartsWith(LightInjectUnableToResolveType) == false) + return; + + var sb = new StringBuilder(); + sb.AppendLine("Unresolved type: " + ex.Message.Substring(LightInjectUnableToResolveType.Length)); + WriteDetails(ex, sb); + throw new LightInjectException(sb.ToString(), e); + } + + public static void TryThrow(Exception e, Type implementingType) + { + var ex = e as InvalidOperationException; + if (ex == null || ex.Message.StartsWith(LightInjectUnableToResolveType) == false) + return; + + var sb = new StringBuilder(); + sb.AppendLine("Unresolved type: " + ex.Message.Substring(LightInjectUnableToResolveType.Length)); + sb.AppendLine("Implementing type: " + implementingType); + WriteDetails(ex, sb); + throw new LightInjectException(sb.ToString(), e); + } + + private static void WriteDetails(InvalidOperationException ex, StringBuilder sb) + { + ex = ex.InnerException as InvalidOperationException; + while (ex != null) + { + var message = ex.Message; + + if (message.StartsWith(LightInjectUnableToResolveType)) + { + sb.AppendLine("-> Unresolved type: " + message.Substring(LightInjectUnableToResolveType.Length)); + } + else if (message.StartsWith(LightInjectUnresolvedDependency)) + { + var pos = message.InvariantIndexOf(LightInjectRequestedDependency); + sb.AppendLine("-> Unresolved dependency: " + message.Substring(pos + LightInjectRequestedDependency.Length + 1).TrimEnd(']')); + } + + ex = ex.InnerException as InvalidOperationException; + } + } + } +} diff --git a/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs b/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs deleted file mode 100644 index d27d38de9a..0000000000 --- a/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Umbraco.Core.Exceptions -{ - /// - /// An exception that is thrown if the umbraco application cannnot boot - /// - public class UmbracoStartupFailedException : Exception - { - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public UmbracoStartupFailedException(string message) : base(message) - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Exceptions/WontImplementException.cs b/src/Umbraco.Core/Exceptions/WontImplementException.cs new file mode 100644 index 0000000000..7774bf53de --- /dev/null +++ b/src/Umbraco.Core/Exceptions/WontImplementException.cs @@ -0,0 +1,27 @@ +using System; + +namespace Umbraco.Core.Exceptions +{ + /// + /// The exception that is thrown when a requested method or operation is not, and will not be, implemented. + /// + /// The is to be used when some code is not implemented, + /// but should eventually be implemented (i.e. work in progress) and is reported by tools such as ReSharper. + /// This exception is to be used when some code is not implemented, and is not meant to be, for whatever + /// reason. + public class WontImplementException : NotImplementedException + { + /// + /// Initializes a new instance of the class. + /// + public WontImplementException() + { } + + /// + /// Initializes a new instance of the class with a specified reason message. + /// + public WontImplementException(string message) + : base(message) + { } + } +} diff --git a/src/Umbraco.Core/ExpressionExtensions.cs b/src/Umbraco.Core/ExpressionExtensions.cs index 521c7753e0..530ae794ee 100644 --- a/src/Umbraco.Core/ExpressionExtensions.cs +++ b/src/Umbraco.Core/ExpressionExtensions.cs @@ -1,28 +1,24 @@ -using System; -using System.Linq; +using System; using System.Linq.Expressions; namespace Umbraco.Core { - internal static class ExpressionExtensions - { - public static Expression> True() { return f => true; } - public static Expression> False() { return f => false; } + internal static class ExpressionExtensions + { + public static Expression> True() { return f => true; } - public static Expression> Or(this Expression> left, - Expression> right) - { - var invokedExpr = Expression.Invoke(right, left.Parameters.Cast()); - return Expression.Lambda> - (Expression.OrElse(left.Body, invokedExpr), left.Parameters); - } + public static Expression> False() { return f => false; } - public static Expression> And(this Expression> left, - Expression> right) - { - var invokedExpr = Expression.Invoke(right, left.Parameters.Cast()); - return Expression.Lambda> - (Expression.AndAlso(left.Body, invokedExpr), left.Parameters); - } - } -} \ No newline at end of file + public static Expression> Or(this Expression> left, Expression> right) + { + var invokedExpr = Expression.Invoke(right, left.Parameters); + return Expression.Lambda>(Expression.OrElse(left.Body, invokedExpr), left.Parameters); + } + + public static Expression> And(this Expression> left, Expression> right) + { + var invokedExpr = Expression.Invoke(right, left.Parameters); + return Expression.Lambda> (Expression.AndAlso(left.Body, invokedExpr), left.Parameters); + } + } +} diff --git a/src/Umbraco.Core/ExpressionHelper.cs b/src/Umbraco.Core/ExpressionHelper.cs index 75dd63a0df..e03cff4001 100644 --- a/src/Umbraco.Core/ExpressionHelper.cs +++ b/src/Umbraco.Core/ExpressionHelper.cs @@ -1,117 +1,123 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Umbraco.Core.Persistence; namespace Umbraco.Core { - /// - /// A set of helper methods for dealing with expressions - /// - /// - internal static class ExpressionHelper - { - private static readonly ConcurrentDictionary PropertyInfoCache = new ConcurrentDictionary(); + /// + /// A set of helper methods for dealing with expressions + /// + /// + internal static class ExpressionHelper + { + private static readonly ConcurrentDictionary PropertyInfoCache = new ConcurrentDictionary(); - /// - /// Gets a object from an expression. - /// - /// The type of the source. - /// The type of the property. - /// The source. - /// The property lambda. - /// - /// - public static PropertyInfo GetPropertyInfo(this TSource source, Expression> propertyLambda) - { - return GetPropertyInfo(propertyLambda); - } - - /// - /// Gets a object from an expression. - /// - /// The type of the source. - /// The type of the property. - /// The property lambda. - /// - /// - public static PropertyInfo GetPropertyInfo(Expression> propertyLambda) - { - return PropertyInfoCache.GetOrAdd( - new LambdaExpressionCacheKey(propertyLambda), - x => - { - var type = typeof(TSource); - - var member = propertyLambda.Body as MemberExpression; - if (member == null) - { - if (propertyLambda.Body.GetType().Name == "UnaryExpression") - { - // The expression might be for some boxing, e.g. representing a value type like HiveId as an object - // in which case the expression will be Convert(x.MyProperty) - var unary = propertyLambda.Body as UnaryExpression; - if (unary != null) - { - var boxedMember = unary.Operand as MemberExpression; - if (boxedMember == null) - throw new ArgumentException("The type of property could not be infered, try specifying the type parameters explicitly. This can happen if you have tried to access PropertyInfo where the property's return type is a value type, but the expression is trying to convert it to an object"); - else member = boxedMember; - } - } - else throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.", propertyLambda)); - } - - - var propInfo = member.Member as PropertyInfo; - if (propInfo == null) - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - propertyLambda)); - - if (type != propInfo.ReflectedType && - !type.IsSubclassOf(propInfo.ReflectedType)) - throw new ArgumentException(string.Format( - "Expresion '{0}' refers to a property that is not from type {1}.", - propertyLambda, - type)); - - return propInfo; - }); - } - - public static MemberInfo FindProperty(LambdaExpression lambdaExpression) + /// + /// Gets a object from an expression. + /// + /// The type of the source. + /// The type of the property. + /// The source. + /// The property lambda. + /// + /// + public static PropertyInfo GetPropertyInfo(this TSource source, Expression> propertyLambda) { - Expression expressionToCheck = lambdaExpression; + return GetPropertyInfo(propertyLambda); + } - bool done = false; + /// + /// Gets a object from an expression. + /// + /// The type of the source. + /// The type of the property. + /// The property lambda. + /// + /// + public static PropertyInfo GetPropertyInfo(Expression> propertyLambda) + { + return PropertyInfoCache.GetOrAdd( + new LambdaExpressionCacheKey(propertyLambda), + x => + { + var type = typeof(TSource); - while (!done) - { - switch (expressionToCheck.NodeType) - { - case ExpressionType.Convert: - expressionToCheck = ((UnaryExpression)expressionToCheck).Operand; - break; - case ExpressionType.Lambda: - expressionToCheck = ((LambdaExpression)expressionToCheck).Body; - break; - case ExpressionType.MemberAccess: - var memberExpression = ((MemberExpression)expressionToCheck); - - if (memberExpression.Expression.NodeType != ExpressionType.Parameter && - memberExpression.Expression.NodeType != ExpressionType.Convert) + var member = propertyLambda.Body as MemberExpression; + if (member == null) { - throw new ArgumentException(string.Format("Expression '{0}' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.", lambdaExpression), "lambdaExpression"); + if (propertyLambda.Body.GetType().Name == "UnaryExpression") + { + // The expression might be for some boxing, e.g. representing a value type like HiveId as an object + // in which case the expression will be Convert(x.MyProperty) + var unary = propertyLambda.Body as UnaryExpression; + if (unary != null) + { + var boxedMember = unary.Operand as MemberExpression; + if (boxedMember == null) + throw new ArgumentException("The type of property could not be infered, try specifying the type parameters explicitly. This can happen if you have tried to access PropertyInfo where the property's return type is a value type, but the expression is trying to convert it to an object"); + else member = boxedMember; + } + } + else throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.", propertyLambda)); } - MemberInfo member = memberExpression.Member; - return member; + var propInfo = member.Member as PropertyInfo; + if (propInfo == null) + throw new ArgumentException(string.Format( + "Expression '{0}' refers to a field, not a property.", + propertyLambda)); + + if (type != propInfo.ReflectedType && + !type.IsSubclassOf(propInfo.ReflectedType)) + throw new ArgumentException(string.Format( + "Expresion '{0}' refers to a property that is not from type {1}.", + propertyLambda, + type)); + + return propInfo; + }); + } + + public static (MemberInfo, string) FindProperty(LambdaExpression lambda) + { + void Throw() + { + throw new ArgumentException($"Expression '{lambda}' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.", nameof(lambda)); + } + + Expression expr = lambda; + var loop = true; + string alias = null; + while (loop) + { + switch (expr.NodeType) + { + case ExpressionType.Convert: + expr = ((UnaryExpression) expr).Operand; + break; + case ExpressionType.Lambda: + expr = ((LambdaExpression) expr).Body; + break; + case ExpressionType.Call: + var callExpr = (MethodCallExpression) expr; + var method = callExpr.Method; + if (method.DeclaringType != typeof(NPocoSqlExtensions.Statics) || method.Name != "Alias" || !(callExpr.Arguments[1] is ConstantExpression aliasExpr)) + Throw(); + expr = callExpr.Arguments[0]; + alias = aliasExpr.Value.ToString(); + break; + case ExpressionType.MemberAccess: + var memberExpr = (MemberExpression) expr; + if (memberExpr.Expression.NodeType != ExpressionType.Parameter && memberExpr.Expression.NodeType != ExpressionType.Convert) + Throw(); + return (memberExpr.Member, alias); default: - done = true; + loop = false; break; } } @@ -119,64 +125,64 @@ namespace Umbraco.Core throw new Exception("Configuration for members is only supported for top-level individual members on a type."); } - public static IDictionary GetMethodParams(Expression> fromExpression) - { - if (fromExpression == null) return null; - var body = fromExpression.Body as MethodCallExpression; - if (body == null) - return new Dictionary(); + public static IDictionary GetMethodParams(Expression> fromExpression) + { + if (fromExpression == null) return null; + var body = fromExpression.Body as MethodCallExpression; + if (body == null) + return new Dictionary(); - var rVal = new Dictionary(); - var parameters = body.Method.GetParameters().Select(x => x.Name).ToArray(); - var i = 0; - foreach (var argument in body.Arguments) - { - var lambda = Expression.Lambda(argument, fromExpression.Parameters); - var d = lambda.Compile(); - var value = d.DynamicInvoke(new object[1]); - rVal.Add(parameters[i], value); - i++; - } - return rVal; - } + var rVal = new Dictionary(); + var parameters = body.Method.GetParameters().Select(x => x.Name).ToArray(); + var i = 0; + foreach (var argument in body.Arguments) + { + var lambda = Expression.Lambda(argument, fromExpression.Parameters); + var d = lambda.Compile(); + var value = d.DynamicInvoke(new object[1]); + rVal.Add(parameters[i], value); + i++; + } + return rVal; + } - /// - /// Gets a from an provided it refers to a method call. - /// - /// - /// From expression. - /// The or null if is null or cannot be converted to . - /// - public static MethodInfo GetMethodInfo(Expression> fromExpression) - { - if (fromExpression == null) return null; - var body = fromExpression.Body as MethodCallExpression; - return body != null ? body.Method : null; - } + /// + /// Gets a from an provided it refers to a method call. + /// + /// + /// From expression. + /// The or null if is null or cannot be converted to . + /// + public static MethodInfo GetMethodInfo(Expression> fromExpression) + { + if (fromExpression == null) return null; + var body = fromExpression.Body as MethodCallExpression; + return body != null ? body.Method : null; + } - /// - /// Gets the method info. - /// - /// The return type of the method. - /// From expression. - /// - public static MethodInfo GetMethodInfo(Expression> fromExpression) - { - if (fromExpression == null) return null; - var body = fromExpression.Body as MethodCallExpression; - return body != null ? body.Method : null; - } + /// + /// Gets the method info. + /// + /// The return type of the method. + /// From expression. + /// + public static MethodInfo GetMethodInfo(Expression> fromExpression) + { + if (fromExpression == null) return null; + var body = fromExpression.Body as MethodCallExpression; + return body != null ? body.Method : null; + } - /// - /// Gets the method info. - /// - /// The type of the 1. - /// The type of the 2. - /// From expression. - /// - public static MethodInfo GetMethodInfo(Expression> fromExpression) - { - if (fromExpression == null) return null; + /// + /// Gets the method info. + /// + /// The type of the 1. + /// The type of the 2. + /// From expression. + /// + public static MethodInfo GetMethodInfo(Expression> fromExpression) + { + if (fromExpression == null) return null; MethodCallExpression me; switch (fromExpression.Body.NodeType) @@ -192,178 +198,175 @@ namespace Umbraco.Core } return me != null ? me.Method : null; - } + } - /// - /// Gets a from an provided it refers to a method call. - /// - /// The expression. - /// The or null if cannot be converted to . - /// - public static MethodInfo GetMethod(Expression expression) - { - if (expression == null) return null; - return IsMethod(expression) ? (((MethodCallExpression)expression).Method) : null; - } + /// + /// Gets a from an provided it refers to a method call. + /// + /// The expression. + /// The or null if cannot be converted to . + /// + public static MethodInfo GetMethod(Expression expression) + { + if (expression == null) return null; + return IsMethod(expression) ? (((MethodCallExpression)expression).Method) : null; + } - /// - /// Gets a from an provided it refers to member access. - /// - /// - /// The type of the return. - /// From expression. - /// The or null if cannot be converted to . - /// - public static MemberInfo GetMemberInfo(Expression> fromExpression) - { - if (fromExpression == null) return null; + /// + /// Gets a from an provided it refers to member access. + /// + /// + /// The type of the return. + /// From expression. + /// The or null if cannot be converted to . + /// + public static MemberInfo GetMemberInfo(Expression> fromExpression) + { + if (fromExpression == null) return null; - MemberExpression me; - switch (fromExpression.Body.NodeType) - { - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - var ue = fromExpression.Body as UnaryExpression; - me = ((ue != null) ? ue.Operand : null) as MemberExpression; - break; - default: - me = fromExpression.Body as MemberExpression; - break; - } + MemberExpression me; + switch (fromExpression.Body.NodeType) + { + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + var ue = fromExpression.Body as UnaryExpression; + me = ((ue != null) ? ue.Operand : null) as MemberExpression; + break; + default: + me = fromExpression.Body as MemberExpression; + break; + } - return me != null ? me.Member : null; - } + return me != null ? me.Member : null; + } - /// - /// Determines whether the MethodInfo is the same based on signature, not based on the equality operator or HashCode. - /// - /// The left. - /// The right. - /// - /// true if [is method signature equal to] [the specified left]; otherwise, false. - /// - /// - /// This is useful for comparing Expression methods that may contain different generic types - /// - public static bool IsMethodSignatureEqualTo(this MethodInfo left, MethodInfo right) - { - if (left.Equals(right)) - return true; - if (left.DeclaringType != right.DeclaringType) - return false; - if (left.Name != right.Name) - return false; - var leftParams = left.GetParameters(); - var rightParams = right.GetParameters(); - if (leftParams.Length != rightParams.Length) - return false; - for (int i = 0; i < leftParams.Length; i++) - { - //if they are delegate parameters, then assume they match as they could be anything - if (typeof(Delegate).IsAssignableFrom(leftParams[i].ParameterType) && typeof(Delegate).IsAssignableFrom(rightParams[i].ParameterType)) - continue; - //if they are not delegates, then compare the types - if (leftParams[i].ParameterType != rightParams[i].ParameterType) - return false; - } - if (left.ReturnType != right.ReturnType) - return false; - return true; - } + /// + /// Determines whether the MethodInfo is the same based on signature, not based on the equality operator or HashCode. + /// + /// The left. + /// The right. + /// + /// true if [is method signature equal to] [the specified left]; otherwise, false. + /// + /// + /// This is useful for comparing Expression methods that may contain different generic types + /// + public static bool IsMethodSignatureEqualTo(this MethodInfo left, MethodInfo right) + { + if (left.Equals(right)) + return true; + if (left.DeclaringType != right.DeclaringType) + return false; + if (left.Name != right.Name) + return false; + var leftParams = left.GetParameters(); + var rightParams = right.GetParameters(); + if (leftParams.Length != rightParams.Length) + return false; + for (int i = 0; i < leftParams.Length; i++) + { + //if they are delegate parameters, then assume they match as they could be anything + if (typeof(Delegate).IsAssignableFrom(leftParams[i].ParameterType) && typeof(Delegate).IsAssignableFrom(rightParams[i].ParameterType)) + continue; + //if they are not delegates, then compare the types + if (leftParams[i].ParameterType != rightParams[i].ParameterType) + return false; + } + if (left.ReturnType != right.ReturnType) + return false; + return true; + } - /// - /// Gets a from an provided it refers to member access. - /// - /// The expression. - /// - /// - public static MemberInfo GetMember(Expression expression) - { - if (expression == null) return null; - return IsMember(expression) ? (((MemberExpression)expression).Member) : null; - } + /// + /// Gets a from an provided it refers to member access. + /// + /// The expression. + /// + /// + public static MemberInfo GetMember(Expression expression) + { + if (expression == null) return null; + return IsMember(expression) ? (((MemberExpression)expression).Member) : null; + } - /// - /// Gets a from a - /// - /// From method group. - /// - /// - public static MethodInfo GetStaticMethodInfo(Delegate fromMethodGroup) - { - if (fromMethodGroup == null) throw new ArgumentNullException("fromMethodGroup"); + /// + /// Gets a from a + /// + /// From method group. + /// + /// + public static MethodInfo GetStaticMethodInfo(Delegate fromMethodGroup) + { + if (fromMethodGroup == null) throw new ArgumentNullException("fromMethodGroup"); - return fromMethodGroup.Method; - } + return fromMethodGroup.Method; + } - ///// - ///// Formats an unhandled item for representing the expression as a string. - ///// - ///// - ///// The unhandled item. - ///// - ///// - //public static string FormatUnhandledItem(T unhandledItem) where T : class - //{ - // if (unhandledItem == null) throw new ArgumentNullException("unhandledItem"); + ///// + ///// Formats an unhandled item for representing the expression as a string. + ///// + ///// + ///// The unhandled item. + ///// + ///// + //public static string FormatUnhandledItem(T unhandledItem) where T : class + //{ + // if (unhandledItem == null) throw new ArgumentNullException("unhandledItem"); - // var itemAsExpression = unhandledItem as Expression; - // return itemAsExpression != null - // ? FormattingExpressionTreeVisitor.Format(itemAsExpression) - // : unhandledItem.ToString(); - //} + // var itemAsExpression = unhandledItem as Expression; + // return itemAsExpression != null + // ? FormattingExpressionTreeVisitor.Format(itemAsExpression) + // : unhandledItem.ToString(); + //} - /// - /// Determines whether the specified expression is a method. - /// - /// The expression. - /// true if the specified expression is method; otherwise, false. - /// - public static bool IsMethod(Expression expression) - { - return expression != null && typeof(MethodCallExpression).IsAssignableFrom(expression.GetType()); - } + /// + /// Determines whether the specified expression is a method. + /// + /// The expression. + /// true if the specified expression is method; otherwise, false. + /// + public static bool IsMethod(Expression expression) + { + return expression is MethodCallExpression; + } + /// + /// Determines whether the specified expression is a member. + /// + /// The expression. + /// true if the specified expression is member; otherwise, false. + /// + public static bool IsMember(Expression expression) + { + return expression is MemberExpression; + } + /// + /// Determines whether the specified expression is a constant. + /// + /// The expression. + /// true if the specified expression is constant; otherwise, false. + /// + public static bool IsConstant(Expression expression) + { + return expression is ConstantExpression; + } - - /// - /// Determines whether the specified expression is a member. - /// - /// The expression. - /// true if the specified expression is member; otherwise, false. - /// - public static bool IsMember(Expression expression) - { - return expression != null && typeof(MemberExpression).IsAssignableFrom(expression.GetType()); - } - - /// - /// Determines whether the specified expression is a constant. - /// - /// The expression. - /// true if the specified expression is constant; otherwise, false. - /// - public static bool IsConstant(Expression expression) - { - return expression != null && typeof(ConstantExpression).IsAssignableFrom(expression.GetType()); - } - - /// - /// Gets the first value from the supplied arguments of an expression, for those arguments that can be cast to . - /// - /// The arguments. - /// - /// - public static object GetFirstValueFromArguments(IEnumerable arguments) - { - if (arguments == null) return false; - return - arguments.Where(x => typeof(ConstantExpression).IsAssignableFrom(x.GetType())).Cast - ().Select(x => x.Value).DefaultIfEmpty(null).FirstOrDefault(); - } - } -} \ No newline at end of file + /// + /// Gets the first value from the supplied arguments of an expression, for those arguments that can be cast to . + /// + /// The arguments. + /// + /// + public static object GetFirstValueFromArguments(IEnumerable arguments) + { + if (arguments == null) return false; + return + arguments.Where(x => x is ConstantExpression).Cast + ().Select(x => x.Value).DefaultIfEmpty(null).FirstOrDefault(); + } + } +} diff --git a/src/Umbraco.Core/FileResources/Files.Designer.cs b/src/Umbraco.Core/FileResources/Files.Designer.cs index 500f9bf36c..afc88ff636 100644 --- a/src/Umbraco.Core/FileResources/Files.Designer.cs +++ b/src/Umbraco.Core/FileResources/Files.Designer.cs @@ -10,8 +10,8 @@ namespace Umbraco.Core.FileResources { using System; - - + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -23,15 +23,15 @@ namespace Umbraco.Core.FileResources { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Files { - + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Files() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// @@ -45,7 +45,7 @@ namespace Umbraco.Core.FileResources { return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. @@ -59,13 +59,13 @@ namespace Umbraco.Core.FileResources { resourceCulture = value; } } - + /// /// Looks up a localized string similar to <?xml version="1.0"?> /// ///<!-- Blocks public downloading of anything in this folder and sub folders --> /// - ///<configuration> + ///<configuration> /// <system.web> /// <httpHandlers> /// <add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/> diff --git a/src/Umbraco.Core/GuidUdi.cs b/src/Umbraco.Core/GuidUdi.cs index 5f4f119901..d78a33a435 100644 --- a/src/Umbraco.Core/GuidUdi.cs +++ b/src/Umbraco.Core/GuidUdi.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; namespace Umbraco.Core @@ -86,4 +86,4 @@ namespace Umbraco.Core return this; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/HashCodeCombiner.cs b/src/Umbraco.Core/HashCodeCombiner.cs index 5f99c65787..d0bb869cc8 100644 --- a/src/Umbraco.Core/HashCodeCombiner.cs +++ b/src/Umbraco.Core/HashCodeCombiner.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using System.IO; -using System.Linq; namespace Umbraco.Core { @@ -12,7 +11,7 @@ namespace Umbraco.Core /// .Net has a class the same as this: System.Web.Util.HashCodeCombiner and of course it works for all sorts of things /// which we've not included here as we just need a quick easy class for this in order to create a unique /// hash of directories/files to see if they have changed. - /// + /// /// NOTE: It's probably best to not relying on the hashing result across AppDomains! If you need a constant/reliable hash value /// between AppDomains use SHA1. This is perfect for hashing things in a very fast way for a single AppDomain. /// @@ -57,7 +56,7 @@ namespace Umbraco.Core AddDateTime(f.CreationTimeUtc); AddDateTime(f.LastWriteTimeUtc); - //check if it is a file or folder + //check if it is a file or folder var fileInfo = f as FileInfo; if (fileInfo != null) { diff --git a/src/Umbraco.Core/HashCodeHelper.cs b/src/Umbraco.Core/HashCodeHelper.cs index f0f281056d..f3dc7fcd0e 100644 --- a/src/Umbraco.Core/HashCodeHelper.cs +++ b/src/Umbraco.Core/HashCodeHelper.cs @@ -66,7 +66,7 @@ namespace Umbraco.Core } /// - /// Gets a hashcode for a collection for that the order of items + /// Gets a hashcode for a collection for that the order of items /// does not matter. /// So {1, 2, 3} and {3, 2, 1} will get same hash code. /// @@ -88,7 +88,7 @@ namespace Umbraco.Core } /// - /// Alternative way to get a hashcode is to use a fluent + /// Alternative way to get a hashcode is to use a fluent /// interface like this:
/// return 0.CombineHashCode(field1).CombineHashCode(field2). /// CombineHashCode(field3); @@ -101,4 +101,4 @@ namespace Umbraco.Core } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/HashGenerator.cs b/src/Umbraco.Core/HashGenerator.cs index bbb06987f2..f8e45c362d 100644 --- a/src/Umbraco.Core/HashGenerator.cs +++ b/src/Umbraco.Core/HashGenerator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.IO; using System.Security.Cryptography; @@ -10,8 +10,8 @@ namespace Umbraco.Core /// Used to generate a string hash using crypto libraries over multiple objects ///
/// - /// This should be used to generate a reliable hash that survives AppDomain restarts. - /// This will use the crypto libs to generate the hash and will try to ensure that + /// This should be used to generate a reliable hash that survives AppDomain restarts. + /// This will use the crypto libs to generate the hash and will try to ensure that /// strings, etc... are not re-allocated so it's not consuming much memory. /// internal class HashGenerator : DisposableObjectSlim @@ -60,7 +60,7 @@ namespace Umbraco.Core //we could go a step further and s.Normalize() but we're not really dealing with crazy unicode with this class so far. if (s != null) - _writer.Write(s.ToUpperInvariant()); + _writer.Write(s.ToUpperInvariant()); } internal void AddFileSystemItem(FileSystemInfo f) @@ -73,7 +73,7 @@ namespace Umbraco.Core AddDateTime(f.CreationTimeUtc); AddDateTime(f.LastWriteTimeUtc); - //check if it is a file or folder + //check if it is a file or folder var fileInfo = f as FileInfo; if (fileInfo != null) { @@ -151,4 +151,4 @@ namespace Umbraco.Core _ms.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/HideFromTypeFinderAttribute.cs b/src/Umbraco.Core/HideFromTypeFinderAttribute.cs deleted file mode 100644 index cc32654c9c..0000000000 --- a/src/Umbraco.Core/HideFromTypeFinderAttribute.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Umbraco.Core -{ - /// - /// Used to notify the TypeFinder to ignore any class attributed with this during it's discovery - /// - [AttributeUsage(AttributeTargets.Class)] - public sealed class HideFromTypeFinderAttribute : Attribute - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/HttpContextExtensions.cs b/src/Umbraco.Core/HttpContextExtensions.cs index aded3d652b..e370b055a4 100644 --- a/src/Umbraco.Core/HttpContextExtensions.cs +++ b/src/Umbraco.Core/HttpContextExtensions.cs @@ -4,6 +4,20 @@ namespace Umbraco.Core { public static class HttpContextExtensions { + public static T GetContextItem(this HttpContextBase httpContext, string key) + { + if (httpContext == null) return default(T); + if (httpContext.Items[key] == null) return default(T); + var val = httpContext.Items[key].TryConvertTo(); + if (val) return val.Result; + return default(T); + } + + public static T GetContextItem(this HttpContext httpContext, string key) + { + return new HttpContextWrapper(httpContext).GetContextItem(key); + } + public static string GetCurrentRequestIpAddress(this HttpContextBase httpContext) { if (httpContext == null) @@ -42,4 +56,4 @@ namespace Umbraco.Core } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/IApplicationEventHandler.cs b/src/Umbraco.Core/IApplicationEventHandler.cs deleted file mode 100644 index a9521d51b6..0000000000 --- a/src/Umbraco.Core/IApplicationEventHandler.cs +++ /dev/null @@ -1,32 +0,0 @@ -using umbraco.interfaces; - -namespace Umbraco.Core -{ - /// - /// Custom IApplicationStartupHandler that auto subscribes to the applications events - /// - public interface IApplicationEventHandler : IApplicationStartupHandler - { - /// - /// ApplicationContext is created and other static objects that require initialization have been setup - /// - /// - /// - void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext); - - /// - /// All resolvers have been initialized but resolution is not frozen so they can be modified in this method - /// - /// - /// - void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext); - - /// - /// Bootup is completed, this allows you to perform any other bootup logic required for the application. - /// Resolution is frozen so now they can be used to resolve instances. - /// - /// - /// - void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/IBootManager.cs b/src/Umbraco.Core/IBootManager.cs deleted file mode 100644 index d1adfb7bf4..0000000000 --- a/src/Umbraco.Core/IBootManager.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace Umbraco.Core -{ - /// - /// A bootstrapper interface for the Umbraco application - /// - public interface IBootManager - { - /// - /// Fires first in the application startup process before any customizations can occur - /// - /// - IBootManager Initialize(); - - /// - /// Fires after initialization and calls the callback to allow for customizations to occur - /// - /// - /// - IBootManager Startup(Action afterStartup); - - /// - /// Fires after startup and calls the callback once customizations are locked - /// - /// - /// - IBootManager Complete(Action afterComplete); - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/IEmailSender.cs b/src/Umbraco.Core/IEmailSender.cs index 7e6565a53b..f2c5fd744d 100644 --- a/src/Umbraco.Core/IEmailSender.cs +++ b/src/Umbraco.Core/IEmailSender.cs @@ -10,4 +10,4 @@ namespace Umbraco.Core { Task SendAsync(MailMessage message); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/IHttpContextAccessor.cs b/src/Umbraco.Core/IHttpContextAccessor.cs deleted file mode 100644 index a0873c78a9..0000000000 --- a/src/Umbraco.Core/IHttpContextAccessor.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Web; - -namespace Umbraco.Core -{ - public interface IHttpContextAccessor - { - HttpContextBase Value { get; } - } -} diff --git a/src/Umbraco.Core/IO/FileSecurityException.cs b/src/Umbraco.Core/IO/FileSecurityException.cs index 8358b5efd2..df5891910c 100644 --- a/src/Umbraco.Core/IO/FileSecurityException.cs +++ b/src/Umbraco.Core/IO/FileSecurityException.cs @@ -9,12 +9,12 @@ namespace Umbraco.Core.IO { public FileSecurityException() { - + } public FileSecurityException(string message) : base(message) { - + } } } diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index be09f1e310..7946a11528 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -6,7 +6,6 @@ namespace Umbraco.Core.IO { public static class FileSystemExtensions { - /// /// Attempts to open the file at filePath up to maxRetries times, /// with a thread sleep time of sleepPerRetryInMilliseconds between retries. @@ -37,19 +36,6 @@ namespace Umbraco.Core.IO throw new ArgumentException("Retries must be greater than zero"); } - // GetSize has been added to IFileSystem2 but not IFileSystem - public static long GetSize(this IFileSystem fs, string path) - { - var fs2 = fs as IFileSystem2; - if (fs2 != null) return fs2.GetSize(path); - - // this is implementing GetSize for IFileSystem, the old way - using (var file = fs.OpenFile(path)) - { - return file.Length; - } - } - public static void CopyFile(this IFileSystem fs, string path, string newPath) { using (var stream = fs.OpenFile(path)) diff --git a/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs b/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs index f94cee537d..db6a414f29 100644 --- a/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs +++ b/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs @@ -5,7 +5,7 @@ using System.Text; using Umbraco.Core.CodeAnnotations; namespace Umbraco.Core.IO -{ +{ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class FileSystemProviderAttribute : Attribute { diff --git a/src/Umbraco.Core/IO/FileSystemProviderConstants.cs b/src/Umbraco.Core/IO/FileSystemProviderConstants.cs deleted file mode 100644 index a447c4df8e..0000000000 --- a/src/Umbraco.Core/IO/FileSystemProviderConstants.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.CodeAnnotations; - -namespace Umbraco.Core.IO -{ - internal class FileSystemProviderConstants - { - public const string Media = "media"; - } -} diff --git a/src/Umbraco.Core/IO/FileSystemProviderManager.cs b/src/Umbraco.Core/IO/FileSystemProviderManager.cs deleted file mode 100644 index e50322d95c..0000000000 --- a/src/Umbraco.Core/IO/FileSystemProviderManager.cs +++ /dev/null @@ -1,361 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Configuration; -using System.Linq; -using System.Reflection; -using Umbraco.Core.Configuration; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.IO -{ - public class FileSystemProviderManager - { - private readonly IFileSystemProvidersSection _config; - private readonly ConcurrentSet _wrappers = new ConcurrentSet(); - - private readonly ConcurrentDictionary _providerLookup = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _filesystems = new ConcurrentDictionary(); - - private ShadowWrapper _macroPartialFileSystem; - private ShadowWrapper _partialViewsFileSystem; - private ShadowWrapper _macroScriptsFileSystem; - private ShadowWrapper _userControlsFileSystem; - private ShadowWrapper _stylesheetsFileSystem; - private ShadowWrapper _scriptsFileSystem; - private ShadowWrapper _xsltFileSystem; - private ShadowWrapper _masterPagesFileSystem; - private ShadowWrapper _mvcViewsFileSystem; - - #region Singleton & Constructor - - private static volatile FileSystemProviderManager _instance; - private static readonly object _instanceLocker = new object(); - - public static FileSystemProviderManager Current - { - get - { - if (_instance != null) return _instance; - lock (_instanceLocker) - { - return _instance ?? (_instance = new FileSystemProviderManager()); - } - } - } - - /// - /// For tests only, allows setting the value of the singleton "Current" property - /// - /// - public static void SetCurrent(FileSystemProviderManager instance) - { - lock (_instanceLocker) - { - _instance = instance; - } - } - - internal static void ResetCurrent() - { - lock (_instanceLocker) - { - if (_instance != null) - _instance.Reset(); - } - } - - // for tests only, totally unsafe - private void Reset() - { - _wrappers.Clear(); - _providerLookup.Clear(); - _filesystems.Clear(); - CreateWellKnownFileSystems(); - } - - private IScopeProviderInternal ScopeProvider - { - // this is bad, but enough for now, and we'll refactor - // in v8 when we'll get rid of this class' singleton - // beware: means that we capture the "current" scope provider - take care in tests! - get { return ApplicationContext.Current == null ? null : ApplicationContext.Current.ScopeProvider as IScopeProviderInternal; } - } - - /// - /// Constructor that can be used for tests - /// - /// - public FileSystemProviderManager(IFileSystemProvidersSection configSection) - { - if (configSection == null) throw new ArgumentNullException("configSection"); - _config = configSection; - CreateWellKnownFileSystems(); - } - - /// - /// Default constructor that will read the config from the locally found config section - /// - public FileSystemProviderManager() - { - _config = (FileSystemProvidersSection)ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders"); - CreateWellKnownFileSystems(); - } - - private void CreateWellKnownFileSystems() - { - var macroPartialFileSystem = new PhysicalFileSystem(SystemDirectories.MacroPartials); - var partialViewsFileSystem = new PhysicalFileSystem(SystemDirectories.PartialViews); - var macroScriptsFileSystem = new PhysicalFileSystem(SystemDirectories.MacroScripts); - var userControlsFileSystem = new PhysicalFileSystem(SystemDirectories.UserControls); - var stylesheetsFileSystem = new PhysicalFileSystem(SystemDirectories.Css); - var scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts); - var xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt); - var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); - var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); - - _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", ScopeProvider); - _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", ScopeProvider); - _macroScriptsFileSystem = new ShadowWrapper(macroScriptsFileSystem, "macroScripts", ScopeProvider); - _userControlsFileSystem = new ShadowWrapper(userControlsFileSystem, "usercontrols", ScopeProvider); - _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", ScopeProvider); - _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", ScopeProvider); - _xsltFileSystem = new ShadowWrapper(xsltFileSystem, "xslt", ScopeProvider); - _masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", ScopeProvider); - _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", ScopeProvider); - - // filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again - MediaFileSystem = GetFileSystemProvider(); - } - - #endregion - - #region Well-Known FileSystems - - public IFileSystem2 MacroPartialsFileSystem { get { return _macroPartialFileSystem; } } - public IFileSystem2 PartialViewsFileSystem { get { return _partialViewsFileSystem; } } - // Legacy /macroScripts folder - public IFileSystem2 MacroScriptsFileSystem { get { return _macroScriptsFileSystem; } } - // Legacy /usercontrols folder - public IFileSystem2 UserControlsFileSystem { get { return _userControlsFileSystem; } } - public IFileSystem2 StylesheetsFileSystem { get { return _stylesheetsFileSystem; } } - public IFileSystem2 ScriptsFileSystem { get { return _scriptsFileSystem; } } - public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem; } } - public IFileSystem2 MasterPagesFileSystem { get { return _mvcViewsFileSystem; } } - public IFileSystem2 MvcViewsFileSystem { get { return _mvcViewsFileSystem; } } - public MediaFileSystem MediaFileSystem { get; private set; } - - #endregion - - #region Providers - - /// - /// used to cache the lookup of how to construct this object so we don't have to reflect each time. - /// - private class ProviderConstructionInfo - { - public object[] Parameters { get; set; } - public ConstructorInfo Constructor { get; set; } - //public string ProviderAlias { get; set; } - } - - /// - /// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem. - /// - /// The alias of the strongly-typed filesystem. - /// The non-typed filesystem supporting the strongly-typed filesystem with the specified alias. - /// This method should not be used directly, used instead. - public IFileSystem GetUnderlyingFileSystemProvider(string alias) - { - return GetUnderlyingFileSystemProvider(alias, null); - } - - /// - /// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem. - /// - /// The alias of the strongly-typed filesystem. - /// /// A fallback creator for the filesystem. - /// The non-typed filesystem supporting the strongly-typed filesystem with the specified alias. - /// This method should not be used directly, used instead. - private IFileSystem GetUnderlyingFileSystemProvider(string alias, Func fallback) - { - // either get the constructor info from cache or create it and add to cache - var ctorInfo = _providerLookup.GetOrAdd(alias, _ => GetUnderlyingFileSystemCtor(alias, fallback)); - return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters); - } - - private IFileSystem GetUnderlyingFileSystemNoCache(string alias, Func fallback) - { - var ctorInfo = GetUnderlyingFileSystemCtor(alias, fallback); - return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters); - } - - private ProviderConstructionInfo GetUnderlyingFileSystemCtor(string alias, Func fallback) - { - // get config - IFileSystemProviderElement providerConfig; - - if (_config.Providers.TryGetValue(alias, out providerConfig) == false) - { - if (fallback != null) return null; - throw new ArgumentException(string.Format("No provider found with alias {0}.", alias)); - } - - // get the filesystem type - var providerType = Type.GetType(providerConfig.Type); - if (providerType == null) - throw new InvalidOperationException(string.Format("Could not find type {0}.", providerConfig.Type)); - - // ensure it implements IFileSystem - if (providerType.IsAssignableFrom(typeof(IFileSystem))) - throw new InvalidOperationException(string.Format("Type {0} does not implement IFileSystem.", providerType.FullName)); - - // find a ctor matching the config parameters - var paramCount = providerConfig.Parameters != null ? providerConfig.Parameters.Count : 0; - var constructor = providerType.GetConstructors().SingleOrDefault(x - => x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.Keys.Contains(y.Name))); - if (constructor == null) - throw new InvalidOperationException(string.Format("Type {0} has no ctor matching the {1} configuration parameter(s).", providerType.FullName, paramCount)); - - var parameters = new object[paramCount]; - - if (providerConfig.Parameters != null) - { - var allKeys = providerConfig.Parameters.Keys.ToArray(); - for (var i = 0; i < paramCount; i++) - parameters[i] = providerConfig.Parameters[allKeys[i]]; - } - - return new ProviderConstructionInfo - { - Constructor = constructor, - Parameters = parameters, - //ProviderAlias = s - }; - } - - /// - /// Gets a strongly-typed filesystem. - /// - /// The type of the filesystem. - /// A strongly-typed filesystem of the specified type. - /// - /// Ideally, this should cache the instances, but that would break backward compatibility, so we - /// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller - /// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains - /// its own shadow and having multiple instances would lead to inconsistencies. - /// Note that any filesystem created by this method *after* shadowing begins, will *not* be - /// shadowing (and an exception will be thrown by the ShadowWrapper). - /// - public TFileSystem GetFileSystemProvider() - where TFileSystem : FileSystemWrapper - { - return GetFileSystemProvider(null); - } - - /// - /// Gets a strongly-typed filesystem. - /// - /// The type of the filesystem. - /// A fallback creator for the inner filesystem. - /// A strongly-typed filesystem of the specified type. - /// - /// The fallback creator is used only if nothing is configured. - /// Ideally, this should cache the instances, but that would break backward compatibility, so we - /// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller - /// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains - /// its own shadow and having multiple instances would lead to inconsistencies. - /// Note that any filesystem created by this method *after* shadowing begins, will *not* be - /// shadowing (and an exception will be thrown by the ShadowWrapper). - /// - public TFileSystem GetFileSystemProvider(Func fallback) - where TFileSystem : FileSystemWrapper - { - var alias = GetFileSystemAlias(); - return (TFileSystem) _filesystems.GetOrAdd(alias, _ => - { - // gets the inner fs, create the strongly-typed fs wrapping the inner fs, register & return - // so we are double-wrapping here - // could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe - var innerFs = GetUnderlyingFileSystemNoCache(alias, fallback); - var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias, ScopeProvider); - var fs = (IFileSystem2) Activator.CreateInstance(typeof (TFileSystem), shadowWrapper); - _wrappers.Add(shadowWrapper); // keeping a reference to the wrapper - return fs; - }); - } - - private string GetFileSystemAlias() - { - var fsType = typeof(TFileSystem); - - // validate the ctor - var constructor = fsType.GetConstructors().SingleOrDefault(x - => x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom(x.GetParameters().Single().ParameterType)); - if (constructor == null) - throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + "."); - - // find the attribute and get the alias - var attr = (FileSystemProviderAttribute)fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).SingleOrDefault(); - if (attr == null) - throw new InvalidOperationException("Type " + fsType.FullName + "is missing the required FileSystemProviderAttribute."); - - return attr.Alias; - } - - #endregion - - #region Shadow - - internal ICompletable Shadow(Guid id) - { - var typed = _wrappers.ToArray(); - var wrappers = new ShadowWrapper[typed.Length + 9]; - var i = 0; - while (i < typed.Length) wrappers[i] = typed[i++]; - wrappers[i++] = _macroPartialFileSystem; - wrappers[i++] = _macroScriptsFileSystem; - wrappers[i++] = _partialViewsFileSystem; - wrappers[i++] = _stylesheetsFileSystem; - wrappers[i++] = _scriptsFileSystem; - wrappers[i++] = _userControlsFileSystem; - wrappers[i++] = _xsltFileSystem; - wrappers[i++] = _masterPagesFileSystem; - wrappers[i] = _mvcViewsFileSystem; - - return new ShadowFileSystems(id, wrappers); - } - - #endregion - - private class ConcurrentSet - where T : class - { - private readonly HashSet _set = new HashSet(); - - public void Add(T item) - { - lock (_set) - { - _set.Add(item); - } - } - - public void Clear() - { - lock (_set) - { - _set.Clear(); - } - } - - public T[] ToArray() - { - lock (_set) - { - return _set.ToArray(); - } - } - } - } -} diff --git a/src/Umbraco.Core/IO/FileSystemWrapper.cs b/src/Umbraco.Core/IO/FileSystemWrapper.cs index 97b8a2f8f6..e9272893e4 100644 --- a/src/Umbraco.Core/IO/FileSystemWrapper.cs +++ b/src/Umbraco.Core/IO/FileSystemWrapper.cs @@ -1,132 +1,118 @@ -using System; +using System; using System.Collections.Generic; using System.IO; namespace Umbraco.Core.IO { - /// - /// All custom file systems that are based upon another IFileSystem should inherit from FileSystemWrapper - /// - /// - /// An IFileSystem is generally used as a base file system, for example like a PhysicalFileSystem or an S3FileSystem. - /// Then, other custom file systems are wrapped upon these files systems like MediaFileSystem, etc... All of the custom - /// file systems must inherit from FileSystemWrapper. - /// - /// This abstract class just wraps the 'real' IFileSystem object passed in to its constructor. - /// - public abstract class FileSystemWrapper : IFileSystem2 - { - protected FileSystemWrapper(IFileSystem wrapped) - { - Wrapped = wrapped; - } + /// + /// All custom file systems that are based upon another IFileSystem should inherit from FileSystemWrapper + /// + /// + /// An IFileSystem is generally used as a base file system, for example like a PhysicalFileSystem or an S3FileSystem. + /// Then, other custom file systems are wrapped upon these files systems like MediaFileSystem, etc... All of the custom + /// file systems must inherit from FileSystemWrapper. + /// + /// This abstract class just wraps the 'real' IFileSystem object passed in to its constructor. + /// + public abstract class FileSystemWrapper : IFileSystem + { + protected FileSystemWrapper(IFileSystem wrapped) + { + Wrapped = wrapped; + } internal IFileSystem Wrapped { get; set; } - public IEnumerable GetDirectories(string path) - { - return Wrapped.GetDirectories(path); - } - - public void DeleteDirectory(string path) - { - Wrapped.DeleteDirectory(path); - } - - public void DeleteDirectory(string path, bool recursive) - { - Wrapped.DeleteDirectory(path, recursive); - } - - public bool DirectoryExists(string path) - { - return Wrapped.DirectoryExists(path); - } - - public void AddFile(string path, Stream stream) - { - Wrapped.AddFile(path, stream); - } - - public void AddFile(string path, Stream stream, bool overrideExisting) - { - Wrapped.AddFile(path, stream, overrideExisting); - } - - public IEnumerable GetFiles(string path) - { - return Wrapped.GetFiles(path); - } - - public IEnumerable GetFiles(string path, string filter) - { - return Wrapped.GetFiles(path, filter); - } - - public Stream OpenFile(string path) - { - return Wrapped.OpenFile(path); - } - - public void DeleteFile(string path) - { - Wrapped.DeleteFile(path); - } - - public bool FileExists(string path) - { - return Wrapped.FileExists(path); - } - - public string GetRelativePath(string fullPathOrUrl) - { - return Wrapped.GetRelativePath(fullPathOrUrl); - } - - public string GetFullPath(string path) - { - return Wrapped.GetFullPath(path); - } - - public string GetUrl(string path) - { - return Wrapped.GetUrl(path); - } - - public DateTimeOffset GetLastModified(string path) - { - return Wrapped.GetLastModified(path); - } - - public DateTimeOffset GetCreated(string path) - { - return Wrapped.GetCreated(path); - } - - // explicitely implementing - not breaking - long IFileSystem2.GetSize(string path) - { - var wrapped2 = Wrapped as IFileSystem2; - return wrapped2 == null ? Wrapped.GetSize(path) : wrapped2.GetSize(path); - } - - // explicitely implementing - not breaking - bool IFileSystem2.CanAddPhysical - { - get - { - var wrapped2 = Wrapped as IFileSystem2; - return wrapped2 != null && wrapped2.CanAddPhysical; - } + public IEnumerable GetDirectories(string path) + { + return Wrapped.GetDirectories(path); } - // explicitely implementing - not breaking - void IFileSystem2.AddFile(string path, string physicalPath, bool overrideIfExists, bool copy) - { - var wrapped2 = Wrapped as IFileSystem2; - if (wrapped2 == null) - throw new NotSupportedException(); - wrapped2.AddFile(path, physicalPath, overrideIfExists, copy); - } + public void DeleteDirectory(string path) + { + Wrapped.DeleteDirectory(path); + } + + public void DeleteDirectory(string path, bool recursive) + { + Wrapped.DeleteDirectory(path, recursive); + } + + public bool DirectoryExists(string path) + { + return Wrapped.DirectoryExists(path); + } + + public void AddFile(string path, Stream stream) + { + Wrapped.AddFile(path, stream); + } + + public void AddFile(string path, Stream stream, bool overrideExisting) + { + Wrapped.AddFile(path, stream, overrideExisting); + } + + public IEnumerable GetFiles(string path) + { + return Wrapped.GetFiles(path); + } + + public IEnumerable GetFiles(string path, string filter) + { + return Wrapped.GetFiles(path, filter); + } + + public Stream OpenFile(string path) + { + return Wrapped.OpenFile(path); + } + + public void DeleteFile(string path) + { + Wrapped.DeleteFile(path); + } + + public bool FileExists(string path) + { + return Wrapped.FileExists(path); + } + + public string GetRelativePath(string fullPathOrUrl) + { + return Wrapped.GetRelativePath(fullPathOrUrl); + } + + public string GetFullPath(string path) + { + return Wrapped.GetFullPath(path); + } + + public string GetUrl(string path) + { + return Wrapped.GetUrl(path); + } + + public DateTimeOffset GetLastModified(string path) + { + return Wrapped.GetLastModified(path); + } + + public DateTimeOffset GetCreated(string path) + { + return Wrapped.GetCreated(path); + } + + public long GetSize(string path) + { + return Wrapped.GetSize(path); + } + + public bool CanAddPhysical => Wrapped.CanAddPhysical; + + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + Wrapped.AddFile(path, physicalPath, overrideIfExists, copy); + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs new file mode 100644 index 0000000000..39e7fa30fc --- /dev/null +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -0,0 +1,415 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.IO +{ + public class FileSystems + { + private readonly IFileSystemProvidersSection _config; + private readonly ConcurrentSet _wrappers = new ConcurrentSet(); + private readonly ILogger _logger; + + private readonly ConcurrentDictionary _providerLookup = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _filesystems = new ConcurrentDictionary(); + + // wrappers for shadow support + private ShadowWrapper _macroPartialFileSystem; + private ShadowWrapper _partialViewsFileSystem; + private ShadowWrapper _stylesheetsFileSystem; + private ShadowWrapper _scriptsFileSystem; + private ShadowWrapper _xsltFileSystem; + private ShadowWrapper _masterPagesFileSystem; + private ShadowWrapper _mvcViewsFileSystem; + private ShadowWrapper _javaScriptLibraryFileSystem; + + // well-known file systems lazy initialization + private object _wkfsLock = new object(); + private bool _wkfsInitialized; + private object _wkfsObject; + + private MediaFileSystem _mediaFileSystem; + + + //fixme - is this needed to be a managed file system? seems irrelevant since it won't ever be moved and is only used in one place in code + private IFileSystem _javascriptLibraryFileSystem; + + #region Constructor + + // DI wants a public ctor + // but IScopeProviderInternal is not public + public FileSystems(ILogger logger) + { + // fixme inject config section => can be used by tests + _config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders"); + _logger = logger; + } + + // for tests only, totally unsafe + internal void Reset() + { + _wrappers.Clear(); + _providerLookup.Clear(); + _filesystems.Clear(); + Volatile.Write(ref _wkfsInitialized, false); + } + + internal Func IsScoped { get; set; } = () => false; + + #endregion + + #region Well-Known FileSystems + + public IFileSystem MacroPartialsFileSystem + { + get + { + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + return _macroPartialFileSystem; + } + } + + public IFileSystem PartialViewsFileSystem + { + get + { + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + return _partialViewsFileSystem; + } + } + + public IFileSystem StylesheetsFileSystem + { + get + { + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + return _stylesheetsFileSystem; + } + } + + public IFileSystem ScriptsFileSystem + { + get + { + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + return _scriptsFileSystem; + } + } + + public IFileSystem XsltFileSystem + { + get + { + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + return _xsltFileSystem; + } + } + + public IFileSystem MasterPagesFileSystem + { + get + { + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + return _masterPagesFileSystem;// fixme - see 7.6?! + } + } + + public IFileSystem MvcViewsFileSystem + { + get + { + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + return _mvcViewsFileSystem; + } + } + + public MediaFileSystem MediaFileSystem + { + get + { + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + return _mediaFileSystem; + } + } + + //fixme - is this needed to be a managed file system? seems irrelevant since it won't ever be moved and is only used in one place in code + internal IFileSystem JavaScriptLibraryFileSystem + { + get + { + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + return _javascriptLibraryFileSystem; + } + } + + private void EnsureWellKnownFileSystems() + { + LazyInitializer.EnsureInitialized(ref _wkfsObject, ref _wkfsInitialized, ref _wkfsLock, CreateWellKnownFileSystems); + } + + // need to return something to LazyInitializer.EnsureInitialized + // but it does not really matter what we return - here, null + private object CreateWellKnownFileSystems() + { + var macroPartialFileSystem = new PhysicalFileSystem(SystemDirectories.MacroPartials); + var partialViewsFileSystem = new PhysicalFileSystem(SystemDirectories.PartialViews); + var stylesheetsFileSystem = new PhysicalFileSystem(SystemDirectories.Css); + var scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts); + var xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt); + var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); + var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); + var javaScriptLibraryFileSystem = new PhysicalFileSystem(SystemDirectories.JavaScriptLibrary); + + _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", () => IsScoped()); + _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", () => IsScoped()); + _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", () => IsScoped()); + _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", () => IsScoped()); + _xsltFileSystem = new ShadowWrapper(xsltFileSystem, "xslt", () => IsScoped()); + _masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", () => IsScoped()); + _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", () => IsScoped()); + _javascriptLibraryFileSystem = new ShadowWrapper(javaScriptLibraryFileSystem, "Lib", () => IsScoped()); + + // filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again + _mediaFileSystem = GetFileSystemProvider(); + + return null; + } + + #endregion + + #region Providers + + /// + /// used to cache the lookup of how to construct this object so we don't have to reflect each time. + /// + private class ProviderConstructionInfo + { + public object[] Parameters { get; set; } + public ConstructorInfo Constructor { get; set; } + //public string ProviderAlias { get; set; } + } + + /// + /// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem. + /// + /// The alias of the strongly-typed filesystem. + /// The non-typed filesystem supporting the strongly-typed filesystem with the specified alias. + /// This method should not be used directly, used instead. + public IFileSystem GetUnderlyingFileSystemProvider(string alias) + { + return GetUnderlyingFileSystemProvider(alias, null); + } + + /// + /// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem. + /// + /// The alias of the strongly-typed filesystem. + /// A fallback creator for the filesystem. + /// The non-typed filesystem supporting the strongly-typed filesystem with the specified alias. + /// This method should not be used directly, used instead. + internal IFileSystem GetUnderlyingFileSystemProvider(string alias, Func fallback) + { + // either get the constructor info from cache or create it and add to cache + var ctorInfo = _providerLookup.GetOrAdd(alias, _ => GetUnderlyingFileSystemCtor(alias, fallback)); + return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters); + } + + private IFileSystem GetUnderlyingFileSystemNoCache(string alias, Func fallback) + { + var ctorInfo = GetUnderlyingFileSystemCtor(alias, fallback); + return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters); + } + + private ProviderConstructionInfo GetUnderlyingFileSystemCtor(string alias, Func fallback) + { + // get config + if (_config.Providers.TryGetValue(alias, out var providerConfig) == false) + { + if (fallback != null) return null; + throw new ArgumentException($"No provider found with alias {alias}."); + } + + // get the filesystem type + var providerType = Type.GetType(providerConfig.Type); + if (providerType == null) + throw new InvalidOperationException($"Could not find type {providerConfig.Type}."); + + // ensure it implements IFileSystem + if (providerType.IsAssignableFrom(typeof (IFileSystem))) + throw new InvalidOperationException($"Type {providerType.FullName} does not implement IFileSystem."); + + // find a ctor matching the config parameters + var paramCount = providerConfig.Parameters?.Count ?? 0; + var constructor = providerType.GetConstructors().SingleOrDefault(x + => x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.Keys.Contains(y.Name))); + if (constructor == null) + throw new InvalidOperationException($"Type {providerType.FullName} has no ctor matching the {paramCount} configuration parameter(s)."); + + var parameters = new object[paramCount]; + if (providerConfig.Parameters != null) // keeps ReSharper happy + { + var allKeys = providerConfig.Parameters.Keys.ToArray(); + for (var i = 0; i < paramCount; i++) + parameters[i] = providerConfig.Parameters[allKeys[i]]; + } + + return new ProviderConstructionInfo + { + Constructor = constructor, + Parameters = parameters, + //ProviderAlias = s + }; + } + + /// + /// Gets a strongly-typed filesystem. + /// + /// The type of the filesystem. + /// A strongly-typed filesystem of the specified type. + /// + /// Ideally, this should cache the instances, but that would break backward compatibility, so we + /// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller + /// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains + /// its own shadow and having multiple instances would lead to inconsistencies. + /// Note that any filesystem created by this method *after* shadowing begins, will *not* be + /// shadowing (and an exception will be thrown by the ShadowWrapper). + /// + // fixme - should it change for v8? + public TFileSystem GetFileSystemProvider() + where TFileSystem : FileSystemWrapper + { + return GetFileSystemProvider(null); + } + + /// + /// Gets a strongly-typed filesystem. + /// + /// The type of the filesystem. + /// A fallback creator for the inner filesystem. + /// A strongly-typed filesystem of the specified type. + /// + /// The fallback creator is used only if nothing is configured. + /// Ideally, this should cache the instances, but that would break backward compatibility, so we + /// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller + /// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains + /// its own shadow and having multiple instances would lead to inconsistencies. + /// Note that any filesystem created by this method *after* shadowing begins, will *not* be + /// shadowing (and an exception will be thrown by the ShadowWrapper). + /// + public TFileSystem GetFileSystemProvider(Func fallback) + where TFileSystem : FileSystemWrapper + { + var alias = GetFileSystemAlias(); + return (TFileSystem)_filesystems.GetOrAdd(alias, _ => + { + // gets the inner fs, create the strongly-typed fs wrapping the inner fs, register & return + // so we are double-wrapping here + // could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe + var innerFs = GetUnderlyingFileSystemNoCache(alias, fallback); + var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias, () => IsScoped()); + var fs = (IFileSystem) Activator.CreateInstance(typeof(TFileSystem), shadowWrapper); + _wrappers.Add(shadowWrapper); // keeping a reference to the wrapper + return fs; + }); + } + + private string GetFileSystemAlias() + { + var fsType = typeof(TFileSystem); + + // validate the ctor + var constructor = fsType.GetConstructors().SingleOrDefault(x + => x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom(x.GetParameters().Single().ParameterType)); + if (constructor == null) + throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + "."); + + // find the attribute and get the alias + var attr = (FileSystemProviderAttribute)fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).SingleOrDefault(); + if (attr == null) + throw new InvalidOperationException("Type " + fsType.FullName + "is missing the required FileSystemProviderAttribute."); + + return attr.Alias; + } + + #endregion + + #region Shadow + + // note + // shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one + // global shadow for the entire application, so great care should be taken to ensure that the + // application is *not* doing anything else when using a shadow. + // shadow applies to well-known filesystems *only* - at the moment, any other filesystem that would + // be created directly (via ctor) or via GetFileSystem is *not* shadowed. + + // shadow must be enabled in an app event handler before anything else ie before any filesystem + // is actually created and used - after, it is too late - enabling shadow has a neglictible perfs + // impact. + // NO! by the time an app event handler is instanciated it is already too late, see note in ctor. + //internal void EnableShadow() + //{ + // if (_mvcViewsFileSystem != null) // test one of the fs... + // throw new InvalidOperationException("Cannot enable shadow once filesystems have been created."); + // _shadowEnabled = true; + //} + + internal ICompletable Shadow(Guid id) + { + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); + + var typed = _wrappers.ToArray(); + var wrappers = new ShadowWrapper[typed.Length + 7]; + var i = 0; + while (i < typed.Length) wrappers[i] = typed[i++]; + wrappers[i++] = _macroPartialFileSystem; + wrappers[i++] = _partialViewsFileSystem; + wrappers[i++] = _stylesheetsFileSystem; + wrappers[i++] = _scriptsFileSystem; + wrappers[i++] = _xsltFileSystem; + wrappers[i++] = _masterPagesFileSystem; + wrappers[i] = _mvcViewsFileSystem; + + return new ShadowFileSystems(id, wrappers, _logger); + } + + #endregion + + private class ConcurrentSet + where T : class + { + private readonly HashSet _set = new HashSet(); + + public void Add(T item) + { + lock (_set) + { + _set.Add(item); + } + } + + public void Clear() + { + lock (_set) + { + _set.Clear(); + } + } + + public T[] ToArray() + { + lock (_set) + { + return _set.ToArray(); + } + } + } + } +} diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index 0d96dd0af1..0baedfd479 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -143,15 +143,29 @@ namespace Umbraco.Core.IO /// . /// DateTimeOffset GetCreated(string path); - } - // this should be part of IFileSystem but we don't want to change the interface - public interface IFileSystem2 : IFileSystem - { + /// + /// Gets the size of a file. + /// + /// The path to the file. + /// The size (in bytes) of the file. long GetSize(string path); + /// + /// Gets a value indicating whether the filesystem can add/copy + /// a file which is on a physical filesystem. + /// + /// In other words, whether the filesystem can copy/move a file + /// that is on local disk, in a fast and efficient way. bool CanAddPhysical { get; } + /// + /// Adds a file which is on a physical filesystem. + /// + /// The path to the file. + /// The absolute physical path to the source file. + /// A value indicating what to do if the file already exists. + /// A value indicating whether to move (default) or copy. void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false); // TODO: implement these diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index a58d983667..0a61e8e542 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -6,14 +6,13 @@ using System.IO; using System.Configuration; using System.Linq; using System.Web; -using System.Text.RegularExpressions; using System.Web.Hosting; -using Umbraco.Core.Configuration; +using System.IO.Compression; namespace Umbraco.Core.IO { - public static class IOHelper - { + public static class IOHelper + { /// /// Gets or sets a value forcing Umbraco to consider it is non-hosted. /// @@ -23,25 +22,23 @@ namespace Umbraco.Core.IO private static string _rootDir = ""; // static compiled regex for faster performance - private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - + //private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + /// /// Gets a value indicating whether Umbraco is hosted. /// - public static bool IsHosted - { - get - { - return ForceNotHosted == false && (HttpContext.Current != null || HostingEnvironment.IsHosted); - } - } + public static bool IsHosted => !ForceNotHosted && (HttpContext.Current != null || HostingEnvironment.IsHosted); - public static char DirSepChar + public static char DirSepChar => Path.DirectorySeparatorChar; + + internal static void UnZip(string zipFilePath, string unPackDirectory, bool deleteZipFile) { - get - { - return Path.DirectorySeparatorChar; - } + // Unzip + var tempDir = unPackDirectory; + Directory.CreateDirectory(tempDir); + ZipFile.ExtractToDirectory(zipFilePath, unPackDirectory); + if (deleteZipFile) + File.Delete(zipFilePath); } //helper to try and match the old path to a new virtual one @@ -58,10 +55,16 @@ namespace Umbraco.Core.IO return retval; } + public static string ResolveVirtualUrl(string path) + { + if (string.IsNullOrWhiteSpace(path)) return path; + return path.StartsWith("~/") ? ResolveUrl(path) : path; + } + //Replaces tildes with the root dir public static string ResolveUrl(string virtualPath) { - if (virtualPath.StartsWith("~")) + if (virtualPath.StartsWith("~")) return virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/"); else if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute)) return virtualPath; @@ -85,33 +88,6 @@ namespace Umbraco.Core.IO } } - [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")] - internal static string ResolveUrlsFromTextString(string text) - { - if (UmbracoConfig.For.UmbracoSettings().Content.ResolveUrlsFromTextString) - { - using (DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) - { - // find all relative urls (ie. urls that contain ~) - var tags = ResolveUrlPattern.Matches(text); - - foreach (Match tag in tags) - { - string url = ""; - if (tag.Groups[1].Success) - url = tag.Groups[1].Value; - - if (String.IsNullOrEmpty(url) == false) - { - string resolvedUrl = (url.Substring(0, 1) == "/") ? ResolveUrl(url.Substring(1)) : ResolveUrl(url); - text = text.Replace(url, resolvedUrl); - } - } - } - } - return text; - } - public static string MapPath(string path, bool useHttpContext) { if (path == null) throw new ArgumentNullException("path"); @@ -123,8 +99,8 @@ namespace Umbraco.Core.IO { return path; } - // Check that we even have an HttpContext! otherwise things will fail anyways - // http://umbraco.codeplex.com/workitem/30946 + // Check that we even have an HttpContext! otherwise things will fail anyways + // http://umbraco.codeplex.com/workitem/30946 if (useHttpContext && HttpContext.Current != null) { @@ -135,11 +111,11 @@ namespace Umbraco.Core.IO return HostingEnvironment.MapPath("~/" + path.TrimStart('/')); } - var root = GetRootDirectorySafe(); - var newPath = path.TrimStart('~', '/').Replace('/', IOHelper.DirSepChar); - var retval = root + IOHelper.DirSepChar.ToString(CultureInfo.InvariantCulture) + newPath; + var root = GetRootDirectorySafe(); + var newPath = path.TrimStart('~', '/').Replace('/', IOHelper.DirSepChar); + var retval = root + IOHelper.DirSepChar.ToString(CultureInfo.InvariantCulture) + newPath; - return retval; + return retval; } public static string MapPath(string path) @@ -147,12 +123,17 @@ namespace Umbraco.Core.IO return MapPath(path, true); } - //use a tilde character instead of the complete path - internal static string ReturnPath(string settingsKey, string standardPath, bool useTilde) + public static string MapPathIfVirtual(string path) { - string retval = ConfigurationManager.AppSettings[settingsKey]; + return path.StartsWith("~/") ? MapPath(path) : path; + } - if (String.IsNullOrEmpty(retval)) + //use a tilde character instead of the complete path + internal static string ReturnPath(string settingsKey, string standardPath, bool useTilde) + { + var retval = ConfigurationManager.AppSettings[settingsKey]; + + if (string.IsNullOrEmpty(retval)) retval = standardPath; return retval.TrimEnd('/'); @@ -228,20 +209,6 @@ namespace Umbraco.Core.IO return false; } - /// - /// Validates that the current filepath matches one of several directories where the user is allowed to edit a file. - /// - /// The filepath to validate. - /// The valid directories. - /// True, if the filepath is valid, else an exception is thrown. - /// The filepath is invalid. - internal static bool ValidateEditPath(string filePath, IEnumerable validDirs) - { - if (VerifyEditPath(filePath, validDirs) == false) - throw new FileSecurityException(String.Format("The filepath '{0}' is not within an allowed directory for this type of files", filePath.Replace(MapPath(SystemDirectories.Root), ""))); - return true; - } - /// /// Verifies that the current filepath has one of several authorized extensions. /// @@ -292,10 +259,10 @@ namespace Umbraco.Core.IO return _rootDir; } - var codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - var baseDirectory = Path.GetDirectoryName(path); + var codeBase = Assembly.GetExecutingAssembly().CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + var baseDirectory = Path.GetDirectoryName(path); if (String.IsNullOrEmpty(baseDirectory)) throw new Exception("No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured."); @@ -339,9 +306,9 @@ namespace Umbraco.Core.IO /// The supplied path should be the absolute path to the root of the umbraco site. /// internal static void SetRootDirectory(string rootPath) - { + { _rootDir = rootPath; - } + } /// /// Check to see if filename passed has any special chars in it and strips them to create a safe filename. Used to overcome an issue when Umbraco is used in IE in an intranet environment. @@ -354,24 +321,22 @@ namespace Umbraco.Core.IO return filePath.ToSafeFileName(); } - public static void EnsurePathExists(string path) - { - var absolutePath = IOHelper.MapPath(path); - if (Directory.Exists(absolutePath) == false) - Directory.CreateDirectory(absolutePath); - } + public static void EnsurePathExists(string path) + { + var absolutePath = MapPath(path); + if (Directory.Exists(absolutePath) == false) + Directory.CreateDirectory(absolutePath); + } - public static void EnsureFileExists(string path, string contents) - { - var absolutePath = IOHelper.MapPath(path); - if (File.Exists(absolutePath) == false) - { - using (var writer = File.CreateText(absolutePath)) - { - writer.Write(contents); - } - } + public static void EnsureFileExists(string path, string contents) + { + var absolutePath = IOHelper.MapPath(path); + if (File.Exists(absolutePath)) return; + using (var writer = File.CreateText(absolutePath)) + { + writer.Write(contents); + } } /// @@ -383,9 +348,9 @@ namespace Umbraco.Core.IO internal static bool IsFullPath(this string path) { return string.IsNullOrWhiteSpace(path) == false - && path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1 - && Path.IsPathRooted(path) - && Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false; + && path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1 + && Path.IsPathRooted(path) + && Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false; } /// diff --git a/src/Umbraco.Core/IO/MasterPageHelper.cs b/src/Umbraco.Core/IO/MasterPageHelper.cs index 9560274b63..e619ec98d8 100644 --- a/src/Umbraco.Core/IO/MasterPageHelper.cs +++ b/src/Umbraco.Core/IO/MasterPageHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Services; +using Umbraco.Core.Xml; namespace Umbraco.Core.IO { @@ -57,8 +58,8 @@ namespace Umbraco.Core.IO using (var ms = new MemoryStream(withBom)) { - _masterPageFileSystem.AddFile(filePath, ms, true); - } + _masterPageFileSystem.AddFile(filePath, ms, true); + } } else { @@ -66,7 +67,7 @@ namespace Umbraco.Core.IO using (var tr = new StreamReader(s, Encoding.UTF8)) { masterpageContent = tr.ReadToEnd(); - tr.Close(); + tr.Close(); } } @@ -82,7 +83,7 @@ namespace Umbraco.Core.IO // using (var tr = new StreamReader(s)) // { // masterpageContent = tr.ReadToEnd(); - // tr.Close(); + // tr.Close(); // } // } @@ -196,7 +197,7 @@ namespace Umbraco.Core.IO private void UpdateChildTemplates(ITemplate template, string currentAlias, ITemplateRepository templateRepo) { //if we have a Old Alias if the alias and therefor the masterpage file name has changed... - //so before we save the new masterfile, we'll clear the old one, so we don't up with + //so before we save the new masterfile, we'll clear the old one, so we don't up with //Unused masterpage files if (string.IsNullOrEmpty(currentAlias) == false && currentAlias != template.Alias) { @@ -280,7 +281,7 @@ namespace Umbraco.Core.IO // } // //we have a Old Alias if the alias and therefor the masterpage file name has changed... - // //so before we save the new masterfile, we'll clear the old one, so we don't up with + // //so before we save the new masterfile, we'll clear the old one, so we don't up with // //Unused masterpage files // if (string.IsNullOrEmpty(currentAlias) == false && currentAlias != template.Alias) // { @@ -294,7 +295,7 @@ namespace Umbraco.Core.IO // UpdateMasterPageFile(t, null, templateRepo); // } - // //then kill the old file.. + // //then kill the old file.. // var oldFile = GetFilePath(currentAlias); // if (_masterPageFileSystem.FileExists(oldFile)) // _masterPageFileSystem.DeleteFile(oldFile); @@ -341,7 +342,7 @@ namespace Umbraco.Core.IO return masterTemplate; } - private static string GetMasterContentElement(ITemplate template) + internal static string GetMasterContentElement(ITemplate template) { if (template.MasterTemplateAlias.IsNullOrWhiteSpace() == false) { @@ -355,96 +356,96 @@ namespace Umbraco.Core.IO } - //internal static string EnsureMasterPageSyntax(string templateAlias, string masterPageContent) - //{ - // ReplaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", true); - // ReplaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", false); + internal static string EnsureMasterPageSyntax(string templateAlias, string masterPageContent) + { + ReplaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", true); + ReplaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", false); - // // Parse the design for macros - // ReplaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", true); - // ReplaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", false); + // Parse the design for macros + ReplaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", true); + ReplaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", false); - // // Parse the design for load childs - // masterPageContent = masterPageContent.Replace("", CreateDefaultPlaceHolder(templateAlias)) - // .Replace("", CreateDefaultPlaceHolder(templateAlias)); - // // Parse the design for aspnet forms - // GetAspNetMasterPageForm(ref masterPageContent, templateAlias); - // masterPageContent = masterPageContent.Replace("", ""); - // // Parse the design for aspnet heads - // masterPageContent = masterPageContent.Replace("", String.Format("", templateAlias.Replace(" ", ""))); - // masterPageContent = masterPageContent.Replace("", ""); - // return masterPageContent; - //} + // Parse the design for load childs + masterPageContent = masterPageContent.Replace("", CreateDefaultPlaceHolder(templateAlias)) + .Replace("", CreateDefaultPlaceHolder(templateAlias)); + // Parse the design for aspnet forms + GetAspNetMasterPageForm(ref masterPageContent, templateAlias); + masterPageContent = masterPageContent.Replace("", ""); + // Parse the design for aspnet heads + masterPageContent = masterPageContent.Replace("", String.Format("", templateAlias.Replace(" ", ""))); + masterPageContent = masterPageContent.Replace("", ""); + return masterPageContent; + } - //private static void GetAspNetMasterPageForm(ref string design, string templateAlias) - //{ - // var formElement = Regex.Match(design, GetElementRegExp("?ASPNET_FORM", false), RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + private static void GetAspNetMasterPageForm(ref string design, string templateAlias) + { + var formElement = Regex.Match(design, GetElementRegExp("?ASPNET_FORM", false), RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - // if (string.IsNullOrEmpty(formElement.Value) == false) - // { - // string formReplace = String.Format("
", templateAlias.Replace(" ", "")); - // if (formElement.Groups.Count == 0) - // { - // formReplace += ""; - // } - // design = design.Replace(formElement.Value, formReplace); - // } - //} + if (string.IsNullOrEmpty(formElement.Value) == false) + { + string formReplace = String.Format("", templateAlias.Replace(" ", "")); + if (formElement.Groups.Count == 0) + { + formReplace += ""; + } + design = design.Replace(formElement.Value, formReplace); + } + } - //private static string CreateDefaultPlaceHolder(string templateAlias) - //{ - // return String.Format("", templateAlias.Replace(" ", "")); - //} + private static string CreateDefaultPlaceHolder(string templateAlias) + { + return String.Format("", templateAlias.Replace(" ", "")); + } - //private static void ReplaceElement(ref string design, string elementName, string newElementName, bool checkForQuotes) - //{ - // var m = - // Regex.Matches(design, GetElementRegExp(elementName, checkForQuotes), - // RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + private static void ReplaceElement(ref string design, string elementName, string newElementName, bool checkForQuotes) + { + var m = + Regex.Matches(design, GetElementRegExp(elementName, checkForQuotes), + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - // foreach (Match match in m) - // { - // GroupCollection groups = match.Groups; + foreach (Match match in m) + { + GroupCollection groups = match.Groups; - // // generate new element (compensate for a closing trail on single elements ("/")) - // string elementAttributes = groups[1].Value; - // // test for macro alias - // if (elementName == "?UMBRACO_MACRO") - // { - // var tags = XmlHelper.GetAttributesFromElement(match.Value); - // if (tags["macroAlias"] != null) - // elementAttributes = String.Format(" Alias=\"{0}\"", tags["macroAlias"]) + elementAttributes; - // else if (tags["macroalias"] != null) - // elementAttributes = String.Format(" Alias=\"{0}\"", tags["macroalias"]) + elementAttributes; - // } - // string newElement = "<" + newElementName + " runat=\"server\" " + elementAttributes.Trim() + ">"; - // if (elementAttributes.EndsWith("/")) - // { - // elementAttributes = elementAttributes.Substring(0, elementAttributes.Length - 1); - // } - // else if (groups[0].Value.StartsWith(""; + // generate new element (compensate for a closing trail on single elements ("/")) + string elementAttributes = groups[1].Value; + // test for macro alias + if (elementName == "?UMBRACO_MACRO") + { + var tags = XmlHelper.GetAttributesFromElement(match.Value); + if (tags["macroAlias"] != null) + elementAttributes = String.Format(" Alias=\"{0}\"", tags["macroAlias"]) + elementAttributes; + else if (tags["macroalias"] != null) + elementAttributes = String.Format(" Alias=\"{0}\"", tags["macroalias"]) + elementAttributes; + } + string newElement = "<" + newElementName + " runat=\"server\" " + elementAttributes.Trim() + ">"; + if (elementAttributes.EndsWith("/")) + { + elementAttributes = elementAttributes.Substring(0, elementAttributes.Length - 1); + } + else if (groups[0].Value.StartsWith(""; - // if (checkForQuotes) - // { - // // if it's inside quotes, we'll change element attribute quotes to single quotes - // newElement = newElement.Replace("\"", "'"); - // newElement = String.Format("\"{0}\"", newElement); - // } - // design = design.Replace(match.Value, newElement); - // } - //} + if (checkForQuotes) + { + // if it's inside quotes, we'll change element attribute quotes to single quotes + newElement = newElement.Replace("\"", "'"); + newElement = String.Format("\"{0}\"", newElement); + } + design = design.Replace(match.Value, newElement); + } + } - //private static string GetElementRegExp(string elementName, bool checkForQuotes) - //{ - // if (checkForQuotes) - // return String.Format("\"<[^>\\s]*\\b{0}(\\b[^>]*)>\"", elementName); - // else - // return String.Format("<[^>\\s]*\\b{0}(\\b[^>]*)>", elementName); + private static string GetElementRegExp(string elementName, bool checkForQuotes) + { + if (checkForQuotes) + return String.Format("\"<[^>\\s]*\\b{0}(\\b[^>]*)>\"", elementName); + else + return String.Format("<[^>\\s]*\\b{0}(\\b[^>]*)>", elementName); - //} + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index fc6490e8cd..b96b0d54bc 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -1,15 +1,16 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using LightInject; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Media; using Umbraco.Core.Media.Exif; @@ -17,65 +18,124 @@ using Umbraco.Core.Models; namespace Umbraco.Core.IO { - /// - /// A custom file system provider for media - /// - [FileSystemProvider("media")] - public class MediaFileSystem : FileSystemWrapper - { - private readonly IContentSection _contentConfig; - private readonly UploadAutoFillProperties _uploadAutoFillProperties; - private readonly ILogger _logger; - + /// + /// A custom file system provider for media + /// + [FileSystemProvider("media")] + public class MediaFileSystem : FileSystemWrapper + { private readonly object _folderCounterLock = new object(); private long _folderCounter; private bool _folderCounterInitialized; - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - private static readonly Dictionary DefaultSizes = new Dictionary - { - { 100, "thumb" }, - { 500, "big-thumb" } - }; + // fixme - remove + //private static readonly Dictionary DefaultSizes = new Dictionary + //{ + // { 100, "thumb" }, + // { 500, "big-thumb" } + //}; public MediaFileSystem(IFileSystem wrapped) - : this(wrapped, UmbracoConfig.For.UmbracoSettings().Content, ApplicationContext.Current.ProfilingLogger.Logger) - { } - - public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig, ILogger logger) : base(wrapped) { - _logger = logger; - _contentConfig = contentConfig; - _uploadAutoFillProperties = new UploadAutoFillProperties(this, logger, contentConfig); + // due to how FileSystems is written at the moment, the ctor cannot be used to inject + // dependencies, so we have to rely on property injection for anything we might need + Current.Container.InjectProperties(this); + + UploadAutoFillProperties = new UploadAutoFillProperties(this, Logger, ContentConfig); } - internal UploadAutoFillProperties UploadAutoFillProperties { get { return _uploadAutoFillProperties; } } + [Inject] + internal IContentSection ContentConfig { get; set; } + + [Inject] + internal ILogger Logger { get; set; } + + internal UploadAutoFillProperties UploadAutoFillProperties { get; } // note - this is currently experimental / being developed //public static bool UseTheNewMediaPathScheme { get; set; } public const bool UseTheNewMediaPathScheme = false; - // none of the methods below are used in Core anymore - - [Obsolete("This low-level method should NOT exist.")] - public string GetRelativePath(int propertyId, string fileName) - { - var sep = _contentConfig.UploadAllowDirectories - ? Path.DirectorySeparatorChar - : '-'; - - return propertyId.ToString(CultureInfo.InvariantCulture) + sep + fileName; - } - - [Obsolete("This low-level method should NOT exist.", false)] - public string GetRelativePath(string subfolder, string fileName) + /// + /// Deletes all files passed in. + /// + /// + /// + /// + internal bool DeleteFiles(IEnumerable files, Action onError = null) { - var sep = _contentConfig.UploadAllowDirectories - ? Path.DirectorySeparatorChar - : '-'; + //ensure duplicates are removed + files = files.Distinct(); - return subfolder + sep + fileName; + var allsuccess = true; + var rootRelativePath = GetRelativePath("/"); + + Parallel.ForEach(files, file => + { + try + { + if (file.IsNullOrWhiteSpace()) return; + + var relativeFilePath = GetRelativePath(file); + if (FileExists(relativeFilePath) == false) return; + + var parentDirectory = Path.GetDirectoryName(relativeFilePath); + + // don't want to delete the media folder if not using directories. + if (ContentConfig.UploadAllowDirectories && parentDirectory != rootRelativePath) + { + //issue U4-771: if there is a parent directory the recursive parameter should be true + DeleteDirectory(parentDirectory, string.IsNullOrEmpty(parentDirectory) == false); + } + else + { + DeleteFile(file); + } + } + catch (Exception e) + { + onError?.Invoke(file, e); + allsuccess = false; + } + }); + + return allsuccess; + } + + public void DeleteMediaFiles(IEnumerable files) + { + files = files.Distinct(); + + Parallel.ForEach(files, file => + { + try + { + if (file.IsNullOrWhiteSpace()) return; + if (FileExists(file) == false) return; + DeleteFile(file); + + if (UseTheNewMediaPathScheme == false) + { + // old scheme: filepath is "/" OR "-" + // remove the directory if any + var dir = Path.GetDirectoryName(file); + if (string.IsNullOrWhiteSpace(dir) == false) + DeleteDirectory(dir, true); + } + else + { + // new scheme: path is "/" where xuid is a combination of cuid and puid + // remove the directory + var dir = Path.GetDirectoryName(file); + DeleteDirectory(dir, true); + } + } + catch (Exception e) + { + Logger.Error("Failed to delete attached file \"" + file + "\".", e); + } + }); } #region Media Path @@ -91,7 +151,7 @@ namespace Umbraco.Core.IO public string GetMediaPath(string filename, Guid cuid, Guid puid) { filename = Path.GetFileName(filename); - if (filename == null) throw new ArgumentException("Cannot become a safe filename.", "filename"); + if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); string folder; @@ -111,7 +171,7 @@ namespace Umbraco.Core.IO } var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories - ? Path.Combine(folder, filename) + ? Path.Combine(folder, filename).Replace('\\', '/') : folder + "-" + filename; return filepath; @@ -123,7 +183,7 @@ namespace Umbraco.Core.IO var bytes2 = guid2.ToByteArray(); var bytes = new byte[bytes1.Length]; for (var i = 0; i < bytes1.Length; i++) - bytes[i] = (byte)(bytes1[i] ^ bytes2[i]); + bytes[i] = (byte) (bytes1[i] ^ bytes2[i]); return bytes; } @@ -143,7 +203,7 @@ namespace Umbraco.Core.IO return GetMediaPath(filename, cuid, puid); filename = Path.GetFileName(filename); - if (filename == null) throw new ArgumentException("Cannot become a safe filename.", "filename"); + if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); filename = IOHelper.SafeFileName(filename.ToLowerInvariant()); // old scheme, with a previous path @@ -159,7 +219,7 @@ namespace Umbraco.Core.IO // ReSharper disable once AssignNullToNotNullAttribute var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories - ? Path.Combine(folder, filename).Replace('\\', '/') + ? Path.Combine(folder, filename) : folder + "-" + filename; return filepath; @@ -203,7 +263,7 @@ namespace Umbraco.Core.IO #region Associated Media Files /// - /// Stores a media file. + /// Stores a media file associated to a property of a content item. /// /// The content item owning the media file. /// The property type owning the media file. @@ -213,20 +273,19 @@ namespace Umbraco.Core.IO /// The filesystem-relative filepath to the media file. /// /// The file is considered "owned" by the content/propertyType. - /// If an is provided then that file (and thumbnails) is deleted - /// before the new file is saved, and depending on the media path scheme, the folder - /// may be reused for the new file. + /// If an is provided then that file (and associated thumbnails if any) is deleted + /// before the new file is saved, and depending on the media path scheme, the folder may be reused for the new file. /// public string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath) { - if (content == null) throw new ArgumentNullException("content"); - if (propertyType == null) throw new ArgumentNullException("propertyType"); - if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException("Null or empty.", "filename"); - if (filestream == null) throw new ArgumentNullException("filestream"); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (propertyType == null) throw new ArgumentNullException(nameof(propertyType)); + if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentNullOrEmptyException(nameof(filename)); + if (filestream == null) throw new ArgumentNullException(nameof(filestream)); // clear the old file, if any if (string.IsNullOrWhiteSpace(oldpath) == false) - DeleteFile(oldpath, true); + DeleteFile(oldpath); // get the filepath, store the data // use oldpath as "prevpath" to try and reuse the folder, in original number-based scheme @@ -236,16 +295,7 @@ namespace Umbraco.Core.IO } /// - /// Clears a media file. - /// - /// The filesystem-relative path to the media file. - public new void DeleteFile(string filepath) - { - DeleteFile(filepath, true); - } - - /// - /// Copies a media file. + /// Copies a media file as a new media file, associated to a property of a content item. /// /// The content item owning the copy of the media file. /// The property type owning the copy of the media file. @@ -253,9 +303,9 @@ namespace Umbraco.Core.IO /// The filesystem-relative path to the copy of the media file. public string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath) { - if (content == null) throw new ArgumentNullException("content"); - if (propertyType == null) throw new ArgumentNullException("propertyType"); - if (string.IsNullOrWhiteSpace(sourcepath)) throw new ArgumentException("Null or empty.", "sourcepath"); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (propertyType == null) throw new ArgumentNullException(nameof(propertyType)); + if (string.IsNullOrWhiteSpace(sourcepath)) throw new ArgumentNullOrEmptyException(nameof(sourcepath)); // ensure we have a file to copy if (FileExists(sourcepath) == false) return null; @@ -264,16 +314,10 @@ namespace Umbraco.Core.IO var filename = Path.GetFileName(sourcepath); var filepath = GetMediaPath(filename, content.Key, propertyType.Key); this.CopyFile(sourcepath, filepath); - return filepath; } - /// - /// Gets or creates a property for a content item. - /// - /// The content item. - /// The property type alias. - /// The property. + // gets or creates a property for a content item. private static Property GetProperty(IContentBase content, string propertyTypeAlias) { var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); @@ -289,41 +333,37 @@ namespace Umbraco.Core.IO return property; } - public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream) + // fixme - what's below belongs to the upload property editor, not the media filesystem! + + public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream, int? languageId = null, string segment = null) { var property = GetProperty(content, propertyTypeAlias); - var svalue = property.Value as string; - var oldpath = svalue == null ? null : GetRelativePath(svalue); + var oldpath = property.GetValue(languageId, segment) is string svalue ? GetRelativePath(svalue) : null; var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath); - property.Value = GetUrl(filepath); - SetUploadFile(content, property, filepath, filestream); + property.SetValue(GetUrl(filepath), languageId, segment); + SetUploadFile(content, property, filepath, filestream, languageId, segment); } - public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath) + public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath, int? languageId = null, string segment = null) { var property = GetProperty(content, propertyTypeAlias); - var svalue = property.Value as string; - var oldpath = svalue == null ? null : GetRelativePath(svalue); + // fixme delete? + var oldpath = property.GetValue(languageId, segment) is string svalue ? GetRelativePath(svalue) : null; if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath) - DeleteFile(oldpath, true); - property.Value = GetUrl(filepath); + DeleteFile(oldpath); + property.SetValue(GetUrl(filepath), languageId, segment); using (var filestream = OpenFile(filepath)) { - SetUploadFile(content, property, filepath, filestream); + SetUploadFile(content, property, filepath, filestream, languageId, segment); } } - /// - /// Sets a file for the FileUpload property editor and populates autofill properties - /// - /// - /// - /// - /// - private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream) - { + // sets a file for the FileUpload property editor + // ie generates thumbnails and populates autofill properties + private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream, int? languageId = null, string segment = null) + { // will use filepath for extension, and filestream for length - _uploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream); + UploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, languageId, segment); } #endregion @@ -339,7 +379,7 @@ namespace Umbraco.Core.IO { if (extension == null) return false; extension = extension.TrimStart('.'); - return UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(extension); + return ContentConfig.ImageFileTypes.InvariantContains(extension); } /// @@ -386,339 +426,281 @@ namespace Umbraco.Core.IO #endregion - #region Manage thumbnails + // fixme - remove - // note: this does not find 'custom' thumbnails? - // will find _thumb and _big-thumb but NOT _custom? - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public IEnumerable GetThumbnails(string path) - { - var parentDirectory = Path.GetDirectoryName(path); - var extension = Path.GetExtension(path); + //#region Manage thumbnails - return GetFiles(parentDirectory) - .Where(x => x.StartsWith(path.TrimEnd(extension) + "_thumb") || x.StartsWith(path.TrimEnd(extension) + "_big-thumb")) - .ToList(); - } + //// note: this does not find 'custom' thumbnails? + //// will find _thumb and _big-thumb but NOT _custom? + //public IEnumerable GetThumbnails(string path) + //{ + // var parentDirectory = Path.GetDirectoryName(path); + // var extension = Path.GetExtension(path); - public void DeleteFile(string path, bool deleteThumbnails) - { - base.DeleteFile(path); + // return GetFiles(parentDirectory) + // .Where(x => x.StartsWith(path.TrimEnd(extension) + "_thumb") || x.StartsWith(path.TrimEnd(extension) + "_big-thumb")) + // .ToList(); + //} - if (deleteThumbnails == false) - return; + //public void DeleteFile(string path, bool deleteThumbnails) + //{ + // base.DeleteFile(path); - DeleteThumbnails(path); - } + // if (deleteThumbnails == false) + // return; - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public void DeleteThumbnails(string path) - { - GetThumbnails(path) - .ForEach(x => base.DeleteFile(x)); - } + // DeleteThumbnails(path); + //} - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public void CopyThumbnails(string sourcePath, string targetPath) - { - var targetPathBase = Path.GetDirectoryName(targetPath) ?? ""; - foreach (var sourceThumbPath in GetThumbnails(sourcePath)) - { - var sourceThumbFilename = Path.GetFileName(sourceThumbPath) ?? ""; - var targetThumbPath = Path.Combine(targetPathBase, sourceThumbFilename); - this.CopyFile(sourceThumbPath, targetThumbPath); - } - } + //public void DeleteThumbnails(string path) + //{ + // GetThumbnails(path) + // .ForEach(x => base.DeleteFile(x)); + //} - public void DeleteMediaFiles(IEnumerable files) - { - files = files.Distinct(); + //public void CopyThumbnails(string sourcePath, string targetPath) + //{ + // var targetPathBase = Path.GetDirectoryName(targetPath) ?? ""; + // foreach (var sourceThumbPath in GetThumbnails(sourcePath)) + // { + // var sourceThumbFilename = Path.GetFileName(sourceThumbPath) ?? ""; + // var targetThumbPath = Path.Combine(targetPathBase, sourceThumbFilename); + // this.CopyFile(sourceThumbPath, targetThumbPath); + // } + //} - Parallel.ForEach(files, file => - { - try - { - if (file.IsNullOrWhiteSpace()) return; + //#endregion - if (FileExists(file) == false) return; - DeleteFile(file, true); + //#region GenerateThumbnails - if (UseTheNewMediaPathScheme == false) - { - // old scheme: filepath is "/" OR "-" - // remove the directory if any - var dir = Path.GetDirectoryName(file); - if (string.IsNullOrWhiteSpace(dir) == false) - DeleteDirectory(dir, true); - } - else - { - // new scheme: path is "/" where xuid is a combination of cuid and puid - // remove the directory - var dir = Path.GetDirectoryName(file); - DeleteDirectory(dir, true); - } - } - catch (Exception e) - { - _logger.Error("Failed to delete attached file \"" + file + "\".", e); - } - }); - } + //public IEnumerable GenerateThumbnails(Image image, string filepath, string preValue) + //{ + // if (string.IsNullOrWhiteSpace(preValue)) + // return GenerateThumbnails(image, filepath); + // var additionalSizes = new List(); + // var sep = preValue.Contains(",") ? "," : ";"; + // var values = preValue.Split(new[] { sep }, StringSplitOptions.RemoveEmptyEntries); + // foreach (var value in values) + // { + // int size; + // if (int.TryParse(value, out size)) + // additionalSizes.Add(size); + // } - #endregion + // return GenerateThumbnails(image, filepath, additionalSizes); + //} - #region GenerateThumbnails + //public IEnumerable GenerateThumbnails(Image image, string filepath, IEnumerable additionalSizes = null) + //{ + // var w = image.Width; + // var h = image.Height; - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public IEnumerable GenerateThumbnails( - Image image, - string filepath, - string preValue) - { - if (string.IsNullOrWhiteSpace(preValue)) - return GenerateThumbnails(image, filepath); + // var sizes = additionalSizes == null ? DefaultSizes.Keys : DefaultSizes.Keys.Concat(additionalSizes); - var additionalSizes = new List(); - var sep = preValue.Contains(",") ? "," : ";"; - var values = preValue.Split(new[] { sep }, StringSplitOptions.RemoveEmptyEntries); - foreach (var value in values) - { - int size; - if (int.TryParse(value, out size)) - additionalSizes.Add(size); - } + // // start with default sizes, + // // add additional sizes, + // // filter out duplicates, + // // filter out those that would be larger that the original image + // // and create the thumbnail + // return sizes + // .Distinct() + // .Where(x => w >= x && h >= x) + // .Select(x => GenerateResized(image, filepath, DefaultSizes.ContainsKey(x) ? DefaultSizes[x] : "", x)) + // .ToList(); // now + //} - return GenerateThumbnails(image, filepath, additionalSizes); - } + //public IEnumerable GenerateThumbnails(Stream filestream, string filepath, PropertyType propertyType) + //{ + // // get the original image from the original stream + // if (filestream.CanSeek) filestream.Seek(0, 0); // fixme - what if we cannot seek? + // using (var image = Image.FromStream(filestream)) + // { + // return GenerateThumbnails(image, filepath, propertyType); + // } + //} - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public IEnumerable GenerateThumbnails( - Image image, - string filepath, - IEnumerable additionalSizes = null) - { - var w = image.Width; - var h = image.Height; + //public IEnumerable GenerateThumbnails(Image image, string filepath, PropertyType propertyType) + //{ + // // if the editor is an upload field, check for additional thumbnail sizes + // // that can be defined in the prevalue for the property data type. otherwise, + // // just use the default sizes. + // var sizes = propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias + // ? DataTypeService + // .GetPreValuesByDataTypeId(propertyType.DataTypeDefinitionId) + // .FirstOrDefault() + // : string.Empty; - var sizes = additionalSizes == null ? DefaultSizes.Keys : DefaultSizes.Keys.Concat(additionalSizes); + // return GenerateThumbnails(image, filepath, sizes); + //} - // start with default sizes, - // add additional sizes, - // filter out duplicates, - // filter out those that would be larger that the original image - // and create the thumbnail - return sizes - .Distinct() - .Where(x => w >= x && h >= x) - .Select(x => GenerateResized(image, filepath, DefaultSizes.ContainsKey(x) ? DefaultSizes[x] : "", x)) - .ToList(); // now - } + //#endregion - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public IEnumerable GenerateThumbnails( - Stream filestream, - string filepath, - PropertyType propertyType) - { - // get the original image from the original stream - if (filestream.CanSeek) filestream.Seek(0, 0); - using (var image = Image.FromStream(filestream)) - { - return GenerateThumbnails(image, filepath, propertyType); - } - } + //#region GenerateResized - Generate at resized filepath derived from origin filepath - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public IEnumerable GenerateThumbnails( - Image image, - string filepath, - PropertyType propertyType) - { - // if the editor is an upload field, check for additional thumbnail sizes - // that can be defined in the prevalue for the property data type. otherwise, - // just use the default sizes. - var sizes = propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias - ? ApplicationContext.Current.Services.DataTypeService - .GetPreValuesByDataTypeId(propertyType.DataTypeDefinitionId) - .FirstOrDefault() - : string.Empty; + //public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight) + //{ + // return GenerateResized(originImage, originFilepath, sizeName, maxWidthHeight, -1, -1); + //} - return GenerateThumbnails(image, filepath, sizes); - } + //public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int fixedWidth, int fixedHeight) + //{ + // return GenerateResized(originImage, originFilepath, sizeName, -1, fixedWidth, fixedHeight); + //} - #endregion + //public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight, int fixedWidth, int fixedHeight) + //{ + // if (string.IsNullOrWhiteSpace(sizeName)) + // sizeName = "UMBRACOSYSTHUMBNAIL"; + // var extension = Path.GetExtension(originFilepath) ?? string.Empty; + // var filebase = originFilepath.TrimEnd(extension); + // var resizedFilepath = filebase + "_" + sizeName + extension; - #region GenerateResized - Generate at resized filepath derived from origin filepath + // return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, fixedWidth, fixedHeight); + //} - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight) - { - return GenerateResized(originImage, originFilepath, sizeName, maxWidthHeight, -1, -1); - } + //#endregion - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int fixedWidth, int fixedHeight) - { - return GenerateResized(originImage, originFilepath, sizeName, -1, fixedWidth, fixedHeight); - } + //#region GenerateResizedAt - Generate at specified resized filepath - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight, int fixedWidth, int fixedHeight) - { - if (string.IsNullOrWhiteSpace(sizeName)) - sizeName = "UMBRACOSYSTHUMBNAIL"; - var extension = Path.GetExtension(originFilepath) ?? string.Empty; - var filebase = originFilepath.TrimEnd(extension); - var resizedFilepath = filebase + "_" + sizeName + extension; + //public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight) + //{ + // return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, -1, -1); + //} - return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, fixedWidth, fixedHeight); - } + //public ResizedImage GenerateResizedAt(Image originImage, int fixedWidth, int fixedHeight, string resizedFilepath) + //{ + // return GenerateResizedAt(originImage, resizedFilepath, -1, fixedWidth, fixedHeight); + //} - #endregion + //public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight, int fixedWidth, int fixedHeight) + //{ + // // target dimensions + // int width, height; - #region GenerateResizedAt - Generate at specified resized filepath + // // if maxWidthHeight then get ratio + // if (maxWidthHeight > 0) + // { + // var fx = (float)originImage.Size.Width / maxWidthHeight; + // var fy = (float)originImage.Size.Height / maxWidthHeight; + // var f = Math.Max(fx, fy); // fit in thumbnail size + // width = (int)Math.Round(originImage.Size.Width / f); + // height = (int)Math.Round(originImage.Size.Height / f); + // if (width == 0) width = 1; + // if (height == 0) height = 1; + // } + // else if (fixedWidth > 0 && fixedHeight > 0) + // { + // width = fixedWidth; + // height = fixedHeight; + // } + // else + // { + // width = height = 1; + // } - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight) - { - return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, -1, -1); - } + // // create new image with best quality settings + // using (var bitmap = new Bitmap(width, height)) + // using (var graphics = Graphics.FromImage(bitmap)) + // { + // // if the image size is rather large we cannot use the best quality interpolation mode + // // because we'll get out of mem exceptions. So we detect how big the image is and use + // // the mid quality interpolation mode when the image size exceeds our max limit. + // graphics.InterpolationMode = originImage.Width > 5000 || originImage.Height > 5000 + // ? InterpolationMode.Bilinear // mid quality + // : InterpolationMode.HighQualityBicubic; // best quality - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public ResizedImage GenerateResizedAt(Image originImage, int fixedWidth, int fixedHeight, string resizedFilepath) - { - return GenerateResizedAt(originImage, resizedFilepath, -1, fixedWidth, fixedHeight); - } + // // everything else is best-quality + // graphics.SmoothingMode = SmoothingMode.HighQuality; + // graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + // graphics.CompositingQuality = CompositingQuality.HighQuality; - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight, int fixedWidth, int fixedHeight) - { - // target dimensions - int width, height; + // // copy the old image to the new and resize + // var rect = new Rectangle(0, 0, width, height); + // graphics.DrawImage(originImage, rect, 0, 0, originImage.Width, originImage.Height, GraphicsUnit.Pixel); - // if maxWidthHeight then get ratio - if (maxWidthHeight > 0) - { - var fx = (float)originImage.Size.Width / maxWidthHeight; - var fy = (float)originImage.Size.Height / maxWidthHeight; - var f = Math.Max(fx, fy); // fit in thumbnail size - width = (int)Math.Round(originImage.Size.Width / f); - height = (int)Math.Round(originImage.Size.Height / f); - if (width == 0) width = 1; - if (height == 0) height = 1; - } - else if (fixedWidth > 0 && fixedHeight > 0) - { - width = fixedWidth; - height = fixedHeight; - } - else - { - width = height = 1; - } + // // copy metadata + // // fixme - er... no? - // create new image with best quality settings - using (var bitmap = new Bitmap(width, height)) - using (var graphics = Graphics.FromImage(bitmap)) - { - // if the image size is rather large we cannot use the best quality interpolation mode - // because we'll get out of mem exceptions. So we detect how big the image is and use - // the mid quality interpolation mode when the image size exceeds our max limit. - graphics.InterpolationMode = originImage.Width > 5000 || originImage.Height > 5000 - ? InterpolationMode.Bilinear // mid quality - : InterpolationMode.HighQualityBicubic; // best quality + // // get an encoder - based upon the file type + // var extension = (Path.GetExtension(resizedFilepath) ?? "").TrimStart('.').ToLowerInvariant(); + // var encoders = ImageCodecInfo.GetImageEncoders(); + // ImageCodecInfo encoder; + // switch (extension) + // { + // case "png": + // encoder = encoders.Single(t => t.MimeType.Equals("image/png")); + // break; + // case "gif": + // encoder = encoders.Single(t => t.MimeType.Equals("image/gif")); + // break; + // case "tif": + // case "tiff": + // encoder = encoders.Single(t => t.MimeType.Equals("image/tiff")); + // break; + // case "bmp": + // encoder = encoders.Single(t => t.MimeType.Equals("image/bmp")); + // break; + // // TODO: this is dirty, defaulting to jpg but the return value of this thing is used all over the + // // place so left it here, but it needs to not set a codec if it doesn't know which one to pick + // // Note: when fixing this: both .jpg and .jpeg should be handled as extensions + // default: + // encoder = encoders.Single(t => t.MimeType.Equals("image/jpeg")); + // break; + // } - // everything else is best-quality - graphics.SmoothingMode = SmoothingMode.HighQuality; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingQuality = CompositingQuality.HighQuality; + // // set compresion ratio to 90% + // var encoderParams = new EncoderParameters(); + // encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 90L); - // copy the old image to the new and resize - var rect = new Rectangle(0, 0, width, height); - graphics.DrawImage(originImage, rect, 0, 0, originImage.Width, originImage.Height, GraphicsUnit.Pixel); + // // save the new image + // using (var stream = new MemoryStream()) + // { + // bitmap.Save(stream, encoder, encoderParams); + // stream.Seek(0, 0); + // if (resizedFilepath.Contains("UMBRACOSYSTHUMBNAIL")) + // { + // var filepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToInvariantString()); + // AddFile(filepath, stream); + // if (extension != "jpg") + // { + // filepath = filepath.TrimEnd(extension) + "jpg"; + // stream.Seek(0, 0); + // AddFile(filepath, stream); + // } + // // TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385 + // stream.Seek(0, 0); + // resizedFilepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", width + "x" + height); + // } - // get an encoder - based upon the file type - var extension = (Path.GetExtension(resizedFilepath) ?? "").TrimStart('.').ToLowerInvariant(); - var encoders = ImageCodecInfo.GetImageEncoders(); - ImageCodecInfo encoder; - switch (extension) - { - case "png": - encoder = encoders.Single(t => t.MimeType.Equals("image/png")); - break; - case "gif": - encoder = encoders.Single(t => t.MimeType.Equals("image/gif")); - break; - case "tif": - case "tiff": - encoder = encoders.Single(t => t.MimeType.Equals("image/tiff")); - break; - case "bmp": - encoder = encoders.Single(t => t.MimeType.Equals("image/bmp")); - break; - // TODO: this is dirty, defaulting to jpg but the return value of this thing is used all over the - // place so left it here, but it needs to not set a codec if it doesn't know which one to pick - // Note: when fixing this: both .jpg and .jpeg should be handled as extensions - default: - encoder = encoders.Single(t => t.MimeType.Equals("image/jpeg")); - break; - } + // AddFile(resizedFilepath, stream); + // } - // set compresion ratio to 90% - var encoderParams = new EncoderParameters(); - encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 90L); + // return new ResizedImage(resizedFilepath, width, height); + // } + //} - // save the new image - using (var stream = new MemoryStream()) - { - bitmap.Save(stream, encoder, encoderParams); - stream.Seek(0, 0); - if (resizedFilepath.Contains("UMBRACOSYSTHUMBNAIL")) - { - var filepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToInvariantString()); - AddFile(filepath, stream); - if (extension != "jpg") - { - filepath = filepath.TrimEnd(extension) + "jpg"; - stream.Seek(0, 0); - AddFile(filepath, stream); - } - // TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385 - stream.Seek(0, 0); - resizedFilepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", width + "x" + height); - } + //#endregion - AddFile(resizedFilepath, stream); - } + //#region Inner classes - return new ResizedImage(resizedFilepath, width, height); - } - } + //public class ResizedImage + //{ + // public ResizedImage() + // { } - #endregion + // public ResizedImage(string filepath, int width, int height) + // { + // Filepath = filepath; + // Width = width; + // Height = height; + // } - #region Inner classes + // public string Filepath { get; set; } + // public int Width { get; set; } + // public int Height { get; set; } + //} - [Obsolete("This should no longer be used, image manipulation should be done via ImageProcessor, Umbraco no longer generates '_thumb' files for media")] - public class ResizedImage - { - public ResizedImage() - { } - - public ResizedImage(string filepath, int width, int height) - { - Filepath = filepath; - Width = width; - Height = height; - } - - public string Filepath { get; set; } - public int Width { get; set; } - public int Height { get; set; } - } - - #endregion + //#endregion } } diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 6cd709f2d3..9a2c6eb1de 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -2,12 +2,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; using System.Threading; using Umbraco.Core.Logging; namespace Umbraco.Core.IO { - public class PhysicalFileSystem : IFileSystem2 + public class PhysicalFileSystem : IFileSystem { // the rooted, filesystem path, using directory separator chars, NOT ending with a separator // eg "c:" or "c:\path\to\site" or "\\server\path" @@ -26,9 +28,9 @@ namespace Umbraco.Core.IO // the "~/" is mandatory. public PhysicalFileSystem(string virtualRoot) { - if (virtualRoot == null) throw new ArgumentNullException("virtualRoot"); - if (virtualRoot.StartsWith("~/") == false) - throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'"); + if (virtualRoot == null) throw new ArgumentNullException("virtualRoot"); + if (virtualRoot.StartsWith("~/") == false) + throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'"); _rootPath = EnsureDirectorySeparatorChar(IOHelper.MapPath(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar); _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); @@ -37,14 +39,9 @@ namespace Umbraco.Core.IO public PhysicalFileSystem(string rootPath, string rootUrl) { - if (string.IsNullOrEmpty(rootPath)) - throw new ArgumentException("The argument 'rootPath' cannot be null or empty."); - - if (string.IsNullOrEmpty(rootUrl)) - throw new ArgumentException("The argument 'rootUrl' cannot be null or empty."); - - if (rootPath.StartsWith("~/")) - throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'"); + if (string.IsNullOrEmpty(rootPath)) throw new ArgumentNullOrEmptyException(nameof(rootPath)); + if (string.IsNullOrEmpty(rootUrl)) throw new ArgumentNullOrEmptyException(nameof(rootUrl)); + if (rootPath.StartsWith("~/")) throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'"); // rootPath should be... rooted, as in, it's a root path! if (Path.IsPathRooted(rootPath) == false) @@ -76,11 +73,11 @@ namespace Umbraco.Core.IO } catch (UnauthorizedAccessException ex) { - LogHelper.Error("Not authorized to get directories", ex); + Current.Logger.Error("Not authorized to get directories", ex); } catch (DirectoryNotFoundException ex) { - LogHelper.Error("Directory not found", ex); + Current.Logger.Error("Directory not found", ex); } return Enumerable.Empty(); @@ -112,7 +109,7 @@ namespace Umbraco.Core.IO } catch (DirectoryNotFoundException ex) { - LogHelper.Error("Directory not found", ex); + Current.Logger.Error("Directory not found", ex); } } @@ -156,10 +153,8 @@ namespace Umbraco.Core.IO if (directory == null) throw new InvalidOperationException("Could not get directory."); Directory.CreateDirectory(directory); // ensure it exists - // if can seek, be safe and go back to start, else... - // hope that the stream hasn't been read already - if (stream.CanSeek) - stream.Seek(0, SeekOrigin.Begin); + if (stream.CanSeek) // fixme - what else? + stream.Seek(0, 0); using (var destination = (Stream) File.Create(fullPath)) stream.CopyTo(destination); @@ -194,11 +189,11 @@ namespace Umbraco.Core.IO } catch (UnauthorizedAccessException ex) { - LogHelper.Error("Not authorized to get directories", ex); + Current.Logger.Error("Not authorized to get directories", ex); } catch (DirectoryNotFoundException ex) { - LogHelper.Error("Directory not found", ex); + Current.Logger.Error("Directory not found", ex); } return Enumerable.Empty(); @@ -226,12 +221,12 @@ namespace Umbraco.Core.IO return; try - { + { WithRetry(() => File.Delete(fullPath)); } catch (FileNotFoundException ex) { - LogHelper.Info(string.Format("DeleteFile failed with FileNotFoundException: {0}", ex.InnerException)); + Current.Logger.Info(string.Format("DeleteFile failed with FileNotFoundException: {0}", ex.InnerException)); } } @@ -289,9 +284,7 @@ namespace Umbraco.Core.IO var opath = path; path = EnsureDirectorySeparatorChar(path); - // TODO in v8 this should be cleaned up - // the first part should probably removed - + // fixme - this part should go! // not sure what we are doing here - so if input starts with a (back) slash, // we assume it's not a FS relative path and we try to convert it... but it // really makes little sense? @@ -369,7 +362,7 @@ namespace Umbraco.Core.IO return file.Exists ? file.Length : -1; } - public bool CanAddPhysical { get { return true; } } + public bool CanAddPhysical => true; public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) { @@ -378,7 +371,7 @@ namespace Umbraco.Core.IO if (File.Exists(fullPath)) { if (overrideIfExists == false) - throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); + throw new InvalidOperationException($"A file at path '{path}' already exists"); WithRetry(() => File.Delete(fullPath)); } @@ -419,10 +412,10 @@ namespace Umbraco.Core.IO } protected void WithRetry(Action action) - { + { // 10 times 100ms is 1s const int count = 10; - const int pausems = 100; + const int pausems = 100; for (var i = 0;; i++) { @@ -437,15 +430,15 @@ namespace Umbraco.Core.IO // some inherited exception such as FileNotFoundException, // and then we don't want to retry if (e.GetType() != typeof(IOException)) throw; - - // if we have tried enough, throw, else swallow + + // if we have tried enough, throw, else swallow // the exception and retry after a pause if (i == count) throw; } Thread.Sleep(pausems); } - } + } #endregion } diff --git a/src/Umbraco.Core/IO/ShadowFileSystem.cs b/src/Umbraco.Core/IO/ShadowFileSystem.cs index 8645399a0b..84ff1b428b 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystem.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystem.cs @@ -6,21 +6,18 @@ using System.Text.RegularExpressions; namespace Umbraco.Core.IO { - internal class ShadowFileSystem : IFileSystem2 + internal class ShadowFileSystem : IFileSystem { private readonly IFileSystem _fs; - private readonly IFileSystem2 _sfs; + private readonly IFileSystem _sfs; - public ShadowFileSystem(IFileSystem fs, IFileSystem2 sfs) + public ShadowFileSystem(IFileSystem fs, IFileSystem sfs) { _fs = fs; _sfs = sfs; } - public IFileSystem Inner - { - get { return _fs; } - } + public IFileSystem Inner => _fs; public void Complete() { @@ -34,10 +31,9 @@ namespace Umbraco.Core.IO { try { - var fs2 = _fs as IFileSystem2; - if (fs2 != null && fs2.CanAddPhysical) + if (_fs.CanAddPhysical) { - fs2.AddFile(kvp.Key, _sfs.GetFullPath(kvp.Key)); // overwrite, move + _fs.AddFile(kvp.Key, _sfs.GetFullPath(kvp.Key)); // overwrite, move } else { @@ -74,7 +70,7 @@ namespace Umbraco.Core.IO private Dictionary _nodes; - private Dictionary Nodes { get { return _nodes ?? (_nodes = new Dictionary()); } } + private Dictionary Nodes => _nodes ?? (_nodes = new Dictionary()); private class ShadowNode { @@ -84,11 +80,11 @@ namespace Umbraco.Core.IO IsDir = isdir; } - public bool IsDelete { get; private set; } - public bool IsDir { get; private set; } + public bool IsDelete { get; } + public bool IsDir { get; } - public bool IsExist { get { return IsDelete == false; } } - public bool IsFile { get { return IsDir == false; } } + public bool IsExist => IsDelete == false; + public bool IsFile => IsDir == false; } private static string NormPath(string path) @@ -286,12 +282,8 @@ namespace Umbraco.Core.IO { ShadowNode sf; if (Nodes.TryGetValue(NormPath(path), out sf) == false) - { - // the inner filesystem (_fs) can be IFileSystem2... or just IFileSystem - // figure it out and use the most effective GetSize method - var fs2 = _fs as IFileSystem2; - return fs2 == null ? _fs.GetSize(path) : fs2.GetSize(path); - } + return _fs.GetSize(path); + if (sf.IsDelete || sf.IsDir) throw new InvalidOperationException("Invalid path."); return _sfs.GetSize(path); } diff --git a/src/Umbraco.Core/IO/ShadowFileSystems.cs b/src/Umbraco.Core/IO/ShadowFileSystems.cs index 2fe703c3df..f1ca0bffd9 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystems.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystems.cs @@ -10,38 +10,109 @@ namespace Umbraco.Core.IO // to avoid using Current everywhere but really, we support only 1 scope at // a time, not multiple scopes in case of multiple managers (not supported) + // fixme - why are we managing logical call context here? should be bound + // to the current scope, always => REFACTOR! but there should be something in + // place (static?) to ensure we only have one concurrent shadow FS? + // + // => yes, that's _currentId - need to cleanup this entirely + // and, we probably need a way to stop shadowing entirely without cycling the app + + private const string ItemKey = "Umbraco.Core.IO.ShadowFileSystems"; + private static readonly object Locker = new object(); private static Guid _currentId = Guid.Empty; + private readonly Guid _id; private readonly ShadowWrapper[] _wrappers; + private readonly ILogger _logger; + private bool _completed; - public ShadowFileSystems(Guid id, ShadowWrapper[] wrappers) + //static ShadowFileSystems() + //{ + // SafeCallContext.Register( + // () => + // { + // var scope = CallContext.LogicalGetData(ItemKey); + // CallContext.FreeNamedDataSlot(ItemKey); + // return scope; + // }, + // o => + // { + // if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException(); + // if (o != null) CallContext.LogicalSetData(ItemKey, o); + // }); + //} + + public ShadowFileSystems(Guid id, ShadowWrapper[] wrappers, ILogger logger) { lock (Locker) { if (_currentId != Guid.Empty) throw new InvalidOperationException("Already shadowing."); _currentId = id; - - LogHelper.Debug("Shadow " + id + "."); - _id = id; - _wrappers = wrappers; - foreach (var wrapper in _wrappers) - wrapper.Shadow(id); } + + _logger = logger; + _logger.Debug("Shadow " + id + "."); + _id = id; + + _wrappers = wrappers; + foreach (var wrapper in _wrappers) + wrapper.Shadow(id); } + // fixme - remove + //// internal for tests + FileSystems + //// do NOT use otherwise + //internal static ShadowFileSystems CreateScope(Guid id, ShadowWrapper[] wrappers, ILogger logger) + //{ + // lock (Locker) + // { + // if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException("Already shadowing."); + // CallContext.LogicalSetData(ItemKey, ItemKey); // value does not matter + // } + // return new ShadowFileSystems(id, wrappers, logger); + //} + + //internal static bool InScope => NoScope == false; + + //internal static bool NoScope => CallContext.LogicalGetData(ItemKey) == null; + public void Complete() { _completed = true; + //lock (Locker) + //{ + // _logger.Debug("UnShadow " + _id + " (complete)."); + + // var exceptions = new List(); + // foreach (var wrapper in _wrappers) + // { + // try + // { + // // this may throw an AggregateException if some of the changes could not be applied + // wrapper.UnShadow(true); + // } + // catch (AggregateException ae) + // { + // exceptions.Add(ae); + // } + // } + + // if (exceptions.Count > 0) + // throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions); + + // // last, & *only* if successful (otherwise we'll unshadow & cleanup as best as we can) + // CallContext.FreeNamedDataSlot(ItemKey); + //} } public void Dispose() { lock (Locker) { - LogHelper.Debug("UnShadow " + _id + " (" + (_completed ? "complete" : "abort") + ")."); + _logger.Debug("UnShadow " + _id + " (" + (_completed ? "complete" : "abort") + ")."); var exceptions = new List(); foreach (var wrapper in _wrappers) @@ -61,6 +132,20 @@ namespace Umbraco.Core.IO if (exceptions.Count > 0) throw new AggregateException(_completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions); + + //if (CallContext.LogicalGetData(ItemKey) == null) return; + + //try + //{ + // _logger.Debug("UnShadow " + _id + " (abort)"); + // foreach (var wrapper in _wrappers) + // wrapper.UnShadow(false); // should not throw + //} + //finally + //{ + // // last, & always + // CallContext.FreeNamedDataSlot(ItemKey); + //} } } diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index ef113a02c2..94bd61b162 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -2,23 +2,24 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Umbraco.Core.Scoping; namespace Umbraco.Core.IO { - internal class ShadowWrapper : IFileSystem2 + internal class ShadowWrapper : IFileSystem { - private readonly IScopeProviderInternal _scopeProvider; + private const string ShadowFsPath = "~/App_Data/TEMP/ShadowFs"; + + private readonly Func _isScoped; private readonly IFileSystem _innerFileSystem; private readonly string _shadowPath; private ShadowFileSystem _shadowFileSystem; private string _shadowDir; - public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath, IScopeProviderInternal scopeProvider) + public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath, Func isScoped = null) { _innerFileSystem = innerFileSystem; _shadowPath = shadowPath; - _scopeProvider = scopeProvider; + _isScoped = isScoped; } internal void Shadow(Guid id) @@ -27,7 +28,7 @@ namespace Umbraco.Core.IO // on ShadowFileSystemsScope.None - and if None is false then we should be running // in a single thread anyways - var virt = "~/App_Data/TEMP/ShadowFs/" + id + "/" + _shadowPath; + var virt = ShadowFsPath + "/" + id + "/" + _shadowPath; _shadowDir = IOHelper.MapPath(virt); Directory.CreateDirectory(_shadowDir); var tempfs = new PhysicalFileSystem(virt); @@ -55,7 +56,7 @@ namespace Umbraco.Core.IO // shadowPath make be path/to/dir, remove each dir = dir.Replace("/", "\\"); - var min = IOHelper.MapPath("~/App_Data/TEMP/ShadowFs").Length; + var min = IOHelper.MapPath(ShadowFsPath).Length; var pos = dir.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase); while (pos > min) { @@ -78,7 +79,7 @@ namespace Umbraco.Core.IO { get { - var isScoped = _scopeProvider != null && _scopeProvider.AmbientScope != null && _scopeProvider.AmbientScope.ScopedFileSystems; + var isScoped = _isScoped(); // if the filesystem is created *after* shadowing starts, it won't be shadowing // better not ignore that situation and raised a meaningful (?) exception @@ -173,29 +174,14 @@ namespace Umbraco.Core.IO public long GetSize(string path) { - var filesystem = FileSystem; // will be either a ShadowFileSystem OR the actual underlying IFileSystem - - // and the underlying filesystem can be IFileSystem2... or just IFileSystem - // figure it out and use the most effective GetSize method - var filesystem2 = filesystem as IFileSystem2; - return filesystem2 == null ? filesystem.GetSize(path) : filesystem2.GetSize(path); + return FileSystem.GetSize(path); } - public bool CanAddPhysical - { - get - { - var fileSystem2 = FileSystem as IFileSystem2; - return fileSystem2 != null && fileSystem2.CanAddPhysical; - } - } + public bool CanAddPhysical => FileSystem.CanAddPhysical; public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) { - var fileSystem2 = FileSystem as IFileSystem2; - if (fileSystem2 == null) - throw new NotSupportedException(); - fileSystem2.AddFile(path, physicalPath, overrideIfExists, copy); + FileSystem.AddFile(path, physicalPath, overrideIfExists, copy); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index 9c815504c7..7d3bfd3ae0 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -1,214 +1,62 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Configuration; - using System.Web; -using System.IO; namespace Umbraco.Core.IO { //all paths has a starting but no trailing / - public class SystemDirectories + public class SystemDirectories { - //TODO: Why on earth is this even configurable? You cannot change the /Bin folder in ASP.Net - public static string Bin - { - get - { - return IOHelper.ReturnPath("umbracoBinDirectory", "~/bin"); - } - } + //TODO: Why on earth is this even configurable? You cannot change the /Bin folder in ASP.Net + public static string Bin => IOHelper.ReturnPath("umbracoBinDirectory", "~/bin"); - public static string Base - { - get - { - return IOHelper.ReturnPath("umbracoBaseDirectory", "~/base"); - } - } + public static string Base => IOHelper.ReturnPath("umbracoBaseDirectory", "~/base"); - public static string Config - { - get - { - return IOHelper.ReturnPath("umbracoConfigDirectory", "~/config"); - } - } - - public static string Css - { - get - { - return IOHelper.ReturnPath("umbracoCssDirectory", "~/css"); - } - } + public static string Config => IOHelper.ReturnPath("umbracoConfigDirectory", "~/config"); - public static string Data - { - get - { - return IOHelper.ReturnPath("umbracoStorageDirectory", "~/App_Data"); - } - } + public static string Css => IOHelper.ReturnPath("umbracoCssDirectory", "~/css"); - public static string Install - { - get - { - return IOHelper.ReturnPath("umbracoInstallPath", "~/install"); - } - } + public static string Data => IOHelper.ReturnPath("umbracoStorageDirectory", "~/App_Data"); - public static string Masterpages - { - get - { - return IOHelper.ReturnPath("umbracoMasterPagesPath", "~/masterpages"); - } - } + public static string Install => IOHelper.ReturnPath("umbracoInstallPath", "~/install"); - public static string AppCode - { - get - { - //NOTE: this is not configurable and shouldn't need to be - return "~/App_Code"; - } - } + public static string Masterpages => IOHelper.ReturnPath("umbracoMasterPagesPath", "~/masterpages"); - public static string AppPlugins - { - get - { - //NOTE: this is not configurable and shouldn't need to be - return "~/App_Plugins"; - } - } + //NOTE: this is not configurable and shouldn't need to be + public static string AppCode => "~/App_Code"; - public static string MvcViews - { - get - { - //NOTE: this is not configurable and shouldn't need to be - return "~/Views"; - } - } + //NOTE: this is not configurable and shouldn't need to be + public static string AppPlugins => "~/App_Plugins"; - public static string PartialViews - { - get - { - return MvcViews + "/Partials/"; - } - } + //NOTE: this is not configurable and shouldn't need to be + public static string MvcViews => "~/Views"; - public static string MacroPartials - { - get - { - return MvcViews + "/MacroPartials/"; - - } - } + public static string PartialViews => MvcViews + "/Partials/"; - public static string Media - { - get - { - return IOHelper.ReturnPath("umbracoMediaPath", "~/media"); - } - } + public static string MacroPartials => MvcViews + "/MacroPartials/"; - //have changed to internal so nobody uses this anymore since this is a new class. - [Obsolete("Please use MacroScripts instead!", true)] - internal static string Python - { - get - { - return MacroScripts; - } - } + public static string Media => IOHelper.ReturnPath("umbracoMediaPath", "~/media"); - public static string MacroScripts - { - get - { - // for legacy we test for the python path first, but else we use the new default location - string tempPath = IOHelper.ReturnPath("umbracoPythonPath", "") == String.Empty - ? IOHelper.ReturnPath("umbracoMacroScriptPath", "~/macroScripts") - : IOHelper.ReturnPath("umbracoPythonPath", "~/python"); - return tempPath; - } - } + public static string Scripts => IOHelper.ReturnPath("umbracoScriptsPath", "~/scripts"); - public static string Scripts - { - get - { - return IOHelper.ReturnPath("umbracoScriptsPath", "~/scripts"); - } - } + public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco"); - public static string Umbraco - { - get - { - return IOHelper.ReturnPath("umbracoPath", "~/umbraco"); - } - } + public static string UmbracoClient => IOHelper.ReturnPath("umbracoClientPath", "~/umbraco_client"); - public static string UmbracoClient - { - get - { - return IOHelper.ReturnPath("umbracoClientPath", "~/umbraco_client"); - } - } + public static string UserControls => IOHelper.ReturnPath("umbracoUsercontrolsPath", "~/usercontrols"); - public static string UserControls - { - get - { - return IOHelper.ReturnPath("umbracoUsercontrolsPath", "~/usercontrols"); - } - } + public static string WebServices => IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices"); - public static string WebServices - { - get - { - return IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices"); - } - } + public static string Xslt => IOHelper.ReturnPath("umbracoXsltPath", "~/xslt"); - public static string Xslt - { - get { - return IOHelper.ReturnPath("umbracoXsltPath", "~/xslt"); - } - } + //by default the packages folder should exist in the data folder + public static string Packages => IOHelper.ReturnPath("umbracoPackagesPath", Data + IOHelper.DirSepChar + "packages"); - public static string Packages - { - get - { - //by default the packages folder should exist in the data folder - return IOHelper.ReturnPath("umbracoPackagesPath", Data + IOHelper.DirSepChar + "packages"); - } - } + public static string Preview => IOHelper.ReturnPath("umbracoPreviewPath", Data + IOHelper.DirSepChar + "preview"); + + public static string JavaScriptLibrary => IOHelper.ReturnPath("umbracoJavaScriptLibraryPath", Umbraco + IOHelper.DirSepChar + "lib"); - public static string Preview - { - get - { - //by default the packages folder should exist in the data folder - return IOHelper.ReturnPath("umbracoPreviewPath", Data + IOHelper.DirSepChar + "preview"); - } - } + private static string _root; - private static string _root; /// /// Gets the root path of the application /// @@ -216,21 +64,18 @@ namespace Umbraco.Core.IO { get { - if (_root == null) - { - string appPath = HttpRuntime.AppDomainAppVirtualPath ?? string.Empty; - if (appPath == "/") - appPath = string.Empty; + if (_root != null) return _root; + + var appPath = HttpRuntime.AppDomainAppVirtualPath; + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (appPath == null || appPath == "/") appPath = string.Empty; + + _root = appPath; - _root = appPath; - } return _root; } //Only required for unit tests - internal set { _root = value; } + internal set => _root = value; } } - - - } diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs index ccf8ea5b2c..a0ca748cd6 100644 --- a/src/Umbraco.Core/IO/SystemFiles.cs +++ b/src/Umbraco.Core/IO/SystemFiles.cs @@ -1,10 +1,5 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Configuration; using System.IO; -using System.Linq; -using System.Text; using System.Web; using Umbraco.Core.Configuration; @@ -12,92 +7,36 @@ namespace Umbraco.Core.IO { public class SystemFiles { - [Obsolete("This file is no longer used and should not be accessed!")] - public static string AccessXml - { - get - { - return SystemDirectories.Data + "/access.config"; - } - } + public static string CreateUiXml => SystemDirectories.Umbraco + "/config/create/UI.xml"; - public static string CreateUiXml - { - get - { - return SystemDirectories.Umbraco + "/config/create/UI.xml"; - } - } + public static string TinyMceConfig => SystemDirectories.Config + "/tinyMceConfig.config"; - public static string TinyMceConfig - { - get - { - return SystemDirectories.Config + "/tinyMceConfig.config"; - } - } + public static string DashboardConfig => SystemDirectories.Config + "/dashboard.config"; - public static string MetablogConfig - { - get - { - return SystemDirectories.Config + "/metablogConfig.config"; - } - } + public static string NotFoundhandlersConfig => SystemDirectories.Config + "/404handlers.config"; - public static string DashboardConfig - { - get - { - return SystemDirectories.Config + "/dashboard.config"; - } - } + public static string FeedProxyConfig => string.Concat(SystemDirectories.Config, "/feedProxy.config"); - public static string NotFoundhandlersConfig + // fixme - kill + public static string GetContentCacheXml(IGlobalSettings globalSettings) { - get + switch (globalSettings.LocalTempStorageLocation) { - return SystemDirectories.Config + "/404handlers.config"; + case LocalTempStorage.AspNetTemp: + return Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco.config"); + case LocalTempStorage.EnvironmentTemp: + var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); + var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", + //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 + appDomainHash); + return Path.Combine(cachePath, "umbraco.config"); + case LocalTempStorage.Default: + return IOHelper.ReturnPath("umbracoContentXML", "~/App_Data/umbraco.config"); + default: + throw new ArgumentOutOfRangeException(); } } - - public static string FeedProxyConfig - { - get - { - return string.Concat(SystemDirectories.Config, "/feedProxy.config"); - } - } - - public static string ContentCacheXml - { - get - { - switch (GlobalSettings.LocalTempStorageLocation) - { - case LocalTempStorage.AspNetTemp: - return Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco.config"); - case LocalTempStorage.EnvironmentTemp: - var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); - var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", - //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 - appDomainHash); - return Path.Combine(cachePath, "umbraco.config"); - case LocalTempStorage.Default: - return IOHelper.ReturnPath("umbracoContentXML", "~/App_Data/umbraco.config"); - default: - throw new ArgumentOutOfRangeException(); - } - } - } - - [Obsolete("Use GlobalSettings.ContentCacheXmlStoredInCodeGen instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - internal static bool ContentCacheXmlStoredInCodeGen - { - get { return GlobalSettings.ContentCacheXmlStoredInCodeGen; } - } } } diff --git a/src/Umbraco.Core/IO/UmbracoMediaFile.cs b/src/Umbraco.Core/IO/UmbracoMediaFile.cs deleted file mode 100644 index 18438831e2..0000000000 --- a/src/Umbraco.Core/IO/UmbracoMediaFile.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using System.Drawing; -using System.IO; -using System.Web; -using Umbraco.Core.Logging; -using Umbraco.Core.Media; - -namespace Umbraco.Core.IO -{ - public class UmbracoMediaFile - { - private readonly MediaFileSystem _fs; - - #region Constructors - - public UmbracoMediaFile() - { - _fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - } - - public UmbracoMediaFile(string path) - { - _fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - - Path = path; - - Initialize(); - } - - #endregion - - #region Static Methods - - //MB: Do we really need all these overloads? looking through the code, only one of them is actually used - - public static UmbracoMediaFile Save(HttpPostedFile file, string path) - { - return Save(file.InputStream, path); - } - - public static UmbracoMediaFile Save(HttpPostedFileBase file, string path) - { - return Save(file.InputStream, path); - } - - public static UmbracoMediaFile Save(Stream inputStream, string path) - { - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - fs.AddFile(path, inputStream); - - return new UmbracoMediaFile(path); - } - - public static UmbracoMediaFile Save(byte[] file, string relativePath) - { - return Save(new MemoryStream(file), relativePath); - } - - public static UmbracoMediaFile Save(HttpPostedFile file) - { - var tempDir = System.IO.Path.Combine("uploads", Guid.NewGuid().ToString()); - return Save(file, tempDir); - } - - //filebase overload... - public static UmbracoMediaFile Save(HttpPostedFileBase file) - { - var tempDir = System.IO.Path.Combine("uploads", Guid.NewGuid().ToString()); - return Save(file, tempDir); - } - - #endregion - - private long? _length; - private Size? _size; - - /// - /// Initialized values that don't require opening the file. - /// - private void Initialize() - { - Filename = _fs.GetFileName(Path); - var ext = _fs.GetExtension(Path); - Extension = string.IsNullOrEmpty(ext) == false - ? ext.TrimStart('.').ToLowerInvariant() - : ""; - Url = _fs.GetUrl(Path); - - Exists = _fs.FileExists(Path); - if (Exists == false) - LogHelper.Warn("The media file doesn't exist: " + Path); - } - - public bool Exists { get; private set; } - - public string Filename { get; private set; } - - public string Extension { get; private set; } - - public string Path { get; private set; } - - public string Url { get; private set; } - - /// - /// Get the length of the file in bytes - /// - /// - /// We are lazy loading this, don't go opening the file on ctor like we were doing. - /// - public long Length - { - get - { - if (_length.HasValue) return _length.Value; - _length = Exists ? _fs.GetSize(Path) : -1; - return _length.Value; - } - } - - public bool SupportsResizing - { - get { return _fs.IsImageFile(Extension); } - } - - public string GetFriendlyName() - { - return Filename.SplitPascalCasing().ToFirstUpperInvariant(); - } - - public Size GetDimensions() - { - if (_size.HasValue) return _size.Value; - - if (_fs.FileExists(Path)) - { - EnsureFileSupportsResizing(); - - using (var fs = _fs.OpenFile(Path)) - { - _size = _fs.GetDimensions(fs); - } - } - else - { - _size = new Size(-1, -1); - } - - return _size.Value; - } - - public string Resize(int width, int height) - { - if (Exists == false) return string.Empty; - - EnsureFileSupportsResizing(); - var filepath = Resize(width, height, -1, string.Empty); - return _fs.GetUrl(filepath); - } - - public string Resize(int maxWidthHeight, string fileNameAddition) - { - if (Exists == false) return string.Empty; - - EnsureFileSupportsResizing(); - var filepath = Resize(-1, -1, maxWidthHeight, fileNameAddition); - return _fs.GetUrl(filepath); - } - - private string Resize(int width, int height, int maxWidthHeight, string sizeName) - { - using (var filestream = _fs.OpenFile(Path)) - using (var image = Image.FromStream(filestream)) - { - return _fs.GenerateResized(image, Path, sizeName, maxWidthHeight, width, height).Filepath; - } - } - - private void EnsureFileSupportsResizing() - { - if (SupportsResizing == false) - throw new InvalidOperationException(string.Format("The file {0} is not an image, so can't get dimensions", Filename)); - } - } -} diff --git a/src/Umbraco.Core/IO/ViewHelper.cs b/src/Umbraco.Core/IO/ViewHelper.cs index 933a9d2956..1645355b72 100644 --- a/src/Umbraco.Core/IO/ViewHelper.cs +++ b/src/Umbraco.Core/IO/ViewHelper.cs @@ -1,8 +1,7 @@ -using System; +using System; using System.IO; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using Umbraco.Core.Models; namespace Umbraco.Core.IO @@ -13,7 +12,7 @@ namespace Umbraco.Core.IO public ViewHelper(IFileSystem viewFileSystem) { - if (viewFileSystem == null) throw new ArgumentNullException("viewFileSystem"); + if (viewFileSystem == null) throw new ArgumentNullException(nameof(viewFileSystem)); _viewFileSystem = viewFileSystem; } @@ -22,16 +21,10 @@ namespace Umbraco.Core.IO return _viewFileSystem.FileExists(ViewPath(t.Alias)); } - [Obsolete("This is only used for legacy purposes and will be removed in future versions")] - internal string GetPhysicalFilePath(ITemplate t) - { - return _viewFileSystem.GetFullPath(ViewPath(t.Alias)); - } - internal string GetFileContents(ITemplate t) { - string viewContent = ""; - string path = ViewPath(t.Alias); + var viewContent = ""; + var path = ViewPath(t.Alias); if (_viewFileSystem.FileExists(path)) { @@ -48,12 +41,12 @@ namespace Umbraco.Core.IO public string CreateView(ITemplate t, bool overWrite = false) { string viewContent; - string path = ViewPath(t.Alias); + var path = ViewPath(t.Alias); if (_viewFileSystem.FileExists(path) == false || overWrite) { - viewContent = SaveTemplateToFile(t); - } + viewContent = SaveTemplateToFile(t); + } else { using (var tr = new StreamReader(_viewFileSystem.OpenFile(path))) @@ -142,7 +135,7 @@ namespace Umbraco.Core.IO if (string.IsNullOrEmpty(currentAlias) == false && currentAlias != t.Alias) { - //then kill the old file.. + //then kill the old file.. var oldFile = ViewPath(currentAlias); if (_viewFileSystem.FileExists(oldFile)) _viewFileSystem.DeleteFile(oldFile); @@ -158,33 +151,19 @@ namespace Umbraco.Core.IO return t.Content; } - internal void RemoveViewFile(string alias) - { - if (string.IsNullOrWhiteSpace(alias) == false) - { - var file = ViewPath(alias); - if (_viewFileSystem.FileExists(file)) - _viewFileSystem.DeleteFile(file); - } - } - public string ViewPath(string alias) { return _viewFileSystem.GetRelativePath(alias.Replace(" ", "") + ".cshtml"); - - //return SystemDirectories.MvcViews + "/" + alias.Replace(" ", "") + ".cshtml"; } - private string EnsureInheritedLayout(ITemplate template) + private static string EnsureInheritedLayout(ITemplate template) { - string design = template.Content; + var design = template.Content; if (string.IsNullOrEmpty(design)) - { design = GetDefaultFileContent(template.MasterTemplateAlias); - } return design; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/IRuntime.cs b/src/Umbraco.Core/IRuntime.cs new file mode 100644 index 0000000000..37b265ad77 --- /dev/null +++ b/src/Umbraco.Core/IRuntime.cs @@ -0,0 +1,21 @@ +using LightInject; + +namespace Umbraco.Core +{ + /// + /// Defines the Umbraco runtime. + /// + public interface IRuntime + { + /// + /// Boots the runtime. + /// + /// The application service container. + void Boot(ServiceContainer container); + + /// + /// Terminates the runtime. + /// + void Terminate(); + } +} diff --git a/src/Umbraco.Core/IRuntimeState.cs b/src/Umbraco.Core/IRuntimeState.cs new file mode 100644 index 0000000000..5fcfab1518 --- /dev/null +++ b/src/Umbraco.Core/IRuntimeState.cs @@ -0,0 +1,75 @@ +using System; +using Semver; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Sync; + +namespace Umbraco.Core +{ + /// + /// Represents the state of the Umbraco runtime. + /// + public interface IRuntimeState + { + /// + /// Gets the version of the executing code. + /// + Version Version { get; } + + /// + /// Gets the version comment of the executing code. + /// + string VersionComment { get; } + + /// + /// Gets the semantic version of the executing code. + /// + SemVersion SemanticVersion { get; } + + /// + /// Gets a value indicating whether the application is running in debug mode. + /// + bool Debug { get; } + + /// + /// Gets a value indicating whether the runtime is the current main domain. + /// + bool IsMainDom { get; } + + /// + /// Get the server's current role. + /// + ServerRole ServerRole { get; } + + /// + /// Gets the Umbraco application url. + /// + /// This is eg "http://www.example.com". + Uri ApplicationUrl { get; } + + /// + /// Gets the Umbraco application virtual path. + /// + /// This is either "/" or eg "/virtual". + string ApplicationVirtualPath { get; } + + /// + /// Gets the runtime level of execution. + /// + RuntimeLevel Level { get; } + + /// + /// Gets the current migration state. + /// + string CurrentMigrationState { get; } + + /// + /// Gets the final migration state. + /// + string FinalMigrationState { get; } + + /// + /// Gets the exception that caused the boot to fail. + /// + BootFailedException BootFailedException { get; } + } +} diff --git a/src/Umbraco.Core/IconPickerBehaviour.cs b/src/Umbraco.Core/IconPickerBehaviour.cs deleted file mode 100644 index 69196c857f..0000000000 --- a/src/Umbraco.Core/IconPickerBehaviour.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace Umbraco.Core -{ - [Obsolete("This is no longer used and will be removed from the core in future versions")] - public enum IconPickerBehaviour - { - /// - /// Default umbraco behavior - show duplicates in files and sprites - /// - ShowDuplicates, - - /// - /// If a file exists on disk with the same name as one in the sprite - /// then the file on disk overrules the one in the sprite, the - /// sprite icon will not be shown - /// - HideSpriteDuplicates, - - /// - /// If a file exists on disk with the same name as one in the sprite - /// then the file in the sprite overrules the one on disk, the file - /// on disk will be shown - /// - HideFileDuplicates - } -} diff --git a/src/Umbraco.Core/IntExtensions.cs b/src/Umbraco.Core/IntExtensions.cs index ae2c01ef95..358c73ab8e 100644 --- a/src/Umbraco.Core/IntExtensions.cs +++ b/src/Umbraco.Core/IntExtensions.cs @@ -1,21 +1,21 @@ -using System; +using System; namespace Umbraco.Core { - public static class IntExtensions - { - /// - /// Does something 'x' amount of times - /// - /// - /// - public static void Times(this int n, Action action) - { - for (int i = 0; i < n; i++) - { - action(i); - } - } + public static class IntExtensions + { + /// + /// Does something 'x' amount of times + /// + /// + /// + public static void Times(this int n, Action action) + { + for (int i = 0; i < n; i++) + { + action(i); + } + } /// /// Creates a Guid based on an integer value @@ -28,5 +28,5 @@ namespace Umbraco.Core BitConverter.GetBytes(value).CopyTo(bytes, 0); return new Guid(bytes); } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Core/KeyValuePairExtensions.cs b/src/Umbraco.Core/KeyValuePairExtensions.cs new file mode 100644 index 0000000000..30fd3fee50 --- /dev/null +++ b/src/Umbraco.Core/KeyValuePairExtensions.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Umbraco.Core +{ + /// + /// Provides extension methods for the struct. + /// + public static class KeyValuePairExtensions + { + /// + /// Implements key/value pair deconstruction. + /// + /// Allows for foreach ((var k, var v) in ...). + public static void Deconstruct(this KeyValuePair kvp, out TKey key, out TValue value) + { + key = kvp.Key; + value = kvp.Value; + } + } +} diff --git a/src/Umbraco.Core/LambdaExpressionCacheKey.cs b/src/Umbraco.Core/LambdaExpressionCacheKey.cs index 9542b041c5..fdc69cde0f 100644 --- a/src/Umbraco.Core/LambdaExpressionCacheKey.cs +++ b/src/Umbraco.Core/LambdaExpressionCacheKey.cs @@ -1,83 +1,83 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace Umbraco.Core { - /// - /// Represents a simple in a form which is suitable for using as a dictionary key - /// by exposing the return type, argument types and expression string form in a single concatenated string. - /// - internal struct LambdaExpressionCacheKey - { - public LambdaExpressionCacheKey(string returnType, string expression, params string[] argTypes) - { - ReturnType = returnType; - ExpressionAsString = expression; - ArgTypes = new HashSet(argTypes); - _toString = null; - } + /// + /// Represents a simple in a form which is suitable for using as a dictionary key + /// by exposing the return type, argument types and expression string form in a single concatenated string. + /// + internal struct LambdaExpressionCacheKey + { + public LambdaExpressionCacheKey(string returnType, string expression, params string[] argTypes) + { + ReturnType = returnType; + ExpressionAsString = expression; + ArgTypes = new HashSet(argTypes); + _toString = null; + } - public LambdaExpressionCacheKey(LambdaExpression obj) - { - ReturnType = obj.ReturnType.FullName; - ExpressionAsString = obj.ToString(); - ArgTypes = new HashSet(obj.Parameters.Select(x => x.Type.FullName)); - _toString = null; - } + public LambdaExpressionCacheKey(LambdaExpression obj) + { + ReturnType = obj.ReturnType.FullName; + ExpressionAsString = obj.ToString(); + ArgTypes = new HashSet(obj.Parameters.Select(x => x.Type.FullName)); + _toString = null; + } - /// - /// The argument type names of the - /// - public readonly HashSet ArgTypes; + /// + /// The argument type names of the + /// + public readonly HashSet ArgTypes; - /// - /// The return type of the - /// - public readonly string ReturnType; + /// + /// The return type of the + /// + public readonly string ReturnType; - /// - /// The original string representation of the - /// - public readonly string ExpressionAsString; + /// + /// The original string representation of the + /// + public readonly string ExpressionAsString; - private string _toString; + private string _toString; - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() - { - return _toString ?? (_toString = String.Concat(String.Join("|", ArgTypes), ",", ReturnType, ",", ExpressionAsString)); - } + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return _toString ?? (_toString = String.Concat(String.Join("|", ArgTypes), ",", ReturnType, ",", ExpressionAsString)); + } - /// - /// Determines whether the specified is equal to this instance. - /// - /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(obj, null)) return false; - var casted = (LambdaExpressionCacheKey)obj; - return casted.ToString() == ToString(); - } + /// + /// Determines whether the specified is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, null)) return false; + var casted = (LambdaExpressionCacheKey)obj; + return casted.ToString() == ToString(); + } - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// - public override int GetHashCode() - { - return ToString().GetHashCode(); - } - } -} \ No newline at end of file + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + } +} diff --git a/src/Umbraco.Core/ListExtensions.cs b/src/Umbraco.Core/ListExtensions.cs new file mode 100644 index 0000000000..e639cbd5c7 --- /dev/null +++ b/src/Umbraco.Core/ListExtensions.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core +{ + /// + /// Provides extensions to the List type. + /// + internal static class ListExtensions + { + // based upon the original Zip method + public static IEnumerable Zip(this IEnumerable e1, IEnumerable e2, IEnumerable e3, + Func resultSelector) + { + if (e1 == null) throw new ArgumentNullException("e1"); + if (e2 == null) throw new ArgumentNullException("e2"); + if (e3 == null) throw new ArgumentNullException("e3"); + if (resultSelector == null) throw new ArgumentNullException("resultSelector"); + return ZipIterator(e1, e2, e3, resultSelector); + } + + private static IEnumerable ZipIterator(IEnumerable ie1, IEnumerable ie2, IEnumerable ie3, + Func resultSelector) + { + var e1 = ie1.GetEnumerator(); + try + { + var e2 = ie2.GetEnumerator(); + var e3 = ie3.GetEnumerator(); + try + { + while (e1.MoveNext() && e2.MoveNext() && e3.MoveNext()) + yield return resultSelector(e1.Current, e2.Current, e3.Current); + } + finally + { + if (e2 != null) + e2.Dispose(); + if (e3 != null) + e3.Dispose(); + } + } + finally + { + if (e1 != null) + e1.Dispose(); + } + } + } +} diff --git a/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs b/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs index fcb0f183ec..3ebcfe458f 100644 --- a/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs +++ b/src/Umbraco.Core/Logging/AsyncForwardingAppenderBase.cs @@ -1,4 +1,4 @@ -using log4net.Appender; +using log4net.Appender; using log4net.Core; using log4net.Util; using System; @@ -118,4 +118,4 @@ namespace Umbraco.Core.Logging #endregion Forwarding } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs index cd62008cd4..5d314c19e0 100644 --- a/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs +++ b/src/Umbraco.Core/Logging/AsynchronousRollingFileAppender.cs @@ -1,4 +1,4 @@ -using log4net.Core; +using log4net.Core; using log4net.Util; using System; using System.ComponentModel; @@ -13,14 +13,14 @@ namespace Umbraco.Core.Logging /// /// Based on https://github.com/cjbhaines/Log4Net.Async /// which is based on code by Chris Haines http://cjbhaines.wordpress.com/2012/02/13/asynchronous-log4net-appenders/ - /// This is an old/deprecated logger and has been superceded by ParallelForwardingAppender which is included in Umbraco and + /// This is an old/deprecated logger and has been superceded by ParallelForwardingAppender which is included in Umbraco and /// also by AsyncForwardingAppender in the Log4Net.Async library. - /// - [Obsolete("This is superceded by the ParallelForwardingAppender, this will be removed in v8, do not use this")] + /// + [Obsolete("This is superceded by the ParallelForwardingAppender, this will be removed in v8, do not use this")] [EditorBrowsable(EditorBrowsableState.Never)] - public class AsynchronousRollingFileAppender : RollingFileAppender - { - private RingBuffer pendingAppends; + public class AsynchronousRollingFileAppender : RollingFileAppender + { + private RingBuffer pendingAppends; private readonly ManualResetEvent manualResetEvent; private bool shuttingDown; private bool hasFinished; @@ -203,4 +203,4 @@ namespace Umbraco.Core.Logging } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Logging/DebugDiagnosticsLogger.cs b/src/Umbraco.Core/Logging/DebugDiagnosticsLogger.cs index f5ae689da9..4bb7aeb464 100644 --- a/src/Umbraco.Core/Logging/DebugDiagnosticsLogger.cs +++ b/src/Umbraco.Core/Logging/DebugDiagnosticsLogger.cs @@ -1,47 +1,113 @@ -using System; +using System; using System.Linq; namespace Umbraco.Core.Logging { /// - /// Implements on top of System.Diagnostics.Debug. + /// Implements on top of . /// - /// Useful for tests. public class DebugDiagnosticsLogger : ILogger { - public void Error(Type callingType, string message, Exception exception) + /// + public void Error(Type reporting, string message, Exception exception = null) { - System.Diagnostics.Debug.WriteLine(message + Environment.NewLine + exception, callingType.ToString()); + System.Diagnostics.Debug.WriteLine(message + Environment.NewLine + exception, reporting.FullName); } - public void Warn(Type callingType, string message, params Func[] formatItems) + /// + public void Warn(Type reporting, string format) { - System.Diagnostics.Debug.WriteLine(string.Format(message, formatItems.Select(x => x()).ToArray()), callingType.ToString()); + System.Diagnostics.Debug.WriteLine(format, reporting.FullName); } - public void WarnWithException(Type callingType, string message, Exception e, params Func[] formatItems) + /// + public void Warn(Type reporting, Func messageBuilder) { - System.Diagnostics.Debug.WriteLine(string.Format(message + Environment.NewLine + e, formatItems.Select(x => x()).ToArray()), callingType.ToString()); + System.Diagnostics.Debug.WriteLine(messageBuilder(), reporting.FullName); } - public void Info(Type callingType, Func generateMessage) + /// + public void Warn(Type reporting, string format, params object[] args) { - System.Diagnostics.Debug.WriteLine(generateMessage(), callingType.ToString()); + System.Diagnostics.Debug.WriteLine(string.Format(format, args), reporting.FullName); } - public void Info(Type type, string generateMessageFormat, params Func[] formatItems) + /// + public void Warn(Type reporting, string format, params Func[] args) { - System.Diagnostics.Debug.WriteLine(string.Format(generateMessageFormat, formatItems.Select(x => x()).ToArray()), type.ToString()); + System.Diagnostics.Debug.WriteLine(string.Format(format, args.Select(x => x()).ToArray()), reporting.FullName); } - public void Debug(Type callingType, Func generateMessage) + /// + public void Warn(Type reporting, Exception exception, string message) { - System.Diagnostics.Debug.WriteLine(generateMessage(), callingType.ToString()); + System.Diagnostics.Debug.WriteLine(message + Environment.NewLine + exception, reporting.FullName); } - public void Debug(Type type, string generateMessageFormat, params Func[] formatItems) + /// + public void Warn(Type reporting, Exception exception, Func messageBuilder) { - System.Diagnostics.Debug.WriteLine(string.Format(generateMessageFormat, formatItems.Select(x => x()).ToArray()), type.ToString()); + System.Diagnostics.Debug.WriteLine(messageBuilder() + Environment.NewLine + exception, reporting.FullName); + } + + /// + public void Warn(Type reporting, Exception exception, string format, params object[] args) + { + System.Diagnostics.Debug.WriteLine(string.Format(format + Environment.NewLine + exception, args), reporting.FullName); + } + + /// + public void Warn(Type reporting, Exception exception, string format, params Func[] args) + { + System.Diagnostics.Debug.WriteLine(string.Format(format + Environment.NewLine + exception, args.Select(x => x()).ToArray()), reporting.FullName); + } + + /// + public void Info(Type reporting, string message) + { + System.Diagnostics.Debug.WriteLine(message, reporting.FullName); + } + + /// + public void Info(Type reporting, Func messageBuilder) + { + System.Diagnostics.Debug.WriteLine(messageBuilder(), reporting.FullName); + } + + /// + public void Info(Type reporting, string format, params object[] args) + { + System.Diagnostics.Debug.WriteLine(string.Format(format, args), reporting.FullName); + } + + /// + public void Info(Type reporting, string format, params Func[] args) + { + System.Diagnostics.Debug.WriteLine(string.Format(format, args.Select(x => x()).ToArray()), reporting.FullName); + } + + /// + public void Debug(Type reporting, string message) + { + System.Diagnostics.Debug.WriteLine(message, reporting.FullName); + } + + /// + public void Debug(Type reporting, Func messageBuilder) + { + System.Diagnostics.Debug.WriteLine(messageBuilder(), reporting.FullName); + } + + /// + public void Debug(Type reporting, string format, params object[] args) + { + System.Diagnostics.Debug.WriteLine(string.Format(format, args), reporting.FullName); + } + + /// + public void Debug(Type reporting, string format, params Func[] args) + { + System.Diagnostics.Debug.WriteLine(string.Format(format, args.Select(x => x()).ToArray()), reporting.FullName); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Logging/ILogger.cs b/src/Umbraco.Core/Logging/ILogger.cs index 3fb6bfc476..905ba9f7fa 100644 --- a/src/Umbraco.Core/Logging/ILogger.cs +++ b/src/Umbraco.Core/Logging/ILogger.cs @@ -1,24 +1,79 @@ -using System; +using System; +using System.ComponentModel; namespace Umbraco.Core.Logging { /// - /// Interface for logging service. + /// Defines the logging service. /// public interface ILogger { - void Error(Type callingType, string message, Exception exception); + /// + /// Logs an error message. + /// + /// The reporting type. + /// A message. + /// An exception. + void Error(Type reporting, string message, Exception exception = null); - void Warn(Type callingType, string message, params Func[] formatItems); - - void WarnWithException(Type callingType, string message, Exception e, params Func[] formatItems); + // note: should we have more overloads for Error too? - void Info(Type callingType, Func generateMessage); + /// + /// Logs a warning message. + /// + /// The reporting type. + /// A message. + void Warn(Type reporting, string message); - void Info(Type type, string generateMessageFormat, params Func[] formatItems); + /// + /// Logs a warning message. + /// + /// The reporting type. + /// A message builder. + void Warn(Type reporting, Func messageBuilder); - void Debug(Type callingType, Func generateMessage); + /// + /// Logs a warning message with an exception. + /// + /// The reporting type. + /// An exception. + /// A message. + void Warn(Type reporting, Exception exception, string message); - void Debug(Type type, string generateMessageFormat, params Func[] formatItems); + /// + /// Logs a warning message with an exception. + /// + /// The reporting type. + /// An exception. + /// A message builder. + void Warn(Type reporting, Exception exception, Func messageBuilder); + + /// + /// Logs an information message. + /// + /// The reporting type. + /// A message. + void Info(Type reporting, string message); + + /// + /// Logs an information message. + /// + /// The reporting type. + /// A message builder. + void Info(Type reporting, Func messageBuilder); + + /// + /// Logs a debugging message. + /// + /// The reporting type. + /// A message. + void Debug(Type reporting, string message); + + /// + /// Logs a debugging message. + /// + /// The reporting type. + /// A message builder. + void Debug(Type reporting, Func messageBuilder); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Logging/IProfiler.cs b/src/Umbraco.Core/Logging/IProfiler.cs new file mode 100644 index 0000000000..53d1d96072 --- /dev/null +++ b/src/Umbraco.Core/Logging/IProfiler.cs @@ -0,0 +1,38 @@ +using System; + +namespace Umbraco.Core.Logging +{ + /// + /// Defines the profiling service. + /// + public interface IProfiler + { + /// + /// Renders the profiling results. + /// + /// The profiling results. + /// Generally used for Html rendering. + string Render(); + + /// + /// Gets an that will time the code between its creation and disposal. + /// + /// The name of the step. + /// A step. + /// The returned is meant to be used within a using (...) {{ ... }} block. + IDisposable Step(string name); + + /// + /// Starts the profiler. + /// + void Start(); + + /// + /// Stops the profiler. + /// + /// A value indicating whether to discard results. + /// Set discardResult to true to abandon all profiling - useful when eg someone is not + /// authenticated or you want to clear the results, based upon some other mechanism. + void Stop(bool discardResults = false); + } +} diff --git a/src/Umbraco.Core/Logging/IQueue.cs b/src/Umbraco.Core/Logging/IQueue.cs index d063993ef6..db87cb7705 100644 --- a/src/Umbraco.Core/Logging/IQueue.cs +++ b/src/Umbraco.Core/Logging/IQueue.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Logging +namespace Umbraco.Core.Logging { /// /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 @@ -9,4 +9,4 @@ namespace Umbraco.Core.Logging void Enqueue(T item); bool TryDequeue(out T ret); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Logging/ImageProcessorLogger.cs b/src/Umbraco.Core/Logging/ImageProcessorLogger.cs index 015aced101..02faac39aa 100644 --- a/src/Umbraco.Core/Logging/ImageProcessorLogger.cs +++ b/src/Umbraco.Core/Logging/ImageProcessorLogger.cs @@ -1,15 +1,16 @@ using System; using System.Runtime.CompilerServices; using ImageProcessor.Common.Exceptions; +using Umbraco.Core.Composing; namespace Umbraco.Core.Logging -{ +{ /// /// A logger for explicitly logging ImageProcessor exceptions. /// /// Creating this logger is enough for ImageProcessor to find and replace its in-built debug logger - /// without any additional configuration required. This class currently has to be public in order + /// without any additional configuration required. This class currently has to be public in order /// to do so. /// /// @@ -25,8 +26,8 @@ namespace Umbraco.Core.Logging public void Log(string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) { // Using LogHelper since the ImageProcessor logger expects a parameterless constructor. - var message = string.Format("{0} {1} : {2}", callerName, lineNumber, text); - LogHelper.Error(string.Empty, new ImageProcessingException(message)); + var message = $"{callerName} {lineNumber} : {text}"; + Current.Logger.Error(string.Empty, new ImageProcessingException(message)); } /// @@ -39,8 +40,8 @@ namespace Umbraco.Core.Logging public void Log(Type type, string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0) { // Using LogHelper since the ImageProcessor logger expects a parameterless constructor. - var message = string.Format("{0} {1} : {2}", callerName, lineNumber, text); - LogHelper.Error(type, string.Empty, new ImageProcessingException(message)); + var message = $"{callerName} {lineNumber} : {text}"; + Current.Logger.Error(type, string.Empty, new ImageProcessingException(message)); } } } diff --git a/src/Umbraco.Core/Logging/LogHelper.cs b/src/Umbraco.Core/Logging/LogHelper.cs deleted file mode 100644 index c789beb489..0000000000 --- a/src/Umbraco.Core/Logging/LogHelper.cs +++ /dev/null @@ -1,234 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Web; -using log4net; - -namespace Umbraco.Core.Logging -{ - /// - /// Used for logging, ILogger should be used instead but this is available for static access to logging - /// - /// - /// this wraps ILogger - /// - public static class LogHelper - { - #region Error - /// - /// Adds an error log - /// - /// - /// - /// - public static void Error(string message, Exception exception) - { - if (LoggerResolver.HasCurrent == false || LoggerResolver.Current.HasValue == false) return; - LoggerResolver.Current.Logger.Error(message, exception); - } - - public static void Error(Type callingType, string message, Exception exception) - { - if (LoggerResolver.HasCurrent == false || LoggerResolver.Current.HasValue == false) return; - LoggerResolver.Current.Logger.Error(callingType, message, exception); - } - - #endregion - - #region Warn - - public static void Warn(Type callingType, string message, params Func[] formatItems) - { - if (LoggerResolver.HasCurrent == false || LoggerResolver.Current.HasValue == false) return; - LoggerResolver.Current.Logger.Warn(callingType, message, formatItems); - } - - [Obsolete("Warnings with http trace should not be used. This method will be removed in future versions")] - public static void Warn(Type callingType, string message, bool showHttpTrace, params Func[] formatItems) - { - Mandate.ParameterNotNull(callingType, "callingType"); - Mandate.ParameterNotNullOrEmpty(message, "message"); - - if (showHttpTrace && HttpContext.Current != null) - { - HttpContext.Current.Trace.Warn(callingType.Name, string.Format(message, formatItems.Select(x => x.Invoke()).ToArray())); - } - - if (LoggerResolver.HasCurrent == false || LoggerResolver.Current.HasValue == false) return; - LoggerResolver.Current.Logger.Warn(callingType, message, formatItems); - - } - - public static void WarnWithException(Type callingType, string message, Exception e, params Func[] formatItems) - { - WarnWithException(callingType, message, false, e, formatItems); - } - - [Obsolete("Warnings with http trace should not be used. This method will be removed in future versions")] - public static void WarnWithException(Type callingType, string message, bool showHttpTrace, Exception e, params Func[] formatItems) - { - Mandate.ParameterNotNull(e, "e"); - Mandate.ParameterNotNull(callingType, "callingType"); - Mandate.ParameterNotNullOrEmpty(message, "message"); - - if (showHttpTrace && HttpContext.Current != null) - { - HttpContext.Current.Trace.Warn( - callingType.Name, - string.Format(message, formatItems.Select(x => x.Invoke()).ToArray()), - e); - } - - if (LoggerResolver.HasCurrent == false || LoggerResolver.Current.HasValue == false) return; - LoggerResolver.Current.Logger.WarnWithException(callingType, message, e, formatItems); - } - - /// - /// Adds a warn log - /// - /// - /// - /// - public static void Warn(string message, params Func[] formatItems) - { - Warn(typeof(T), message, formatItems); - } - - [Obsolete("Warnings with http trace should not be used. This method will be removed in future versions")] - public static void Warn(string message, bool showHttpTrace, params Func[] formatItems) - { - Warn(typeof(T), message, showHttpTrace, formatItems); - } - - public static void WarnWithException(string message, Exception e, params Func[] formatItems) - { - WarnWithException(typeof(T), message, e, formatItems); - } - - [Obsolete("Warnings with http trace should not be used. This method will be removed in future versions")] - public static void WarnWithException(string message, bool showHttpTrace, Exception e, params Func[] formatItems) - { - WarnWithException(typeof(T), message, showHttpTrace, e, formatItems); - } - - #endregion - - #region Info - /// - /// Traces a message, only generating the message if tracing is actually enabled. Use this method to avoid calling any long-running methods such as "ToDebugString" if logging is disabled. - /// - /// - /// The delegate to generate a message. - /// - public static void Info(Func generateMessage) - { - Info(typeof(T), generateMessage); - } - - /// - /// Traces if tracing is enabled. - /// - /// - /// - public static void Info(Type callingType, Func generateMessage) - { - if (LoggerResolver.HasCurrent == false || LoggerResolver.Current.HasValue == false) return; - LoggerResolver.Current.Logger.Info(callingType, generateMessage); - } - - /// - /// Traces if tracing is enabled. - /// - /// The type for the logging namespace. - /// The message format. - /// The format items. - public static void Info(Type type, string generateMessageFormat, params Func[] formatItems) - { - if (LoggerResolver.HasCurrent == false || LoggerResolver.Current.HasValue == false) return; - LoggerResolver.Current.Logger.Info(type, generateMessageFormat, formatItems); - } - - /// - /// Traces a message, only generating the message if tracing is actually enabled. Use this method to avoid calling any long-running methods such as "ToDebugString" if logging is disabled. - /// - /// - /// The generate message format. - /// The format items. - /// - public static void Info(string generateMessageFormat, params Func[] formatItems) - { - Info(typeof(T), generateMessageFormat, formatItems); - } - #endregion - - #region Debug - /// - /// Debugs a message, only generating the message if tracing is actually enabled. Use this method to avoid calling any long-running methods such as "ToDebugString" if logging is disabled. - /// - /// - /// The delegate to generate a message. - /// - public static void Debug(Func generateMessage) - { - Debug(typeof(T), generateMessage); - } - - /// - /// Debugs if tracing is enabled. - /// - /// - /// - public static void Debug(Type callingType, Func generateMessage) - { - if (LoggerResolver.HasCurrent == false || LoggerResolver.Current.HasValue == false) return; - LoggerResolver.Current.Logger.Debug(callingType, generateMessage); - } - - /// - /// Debugs if tracing is enabled. - /// - /// The type for the logging namespace. - /// The message format. - /// The format items. - public static void Debug(Type type, string generateMessageFormat, params Func[] formatItems) - { - if (LoggerResolver.HasCurrent == false || LoggerResolver.Current.HasValue == false) return; - LoggerResolver.Current.Logger.Debug(type, generateMessageFormat, formatItems); - } - - /// - /// Debugs a message, only generating the message if debug is actually enabled. Use this method to avoid calling any long-running methods such as "ToDebugString" if logging is disabled. - /// - /// - /// The generate message format. - /// The format items. - /// - public static void Debug(string generateMessageFormat, params Func[] formatItems) - { - Debug(typeof(T), generateMessageFormat, formatItems); - } - - /// - /// Debugs a message and also writes to the TraceContext specified, useful for when you would like the debug - /// output also displayed in the Http trace output. - /// - /// - /// - /// - /// - [Obsolete("Warnings with http trace should not be used. This method will be removed in future versions")] - public static void Debug(string generateMessageFormat, bool showHttpTrace, params Func[] formatItems) - { - if (showHttpTrace && HttpContext.Current != null) - { - HttpContext.Current.Trace.Write( - typeof(T).Name, - string.Format(generateMessageFormat, formatItems.Select(x => x()).ToArray())); - } - Debug(typeof(T), generateMessageFormat, formatItems); - } - - #endregion - - } -} diff --git a/src/Umbraco.Core/Logging/LogProfiler.cs b/src/Umbraco.Core/Logging/LogProfiler.cs new file mode 100644 index 0000000000..b556a1b549 --- /dev/null +++ b/src/Umbraco.Core/Logging/LogProfiler.cs @@ -0,0 +1,62 @@ +using System; +using System.Diagnostics; + +namespace Umbraco.Core.Logging +{ + /// + /// Implements by writing profiling results to an . + /// + internal class LogProfiler : IProfiler + { + private readonly ILogger _logger; + + public LogProfiler(ILogger logger) + { + _logger = logger; + } + + /// + public string Render() + { + return string.Empty; + } + + /// + public IDisposable Step(string name) + { + _logger.Debug($"Begin: {name}."); + return new LightDisposableTimer(duration => _logger.Info($"End {name}. ({duration}ms)")); + } + + /// + public void Start() + { + // the log will always be started + } + + /// + public void Stop(bool discardResults = false) + { + // the log never stops + } + + // a lightweight disposable timer + private class LightDisposableTimer : DisposableObject + { + private readonly Action _callback; + private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); + + protected internal LightDisposableTimer(Action callback) + { + if (callback == null) throw new ArgumentNullException(nameof(callback)); + _callback = callback; + } + + protected override void DisposeResources() + { + _stopwatch.Stop(); + _callback(_stopwatch.ElapsedMilliseconds); + } + } + } +} diff --git a/src/Umbraco.Core/Logging/Logger.cs b/src/Umbraco.Core/Logging/Logger.cs index d0bfcbfca0..188cf01ac9 100644 --- a/src/Umbraco.Core/Logging/Logger.cs +++ b/src/Umbraco.Core/Logging/Logger.cs @@ -1,88 +1,71 @@ using System; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Threading; -using System.Web; using log4net; using log4net.Config; using Umbraco.Core.Configuration; using Umbraco.Core.Diagnostics; +using log4net.Util; namespace Umbraco.Core.Logging { /// - /// Used for logging - /// + /// Implements on top of log4net. + /// public class Logger : ILogger { - + /// + /// Initialize a new instance of the class with a log4net configuration file. + /// + /// public Logger(FileInfo log4NetConfigFile) - :this() + : this() { XmlConfigurator.Configure(log4NetConfigFile); } + // private for CreateWithDefaultLog4NetConfiguration private Logger() { - //Add custom global properties to the log4net context that we can use in our logging output - - log4net.GlobalContext.Properties["processId"] = Process.GetCurrentProcess().Id; - log4net.GlobalContext.Properties["appDomainId"] = AppDomain.CurrentDomain.Id; + // add custom global properties to the log4net context that we can use in our logging output + GlobalContext.Properties["processId"] = Process.GetCurrentProcess().Id; + GlobalContext.Properties["appDomainId"] = AppDomain.CurrentDomain.Id; } /// - /// Creates a logger with the default log4net configuration discovered (i.e. from the web.config) + /// Creates a logger with the default log4net configuration discovered (i.e. from the web.config). /// - /// + /// Used by UmbracoApplicationBase to get its logger. public static Logger CreateWithDefaultLog4NetConfiguration() { return new Logger(); } - /// - /// Returns a logger for the type specified - /// - /// - /// - internal ILog LoggerFor() - { - return LogManager.GetLogger(typeof(T)); - } + /// + public void Error(Type reporting, string message, Exception exception = null) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null) return; - /// - /// Returns a logger for the object's type - /// - /// - /// - internal ILog LoggerFor(object getTypeFromInstance) - { - if (getTypeFromInstance == null) throw new ArgumentNullException("getTypeFromInstance"); + var dump = false; - return LogManager.GetLogger(getTypeFromInstance.GetType()); - } - - public void Error(Type callingType, string message, Exception exception) - { - var logger = LogManager.GetLogger(callingType); - if (logger == null) return; - - var dump = false; - - if (IsTimeoutThreadAbortException(exception)) - { - message += "\r\nThe thread has been aborted, because the request has timed out."; + if (IsTimeoutThreadAbortException(exception)) + { + message += "\r\nThe thread has been aborted, because the request has timed out."; // dump if configured, or if stacktrace contains Monitor.ReliableEnter - dump = UmbracoConfig.For.CoreDebug().DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception); + dump = UmbracoConfig.For.CoreDebug().DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception); // dump if it is ok to dump (might have a cap on number of dump...) - dump &= MiniDump.OkToDump(); - } + dump &= MiniDump.OkToDump(); + } if (dump) - { + { try { var dumped = MiniDump.Dump(withException: true); @@ -97,7 +80,7 @@ namespace Umbraco.Core.Logging } logger.Error(message, exception); - } + } private static bool IsMonitorEnterThreadAbortException(Exception exception) { @@ -124,94 +107,138 @@ namespace Umbraco.Core.Logging return (bool) timeoutField.GetValue(abort.ExceptionState); } - public void Warn(Type callingType, string message, params Func[] formatItems) - { - var logger = LogManager.GetLogger(callingType); - if (logger == null || logger.IsWarnEnabled == false) return; - logger.WarnFormat((message), formatItems.Select(x => x.Invoke()).ToArray()); - } - - public void Warn(Type callingType, string message, bool showHttpTrace, params Func[] formatItems) - { - Mandate.ParameterNotNull(callingType, "callingType"); - Mandate.ParameterNotNullOrEmpty(message, "message"); - - if (showHttpTrace && HttpContext.Current != null) - { - HttpContext.Current.Trace.Warn(callingType.Name, string.Format(message, formatItems.Select(x => x.Invoke()).ToArray())); - } - - var logger = LogManager.GetLogger(callingType); - if (logger == null || logger.IsWarnEnabled == false) return; - logger.WarnFormat((message), formatItems.Select(x => x.Invoke()).ToArray()); - - } - - public void WarnWithException(Type callingType, string message, Exception e, params Func[] formatItems) - { - Mandate.ParameterNotNull(e, "e"); - Mandate.ParameterNotNull(callingType, "callingType"); - Mandate.ParameterNotNullOrEmpty(message, "message"); - - var logger = LogManager.GetLogger(callingType); + /// + public void Warn(Type reporting, string format) + { + var logger = LogManager.GetLogger(reporting); if (logger == null || logger.IsWarnEnabled == false) return; - var executedParams = formatItems.Select(x => x.Invoke()).ToArray(); - logger.WarnFormat((message) + ". Exception: " + e, executedParams); - } + logger.Warn(format); + } - /// - /// Traces if tracing is enabled. - /// - /// - /// - public void Info(Type callingType, Func generateMessage) - { - var logger = LogManager.GetLogger(callingType); - if (logger == null || logger.IsInfoEnabled == false) return; - logger.Info((generateMessage.Invoke())); - } + /// + public void Warn(Type reporting, Func messageBuilder) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsWarnEnabled == false) return; + logger.Warn(messageBuilder()); + } - /// - /// Traces if tracing is enabled. - /// - /// The type for the logging namespace. - /// The message format. - /// The format items. - public void Info(Type type, string generateMessageFormat, params Func[] formatItems) - { - var logger = LogManager.GetLogger(type); - if (logger == null || logger.IsInfoEnabled == false) return; - var executedParams = formatItems.Select(x => x.Invoke()).ToArray(); - logger.InfoFormat((generateMessageFormat), executedParams); - } + /// + public void Warn(Type reporting, string format, params object[] args) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsWarnEnabled == false) return; + logger.WarnFormat(format, args); + } + /// + public void Warn(Type reporting, string format, params Func[] args) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsWarnEnabled == false) return; + logger.WarnFormat(format, args.Select(x => x.Invoke()).ToArray()); + } - /// - /// Debugs if tracing is enabled. - /// - /// - /// - public void Debug(Type callingType, Func generateMessage) - { - var logger = LogManager.GetLogger(callingType); - if (logger == null || logger.IsDebugEnabled == false) return; - logger.Debug((generateMessage.Invoke())); - } + /// + public void Warn(Type reporting, Exception exception, string message) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsWarnEnabled == false) return; + logger.Warn(message, exception); + } - /// - /// Debugs if tracing is enabled. - /// - /// The type for the logging namespace. - /// The message format. - /// The format items. - public void Debug(Type type, string generateMessageFormat, params Func[] formatItems) - { - var logger = LogManager.GetLogger(type); - if (logger == null || logger.IsDebugEnabled == false) return; - var executedParams = formatItems.Select(x => x.Invoke()).ToArray(); - logger.DebugFormat((generateMessageFormat), executedParams); - } + /// + public void Warn(Type reporting, Exception exception, Func messageBuilder) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsWarnEnabled == false) return; + logger.Warn(messageBuilder(), exception); + } + /// + public void Warn(Type reporting, Exception exception, string format, params object[] args) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsWarnEnabled == false) return; + // there is no WarnFormat overload that accepts an exception + // format the message the way log4net would do it (see source code LogImpl.cs) + var message = new SystemStringFormat(CultureInfo.InvariantCulture, format, args); + logger.Warn(message, exception); + } - } + /// + public void Warn(Type reporting, Exception exception, string format, params Func[] args) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsWarnEnabled == false) return; + // there is no WarnFormat overload that accepts an exception + // format the message the way log4net would do it (see source code LogImpl.cs) + var message = new SystemStringFormat(CultureInfo.InvariantCulture, format, args.Select(x => x.Invoke()).ToArray()); + logger.Warn(message, exception); + } + + /// + public void Info(Type reporting, string message) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsInfoEnabled == false) return; + logger.Info(message); + } + + /// + public void Info(Type reporting, Func generateMessage) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsInfoEnabled == false) return; + logger.Info(generateMessage()); + } + + /// + public void Info(Type reporting, string format, params object[] args) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsInfoEnabled == false) return; + logger.InfoFormat(format, args); + } + + /// + public void Info(Type reporting, string format, params Func[] args) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsInfoEnabled == false) return; + logger.InfoFormat(format, args.Select(x => x.Invoke()).ToArray()); + } + + /// + public void Debug(Type reporting, string message) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsDebugEnabled == false) return; + logger.Debug(message); + } + + /// + public void Debug(Type reporting, Func messageBuilder) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsDebugEnabled == false) return; + logger.Debug(messageBuilder()); + } + + /// + public void Debug(Type reporting, string format, params object[] args) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsDebugEnabled == false) return; + logger.DebugFormat(format, args); + } + + /// + public void Debug(Type reporting, string format, params Func[] args) + { + var logger = LogManager.GetLogger(reporting); + if (logger == null || logger.IsDebugEnabled == false) return; + logger.DebugFormat(format, args.Select(x => x.Invoke()).ToArray()); + } + } } diff --git a/src/Umbraco.Core/Logging/LoggerExtensions.cs b/src/Umbraco.Core/Logging/LoggerExtensions.cs index 58bdc1da0b..4c0a6b026a 100644 --- a/src/Umbraco.Core/Logging/LoggerExtensions.cs +++ b/src/Umbraco.Core/Logging/LoggerExtensions.cs @@ -1,90 +1,113 @@ -using System; +using System; +using System.Linq; namespace Umbraco.Core.Logging { /// - /// Allows for strongly typed log sources + /// Provides extension methods for the interface. /// public static class LoggerExtensions { /// - /// Adds an error log + /// Logs an error message. /// - /// - /// - /// - /// - public static void Error(this ILogger logger, string message, Exception exception) + /// The reporting type. + /// The logger. + /// A message. + /// An exception. + public static void Error(this ILogger logger, string message, Exception exception = null) { logger.Error(typeof(T), message, exception); } /// - /// Adds a warn log + /// Logs a warning message. /// - /// - /// - /// - /// - public static void Warn(this ILogger logger, string message, params Func[] formatItems) + /// The reporting type. + /// The logger. + /// A message. + public static void Warn(this ILogger logger, string message) { - logger.Warn(typeof(T), message, formatItems); - } - - public static void WarnWithException(this ILogger logger, string message, Exception e, params Func[] formatItems) - { - logger.WarnWithException(typeof(T), message, e, formatItems); - } - - - /// - /// Traces a message, only generating the message if tracing is actually enabled. Use this method to avoid calling any long-running methods such as "ToDebugString" if logging is disabled. - /// - /// - /// - /// The generate message format. - /// The format items. - /// - public static void Info(this ILogger logger, string generateMessageFormat, params Func[] formatItems) - { - logger.Info(typeof(T), generateMessageFormat, formatItems); + logger.Warn(typeof(T), message); } /// - /// Traces a message, only generating the message if tracing is actually enabled. Use this method to avoid calling any long-running methods such as "ToDebugString" if logging is disabled. + /// Logs a warning message. /// - /// - /// - /// The delegate to generate a message. - /// - public static void Info(this ILogger logger, Func generateMessage) + /// The reporting type. + /// The logger. + /// A message builder. + public static void Warn(this ILogger logger, Func messageBuilder) { - logger.Info(typeof(T), generateMessage); + logger.Warn(typeof(T), messageBuilder); } /// - /// Debugs a message, only generating the message if tracing is actually enabled. Use this method to avoid calling any long-running methods such as "ToDebugString" if logging is disabled. + /// Logs a formatted warning message with an exception. /// - /// - /// - /// The delegate to generate a message. - /// - public static void Debug(this ILogger logger, Func generateMessage) + /// The reporting type. + /// The logger. + /// An exception. + /// A message builder. + public static void Warn(this ILogger logger, Exception exception, Func messageBuilder) { - logger.Debug(typeof(T), generateMessage); + logger.Warn(typeof(T), exception, messageBuilder); } /// - /// Debugs a message, only generating the message if debug is actually enabled. Use this method to avoid calling any long-running methods such as "ToDebugString" if logging is disabled. + /// Logs a formatted warning message with an exception. /// - /// - /// - /// The generate message format. - /// The format items. - /// - public static void Debug(this ILogger logger, string generateMessageFormat, params Func[] formatItems) + /// The reporting type. + /// The logger. + /// An exception. + /// A message. + public static void Warn(this ILogger logger, Exception exception, string message) { - logger.Debug(typeof(T), generateMessageFormat, formatItems); + logger.Warn(typeof(T), exception, message); + } + + /// + /// Logs an information message. + /// + /// The reporting type. + /// The logger. + /// A message. + public static void Info(this ILogger logger, string message) + { + logger.Info(typeof(T), message); + } + + /// + /// Logs an information message. + /// + /// The reporting type. + /// The logger. + /// A message builder. + public static void Info(this ILogger logger, Func messageBuilder) + { + logger.Info(typeof(T), messageBuilder); + } + + /// + /// Logs a debugging message. + /// + /// The reporting type. + /// The logger. + /// A message. + public static void Debug(this ILogger logger, string message) + { + logger.Debug(typeof(T), message); + } + + /// + /// Logs a debugging message. + /// + /// The reporting type. + /// The logger. + /// A message builder. + public static void Debug(this ILogger logger, Func messageBuilder) + { + logger.Debug(typeof(T), messageBuilder); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Logging/LoggerResolver.cs b/src/Umbraco.Core/Logging/LoggerResolver.cs deleted file mode 100644 index 5ca0839c60..0000000000 --- a/src/Umbraco.Core/Logging/LoggerResolver.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Core.Logging -{ - public sealed class LoggerResolver : SingleObjectResolverBase - { - public LoggerResolver(ILogger logger) - : base(logger) - { - - } - - /// - /// Method allowing to change the logger during startup - /// - /// - internal void SetLogger(ILogger profiler) - { - Value = profiler; - } - - /// - /// Gets the current logger - /// - public ILogger Logger - { - get { return Value; } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Logging/LoggingEventContext.cs b/src/Umbraco.Core/Logging/LoggingEventContext.cs index 88222e3c05..2dac3c4676 100644 --- a/src/Umbraco.Core/Logging/LoggingEventContext.cs +++ b/src/Umbraco.Core/Logging/LoggingEventContext.cs @@ -1,4 +1,4 @@ -using log4net.Core; +using log4net.Core; namespace Umbraco.Core.Logging { @@ -17,4 +17,4 @@ namespace Umbraco.Core.Logging public object HttpContext { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Logging/LoggingEventHelper.cs b/src/Umbraco.Core/Logging/LoggingEventHelper.cs index 129098279a..a92e557f34 100644 --- a/src/Umbraco.Core/Logging/LoggingEventHelper.cs +++ b/src/Umbraco.Core/Logging/LoggingEventHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using log4net.Core; namespace Umbraco.Core.Logging @@ -30,4 +30,4 @@ namespace Umbraco.Core.Logging return loggingEvent; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs index 98924b4bf5..614f1ce14c 100644 --- a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs +++ b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs @@ -1,44 +1,44 @@ -using System; +using System; using System.Threading.Tasks; namespace Umbraco.Core.Logging { - internal static class LoggingTaskExtension - { - /// - /// This task shouldn't be waited on (as it's not guaranteed to run), and you shouldn't wait on the parent task either (because it might throw an - /// exception that doesn't get handled). If you want to be waiting on something, use LogErrorsWaitable instead. - /// - /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably be handling - /// errors yourself. - /// - public static Task LogErrors(this Task task, Action logMethod) - { - return task.ContinueWith(t => LogErrorsInner(t, logMethod), TaskContinuationOptions.OnlyOnFaulted); - } + internal static class LoggingTaskExtension + { + /// + /// This task shouldn't be waited on (as it's not guaranteed to run), and you shouldn't wait on the parent task either (because it might throw an + /// exception that doesn't get handled). If you want to be waiting on something, use LogErrorsWaitable instead. + /// + /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably be handling + /// errors yourself. + /// + public static Task LogErrors(this Task task, Action logMethod) + { + return task.ContinueWith(t => LogErrorsInner(t, logMethod), TaskContinuationOptions.OnlyOnFaulted); + } - /// - /// This task can be waited on (as it's guaranteed to run), and you should wait on this rather than the parent task. Because it's - /// guaranteed to run, it may be slower than using LogErrors, and you should consider using that method if you don't want to wait. - /// - /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably be handling - /// errors yourself. - /// - public static Task LogErrorsWaitable(this Task task, Action logMethod) - { - return task.ContinueWith(t => LogErrorsInner(t, logMethod)); - } + /// + /// This task can be waited on (as it's guaranteed to run), and you should wait on this rather than the parent task. Because it's + /// guaranteed to run, it may be slower than using LogErrors, and you should consider using that method if you don't want to wait. + /// + /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably be handling + /// errors yourself. + /// + public static Task LogErrorsWaitable(this Task task, Action logMethod) + { + return task.ContinueWith(t => LogErrorsInner(t, logMethod)); + } - private static void LogErrorsInner(Task task, Action logAction) - { - if (task.Exception != null) - { - logAction("Aggregate Exception with " + task.Exception.InnerExceptions.Count + " inner exceptions: ", task.Exception); - foreach (var innerException in task.Exception.InnerExceptions) - { - logAction("Inner exception from aggregate exception: ", innerException); - } - } - } - } -} \ No newline at end of file + private static void LogErrorsInner(Task task, Action logAction) + { + if (task.Exception != null) + { + logAction("Aggregate Exception with " + task.Exception.InnerExceptions.Count + " inner exceptions: ", task.Exception); + foreach (var innerException in task.Exception.InnerExceptions) + { + logAction("Inner exception from aggregate exception: ", innerException); + } + } + } + } +} diff --git a/src/Umbraco.Core/Logging/OwinLogger.cs b/src/Umbraco.Core/Logging/OwinLogger.cs index f01c83b738..a6ae0c332a 100644 --- a/src/Umbraco.Core/Logging/OwinLogger.cs +++ b/src/Umbraco.Core/Logging/OwinLogger.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; namespace Umbraco.Core.Logging @@ -60,4 +60,4 @@ namespace Umbraco.Core.Logging } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Logging/OwinLoggerFactory.cs b/src/Umbraco.Core/Logging/OwinLoggerFactory.cs index c60ecd1f04..31805b2edc 100644 --- a/src/Umbraco.Core/Logging/OwinLoggerFactory.cs +++ b/src/Umbraco.Core/Logging/OwinLoggerFactory.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Owin.Logging; +using Umbraco.Core.Composing; namespace Umbraco.Core.Logging { @@ -16,9 +13,7 @@ namespace Umbraco.Core.Logging /// public Microsoft.Owin.Logging.ILogger Create(string name) { - return new OwinLogger( - LoggerResolver.HasCurrent ? LoggerResolver.Current.Logger : new DebugDiagnosticsLogger(), - new Lazy(() => Type.GetType(name) ?? typeof (OwinLogger))); + return new OwinLogger(Current.Logger, new Lazy(() => Type.GetType(name) ?? typeof (OwinLogger))); } } } diff --git a/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs b/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs index c930347791..e8dad743e1 100644 --- a/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs +++ b/src/Umbraco.Core/Logging/ParallelForwardingAppender.cs @@ -1,20 +1,21 @@ -using log4net.Core; -using log4net.Util; -using System; -using System.Collections.Concurrent; -using System.Threading; -using System.Threading.Tasks; +// fixme - remove this file +//using log4net.Core; +//using log4net.Util; +//using System; +//using System.Collections.Concurrent; +//using System.Threading; +//using System.Threading.Tasks; -namespace Umbraco.Core.Logging -{ - /// - /// An asynchronous appender based on - /// - /// - /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 - /// - [Obsolete("Use the Log4Net.Async.ParallelForwardingAppender instead this will be removed in future versions")] - public class ParallelForwardingAppender : Log4Net.Async.ParallelForwardingAppender - { - } -} \ No newline at end of file +//namespace Umbraco.Core.Logging +//{ +// /// +// /// An asynchronous appender based on +// /// +// /// +// /// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8 +// /// +// [Obsolete("Use the Log4Net.Async.ParallelForwardingAppender instead this will be removed in future versions")] +// public class ParallelForwardingAppender : Log4Net.Async.ParallelForwardingAppender +// { +// } +//} diff --git a/src/Umbraco.Core/Logging/ProfilerExtensions.cs b/src/Umbraco.Core/Logging/ProfilerExtensions.cs new file mode 100644 index 0000000000..533837b08b --- /dev/null +++ b/src/Umbraco.Core/Logging/ProfilerExtensions.cs @@ -0,0 +1,39 @@ +using System; + +namespace Umbraco.Core.Logging +{ + internal static class ProfilerExtensions + { + /// + /// Gets an that will time the code between its creation and disposal, + /// prefixing the name of the step with a reporting type name. + /// + /// The reporting type. + /// The profiler. + /// The name of the step. + /// A step. + /// The returned is meant to be used within a using (...) {{ ... }} block. + internal static IDisposable Step(this IProfiler profiler, string name) + { + if (profiler == null) throw new ArgumentNullException(nameof(profiler)); + return profiler.Step(typeof (T), name); + } + + /// + /// Gets an that will time the code between its creation and disposal, + /// prefixing the name of the step with a reporting type name. + /// + /// The profiler. + /// The reporting type. + /// The name of the step. + /// A step. + /// The returned is meant to be used within a using (...) {{ ... }} block. + internal static IDisposable Step(this IProfiler profiler, Type reporting, string name) + { + if (profiler == null) throw new ArgumentNullException(nameof(profiler)); + if (reporting == null) throw new ArgumentNullException(nameof(reporting)); + if (name == null) throw new ArgumentNullException(nameof(name)); + return profiler.Step($"[{reporting.Name}] {name}"); + } + } +} diff --git a/src/Umbraco.Core/Logging/ProfilingLogger.cs b/src/Umbraco.Core/Logging/ProfilingLogger.cs index 0d1efeb0d9..28d2494b0d 100644 --- a/src/Umbraco.Core/Logging/ProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/ProfilingLogger.cs @@ -1,52 +1,52 @@ -using System; -using Umbraco.Core.Profiling; +using System; namespace Umbraco.Core.Logging { /// - /// Used to create DisposableTimer instances for debugging or tracing durations + /// Provides debug or trace logging with duration management. /// public sealed class ProfilingLogger { - public ILogger Logger { get; private set; } - public IProfiler Profiler { get; private set; } + public ILogger Logger { get; } + + public IProfiler Profiler { get; } public ProfilingLogger(ILogger logger, IProfiler profiler) { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (profiler == null) throw new ArgumentNullException(nameof(profiler)); Logger = logger; Profiler = profiler; - if (logger == null) throw new ArgumentNullException("logger"); - if (profiler == null) throw new ArgumentNullException("profiler"); - } - - public DisposableTimer TraceDuration(string startMessage, string completeMessage) - { - return new DisposableTimer(Logger, DisposableTimer.LogType.Info, Profiler, typeof(T), startMessage, completeMessage); } public DisposableTimer TraceDuration(string startMessage) { - return new DisposableTimer(Logger, DisposableTimer.LogType.Info, Profiler, typeof(T), startMessage, "Complete"); + return TraceDuration(startMessage, "Completed."); } - public DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage) + public DisposableTimer TraceDuration(string startMessage, string completeMessage, string failMessage = null) { - return new DisposableTimer(Logger, DisposableTimer.LogType.Info, Profiler, loggerType, startMessage, completeMessage); + return new DisposableTimer(Logger, DisposableTimer.LogType.Info, Profiler, typeof(T), startMessage, completeMessage, failMessage); } - public DisposableTimer DebugDuration(string startMessage, string completeMessage) + public DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage, string failMessage = null) { - return new DisposableTimer(Logger, DisposableTimer.LogType.Debug, Profiler, typeof(T), startMessage, completeMessage); + return new DisposableTimer(Logger, DisposableTimer.LogType.Info, Profiler, loggerType, startMessage, completeMessage, failMessage); } public DisposableTimer DebugDuration(string startMessage) { - return new DisposableTimer(Logger, DisposableTimer.LogType.Debug, Profiler, typeof(T), startMessage, "Complete"); + return DebugDuration(startMessage, "Completed."); } - public DisposableTimer DebugDuration(Type loggerType, string startMessage, string completeMessage) + public DisposableTimer DebugDuration(string startMessage, string completeMessage, string failMessage = null, int thresholdMilliseconds = 0) { - return new DisposableTimer(Logger, DisposableTimer.LogType.Debug, Profiler, loggerType, startMessage, completeMessage); + return new DisposableTimer(Logger, DisposableTimer.LogType.Debug, Profiler, typeof(T), startMessage, completeMessage, failMessage, thresholdMilliseconds); + } + + public DisposableTimer DebugDuration(Type loggerType, string startMessage, string completeMessage, string failMessage = null, int thresholdMilliseconds = 0) + { + return new DisposableTimer(Logger, DisposableTimer.LogType.Debug, Profiler, loggerType, startMessage, completeMessage, failMessage, thresholdMilliseconds); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Logging/RingBuffer.cs b/src/Umbraco.Core/Logging/RingBuffer.cs index 8dd78129b8..18f28faf6b 100644 --- a/src/Umbraco.Core/Logging/RingBuffer.cs +++ b/src/Umbraco.Core/Logging/RingBuffer.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Core.Logging { @@ -75,4 +75,4 @@ namespace Umbraco.Core.Logging } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Logging/VoidProfiler.cs b/src/Umbraco.Core/Logging/VoidProfiler.cs new file mode 100644 index 0000000000..a33f1b2444 --- /dev/null +++ b/src/Umbraco.Core/Logging/VoidProfiler.cs @@ -0,0 +1,31 @@ +using System; + +namespace Umbraco.Core.Logging +{ + internal class VoidProfiler : IProfiler + { + private readonly VoidDisposable _disposable = new VoidDisposable(); + + public string Render() + { + return string.Empty; + } + + public IDisposable Step(string name) + { + return _disposable; + } + + public void Start() + { } + + public void Stop(bool discardResults = false) + { } + + private class VoidDisposable : DisposableObject + { + protected override void DisposeResources() + { } + } + } +} diff --git a/src/Umbraco.Core/Logging/WebProfiler.cs b/src/Umbraco.Core/Logging/WebProfiler.cs new file mode 100644 index 0000000000..7284ea05bd --- /dev/null +++ b/src/Umbraco.Core/Logging/WebProfiler.cs @@ -0,0 +1,106 @@ +using System; +using System.Threading; +using System.Web; +using StackExchange.Profiling; +using StackExchange.Profiling.SqlFormatters; + +namespace Umbraco.Core.Logging +{ + /// + /// Implements by using the MiniProfiler framework. + /// + internal class WebProfiler : IProfiler + { + private const string BootRequestItemKey = "Umbraco.Core.Logging.WebProfiler__isBootRequest"; + private readonly WebProfilerProvider _provider; + private int _first; + + public WebProfiler() + { + // create our own provider, which can provide a profiler even during boot + _provider = new WebProfilerProvider(); + + // settings + MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); + MiniProfiler.Settings.StackMaxLength = 5000; + MiniProfiler.Settings.ProfilerProvider = _provider; + } + + public void UmbracoApplicationBeginRequest(object sender, EventArgs e) + { + // if this is the first request, notify our own provider that this request is the boot request + var first = Interlocked.Exchange(ref _first, 1) == 0; + if (first) + { + _provider.BeginBootRequest(); + ((HttpApplication) sender).Context.Items[BootRequestItemKey] = true; + // and no need to start anything, profiler is already there + } + // else start a profiler, the normal way + else if (ShouldProfile(sender)) + Start(); + } + + public void UmbracoApplicationEndRequest(object sender, EventArgs e) + { + // if this is the boot request, or if we should profile this request, stop + // (the boot request is always profiled, no matter what) + var isBootRequest = ((HttpApplication) sender).Context.Items[BootRequestItemKey] != null; // fixme perfs + if (isBootRequest) + _provider.EndBootRequest(); + if (isBootRequest || ShouldProfile(sender)) + Stop(); + } + + private static bool ShouldProfile(object sender) + { + var request = TryGetRequest(sender); + if (request.Success == false) return false; + + if (request.Result.Url.IsClientSideRequest()) return false; + if (bool.TryParse(request.Result.QueryString["umbDebug"], out var umbDebug)) return umbDebug; + if (bool.TryParse(request.Result.Headers["X-UMB-DEBUG"], out var xUmbDebug)) return xUmbDebug; + return false; + } + + /// + public string Render() + { + return MiniProfiler.RenderIncludes(RenderPosition.Right).ToString(); + } + + /// + public IDisposable Step(string name) + { + return MiniProfiler.Current.Step(name); + } + + /// + public void Start() + { + MiniProfiler.Start(); + } + + /// + public void Stop(bool discardResults = false) + { + MiniProfiler.Stop(discardResults); + } + + private static Attempt TryGetRequest(object sender) + { + var app = sender as HttpApplication; + if (app == null) return Attempt.Fail(); + + try + { + var req = app.Request; + return Attempt.Succeed(new HttpRequestWrapper(req)); + } + catch (HttpException ex) + { + return Attempt.Fail(ex); + } + } + } +} diff --git a/src/Umbraco.Core/Logging/WebProfilerComponent.cs b/src/Umbraco.Core/Logging/WebProfilerComponent.cs new file mode 100644 index 0000000000..338a7e72d6 --- /dev/null +++ b/src/Umbraco.Core/Logging/WebProfilerComponent.cs @@ -0,0 +1,49 @@ +using System; +using System.Web; +using Umbraco.Core.Components; + +namespace Umbraco.Core.Logging +{ + internal class WebProfilerComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + // the profiler is too important to be composed in a component, + // it is composed first thing in WebRuntime.Compose - this component + // only initializes it if needed. + // + //public override void Compose(Composition Composition) + //{ + // composition.Container.RegisterSingleton(); + //} + + private WebProfiler _profiler; + + public void Initialize(IProfiler profiler, ILogger logger, IRuntimeState runtime) + { + // although registered in WebRuntime.Compose, ensure that we have not + // been replaced by another component, and we are still "the" profiler + _profiler = profiler as WebProfiler; + if (_profiler == null) + { + // if VoidProfiler was registered, let it be known + var vp = profiler as VoidProfiler; + if (vp != null) + logger.Info("Profiler is VoidProfiler, not profiling (must run debug mode to profile)."); + return; + } + + // bind to ApplicationInit - ie execute the application initialization for *each* application + // it would be a mistake to try and bind to the current application events + UmbracoApplicationBase.ApplicationInit += InitializeApplication; + } + + private void InitializeApplication(object sender, EventArgs args) + { + var app = sender as HttpApplication; + if (app == null) return; + + // for *each* application (this will run more than once) + app.BeginRequest += (s, a) => _profiler.UmbracoApplicationBeginRequest(s, a); + app.EndRequest += (s, a) => _profiler.UmbracoApplicationEndRequest(s, a); + } + } +} diff --git a/src/Umbraco.Core/Logging/WebProfilerProvider.cs b/src/Umbraco.Core/Logging/WebProfilerProvider.cs new file mode 100644 index 0000000000..8fe657a01b --- /dev/null +++ b/src/Umbraco.Core/Logging/WebProfilerProvider.cs @@ -0,0 +1,126 @@ +using System; +using System.Threading; +using System.Web; +using StackExchange.Profiling; + +namespace Umbraco.Core.Logging +{ + /// + /// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows + /// us to profile items during app startup - before an HttpRequest is created + /// + /// + /// Once the boot phase is changed to BootPhase.BootRequest then the base class (default) provider will handle all + /// profiling data and this sub class no longer performs any logic. + /// + internal class WebProfilerProvider : WebRequestProfilerProvider + { + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + private MiniProfiler _startupProfiler; + private int _first; + private volatile BootPhase _bootPhase; + + public WebProfilerProvider() + { + // booting... + _bootPhase = BootPhase.Boot; + } + + /// + /// Indicates the boot phase. + /// + private enum BootPhase + { + Boot = 0, // boot phase (before the 1st request even begins) + BootRequest = 1, // request boot phase (during the 1st request) + Booted = 2 // done booting + } + + public void BeginBootRequest() + { + _locker.EnterWriteLock(); + try + { + if (_bootPhase != BootPhase.Boot) + throw new InvalidOperationException("Invalid boot phase."); + _bootPhase = BootPhase.BootRequest; + + // assign the profiler to be the current MiniProfiler for the request + // is's already active, starting and all + HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; + } + finally + { + _locker.ExitWriteLock(); + } + } + + public void EndBootRequest() + { + _locker.EnterWriteLock(); + try + { + if (_bootPhase != BootPhase.BootRequest) + throw new InvalidOperationException("Invalid boot phase."); + _bootPhase = BootPhase.Booted; + + _startupProfiler = null; + } + finally + { + _locker.ExitWriteLock(); + } + } + + /// + /// Starts a new MiniProfiler. + /// + /// + /// This is called when WebProfiler calls MiniProfiler.Start() so, + /// - as a result of WebRuntime starting the WebProfiler, and + /// - assuming profiling is enabled, on every BeginRequest that should be profiled, + /// - except for the very first one which is the boot request. + /// + public override MiniProfiler Start(string sessionName = null) + { + var first = Interlocked.Exchange(ref _first, 1) == 0; + if (first == false) return base.Start(sessionName); + + _startupProfiler = new MiniProfiler("http://localhost/umbraco-startup") { Name = "StartupProfiler" }; + SetProfilerActive(_startupProfiler); + return _startupProfiler; + } + + // obsolete but that's the one that's called ;-( + public override MiniProfiler Start(ProfileLevel level, string sessionName = null) + { + return Start(sessionName); + } + + /// + /// Gets the current profiler. + /// + /// + /// If the boot phase is not Booted, then this will return the startup profiler (this), otherwise + /// returns the base class + /// + public override MiniProfiler GetCurrentProfiler() + { + // if not booting then just use base (fast) + // no lock, _bootPhase is volatile + if (_bootPhase == BootPhase.Booted) + return base.GetCurrentProfiler(); + + // else + try + { + var current = base.GetCurrentProfiler(); + return current ?? _startupProfiler; + } + catch + { + return _startupProfiler; + } + } + } +} diff --git a/src/Umbraco.Core/MacroErrorBehaviour.cs b/src/Umbraco.Core/MacroErrorBehaviour.cs deleted file mode 100644 index 50d9faa19c..0000000000 --- a/src/Umbraco.Core/MacroErrorBehaviour.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Umbraco.Core -{ - public enum MacroErrorBehaviour - { - /// - /// Default umbraco behavior - show an inline error within the - /// macro but allow the page to continue rendering. - /// - Inline, - - /// - /// Silently eat the error and do not display the offending macro. - /// - Silent, - - /// - /// Throw an exception which can be caught by the global error handler - /// defined in Application_OnError. If no such error handler is defined - /// then you'll see the Yellow Screen Of Death (YSOD) error page. - /// - Throw - } -} diff --git a/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs b/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs new file mode 100644 index 0000000000..dd3d506b23 --- /dev/null +++ b/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs @@ -0,0 +1,29 @@ +namespace Umbraco.Core.Macros +{ + public enum MacroErrorBehaviour + { + /// + /// Default umbraco behavior - show an inline error within the + /// macro but allow the page to continue rendering. + /// + Inline, + + /// + /// Silently eat the error and do not display the offending macro. + /// + Silent, + + /// + /// Throw an exception which can be caught by the global error handler + /// defined in Application_OnError. If no such error handler is defined + /// then you'll see the Yellow Screen Of Death (YSOD) error page. + /// + Throw, + + /// + /// Silently eat the error and display the custom content reported in + /// the error event args + /// + Content + } +} diff --git a/src/Umbraco.Core/Macros/MacroFieldEditorsResolver.cs b/src/Umbraco.Core/Macros/MacroFieldEditorsResolver.cs deleted file mode 100644 index 1788932c8f..0000000000 --- a/src/Umbraco.Core/Macros/MacroFieldEditorsResolver.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.UI; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using umbraco.interfaces; - -namespace Umbraco.Core.Macros -{ - /// - /// A resolver to return all IMacroGuiRendering objects - /// - /// - /// Much of this classes methods are based on legacy code from umbraco.editorControls.macrocontainer.MacroControlFactory - /// this code should probably be reviewed and cleaned up if necessary. - /// - internal sealed class MacroFieldEditorsResolver : LazyManyObjectsResolverBase - { - /// - /// Constructor - /// - /// - /// - /// - internal MacroFieldEditorsResolver(IServiceProvider serviceProvider, ILogger logger, Func> macroEditors) - : base(serviceProvider, logger, macroEditors, ObjectLifetimeScope.Transient) - { } - - /// - /// Gets the implementations. - /// - public IEnumerable MacroFieldEditors - { - get - { - return Values; - } - } - - /// - /// Gets the value based on the type of control - /// - /// - /// - /// - /// This is legacy code migrated from umbraco.editorControls.macrocontainer.MacroControlFactory - /// - internal string GetValueFromMacroControl(Control macroControl) - { - return HttpUtility.HtmlDecode(((IMacroGuiRendering)macroControl).Value); - } - - /// - /// This is legacy code migrated from umbraco.editorControls.macrocontainer.MacroControlFactory - /// - internal List MacroControlTypes - { - get { return InstanceTypes.ToList(); } - } - - /// - /// Create an instance of a Macro control and return it. - /// Because the macro control uses inline client script whichs is not generated after postback - /// That's why we use the Page Picker instead of the content picker of the macro. - /// - /// - /// This is legacy code migrated from umbraco.editorControls.macrocontainer.MacroControlFactory - /// - internal Control GetMacroRenderControlByType(PersistableMacroProperty prop, string uniqueId) - { - var m = MacroControlTypes.FindLast(macroGuiCcontrol => macroGuiCcontrol.ToString() == string.Format("{0}.{1}", prop.AssemblyName, prop.TypeName)); - var instance = ServiceProvider.GetService(m) as IMacroGuiRendering; - if (instance != null) - { - if (string.IsNullOrEmpty(prop.Value) == false) - { - instance.Value = HttpUtility.HtmlDecode(prop.Value); - } - var macroControl = instance as Control; - if (macroControl != null) - { - macroControl.ID = uniqueId; - return macroControl; - } - } - return null; - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Macros/MacroTagParser.cs b/src/Umbraco.Core/Macros/MacroTagParser.cs index 66f8a8a568..26f2b34ae5 100644 --- a/src/Umbraco.Core/Macros/MacroTagParser.cs +++ b/src/Umbraco.Core/Macros/MacroTagParser.cs @@ -1,40 +1,41 @@ -using System; +using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using HtmlAgilityPack; +using Umbraco.Core.Xml; namespace Umbraco.Core.Macros { - /// - /// Parses the macro syntax in a string and renders out it's contents - /// - internal class MacroTagParser - { - private static readonly Regex MacroRteContent = new Regex(@"()", - RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); + /// + /// Parses the macro syntax in a string and renders out it's contents + /// + internal class MacroTagParser + { + private static readonly Regex MacroRteContent = new Regex(@"()", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); - private static readonly Regex MacroPersistedFormat = - new Regex(@"(<\?UMBRACO_MACRO (?:.+?)??macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?)", - RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); + private static readonly Regex MacroPersistedFormat = + new Regex(@"(<\?UMBRACO_MACRO (?:.+?)??macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?)", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); - /// - /// This formats the persisted string to something useful for the rte so that the macro renders properly since we - /// persist all macro formats like {?UMBRACO_MACRO macroAlias=\"myMacro\" /} - /// - /// - /// The html attributes to be added to the div - /// - /// - /// This converts the persisted macro format to this: - /// - /// {div class='umb-macro-holder'} - /// - /// {ins}Macro alias: {strong}My Macro{/strong}{/ins} - /// {/div} - /// - /// - internal static string FormatRichTextPersistedDataForEditor(string persistedContent, IDictionary htmlAttributes) + /// + /// This formats the persisted string to something useful for the rte so that the macro renders properly since we + /// persist all macro formats like {?UMBRACO_MACRO macroAlias=\"myMacro\" /} + /// + /// + /// The html attributes to be added to the div + /// + /// + /// This converts the persisted macro format to this: + /// + /// {div class='umb-macro-holder'} + /// + /// {ins}Macro alias: {strong}My Macro{/strong}{/ins} + /// {/div} + /// + /// + internal static string FormatRichTextPersistedDataForEditor(string persistedContent, IDictionary htmlAttributes) { return MacroPersistedFormat.Replace(persistedContent, match => { @@ -75,21 +76,21 @@ namespace Umbraco.Core.Macros /// /// /// - /// - /// This is required because when editors are using the rte, the html that is contained in the editor might actually be displaying + /// + /// This is required because when editors are using the rte, the html that is contained in the editor might actually be displaying /// the entire macro content, when the data is submitted the editor will clear most of this data out but we'll still need to parse it properly /// and ensure the correct sytnax is persisted to the db. - /// + /// /// When a macro is inserted into the rte editor, the html will be: - /// + /// /// {div class='umb-macro-holder'} /// /// This could be some macro content /// {/div} - /// + /// /// What this method will do is remove the {div} and parse out the commented special macro syntax: {?UMBRACO_MACRO macroAlias=\"myMacro\" /} /// since this is exactly how we need to persist it to the db. - /// + /// /// internal static string FormatRichTextContentForPersistence(string rteContent) { @@ -109,7 +110,7 @@ namespace Umbraco.Core.Macros return rteContent; } - //replace each containing parent
with the comment node itself. + //replace each containing parent
with the comment node itself. foreach (var c in commentNodes) { var div = c.ParentNode; @@ -132,79 +133,79 @@ namespace Umbraco.Core.Macros }); } - /// - /// This will accept a text block and seach/parse it for macro markup. - /// When either a text block or a a macro is found, it will call the callback method. - /// - /// - /// - /// - /// - /// - /// This method simply parses the macro contents, it does not create a string or result, - /// this is up to the developer calling this method to implement this with the callbacks. - /// - internal static void ParseMacros( - string text, - Action textFoundCallback, - Action> macroFoundCallback ) - { - if (textFoundCallback == null) throw new ArgumentNullException("textFoundCallback"); - if (macroFoundCallback == null) throw new ArgumentNullException("macroFoundCallback"); + /// + /// This will accept a text block and seach/parse it for macro markup. + /// When either a text block or a a macro is found, it will call the callback method. + /// + /// + /// + /// + /// + /// + /// This method simply parses the macro contents, it does not create a string or result, + /// this is up to the developer calling this method to implement this with the callbacks. + /// + internal static void ParseMacros( + string text, + Action textFoundCallback, + Action> macroFoundCallback ) + { + if (textFoundCallback == null) throw new ArgumentNullException("textFoundCallback"); + if (macroFoundCallback == null) throw new ArgumentNullException("macroFoundCallback"); - string elementText = text; + string elementText = text; - var fieldResult = new StringBuilder(elementText); + var fieldResult = new StringBuilder(elementText); - //NOTE: This is legacy code, this is definitely not the correct way to do a while loop! :) - var stop = false; - while (!stop) - { - var tagIndex = fieldResult.ToString().ToLower().IndexOf(" -1) - { - var tempElementContent = ""; + //NOTE: This is legacy code, this is definitely not the correct way to do a while loop! :) + var stop = false; + while (!stop) + { + var tagIndex = fieldResult.ToString().ToLower().IndexOf(" -1) + { + var tempElementContent = ""; - //text block found, call the call back method - textFoundCallback(fieldResult.ToString().Substring(0, tagIndex)); + //text block found, call the call back method + textFoundCallback(fieldResult.ToString().Substring(0, tagIndex)); - fieldResult.Remove(0, tagIndex); + fieldResult.Remove(0, tagIndex); - var tag = fieldResult.ToString().Substring(0, fieldResult.ToString().IndexOf(">") + 1); - var attributes = XmlHelper.GetAttributesFromElement(tag); + var tag = fieldResult.ToString().Substring(0, fieldResult.ToString().IndexOf(">") + 1); + var attributes = XmlHelper.GetAttributesFromElement(tag); - // Check whether it's a single tag () or a tag with children (...) - if (tag.Substring(tag.Length - 2, 1) != "/" && tag.IndexOf(" ") > -1) - { - String closingTag = ""; - // Tag with children are only used when a macro is inserted by the umbraco-editor, in the - // following format: "", so we - // need to delete extra information inserted which is the image-tag and the closing - // umbraco_macro tag - if (fieldResult.ToString().IndexOf(closingTag) > -1) - { - fieldResult.Remove(0, fieldResult.ToString().IndexOf(closingTag)); - } - } + // Check whether it's a single tag () or a tag with children (...) + if (tag.Substring(tag.Length - 2, 1) != "/" && tag.IndexOf(" ") > -1) + { + String closingTag = ""; + // Tag with children are only used when a macro is inserted by the umbraco-editor, in the + // following format: "", so we + // need to delete extra information inserted which is the image-tag and the closing + // umbraco_macro tag + if (fieldResult.ToString().IndexOf(closingTag) > -1) + { + fieldResult.Remove(0, fieldResult.ToString().IndexOf(closingTag)); + } + } - var macroAlias = attributes.ContainsKey("macroalias") ? attributes["macroalias"] : attributes["alias"]; + var macroAlias = attributes.ContainsKey("macroalias") ? attributes["macroalias"] : attributes["alias"]; - //call the callback now that we have the macro parsed - macroFoundCallback(macroAlias, attributes); + //call the callback now that we have the macro parsed + macroFoundCallback(macroAlias, attributes); - fieldResult.Remove(0, fieldResult.ToString().IndexOf(">") + 1); - fieldResult.Insert(0, tempElementContent); - } - else - { - //text block found, call the call back method - textFoundCallback(fieldResult.ToString()); + fieldResult.Remove(0, fieldResult.ToString().IndexOf(">") + 1); + fieldResult.Insert(0, tempElementContent); + } + else + { + //text block found, call the call back method + textFoundCallback(fieldResult.ToString()); - stop = true; //break; - } - } - } - } -} \ No newline at end of file + stop = true; //break; + } + } + } + } +} diff --git a/src/Umbraco.Core/Macros/PersistableMacroProperty.cs b/src/Umbraco.Core/Macros/PersistableMacroProperty.cs deleted file mode 100644 index db3f824e4b..0000000000 --- a/src/Umbraco.Core/Macros/PersistableMacroProperty.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections; - -namespace Umbraco.Core.Macros -{ - /// - /// NOTE: This is legacy code, might require a cleanup - /// - [Serializable] - internal class PersistableMacroProperty - { - #region Private Fields - private string _name; - private string _alias; - private string _value; - private string _assemblyName; - private string _typeName; - #endregion - - #region Properties - /// - /// Macro Caption - /// - public string Name - { - get { return _name; } - set { _name = value; } - } - - /// - /// Macro Alias - /// - public string Alias - { - get { return _alias; } - set { _alias = value; } - } - - /// - /// Macro Value - /// - public string Value - { - get { return _value; } - set { _value = value; } - } - - /// - /// AssemblyName of the Property of teh Macro - /// - public string AssemblyName - { - get { return _assemblyName; } - set { _assemblyName = value; } - } - - /// - /// TypeName of the property of the macro - /// - public string TypeName - { - get { return _typeName; } - set { _typeName = value; } - } - #endregion - - } -} diff --git a/src/Umbraco.Core/Macros/XsltExtension.cs b/src/Umbraco.Core/Macros/XsltExtension.cs index 1bc11b4618..7bc9277ac3 100644 --- a/src/Umbraco.Core/Macros/XsltExtension.cs +++ b/src/Umbraco.Core/Macros/XsltExtension.cs @@ -14,4 +14,4 @@ public string Namespace { get; private set; } public object ExtensionObject { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Macros/XsltExtensionAttribute.cs b/src/Umbraco.Core/Macros/XsltExtensionAttribute.cs index d316de8de6..8cde062046 100644 --- a/src/Umbraco.Core/Macros/XsltExtensionAttribute.cs +++ b/src/Umbraco.Core/Macros/XsltExtensionAttribute.cs @@ -30,4 +30,4 @@ namespace Umbraco.Core.Macros return Namespace; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Macros/XsltExtensionCollection.cs b/src/Umbraco.Core/Macros/XsltExtensionCollection.cs new file mode 100644 index 0000000000..4d32382d06 --- /dev/null +++ b/src/Umbraco.Core/Macros/XsltExtensionCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Macros +{ + internal class XsltExtensionCollection : BuilderCollectionBase + { + public XsltExtensionCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Core/Macros/XsltExtensionCollectionBuilder.cs b/src/Umbraco.Core/Macros/XsltExtensionCollectionBuilder.cs new file mode 100644 index 0000000000..45a7615e3d --- /dev/null +++ b/src/Umbraco.Core/Macros/XsltExtensionCollectionBuilder.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LightInject; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Macros +{ + // that one is special since it's not initialized with XsltExtension types, but with Xslt extension object types, + // which are then wrapped in an XsltExtension object when the collection is created. so, cannot really inherit + // from (Lazy)CollectionBuilderBase and have to re-implement it. but almost everything is copied from CollectionBuilderBase. + + internal class XsltExtensionCollectionBuilder : ICollectionBuilder + { + private readonly IServiceContainer _container; + private readonly List>> _producers = new List>>(); + private readonly object _locker = new object(); + private ServiceRegistration[] _registrations; + + public XsltExtensionCollectionBuilder(IServiceContainer container) + { + _container = container; + + // register the collection + container.Register(_ => CreateCollection(), new PerContainerLifetime()); + } + + public static XsltExtensionCollectionBuilder Register(IServiceContainer container) + { + // register the builder - per container + var builderLifetime = new PerContainerLifetime(); + container.Register(builderLifetime); + return container.GetInstance(); + } + + public XsltExtensionCollectionBuilder AddExtensionObjectProducer(Func> producer) + { + lock (_locker) + { + if (_registrations != null) + throw new InvalidOperationException("Cannot configure a collection builder after its types have been resolved."); + _producers.Add(producer); + } + return this; + } + + private void RegisterTypes() + { + lock (_locker) + { + if (_registrations != null) return; + + var prefix = GetType().FullName + "_"; + var i = 0; + foreach (var type in _producers.SelectMany(x => x()).Distinct()) + { + var name = $"{prefix}{i++:00000}"; + _container.Register(type, type, name); + } + + _registrations = _container.AvailableServices + .Where(x => x.ServiceName.StartsWith(prefix)) + .OrderBy(x => x.ServiceName) + .ToArray(); + } + } + + public XsltExtensionCollection CreateCollection() + { + RegisterTypes(); // will do it only once + + var exts = _registrations.SelectMany(r => r.ServiceType.GetCustomAttributes(true) + .Select(a => new XsltExtension(a.Namespace.IfNullOrWhiteSpace(r.ServiceType.FullName), _container.GetInstance(r.ServiceType, r.ServiceName)))); + + return new XsltExtensionCollection(exts); + } + } +} diff --git a/src/Umbraco.Core/Macros/XsltExtensionsResolver.cs b/src/Umbraco.Core/Macros/XsltExtensionsResolver.cs deleted file mode 100644 index 0c754c9d3f..0000000000 --- a/src/Umbraco.Core/Macros/XsltExtensionsResolver.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Core.Macros -{ - /// - /// Used to resolve all xslt extension plugins - /// - internal sealed class XsltExtensionsResolver : LazyManyObjectsResolverBase - { - /// - /// Constructor - /// - /// - /// - /// - internal XsltExtensionsResolver(IServiceProvider serviceProvider, ILogger logger, Func> packageActions) - : base(serviceProvider, logger, packageActions, ObjectLifetimeScope.Application) - { - - } - - /// - /// Returns the list of all xslt extensions - /// - public IEnumerable XsltExtensions - { - get { return Values; } - } - - protected override IEnumerable CreateInstances() - { - var result = new HashSet(); - foreach (var xsltType in InstanceTypes) - { - var tpAttributes = xsltType.GetCustomAttributes(typeof(XsltExtensionAttribute), true); - foreach (XsltExtensionAttribute tpAttribute in tpAttributes) - { - var ns = string.IsNullOrEmpty(tpAttribute.Namespace) == false - ? tpAttribute.Namespace - : xsltType.FullName; - - result.Add(new XsltExtension(ns, Activator.CreateInstance(xsltType))); - } - } - return result; - } - - /// - /// We override this because really there's no limit to the type that can be used here - /// - /// - protected override void EnsureCorrectType(Type value) - { - //do nothing. - } - - } -} diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index 8e7efc5171..1ee409b839 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Web.Hosting; using Umbraco.Core.Logging; @@ -37,26 +38,16 @@ namespace Umbraco.Core private volatile bool _signaled; // we have been signaled // actions to run before releasing the main domain - private readonly SortedList _callbacks = new SortedList(new WeightComparer()); + private readonly List> _callbacks = new List>(); private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds - private class WeightComparer : IComparer - { - public int Compare(int x, int y) - { - var result = x.CompareTo(y); - // return "equal" as "greater than" - return result == 0 ? 1 : result; - } - } - #endregion #region Ctor // initializes a new instance of MainDom - internal MainDom(ILogger logger) + public MainDom(ILogger logger) { _logger = logger; @@ -111,10 +102,9 @@ namespace Umbraco.Core lock (_locko) { if (_signaled) return false; - if (install != null) - install(); + install?.Invoke(); if (release != null) - _callbacks.Add(weight, release); + _callbacks.Add(new KeyValuePair(weight, release)); return true; } } @@ -135,8 +125,8 @@ namespace Umbraco.Core try { - _logger.Info("Stopping..."); - foreach (var callback in _callbacks.Values) + _logger.Info("Stopping."); + foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) { try { @@ -156,7 +146,7 @@ namespace Umbraco.Core // in any case... _isMainDom = false; _asyncLocker.Dispose(); - _logger.Info("Released MainDom."); + _logger.Info("Released."); } } @@ -169,11 +159,11 @@ namespace Umbraco.Core // the handler is not installed so that would be the hosting environment if (_signaled) { - _logger.Info("Cannot acquire MainDom (signaled)."); + _logger.Info("Cannot acquire (signaled)."); return false; } - _logger.Info("Acquiring MainDom..."); + _logger.Info("Acquiring."); // signal other instances that we want the lock, then wait one the lock, // which may timeout, and this is accepted - see comments below @@ -200,16 +190,13 @@ namespace Umbraco.Core HostingEnvironment.RegisterObject(this); - _logger.Info("Acquired MainDom."); + _logger.Info("Acquired."); return true; } } // gets a value indicating whether we are the main domain - public bool IsMainDom - { - get { return _isMainDom; } - } + public bool IsMainDom => _isMainDom; // IRegisteredObject void IRegisteredObject.Stop(bool immediate) diff --git a/src/Umbraco.Core/Mandate.cs b/src/Umbraco.Core/Mandate.cs deleted file mode 100644 index 7ae47fd5c0..0000000000 --- a/src/Umbraco.Core/Mandate.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Core -{ - /// - /// Helper class for mandating values, for example on method parameters. - /// - public static class Mandate - { - /// - /// Mandates that the specified parameter is not null. - /// - /// The value. - /// Name of the param. - /// If is null. - public static void ParameterNotNull(T value, string paramName) where T : class - { - That(value != null, () => new ArgumentNullException(paramName)); - } - - - /// - /// Mandates that the specified parameter is not null. - /// - /// The value. - /// Name of the param. - /// If is null or whitespace. - public static void ParameterNotNullOrEmpty(string value, string paramName) - { - That(!string.IsNullOrWhiteSpace(value), () => new ArgumentNullException(paramName)); - } - - /// - /// Mandates that the specified sequence is not null and has at least one element. - /// - /// - /// The sequence. - /// Name of the param. - public static void ParameterNotNullOrEmpty(IEnumerable sequence, string paramName) - { - ParameterNotNull(sequence, paramName); - ParameterCondition(sequence.Any(), paramName); - } - - - /// - /// Mandates that the specified parameter matches the condition. - /// - /// The condition to check. - /// Name of the param. - /// If the condition is false. - public static void ParameterCondition(bool condition, string paramName) - { - ParameterCondition(condition, paramName, (string)null); - } - - /// - /// Mandates that the specified parameter matches the condition. - /// - /// The condition to check. - /// Name of the param. - /// The message. - /// If the condition is false. - public static void ParameterCondition(bool condition, string paramName, string message) - { - // Warning: don't make this method have an optional message parameter (removing the other ParameterCondition overload) as it will - // make binaries compiled against previous Framework libs incompatible unneccesarily - message = message ?? "A parameter passed into a method was not a valid value"; - That(condition, () => new ArgumentException(message, paramName)); - } - - /// - /// Mandates that the specified condition is true, otherwise throws an exception specified in . - /// - /// The type of the exception. - /// if set to true, throws exception . - /// An exception of type is raised if the condition is false. - public static void That(bool condition) where TException : Exception, new() - { - if (!condition) - throw ActivatorHelper.CreateInstance(); - } - - /// - /// Mandates that the specified condition is true, otherwise throws an exception specified in . - /// - /// The type of the exception. - /// if set to true, throws exception . - /// Deffered expression to call if the exception should be raised. - /// An exception of type is raised if the condition is false. - public static void That(bool condition, Func defer) where TException : Exception, new() - { - if (!condition) - { - throw defer.Invoke(); - } - - // Here is an example of how this method is actually called - //object myParam = null; - //Mandate.That(myParam != null, - // textManager => new ArgumentNullException(textManager.Get("blah", new {User = "blah"}))); - } - } -} diff --git a/src/Umbraco.Core/Manifest/DataEditorConverter.cs b/src/Umbraco.Core/Manifest/DataEditorConverter.cs new file mode 100644 index 0000000000..20efee1607 --- /dev/null +++ b/src/Umbraco.Core/Manifest/DataEditorConverter.cs @@ -0,0 +1,168 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Serialization; + +namespace Umbraco.Core.Manifest +{ + /// + /// Provides a json read converter for in manifests. + /// + internal class DataEditorConverter : JsonReadConverter + { + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + public DataEditorConverter(ILogger logger) + { + _logger = logger; + } + + /// + protected override IDataEditor Create(Type objectType, string path, JObject jobject) + { + // in PackageManifest, property editors are IConfiguredDataEditor[] whereas + // parameter editors are IDataEditor[] - both will end up here because we handle + // IDataEditor and IConfiguredDataEditor implements it, but we can check the + // type to figure out what to create + + var type = EditorType.PropertyValue; + + var isPropertyEditor = path.StartsWith("propertyEditors["); + + if (isPropertyEditor) + { + // property editor + jobject["isPropertyEditor"] = JToken.FromObject(true); + if (jobject["isParameterEditor"] is JToken jToken && jToken.Value()) + type |= EditorType.MacroParameter; + } + else + { + // parameter editor + type = EditorType.MacroParameter; + } + + return new DataEditor(_logger, type); + } + + /// + protected override void Deserialize(JObject jobject, IDataEditor target, JsonSerializer serializer) + { + // see Create above, target is either DataEditor (parameter) or ConfiguredDataEditor (property) + + if (!(target is DataEditor dataEditor)) + throw new Exception("panic."); + + if (jobject["isPropertyEditor"] is JToken jtoken && jtoken.Value()) + PrepareForPropertyEditor(jobject, dataEditor); + else + PrepareForParameterEditor(jobject, dataEditor); + + base.Deserialize(jobject, target, serializer); + } + + private static void PrepareForPropertyEditor(JObject jobject, DataEditor target) + { + if (jobject["editor"] == null) + throw new InvalidOperationException("Missing 'editor' value."); + + // explicitely assign a value editor of type ValueEditor + // (else the deserializer will try to read it before setting it) + // (and besides it's an interface) + target.ExplicitValueEditor = new DataValueEditor(); + + // in the manifest, validators are a simple dictionary eg + // { + // required: true, + // regex: '\\d*' + // } + // and we need to turn this into a list of IPropertyValidator + // so, rewrite the json structure accordingly + if (jobject["editor"]["validation"] is JObject validation) + jobject["editor"]["validation"] = RewriteValidators(validation); + + if (jobject["prevalues"] is JObject config) + { + // explicitely assign a configuration editor of type ConfigurationEditor + // (else the deserializer will try to read it before setting it) + // (and besides it's an interface) + target.ExplicitConfigurationEditor = new ConfigurationEditor(); + + // see note about validators, above - same applies to field validators + if (config["fields"] is JArray jarray) + { + foreach (var field in jarray) + { + if (field["validation"] is JObject fvalidation) + field["validation"] = RewriteValidators(fvalidation); + } + } + + // in the manifest, default configuration is at editor level + // move it down to configuration editor level so it can be deserialized properly + if (jobject["defaultConfig"] is JObject defaultConfig) + { + config["defaultConfig"] = defaultConfig; + jobject.Remove("defaultConfig"); + } + + // in the manifest, configuration is named 'prevalues', rename + // it is important to do this LAST + jobject["config"] = config; + jobject.Remove("prevalues"); + } + } + + private static void PrepareForParameterEditor(JObject jobject, DataEditor target) + { + // in a manifest, a parameter editor looks like: + // + // { + // "alias": "...", + // "name": "...", + // "view": "...", + // "config": { "key1": "value1", "key2": "value2" ... } + // } + // + // the view is at top level, but should be down one level to be propertly + // deserialized as a ParameterValueEditor property -> need to move it + + if (jobject.Property("view") != null) + { + // explicitely assign a value editor of type ParameterValueEditor + target.ExplicitValueEditor = new DataValueEditor(); + + // move the 'view' property + jobject["editor"] = new JObject { ["view"] = jobject["view"] }; + jobject.Property("view").Remove(); + } + + // in the manifest, default configuration is named 'config', rename + if (jobject["config"] is JObject config) + { + jobject["defaultConfig"] = config; + jobject.Remove("config"); + } + } + + private static JArray RewriteValidators(JObject validation) + { + var jarray = new JArray(); + + foreach (var v in validation) + { + var key = v.Key; + var val = v.Value; + var jo = new JObject { { "type", key }, { "configuration", val } }; + jarray.Add(jo); + } + + return jarray; + } + } +} diff --git a/src/Umbraco.Core/Manifest/GridEditorConverter.cs b/src/Umbraco.Core/Manifest/GridEditorConverter.cs deleted file mode 100644 index 76aaf4fc16..0000000000 --- a/src/Umbraco.Core/Manifest/GridEditorConverter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Serialization; - -namespace Umbraco.Core.Manifest -{ - /// - /// Ensures that virtual paths are taken care of - /// - internal class GridEditorConverter : JsonCreationConverter - { - protected override GridEditor Create(Type objectType, JObject jObject) - { - return new GridEditor(); - } - - protected override void Deserialize(JObject jObject, GridEditor target, JsonSerializer serializer) - { - base.Deserialize(jObject, target, serializer); - - if (target.View.IsNullOrWhiteSpace() == false && target.View.StartsWith("~/")) - { - target.View = IOHelper.ResolveUrl(target.View); - } - - if (target.Render.IsNullOrWhiteSpace() == false && target.Render.StartsWith("~/")) - { - target.Render = IOHelper.ResolveUrl(target.Render); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Manifest/ManifestBuilder.cs b/src/Umbraco.Core/Manifest/ManifestBuilder.cs deleted file mode 100644 index 5f95deea22..0000000000 --- a/src/Umbraco.Core/Manifest/ManifestBuilder.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using Umbraco.Core.Cache; -using Umbraco.Core.IO; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Manifest -{ - /// - /// This reads in the manifests and stores some definitions in memory so we can look them on the server side - /// - internal class ManifestBuilder - { - private readonly IRuntimeCacheProvider _cache; - private readonly ManifestParser _parser; - - public ManifestBuilder(IRuntimeCacheProvider cache, ManifestParser parser) - { - _cache = cache; - _parser = parser; - } - - private const string GridEditorsKey = "grideditors"; - private const string PropertyEditorsKey = "propertyeditors"; - private const string ParameterEditorsKey = "parametereditors"; - - /// - /// Returns all grid editors found in the manfifests - /// - internal IEnumerable GridEditors - { - get - { - return _cache.GetCacheItem>( - typeof (ManifestBuilder) + GridEditorsKey, - () => - { - var editors = new List(); - foreach (var manifest in _parser.GetManifests()) - { - if (manifest.GridEditors != null) - { - editors.AddRange(ManifestParser.GetGridEditors(manifest.GridEditors)); - } - - } - return editors; - }, new TimeSpan(0, 10, 0)); - } - } - - /// - /// Returns all property editors found in the manfifests - /// - internal IEnumerable PropertyEditors - { - get - { - return _cache.GetCacheItem>( - typeof(ManifestBuilder) + PropertyEditorsKey, - () => - { - var editors = new List(); - foreach (var manifest in _parser.GetManifests()) - { - if (manifest.PropertyEditors != null) - { - editors.AddRange(ManifestParser.GetPropertyEditors(manifest.PropertyEditors)); - } - - } - return editors; - }, new TimeSpan(0, 10, 0)); - } - } - - /// - /// Returns all parameter editors found in the manfifests and all property editors that are flagged to be parameter editors - /// - internal IEnumerable ParameterEditors - { - get - { - return _cache.GetCacheItem>( - typeof (ManifestBuilder) + ParameterEditorsKey, - () => - { - var editors = new List(); - foreach (var manifest in _parser.GetManifests()) - { - if (manifest.ParameterEditors != null) - { - editors.AddRange(ManifestParser.GetParameterEditors(manifest.ParameterEditors)); - } - } - return editors; - }, new TimeSpan(0, 10, 0)); - } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index e30d188339..cad491aeb1 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -3,10 +3,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; @@ -16,341 +15,158 @@ namespace Umbraco.Core.Manifest /// /// Parses the Main.js file and replaces all tokens accordingly. /// - internal class ManifestParser + public class ManifestParser { - private readonly DirectoryInfo _pluginsDir; + private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); + private readonly IRuntimeCacheProvider _cache; + private readonly ILogger _logger; + private readonly ManifestValueValidatorCollection _validators; - //used to strip comments - private static readonly Regex CommentsSurround = new Regex(@"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/", RegexOptions.Compiled); - private static readonly Regex CommentsLine = new Regex(@"^\s*//.*?$", RegexOptions.Compiled | RegexOptions.Multiline); + private string _path; - public ManifestParser(DirectoryInfo pluginsDir, IRuntimeCacheProvider cache) + /// + /// Initializes a new instance of the class. + /// + public ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, ILogger logger) + : this(cache, validators, "~/App_Plugins", logger) + { } + + /// + /// Initializes a new instance of the class. + /// + private ManifestParser(IRuntimeCacheProvider cache, ManifestValueValidatorCollection validators, string path, ILogger logger) { - if (pluginsDir == null) throw new ArgumentNullException("pluginsDir"); - _pluginsDir = pluginsDir; - _cache = cache; + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + _validators = validators ?? throw new ArgumentNullException(nameof(validators)); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); + Path = path; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public string Path + { + get => _path; + set => _path = value.StartsWith("~/") ? IOHelper.MapPath(value) : value; } /// - /// Parse the grid editors from the json array - /// - /// - /// - internal static IEnumerable GetGridEditors(JArray jsonEditors) - { - return JsonConvert.DeserializeObject>( - jsonEditors.ToString(), - new GridEditorConverter()); - } - - /// - /// Parse the property editors from the json array - /// - /// - /// - internal static IEnumerable GetPropertyEditors(JArray jsonEditors) - { - return JsonConvert.DeserializeObject>( - jsonEditors.ToString(), - new PropertyEditorConverter(), - new PreValueFieldConverter()); - } - - /// - /// Parse the property editors from the json array - /// - /// - /// - internal static IEnumerable GetParameterEditors(JArray jsonEditors) - { - return JsonConvert.DeserializeObject>( - jsonEditors.ToString(), - new ParameterEditorConverter()); - } - - /// - /// Get all registered manifests + /// Gets all manifests, merged into a single manifest object. /// /// - /// - /// This ensures that we only build and look for all manifests once per Web app (based on the IRuntimeCache) - /// - public IEnumerable GetManifests() - { - return _cache.GetCacheItem>(typeof (ManifestParser) + "GetManifests", () => + public PackageManifest Manifest + => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => { - //get all Manifest.js files in the appropriate folders - var manifestFileContents = GetAllManifestFileContents(_pluginsDir); - return CreateManifests(manifestFileContents.ToArray()); - }, new TimeSpan(0, 10, 0)); - } + var manifests = GetManifests(); + return MergeManifests(manifests); + }, new TimeSpan(0, 4, 0)); /// - /// Get the file contents from all declared manifest files + /// Gets all manifests. /// - /// - /// - private IEnumerable GetAllManifestFileContents(DirectoryInfo currDir) + private IEnumerable GetManifests() { - var depth = FolderDepth(_pluginsDir, currDir); - - if (depth < 1) + var manifests = new List(); + + foreach (var path in GetManifestFiles()) { - var result = new List(); - if (currDir.Exists) - { - var dirs = currDir.GetDirectories(); - - foreach (var d in dirs) - { - result.AddRange(GetAllManifestFileContents(d)); - } - } - return result; - } - - //look for files here - return currDir.GetFiles("Package.manifest") - .Select(f => File.ReadAllText(f.FullName)) - .ToList(); - } - - /// - /// Get the folder depth compared to the base folder - /// - /// - /// - /// - internal static int FolderDepth(DirectoryInfo baseDir, DirectoryInfo currDir) - { - var removed = currDir.FullName.Remove(0, baseDir.FullName.Length).TrimStart('\\').TrimEnd('\\'); - return removed.Split(new char[] {'\\'}, StringSplitOptions.RemoveEmptyEntries).Length; - } - - /// - /// Creates a list of PropertyEditorManifest from the file contents of each manifest file - /// - /// - /// - /// - /// This ensures that comments are removed (but they have to be /* */ style comments - /// and ensures that virtual paths are replaced with real ones - /// - internal static IEnumerable CreateManifests(params string[] manifestFileContents) - { - var result = new List(); - foreach (var m in manifestFileContents) - { - var manifestContent = m; - - if (manifestContent.IsNullOrWhiteSpace()) continue; - - // Strip byte object marker, JSON.NET does not like it - var preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); - - // Strangely StartsWith(preamble) would always return true - if (manifestContent.Substring(0, 1) == preamble) - manifestContent = manifestContent.Remove(0, preamble.Length); - - if (manifestContent.IsNullOrWhiteSpace()) continue; - - //remove any comments first - var replaced = CommentsSurround.Replace(manifestContent, match => " "); - replaced = CommentsLine.Replace(replaced, match => ""); - - JObject deserialized; try { - deserialized = JsonConvert.DeserializeObject(replaced); + var text = File.ReadAllText(path); + text = TrimPreamble(text); + if (string.IsNullOrWhiteSpace(text)) + continue; + var manifest = ParseManifest(text); + manifests.Add(manifest); } - catch (Exception ex) + catch (Exception e) { - LogHelper.Error("An error occurred parsing manifest with contents: " + manifestContent, ex); - continue; + _logger.Error($"Failed to parse manifest at \"{path}\", ignoring.", e); } - - //validate the javascript - var init = deserialized.Properties().Where(x => x.Name == "javascript").ToArray(); - if (init.Length > 1) - { - throw new FormatException("The manifest is not formatted correctly contains more than one 'javascript' element"); - } - - //validate the css - var cssinit = deserialized.Properties().Where(x => x.Name == "css").ToArray(); - if (cssinit.Length > 1) - { - throw new FormatException("The manifest is not formatted correctly contains more than one 'css' element"); - } - - //validate the property editors section - var propEditors = deserialized.Properties().Where(x => x.Name == "propertyEditors").ToArray(); - if (propEditors.Length > 1) - { - throw new FormatException("The manifest is not formatted correctly contains more than one 'propertyEditors' element"); - } - - //validate the parameterEditors section - var paramEditors = deserialized.Properties().Where(x => x.Name == "parameterEditors").ToArray(); - if (paramEditors.Length > 1) - { - throw new FormatException("The manifest is not formatted correctly contains more than one 'parameterEditors' element"); - } - - //validate the gridEditors section - var gridEditors = deserialized.Properties().Where(x => x.Name == "gridEditors").ToArray(); - if (gridEditors.Length > 1) - { - throw new FormatException("The manifest is not formatted correctly contains more than one 'gridEditors' element"); - } - - var jConfig = init.Any() ? (JArray)deserialized["javascript"] : new JArray(); - ReplaceVirtualPaths(jConfig); - - var cssConfig = cssinit.Any() ? (JArray)deserialized["css"] : new JArray(); - ReplaceVirtualPaths(cssConfig); - - //replace virtual paths for each property editor - if (deserialized["propertyEditors"] != null) - { - foreach (JObject p in deserialized["propertyEditors"]) - { - if (p["editor"] != null) - { - ReplaceVirtualPaths((JObject) p["editor"]); - } - if (p["preValues"] != null) - { - ReplaceVirtualPaths((JObject)p["preValues"]); - } - } - } - - //replace virtual paths for each property editor - if (deserialized["gridEditors"] != null) - { - foreach (JObject p in deserialized["gridEditors"]) - { - if (p["view"] != null) - { - ReplaceVirtualPaths(p["view"]); - } - if (p["render"] != null) - { - ReplaceVirtualPaths(p["render"]); - } - } - } - - var manifest = new PackageManifest() - { - JavaScriptInitialize = jConfig, - StylesheetInitialize = cssConfig, - PropertyEditors = propEditors.Any() ? (JArray)deserialized["propertyEditors"] : new JArray(), - ParameterEditors = paramEditors.Any() ? (JArray)deserialized["parameterEditors"] : new JArray(), - GridEditors = gridEditors.Any() ? (JArray)deserialized["gridEditors"] : new JArray() - }; - result.Add(manifest); } - return result; + + return manifests; } /// - /// Replaces any virtual paths found in properties + /// Merges all manifests into one. /// - /// - private static void ReplaceVirtualPaths(JArray jarr) + private static PackageManifest MergeManifests(IEnumerable manifests) { - foreach (var i in jarr) + var scripts = new HashSet(); + var stylesheets = new HashSet(); + var propertyEditors = new List(); + var parameterEditors = new List(); + var gridEditors = new List(); + + foreach (var manifest in manifests) { - ReplaceVirtualPaths(i); + if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script); + if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet); + if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors); + if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors); + if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors); } + + return new PackageManifest + { + Scripts = scripts.ToArray(), + Stylesheets = stylesheets.ToArray(), + PropertyEditors = propertyEditors.ToArray(), + ParameterEditors = parameterEditors.ToArray(), + GridEditors = gridEditors.ToArray() + }; + } + + // gets all manifest files (recursively) + private IEnumerable GetManifestFiles() + { + if (Directory.Exists(_path) == false) + return new string[0]; + return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories); + } + + + private static string TrimPreamble(string text) + { + // strangely StartsWith(preamble) would always return true + if (text.Substring(0, 1) == Utf8Preamble) + text = text.Remove(0, Utf8Preamble.Length); + + return text; } /// - /// Replaces any virtual paths found in properties + /// Parses a manifest. /// - /// - private static void ReplaceVirtualPaths(JToken jToken) + internal PackageManifest ParseManifest(string text) { - if (jToken.Type == JTokenType.Object) - { - //recurse - ReplaceVirtualPaths((JObject)jToken); - } - else - { - var value = jToken as JValue; - if (value != null) - { - if (value.Type == JTokenType.String) - { - if (value.Value().StartsWith("~/")) - { - //replace the virtual path - value.Value = IOHelper.ResolveUrl(value.Value()); - } - } - } - } + if (string.IsNullOrWhiteSpace(text)) + throw new ArgumentNullOrEmptyException(nameof(text)); + + var manifest = JsonConvert.DeserializeObject(text, + new DataEditorConverter(_logger), + new ValueValidatorConverter(_validators)); + + // scripts and stylesheets are raw string, must process here + for (var i = 0; i < manifest.Scripts.Length; i++) + manifest.Scripts[i] = IOHelper.ResolveVirtualUrl(manifest.Scripts[i]); + for (var i = 0; i < manifest.Stylesheets.Length; i++) + manifest.Stylesheets[i] = IOHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); + + // add property editors that are also parameter editors, to the parameter editors list + // (the manifest format is kinda legacy) + var ppEditors = manifest.PropertyEditors.Where(x => (x.Type & EditorType.MacroParameter) > 0).ToList(); + if (ppEditors.Count > 0) + manifest.ParameterEditors = manifest.ParameterEditors.Union(ppEditors).ToArray(); + + return manifest; } - /// - /// Replaces any virtual paths found in properties - /// - /// - private static void ReplaceVirtualPaths(JObject jObj) + // purely for tests + internal IEnumerable ParseGridEditors(string text) { - foreach (var p in jObj.Properties().Select(x => x.Value)) - { - ReplaceVirtualPaths(p); - } + return JsonConvert.DeserializeObject>(text); } - - /// - /// Merges two json objects together - /// - /// - /// - /// set to true if we will keep the receiver value if the proeprty already exists - /// - /// taken from - /// http://stackoverflow.com/questions/4002508/does-c-sharp-have-a-library-for-parsing-multi-level-cascading-json/4002550#4002550 - /// - internal static void MergeJObjects(JObject receiver, JObject donor, bool keepOriginal = false) - { - foreach (var property in donor) - { - var receiverValue = receiver[property.Key] as JObject; - var donorValue = property.Value as JObject; - if (receiverValue != null && donorValue != null) - { - MergeJObjects(receiverValue, donorValue); - } - else if (receiver[property.Key] == null || !keepOriginal) - { - receiver[property.Key] = property.Value; - } - } - } - - /// - /// Merges the donor array values into the receiver array - /// - /// - /// - internal static void MergeJArrays(JArray receiver, JArray donor) - { - foreach (var item in donor) - { - if (!receiver.Any(x => x.Equals(item))) - { - receiver.Add(item); - } - } - } - - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Manifest/ManifestValidatorConverter.cs b/src/Umbraco.Core/Manifest/ManifestValidatorConverter.cs deleted file mode 100644 index 41946908c4..0000000000 --- a/src/Umbraco.Core/Manifest/ManifestValidatorConverter.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Newtonsoft.Json.Linq; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Serialization; - -namespace Umbraco.Core.Manifest -{ - /// - /// Used when deserialing the validation collection, any serialized property editors are from a manifest and thus the - /// validators are manifest validators. - /// - internal class ManifestValidatorConverter : JsonCreationConverter - { - protected override IPropertyValidator Create(Type objectType, JObject jObject) - { - return new ManifestPropertyValidator(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Manifest/ManifestWatcher.cs b/src/Umbraco.Core/Manifest/ManifestWatcher.cs index e18ec068b6..3bc70e2d78 100644 --- a/src/Umbraco.Core/Manifest/ManifestWatcher.cs +++ b/src/Umbraco.Core/Manifest/ManifestWatcher.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Web; using Umbraco.Core.Logging; @@ -8,64 +9,61 @@ namespace Umbraco.Core.Manifest { internal class ManifestWatcher : DisposableObjectSlim { + private static readonly object Locker = new object(); + private static volatile bool _isRestarting; + private readonly ILogger _logger; private readonly List _fws = new List(); - private static volatile bool _isRestarting = false; - private static readonly object Locker = new object(); public ManifestWatcher(ILogger logger) { - if (logger == null) throw new ArgumentNullException("logger"); - _logger = logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public void Start(params string[] packageFolders) { - foreach (var packageFolder in packageFolders) + foreach (var packageFolder in packageFolders.Where(IsWatchable)) { - if (Directory.Exists(packageFolder) && File.Exists(Path.Combine(packageFolder, "package.manifest"))) + // for some reason *.manifest doesn't work + var fsw = new FileSystemWatcher(packageFolder, "*package.*") { - //NOTE: for some reason *.manifest doesn't work! - var fsw = new FileSystemWatcher(packageFolder, "*package.*") - { - IncludeSubdirectories = false, - NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite - }; - _fws.Add(fsw); - fsw.Changed += FswChanged; - fsw.EnableRaisingEvents = true; - } + IncludeSubdirectories = false, + NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite + }; + + _fws.Add(fsw); + + fsw.Changed += FswChanged; + fsw.EnableRaisingEvents = true; } } - void FswChanged(object sender, FileSystemEventArgs e) + private static bool IsWatchable(string folder) { - if (e.Name.InvariantContains("package.manifest")) - { - //Ensure the app is not restarted multiple times for multiple saving during the same app domain execution - if (_isRestarting == false) - { - lock (Locker) - { - if (_isRestarting == false) - { - _isRestarting = true; + return Directory.Exists(folder) && File.Exists(Path.Combine(folder, "package.manifest")); + } - _logger.Info("manifest has changed, app pool is restarting (" + e.FullPath + ")"); - HttpRuntime.UnloadAppDomain(); - Dispose(); - } - } - } + private void FswChanged(object sender, FileSystemEventArgs e) + { + if (e.Name.InvariantContains("package.manifest") == false) return; + + // ensure the app is not restarted multiple times for multiple + // savings during the same app domain execution - restart once + lock (Locker) + { + if (_isRestarting) return; + + _isRestarting = true; + _logger.Info("manifest has changed, app pool is restarting (" + e.FullPath + ")"); + HttpRuntime.UnloadAppDomain(); + Dispose(); // uh? if the app restarts then this should be disposed anyways? } } protected override void DisposeResources() { foreach (var fw in _fws) - { fw.Dispose(); - } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index dea1eb9877..1b449e4570 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -1,35 +1,27 @@ -using Newtonsoft.Json.Linq; +using System; +using Newtonsoft.Json; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Manifest { /// - /// Represents a manifest file for packages + /// Represents the content of a package manifest. /// - internal class PackageManifest + public class PackageManifest { - /// - /// The json array used to initialize the application with the JS dependencies required - /// - public JArray JavaScriptInitialize { get; set; } + [JsonProperty("javascript")] + public string[] Scripts { get; set; } = Array.Empty(); - /// - /// The json array used to initialize the application with the CSS dependencies required - /// - public JArray StylesheetInitialize { get; set; } + [JsonProperty("css")] + public string[] Stylesheets { get; set; }= Array.Empty(); - /// - /// The json array of property editors - /// - public JArray PropertyEditors { get; set; } + [JsonProperty("propertyEditors")] + public IDataEditor[] PropertyEditors { get; set; } = Array.Empty(); - /// - /// The json array of parameter editors - /// - public JArray ParameterEditors { get; set; } + [JsonProperty("parameterEditors")] + public IDataEditor[] ParameterEditors { get; set; } = Array.Empty(); - /// - /// The json array of grid editors - /// - public JArray GridEditors { get; set; } + [JsonProperty("gridEditors")] + public GridEditor[] GridEditors { get; set; } = Array.Empty(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Manifest/ParameterEditorConverter.cs b/src/Umbraco.Core/Manifest/ParameterEditorConverter.cs deleted file mode 100644 index 5f25f9ad81..0000000000 --- a/src/Umbraco.Core/Manifest/ParameterEditorConverter.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Serialization; - -namespace Umbraco.Core.Manifest -{ - /// - /// Used to convert a parameter editor manifest to a property editor object - /// - internal class ParameterEditorConverter : JsonCreationConverter - { - protected override ParameterEditor Create(Type objectType, JObject jObject) - { - return new ParameterEditor(); - } - - protected override void Deserialize(JObject jObject, ParameterEditor target, JsonSerializer serializer) - { - //since it's a manifest editor, we need to create it's instance. - //we need to specify the view value for the editor here otherwise we'll get an exception. - target.ManifestDefinedParameterValueEditor = new ParameterValueEditor - { - View = jObject["view"].ToString() - }; - - base.Deserialize(jObject, target, serializer); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Manifest/PreValueFieldConverter.cs b/src/Umbraco.Core/Manifest/PreValueFieldConverter.cs deleted file mode 100644 index 529bcbe744..0000000000 --- a/src/Umbraco.Core/Manifest/PreValueFieldConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Newtonsoft.Json.Linq; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Serialization; - -namespace Umbraco.Core.Manifest -{ - /// - /// Used to convert a pre-value field manifest to a real pre value field - /// - internal class PreValueFieldConverter : JsonCreationConverter - { - protected override PreValueField Create(Type objectType, JObject jObject) - { - return new PreValueField(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Manifest/PropertyEditorConverter.cs b/src/Umbraco.Core/Manifest/PropertyEditorConverter.cs deleted file mode 100644 index be99c2f241..0000000000 --- a/src/Umbraco.Core/Manifest/PropertyEditorConverter.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Serialization; - -namespace Umbraco.Core.Manifest -{ - /// - /// Used to convert a property editor manifest to a property editor object - /// - internal class PropertyEditorConverter : JsonCreationConverter - { - protected override PropertyEditor Create(Type objectType, JObject jObject) - { - return new PropertyEditor(); - } - - protected override void Deserialize(JObject jObject, PropertyEditor target, JsonSerializer serializer) - { - if (jObject["editor"] != null) - { - //since it's a manifest editor, we need to create it's instance. - //we need to specify the view value for the editor here otherwise we'll get an exception. - target.ManifestDefinedPropertyValueEditor = new PropertyValueEditor - { - View = jObject["editor"]["view"].ToString() - }; - - //the manifest JSON is a simplified json for the validators which is actually a dictionary, however, the - //c# model requires an array of validators not a dictionary so we need to change the json to an array - //to deserialize properly. - JArray converted; - if (TryConvertValidatorDictionaryToArray(jObject["editor"]["validation"] as JObject, out converted)) - { - jObject["editor"]["validation"] = converted; - } - - } - if (jObject["prevalues"] != null) - { - target.ManifestDefinedPreValueEditor = new PreValueEditor(); - - //the manifest JSON is a simplified json for the validators which is actually a dictionary, however, the - //c# model requires an array of validators not a dictionary so we need to change the json to an array - //to deserialize properly. - var fields = jObject["prevalues"]["fields"] as JArray; - if (fields != null) - { - foreach (var f in fields) - { - JArray converted; - if (TryConvertValidatorDictionaryToArray(f["validation"] as JObject, out converted)) - { - f["validation"] = converted; - } - } - } - } - - base.Deserialize(jObject, target, serializer); - } - - private bool TryConvertValidatorDictionaryToArray(JObject validation, out JArray result) - { - if (validation == null) - { - result = null; - return false; - } - - result = new JArray(); - foreach (var entry in validation) - { - //in a special case if the value is simply 'true' (boolean) this just indicates that the - // validator is enabled, the config should just be empty. - var formattedItem = JObject.FromObject(new { type = entry.Key, config = entry.Value }); - if (entry.Value.Type == JTokenType.Boolean) - { - formattedItem["config"] = ""; - } - - result.Add(formattedItem); - } - return true; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Manifest/ValueValidatorConverter.cs b/src/Umbraco.Core/Manifest/ValueValidatorConverter.cs new file mode 100644 index 0000000000..75b0b2573b --- /dev/null +++ b/src/Umbraco.Core/Manifest/ValueValidatorConverter.cs @@ -0,0 +1,34 @@ +using System; +using Newtonsoft.Json.Linq; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Serialization; + +namespace Umbraco.Core.Manifest +{ + /// + /// Implements a json read converter for . + /// + internal class ValueValidatorConverter : JsonReadConverter + { + private readonly ManifestValueValidatorCollection _validators; + + /// + /// Initializes a new instance of the class. + /// + public ValueValidatorConverter(ManifestValueValidatorCollection validators) + { + _validators = validators; + } + + protected override IValueValidator Create(Type objectType, string path, JObject jObject) + { + var type = jObject["type"].Value(); + if (string.IsNullOrWhiteSpace(type)) + throw new InvalidOperationException("Could not get the type of the validator."); + + return _validators.Create(type); + + // jObject["configuration"] is going to be deserialized in a Configuration property, if any + } + } +} diff --git a/src/Umbraco.Core/Media/Exif/BitConverterEx.cs b/src/Umbraco.Core/Media/Exif/BitConverterEx.cs index 5a4e7f846b..9447b057b1 100644 --- a/src/Umbraco.Core/Media/Exif/BitConverterEx.cs +++ b/src/Umbraco.Core/Media/Exif/BitConverterEx.cs @@ -3,7 +3,7 @@ namespace Umbraco.Core.Media.Exif { /// - /// An endian-aware converter for converting between base data types + /// An endian-aware converter for converting between base data types /// and an array of bytes. /// internal class BitConverterEx diff --git a/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs b/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs index 779da321f5..6247929cf6 100644 --- a/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs +++ b/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs @@ -96,7 +96,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Returns an unsigned rational number converted from the first + /// Returns an unsigned rational number converted from the first /// eight bytes of the given byte array. The first four bytes are /// assumed to be the numerator and the next four bytes are the /// denumerator. @@ -112,7 +112,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Returns a signed rational number converted from the first + /// Returns a signed rational number converted from the first /// eight bytes of the given byte array. The first four bytes are /// assumed to be the numerator and the next four bytes are the /// denumerator. @@ -128,7 +128,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Returns an array of 16-bit unsigned integers converted from + /// Returns an array of 16-bit unsigned integers converted from /// the given byte array. /// Numbers are converted from the given byte-order to platform byte-order. /// @@ -145,7 +145,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Returns an array of 32-bit unsigned integers converted from + /// Returns an array of 32-bit unsigned integers converted from /// the given byte array. /// Numbers are converted from the given byte-order to platform byte-order. /// @@ -162,7 +162,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Returns an array of 32-bit signed integers converted from + /// Returns an array of 32-bit signed integers converted from /// the given byte array. /// Numbers are converted from the given byte-order to platform byte-order. /// @@ -179,7 +179,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Returns an array of unsigned rational numbers converted from + /// Returns an array of unsigned rational numbers converted from /// the given byte array. /// Numbers are converted from the given byte-order to platform byte-order. /// @@ -198,7 +198,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Returns an array of signed rational numbers converted from + /// Returns an array of signed rational numbers converted from /// the given byte array. /// Numbers are converted from the given byte-order to platform byte-order. /// diff --git a/src/Umbraco.Core/Media/Exif/ExifExceptions.cs b/src/Umbraco.Core/Media/Exif/ExifExceptions.cs index 4dc1856903..b0301e951a 100644 --- a/src/Umbraco.Core/Media/Exif/ExifExceptions.cs +++ b/src/Umbraco.Core/Media/Exif/ExifExceptions.cs @@ -22,25 +22,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// The exception that is thrown when the IFD section ID could not be understood. - /// - internal class UnknownIFDSectionException : Exception - { - public UnknownIFDSectionException() - : base("Unknown IFD section.") - { - ; - } - - public UnknownIFDSectionException(string message) - : base(message) - { - ; - } - } - - /// - /// The exception that is thrown when an invalid enum type is given to an + /// The exception that is thrown when an invalid enum type is given to an /// ExifEnumProperty. /// internal class UnknownEnumTypeException : Exception @@ -57,22 +39,4 @@ namespace Umbraco.Core.Media.Exif ; } } - - /// - /// The exception that is thrown when the 0th IFD section does not contain any fields. - /// - internal class IFD0IsEmptyException : Exception - { - public IFD0IsEmptyException() - : base("0th IFD section cannot be empty.") - { - ; - } - - public IFD0IsEmptyException(string message) - : base(message) - { - ; - } - } } diff --git a/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs b/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs index 2c02663b1b..2a3ed38928 100644 --- a/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs +++ b/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs @@ -147,7 +147,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Represents the exif version as a 4 byte ASCII string. (EXIF Specification: UNDEFINED) + /// Represents the exif version as a 4 byte ASCII string. (EXIF Specification: UNDEFINED) /// Used for the ExifVersion, FlashpixVersion, InteroperabilityVersion and GPSVersionID fields. /// internal class ExifVersion : ExifProperty @@ -191,7 +191,7 @@ namespace Umbraco.Core.Media.Exif /// /// Represents the location and area of the subject (EXIF Specification: 2xSHORT) - /// The coordinate values, width, and height are expressed in relation to the + /// The coordinate values, width, and height are expressed in relation to the /// upper left as origin, prior to rotation processing as per the Rotation tag. /// internal class ExifPointSubjectArea : ExifUShortArray @@ -222,7 +222,7 @@ namespace Umbraco.Core.Media.Exif /// /// Represents the location and area of the subject (EXIF Specification: 3xSHORT) - /// The coordinate values, width, and height are expressed in relation to the + /// The coordinate values, width, and height are expressed in relation to the /// upper left as origin, prior to rotation processing as per the Rotation tag. /// internal class ExifCircularSubjectArea : ExifPointSubjectArea @@ -251,7 +251,7 @@ namespace Umbraco.Core.Media.Exif /// /// Represents the location and area of the subject (EXIF Specification: 4xSHORT) - /// The coordinate values, width, and height are expressed in relation to the + /// The coordinate values, width, and height are expressed in relation to the /// upper left as origin, prior to rotation processing as per the Rotation tag. /// internal class ExifRectangularSubjectArea : ExifPointSubjectArea @@ -342,7 +342,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Represents an ASCII string. (EXIF Specification: BYTE) + /// Represents an ASCII string. (EXIF Specification: BYTE) /// Used by Windows XP. /// internal class WindowsByteString : ExifProperty diff --git a/src/Umbraco.Core/Media/Exif/ExifProperty.cs b/src/Umbraco.Core/Media/Exif/ExifProperty.cs index 16ae5bc08e..b05d88eb0a 100644 --- a/src/Umbraco.Core/Media/Exif/ExifProperty.cs +++ b/src/Umbraco.Core/Media/Exif/ExifProperty.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core.Media.Exif { get { - if (mName == null || mName.Length == 0) + if (string.IsNullOrEmpty(mName)) return ExifTagFactory.GetTagName(mTag); else return mName; @@ -130,7 +130,7 @@ namespace Umbraco.Core.Media.Exif protected string mValue; protected override object _Value { get { return Value; } set { Value = (string)value; } } public new string Value { get { return mValue; } set { mValue = value; } } - + public Encoding Encoding { get; private set; } static public implicit operator string(ExifAscii obj) { return obj.mValue; } @@ -182,7 +182,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Represents an array of 16-bit unsigned integers. + /// Represents an array of 16-bit unsigned integers. /// (EXIF Specification: SHORT with count > 1) /// internal class ExifUShortArray : ExifProperty @@ -251,7 +251,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Represents an array of 16-bit unsigned integers. + /// Represents an array of 16-bit unsigned integers. /// (EXIF Specification: LONG with count > 1) /// internal class ExifUIntArray : ExifProperty @@ -292,7 +292,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Represents a rational number defined with a 32-bit unsigned numerator + /// Represents a rational number defined with a 32-bit unsigned numerator /// and denominator. (EXIF Specification: RATIONAL) /// internal class ExifURational : ExifProperty @@ -333,7 +333,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Represents an array of unsigned rational numbers. + /// Represents an array of unsigned rational numbers. /// (EXIF Specification: RATIONAL with count > 1) /// internal class ExifURationalArray : ExifProperty @@ -448,7 +448,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Represents an array of 32-bit signed integers. + /// Represents an array of 32-bit signed integers. /// (EXIF Specification: SLONG with count > 1) /// internal class ExifSIntArray : ExifProperty @@ -489,7 +489,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Represents a rational number defined with a 32-bit signed numerator + /// Represents a rational number defined with a 32-bit signed numerator /// and denominator. (EXIF Specification: SRATIONAL) /// internal class ExifSRational : ExifProperty @@ -530,7 +530,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Represents an array of signed rational numbers. + /// Represents an array of signed rational numbers. /// (EXIF Specification: SRATIONAL with count > 1) /// internal class ExifSRationalArray : ExifProperty diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs index 1be504ea45..233fe27a91 100644 --- a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs +++ b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs @@ -4,377 +4,377 @@ using System.Text; namespace Umbraco.Core.Media.Exif { - /// - /// Represents a collection of objects. - /// - internal class ExifPropertyCollection : IDictionary - { - #region Member Variables + /// + /// Represents a collection of objects. + /// + internal class ExifPropertyCollection : IDictionary + { + #region Member Variables private ImageFile parent; - private Dictionary items; - #endregion + private Dictionary items; + #endregion - #region Constructor - internal ExifPropertyCollection (ImageFile parentFile) - { + #region Constructor + internal ExifPropertyCollection (ImageFile parentFile) + { parent = parentFile; - items = new Dictionary (); - } - #endregion + items = new Dictionary (); + } + #endregion - #region Properties - /// - /// Gets the number of elements contained in the collection. - /// - public int Count { - get { return items.Count; } - } - /// - /// Gets a collection containing the keys in this collection. - /// - public ICollection Keys { - get { return items.Keys; } - } - /// - /// Gets a collection containing the values in this collection. - /// - public ICollection Values { - get { return items.Values; } - } - /// - /// Gets or sets the with the specified key. - /// - public ExifProperty this[ExifTag key] { - get { return items[key]; } - set { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, value); - } - } - #endregion + #region Properties + /// + /// Gets the number of elements contained in the collection. + /// + public int Count { + get { return items.Count; } + } + /// + /// Gets a collection containing the keys in this collection. + /// + public ICollection Keys { + get { return items.Keys; } + } + /// + /// Gets a collection containing the values in this collection. + /// + public ICollection Values { + get { return items.Values; } + } + /// + /// Gets or sets the with the specified key. + /// + public ExifProperty this[ExifTag key] { + get { return items[key]; } + set { + if (items.ContainsKey (key)) + items.Remove (key); + items.Add (key, value); + } + } + #endregion - #region ExifProperty Collection Setters - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, byte value) - { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifByte (key, value)); - } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, string value) - { - if (items.ContainsKey (key)) - items.Remove (key); - if (key == ExifTag.WindowsTitle || key == ExifTag.WindowsComment || key == ExifTag.WindowsAuthor || key == ExifTag.WindowsKeywords || key == ExifTag.WindowsSubject) { - items.Add (key, new WindowsByteString (key, value)); - } else { - items.Add (key, new ExifAscii (key, value, parent.Encoding)); - } - } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, ushort value) - { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifUShort (key, value)); - } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, int value) - { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifSInt (key, value)); - } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, uint value) - { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifUInt (key, value)); - } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, float value) - { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifURational (key, new MathEx.UFraction32 (value))); - } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, double value) - { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifURational (key, new MathEx.UFraction32 (value))); - } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, object value) - { - Type type = value.GetType (); - if (type.IsEnum) { - Type etype = typeof(ExifEnumProperty<>).MakeGenericType (new Type[] { type }); - object prop = Activator.CreateInstance (etype, new object[] { key, value }); - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, (ExifProperty)prop); - } else - throw new ArgumentException ("No exif property exists for this tag.", "value"); - } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - /// String encoding. - public void Set (ExifTag key, string value, Encoding encoding) - { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifEncodedString (key, value, encoding)); - } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, DateTime value) - { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifDateTime (key, value)); - } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// Angular degrees (or clock hours for a timestamp). - /// Angular minutes (or clock minutes for a timestamp). - /// Angular seconds (or clock seconds for a timestamp). - public void Set (ExifTag key, float d, float m, float s) - { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifURationalArray (key, new MathEx.UFraction32[] { new MathEx.UFraction32 (d), new MathEx.UFraction32 (m), new MathEx.UFraction32 (s) })); - } - #endregion + #region ExifProperty Collection Setters + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set (ExifTag key, byte value) + { + if (items.ContainsKey (key)) + items.Remove (key); + items.Add (key, new ExifByte (key, value)); + } + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set (ExifTag key, string value) + { + if (items.ContainsKey (key)) + items.Remove (key); + if (key == ExifTag.WindowsTitle || key == ExifTag.WindowsComment || key == ExifTag.WindowsAuthor || key == ExifTag.WindowsKeywords || key == ExifTag.WindowsSubject) { + items.Add (key, new WindowsByteString (key, value)); + } else { + items.Add (key, new ExifAscii (key, value, parent.Encoding)); + } + } + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set (ExifTag key, ushort value) + { + if (items.ContainsKey (key)) + items.Remove (key); + items.Add (key, new ExifUShort (key, value)); + } + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set (ExifTag key, int value) + { + if (items.ContainsKey (key)) + items.Remove (key); + items.Add (key, new ExifSInt (key, value)); + } + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set (ExifTag key, uint value) + { + if (items.ContainsKey (key)) + items.Remove (key); + items.Add (key, new ExifUInt (key, value)); + } + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set (ExifTag key, float value) + { + if (items.ContainsKey (key)) + items.Remove (key); + items.Add (key, new ExifURational (key, new MathEx.UFraction32 (value))); + } + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set (ExifTag key, double value) + { + if (items.ContainsKey (key)) + items.Remove (key); + items.Add (key, new ExifURational (key, new MathEx.UFraction32 (value))); + } + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set (ExifTag key, object value) + { + Type type = value.GetType (); + if (type.IsEnum) { + Type etype = typeof(ExifEnumProperty<>).MakeGenericType (new Type[] { type }); + object prop = Activator.CreateInstance (etype, new object[] { key, value }); + if (items.ContainsKey (key)) + items.Remove (key); + items.Add (key, (ExifProperty)prop); + } else + throw new ArgumentException ("No exif property exists for this tag.", "value"); + } + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + /// String encoding. + public void Set (ExifTag key, string value, Encoding encoding) + { + if (items.ContainsKey (key)) + items.Remove (key); + items.Add (key, new ExifEncodedString (key, value, encoding)); + } + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set (ExifTag key, DateTime value) + { + if (items.ContainsKey (key)) + items.Remove (key); + items.Add (key, new ExifDateTime (key, value)); + } + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// Angular degrees (or clock hours for a timestamp). + /// Angular minutes (or clock minutes for a timestamp). + /// Angular seconds (or clock seconds for a timestamp). + public void Set (ExifTag key, float d, float m, float s) + { + if (items.ContainsKey (key)) + items.Remove (key); + items.Add (key, new ExifURationalArray (key, new MathEx.UFraction32[] { new MathEx.UFraction32 (d), new MathEx.UFraction32 (m), new MathEx.UFraction32 (s) })); + } + #endregion - #region Instance Methods - /// - /// Adds the specified item to the collection. - /// - /// The to add to the collection. - public void Add (ExifProperty item) - { - ExifProperty oldItem = null; - if (items.TryGetValue (item.Tag, out oldItem)) - items[item.Tag] = item; - else - items.Add (item.Tag, item); - } - /// - /// Removes all items from the collection. - /// - public void Clear () - { - items.Clear (); - } - /// - /// Determines whether the collection contains an element with the specified key. - /// - /// The key to locate in the collection. - /// - /// true if the collection contains an element with the key; otherwise, false. - /// - /// - /// is null. - public bool ContainsKey (ExifTag key) - { - return items.ContainsKey (key); - } - /// - /// Removes the element with the specified key from the collection. - /// - /// The key of the element to remove. - /// - /// true if the element is successfully removed; otherwise, false. This method also returns false if was not found in the original collection. - /// - /// - /// is null. - public bool Remove (ExifTag key) - { - return items.Remove (key); - } - /// - /// Removes all items with the given IFD from the collection. - /// - /// The IFD section to remove. - public void Remove (IFD ifd) - { - List toRemove = new List (); - foreach (KeyValuePair item in items) { - if (item.Value.IFD == ifd) - toRemove.Add (item.Key); - } - foreach (ExifTag tag in toRemove) - items.Remove (tag); - } - /// - /// Gets the value associated with the specified key. - /// - /// The key whose value to get. - /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. - /// - /// true if the collection contains an element with the specified key; otherwise, false. - /// - /// - /// is null. - public bool TryGetValue (ExifTag key, out ExifProperty value) - { - return items.TryGetValue (key, out value); - } - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator () - { - return Values.GetEnumerator (); - } - #endregion + #region Instance Methods + /// + /// Adds the specified item to the collection. + /// + /// The to add to the collection. + public void Add (ExifProperty item) + { + ExifProperty oldItem = null; + if (items.TryGetValue (item.Tag, out oldItem)) + items[item.Tag] = item; + else + items.Add (item.Tag, item); + } + /// + /// Removes all items from the collection. + /// + public void Clear () + { + items.Clear (); + } + /// + /// Determines whether the collection contains an element with the specified key. + /// + /// The key to locate in the collection. + /// + /// true if the collection contains an element with the key; otherwise, false. + /// + /// + /// is null. + public bool ContainsKey (ExifTag key) + { + return items.ContainsKey (key); + } + /// + /// Removes the element with the specified key from the collection. + /// + /// The key of the element to remove. + /// + /// true if the element is successfully removed; otherwise, false. This method also returns false if was not found in the original collection. + /// + /// + /// is null. + public bool Remove (ExifTag key) + { + return items.Remove (key); + } + /// + /// Removes all items with the given IFD from the collection. + /// + /// The IFD section to remove. + public void Remove (IFD ifd) + { + List toRemove = new List (); + foreach (KeyValuePair item in items) { + if (item.Value.IFD == ifd) + toRemove.Add (item.Key); + } + foreach (ExifTag tag in toRemove) + items.Remove (tag); + } + /// + /// Gets the value associated with the specified key. + /// + /// The key whose value to get. + /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. + /// + /// true if the collection contains an element with the specified key; otherwise, false. + /// + /// + /// is null. + public bool TryGetValue (ExifTag key, out ExifProperty value) + { + return items.TryGetValue (key, out value); + } + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + public IEnumerator GetEnumerator () + { + return Values.GetEnumerator (); + } + #endregion - #region Hidden Interface - /// - /// Adds an element with the provided key and value to the . - /// - /// The object to use as the key of the element to add. - /// The object to use as the value of the element to add. - /// - /// is null. - /// An element with the same key already exists in the . - /// The is read-only. - void IDictionary.Add (ExifTag key, ExifProperty value) - { - Add (value); - } - /// - /// Adds an item to the . - /// - /// The object to add to the . - /// The is read-only. - void ICollection>.Add (KeyValuePair item) - { - Add (item.Value); - } - bool ICollection>.Contains (KeyValuePair item) - { - throw new NotSupportedException (); - } - /// - /// Copies the elements of the to an , starting at a particular index. - /// - /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. - /// The zero-based index in at which copying begins. - /// - /// is null. - /// - /// is less than 0. - /// - /// is multidimensional.-or- is equal to or greater than the length of .-or-The number of elements in the source is greater than the available space from to the end of the destination .-or-Type cannot be cast automatically to the type of the destination . - void ICollection>.CopyTo (KeyValuePair[] array, int arrayIndex) - { - if (array == null) - throw new ArgumentNullException ("array"); - if (arrayIndex < 0) - throw new ArgumentOutOfRangeException ("arrayIndex"); - if (array.Rank > 1) - throw new ArgumentException ("Destination array is multidimensional.", "array"); - if (arrayIndex >= array.Length) - throw new ArgumentException ("arrayIndex is equal to or greater than the length of destination array", "array"); - if (arrayIndex + items.Count > array.Length) - throw new ArgumentException ("There is not enough space in destination array.", "array"); - - int i = 0; - foreach (KeyValuePair item in items) { - if (i >= arrayIndex) { - array[i] = item; - } - i++; - } - } - /// - /// Gets a value indicating whether the is read-only. - /// - /// true if the is read-only; otherwise, false. - bool ICollection>.IsReadOnly { - get { return false; } - } - /// - /// Removes the first occurrence of a specific object from the . - /// - /// The object to remove from the . - /// - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - /// - /// The is read-only. - bool ICollection>.Remove (KeyValuePair item) - { - throw new NotSupportedException (); - } - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () - { - return GetEnumerator (); - } - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - IEnumerator> IEnumerable>.GetEnumerator () - { - return items.GetEnumerator (); - } - #endregion - } + #region Hidden Interface + /// + /// Adds an element with the provided key and value to the . + /// + /// The object to use as the key of the element to add. + /// The object to use as the value of the element to add. + /// + /// is null. + /// An element with the same key already exists in the . + /// The is read-only. + void IDictionary.Add (ExifTag key, ExifProperty value) + { + Add (value); + } + /// + /// Adds an item to the . + /// + /// The object to add to the . + /// The is read-only. + void ICollection>.Add (KeyValuePair item) + { + Add (item.Value); + } + bool ICollection>.Contains (KeyValuePair item) + { + throw new NotSupportedException (); + } + /// + /// Copies the elements of the to an , starting at a particular index. + /// + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. + /// The zero-based index in at which copying begins. + /// + /// is null. + /// + /// is less than 0. + /// + /// is multidimensional.-or- is equal to or greater than the length of .-or-The number of elements in the source is greater than the available space from to the end of the destination .-or-Type cannot be cast automatically to the type of the destination . + void ICollection>.CopyTo (KeyValuePair[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException ("array"); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException ("arrayIndex"); + if (array.Rank > 1) + throw new ArgumentException ("Destination array is multidimensional.", "array"); + if (arrayIndex >= array.Length) + throw new ArgumentException ("arrayIndex is equal to or greater than the length of destination array", "array"); + if (arrayIndex + items.Count > array.Length) + throw new ArgumentException ("There is not enough space in destination array.", "array"); + + int i = 0; + foreach (KeyValuePair item in items) { + if (i >= arrayIndex) { + array[i] = item; + } + i++; + } + } + /// + /// Gets a value indicating whether the is read-only. + /// + /// true if the is read-only; otherwise, false. + bool ICollection>.IsReadOnly { + get { return false; } + } + /// + /// Removes the first occurrence of a specific object from the . + /// + /// The object to remove from the . + /// + /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + /// + /// The is read-only. + bool ICollection>.Remove (KeyValuePair item) + { + throw new NotSupportedException (); + } + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () + { + return GetEnumerator (); + } + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + IEnumerator> IEnumerable>.GetEnumerator () + { + return items.GetEnumerator (); + } + #endregion + } } diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs b/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs index dbcc2c5c1d..69efb809fc 100644 --- a/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs +++ b/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs @@ -204,7 +204,7 @@ namespace Umbraco.Core.Media.Exif else return new ExifByteArray(etag, value); } - else if (type == 2) // 2 = ASCII An 8-bit byte containing one 7-bit ASCII code. + else if (type == 2) // 2 = ASCII An 8-bit byte containing one 7-bit ASCII code. { return new ExifAscii(etag, ExifBitConverter.ToAscii(value, encoding), encoding); } diff --git a/src/Umbraco.Core/Media/Exif/ExifTag.cs b/src/Umbraco.Core/Media/Exif/ExifTag.cs index 270f884dc7..47facc28b8 100644 --- a/src/Umbraco.Core/Media/Exif/ExifTag.cs +++ b/src/Umbraco.Core/Media/Exif/ExifTag.cs @@ -282,7 +282,7 @@ namespace Umbraco.Core.Media.Exif /// JFXXPalette = IFD.JFXX + 201, /// - /// JFIF thumbnail. The thumbnail will be either a JPEG, + /// JFIF thumbnail. The thumbnail will be either a JPEG, /// a 256 color palette bitmap, or a 24-bit RGB bitmap. /// JFXXThumbnail = IFD.JFXX + 202, diff --git a/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs b/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs index fb8b6ba592..729ce9c345 100644 --- a/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs +++ b/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs @@ -51,7 +51,7 @@ namespace Umbraco.Core.Media.Exif } /// - /// Returns the string representation for the given exif tag including + /// Returns the string representation for the given exif tag including /// IFD section and tag id. /// public static string GetTagLongName(ExifTag tag) diff --git a/src/Umbraco.Core/Media/Exif/ImageFile.cs b/src/Umbraco.Core/Media/Exif/ImageFile.cs index 66350338e9..24eb780c9d 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFile.cs +++ b/src/Umbraco.Core/Media/Exif/ImageFile.cs @@ -5,86 +5,86 @@ using System.Text; namespace Umbraco.Core.Media.Exif { - /// - /// Represents the base class for image files. - /// - [TypeDescriptionProvider(typeof(ExifFileTypeDescriptionProvider))] - internal abstract class ImageFile - { - #region Constructor - /// - /// Initializes a new instance of the class. - /// - protected ImageFile () - { - Format = ImageFileFormat.Unknown; - Properties = new ExifPropertyCollection (this); + /// + /// Represents the base class for image files. + /// + [TypeDescriptionProvider(typeof(ExifFileTypeDescriptionProvider))] + internal abstract class ImageFile + { + #region Constructor + /// + /// Initializes a new instance of the class. + /// + protected ImageFile () + { + Format = ImageFileFormat.Unknown; + Properties = new ExifPropertyCollection (this); Encoding = Encoding.Default; - } - #endregion + } + #endregion - #region Properties - /// - /// Returns the format of the . - /// - public ImageFileFormat Format { get; protected set; } - /// - /// Gets the collection of Exif properties contained in the . - /// - public ExifPropertyCollection Properties { get; private set; } - /// - /// Gets or sets the embedded thumbnail image. - /// - public ImageFile Thumbnail { get; set; } - /// - /// Gets or sets the Exif property with the given key. - /// - /// The Exif tag associated with the Exif property. - public ExifProperty this[ExifTag key] { - get { return Properties[key]; } - set { Properties[key] = value; } - } + #region Properties + /// + /// Returns the format of the . + /// + public ImageFileFormat Format { get; protected set; } + /// + /// Gets the collection of Exif properties contained in the . + /// + public ExifPropertyCollection Properties { get; private set; } + /// + /// Gets or sets the embedded thumbnail image. + /// + public ImageFile Thumbnail { get; set; } + /// + /// Gets or sets the Exif property with the given key. + /// + /// The Exif tag associated with the Exif property. + public ExifProperty this[ExifTag key] { + get { return Properties[key]; } + set { Properties[key] = value; } + } /// /// Gets the encoding used for text metadata when the source encoding is unknown. /// public Encoding Encoding { get; protected set; } - #endregion + #endregion - #region Instance Methods - /// - /// Converts the to a . - /// - /// Returns a containing image data. - public abstract Image ToImage (); + #region Instance Methods + /// + /// Converts the to a . + /// + /// Returns a containing image data. + public abstract Image ToImage (); - /// - /// Saves the to the specified file. - /// - /// A string that contains the name of the file. - public virtual void Save (string filename) - { - using (FileStream stream = new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None)) { - Save (stream); - } - } + /// + /// Saves the to the specified file. + /// + /// A string that contains the name of the file. + public virtual void Save (string filename) + { + using (FileStream stream = new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None)) { + Save (stream); + } + } - /// - /// Saves the to the specified stream. - /// - /// A to save image data to. - public abstract void Save (Stream stream); - #endregion + /// + /// Saves the to the specified stream. + /// + /// A to save image data to. + public abstract void Save (Stream stream); + #endregion - #region Static Methods - /// - /// Creates an from the specified file. - /// - /// A string that contains the name of the file. - /// The created from the file. - public static ImageFile FromFile (string filename) - { + #region Static Methods + /// + /// Creates an from the specified file. + /// + /// A string that contains the name of the file. + /// The created from the file. + public static ImageFile FromFile (string filename) + { return FromFile(filename, Encoding.Default); - } + } /// /// Creates an from the specified file. @@ -101,40 +101,40 @@ namespace Umbraco.Core.Media.Exif } /// - /// Creates an from the specified data stream. - /// - /// A that contains image data. - /// The created from the file. + /// Creates an from the specified data stream. + /// + /// A that contains image data. + /// The created from the file. public static ImageFile FromStream(Stream stream) { return FromStream(stream, Encoding.Default); } - /// - /// Creates an from the specified data stream. - /// - /// A that contains image data. + /// + /// Creates an from the specified data stream. + /// + /// A that contains image data. /// The encoding to be used for text metadata when the source encoding is unknown. - /// The created from the file. + /// The created from the file. public static ImageFile FromStream(Stream stream, Encoding encoding) - { - stream.Seek (0, SeekOrigin.Begin); - byte[] header = new byte[8]; - stream.Seek (0, SeekOrigin.Begin); - if (stream.Read (header, 0, header.Length) != header.Length) - throw new NotValidImageFileException (); - - // JPEG - if (header[0] == 0xFF && header[1] == 0xD8) - return new JPEGFile (stream, encoding); - - // TIFF - string tiffHeader = Encoding.ASCII.GetString (header, 0, 4); - if (tiffHeader == "MM\x00\x2a" || tiffHeader == "II\x2a\x00") - return new TIFFFile (stream, encoding); - - throw new NotValidImageFileException (); - } - #endregion - } + { + stream.Seek (0, SeekOrigin.Begin); + byte[] header = new byte[8]; + stream.Seek (0, SeekOrigin.Begin); + if (stream.Read (header, 0, header.Length) != header.Length) + throw new NotValidImageFileException (); + + // JPEG + if (header[0] == 0xFF && header[1] == 0xD8) + return new JPEGFile (stream, encoding); + + // TIFF + string tiffHeader = Encoding.ASCII.GetString (header, 0, 4); + if (tiffHeader == "MM\x00\x2a" || tiffHeader == "II\x2a\x00") + return new TIFFFile (stream, encoding); + + throw new NotValidImageFileException (); + } + #endregion + } } diff --git a/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs b/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs index a887b11511..573eedf146 100644 --- a/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs +++ b/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs @@ -3,7 +3,7 @@ namespace Umbraco.Core.Media.Exif { /// - /// Represents the JFIF version as a 16 bit unsigned integer. (EXIF Specification: SHORT) + /// Represents the JFIF version as a 16 bit unsigned integer. (EXIF Specification: SHORT) /// internal class JFIFVersion : ExifUShort { @@ -28,7 +28,7 @@ namespace Umbraco.Core.Media.Exif } } /// - /// Represents a JFIF thumbnail. (EXIF Specification: BYTE) + /// Represents a JFIF thumbnail. (EXIF Specification: BYTE) /// internal class JFIFThumbnailProperty : ExifProperty { diff --git a/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs b/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs index 5e7195ab98..40b62b25cc 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs +++ b/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs @@ -2,24 +2,6 @@ namespace Umbraco.Core.Media.Exif { - /// - /// The exception that is thrown when the format of the image file - /// could not be understood. - /// - internal class UnknownImageFormatException : Exception - { - public UnknownImageFormatException() - : base("Unkown image format.") - { - ; - } - - public UnknownImageFormatException(string message) - : base(message) - { - ; - } - } /// /// The exception that is thrown when the format of the image file diff --git a/src/Umbraco.Core/Media/Exif/JPEGFile.cs b/src/Umbraco.Core/Media/Exif/JPEGFile.cs index 2c1473da76..032668dc0a 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGFile.cs +++ b/src/Umbraco.Core/Media/Exif/JPEGFile.cs @@ -62,7 +62,7 @@ namespace Umbraco.Core.Media.Exif // Search and read sections until we reach the end of file. while (stream.Position != stream.Length) { - // Read the next section marker. Section markers are two bytes + // Read the next section marker. Section markers are two bytes // with values 0xFF, 0x?? where ?? must not be 0x00 or 0xFF. if (stream.Read(markerbytes, 0, 2) != 2 || markerbytes[0] != 0xFF || markerbytes[1] == 0x00 || markerbytes[1] == 0xFF) throw new NotValidJPEGFileException(); @@ -74,7 +74,7 @@ namespace Umbraco.Core.Media.Exif if (marker != JPEGMarker.SOI && marker != JPEGMarker.EOI && !(marker >= JPEGMarker.RST0 && marker <= JPEGMarker.RST7)) { // Length of the header including the length bytes. - // This value is a 16-bit unsigned integer + // This value is a 16-bit unsigned integer // in big endian byte-order. byte[] lengthbytes = new byte[2]; if (stream.Read(lengthbytes, 0, 2) != 2) @@ -194,7 +194,7 @@ namespace Umbraco.Core.Media.Exif // Write sections foreach (JPEGSection section in Sections) { - // Section header (including length bytes and section marker) + // Section header (including length bytes and section marker) // must not exceed 64 kB. if (section.Header.Length + 2 + 2 > 64 * 1024) throw new SectionExceeds64KBException(); diff --git a/src/Umbraco.Core/Media/Exif/JPEGSection.cs b/src/Umbraco.Core/Media/Exif/JPEGSection.cs index 6e6804fb42..5062f05491 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGSection.cs +++ b/src/Umbraco.Core/Media/Exif/JPEGSection.cs @@ -13,7 +13,7 @@ public JPEGMarker Marker { get; private set; } /// /// Section header as a byte array. This is different from the header - /// definition in JPEG specification in that it does not include the + /// definition in JPEG specification in that it does not include the /// two byte section length. /// public byte[] Header { get; set; } diff --git a/src/Umbraco.Core/Media/Exif/MathEx.cs b/src/Umbraco.Core/Media/Exif/MathEx.cs index d07f344476..5364d18d0f 100644 --- a/src/Umbraco.Core/Media/Exif/MathEx.cs +++ b/src/Umbraco.Core/Media/Exif/MathEx.cs @@ -191,7 +191,7 @@ namespace Umbraco.Core.Media.Exif /// s is null /// s is not in the correct format /// - /// s represents a number less than System.UInt32.MinValue or greater than + /// s represents a number less than System.UInt32.MinValue or greater than /// System.UInt32.MaxValue. /// public static Fraction32 Parse(string s) @@ -469,7 +469,7 @@ namespace Umbraco.Core.Media.Exif /// Indicates whether this instance and a specified object are equal value-wise. /// /// Another object to compare to. - /// true if obj and this instance are the same type and represent + /// true if obj and this instance are the same type and represent /// the same value; otherwise, false. public override bool Equals(object obj) { @@ -486,7 +486,7 @@ namespace Umbraco.Core.Media.Exif /// Indicates whether this instance and a specified object are equal value-wise. /// /// Another fraction object to compare to. - /// true if obj and this instance represent the same value; + /// true if obj and this instance represent the same value; /// otherwise, false. public bool Equals(Fraction32 obj) { @@ -507,11 +507,11 @@ namespace Umbraco.Core.Media.Exif /// /// A numeric format string. /// - /// An System.IFormatProvider that supplies culture-specific + /// An System.IFormatProvider that supplies culture-specific /// formatting information. /// /// - /// The string representation of the value of this instance as + /// The string representation of the value of this instance as /// specified by format and provider. /// /// @@ -531,7 +531,7 @@ namespace Umbraco.Core.Media.Exif /// /// A numeric format string. /// - /// The string representation of the value of this instance as + /// The string representation of the value of this instance as /// specified by format. /// /// @@ -550,11 +550,11 @@ namespace Umbraco.Core.Media.Exif /// Returns a string representation of the fraction. /// /// - /// An System.IFormatProvider that supplies culture-specific + /// An System.IFormatProvider that supplies culture-specific /// formatting information. /// /// - /// The string representation of the value of this instance as + /// The string representation of the value of this instance as /// specified by provider. /// public string ToString(IFormatProvider formatProvider) @@ -587,7 +587,7 @@ namespace Umbraco.Core.Media.Exif /// /// A signed number indicating the relative values of this instance and value. /// Less than zero: This instance is less than obj. - /// Zero: This instance is equal to obj. + /// Zero: This instance is equal to obj. /// Greater than zero: This instance is greater than obj or obj is null. /// /// obj is not a Fraction. @@ -607,7 +607,7 @@ namespace Umbraco.Core.Media.Exif /// /// A signed number indicating the relative values of this instance and value. /// Less than zero: This instance is less than obj. - /// Zero: This instance is equal to obj. + /// Zero: This instance is equal to obj. /// Greater than zero: This instance is greater than obj or obj is null. /// public int CompareTo(Fraction32 obj) @@ -686,7 +686,7 @@ namespace Umbraco.Core.Media.Exif /// s is null. /// s is not formatted as numerator/denominator. /// - /// s represents numbers less than System.Int32.MinValue or greater than + /// s represents numbers less than System.Int32.MinValue or greater than /// System.Int32.MaxValue. /// private static Fraction32 FromString(string s) @@ -833,7 +833,7 @@ namespace Umbraco.Core.Media.Exif /// s is null /// s is not in the correct format /// - /// s represents a number less than System.UInt32.MinValue or greater than + /// s represents a number less than System.UInt32.MinValue or greater than /// System.UInt32.MaxValue. /// public static UFraction32 Parse(string s) @@ -1097,7 +1097,7 @@ namespace Umbraco.Core.Media.Exif /// Indicates whether this instance and a specified object are equal value-wise. /// /// Another object to compare to. - /// true if obj and this instance are the same type and represent + /// true if obj and this instance are the same type and represent /// the same value; otherwise, false. public override bool Equals(object obj) { @@ -1114,7 +1114,7 @@ namespace Umbraco.Core.Media.Exif /// Indicates whether this instance and a specified object are equal value-wise. /// /// Another fraction object to compare to. - /// true if obj and this instance represent the same value; + /// true if obj and this instance represent the same value; /// otherwise, false. public bool Equals(UFraction32 obj) { @@ -1135,11 +1135,11 @@ namespace Umbraco.Core.Media.Exif /// /// A numeric format string. /// - /// An System.IFormatProvider that supplies culture-specific + /// An System.IFormatProvider that supplies culture-specific /// formatting information. /// /// - /// The string representation of the value of this instance as + /// The string representation of the value of this instance as /// specified by format and provider. /// /// @@ -1159,7 +1159,7 @@ namespace Umbraco.Core.Media.Exif /// /// A numeric format string. /// - /// The string representation of the value of this instance as + /// The string representation of the value of this instance as /// specified by format. /// /// @@ -1178,11 +1178,11 @@ namespace Umbraco.Core.Media.Exif /// Returns a string representation of the fraction. /// /// - /// An System.IFormatProvider that supplies culture-specific + /// An System.IFormatProvider that supplies culture-specific /// formatting information. /// /// - /// The string representation of the value of this instance as + /// The string representation of the value of this instance as /// specified by provider. /// public string ToString(IFormatProvider formatProvider) @@ -1215,7 +1215,7 @@ namespace Umbraco.Core.Media.Exif /// /// A signed number indicating the relative values of this instance and value. /// Less than zero: This instance is less than obj. - /// Zero: This instance is equal to obj. + /// Zero: This instance is equal to obj. /// Greater than zero: This instance is greater than obj or obj is null. /// /// obj is not a Fraction. @@ -1235,7 +1235,7 @@ namespace Umbraco.Core.Media.Exif /// /// A signed number indicating the relative values of this instance and value. /// Less than zero: This instance is less than obj. - /// Zero: This instance is equal to obj. + /// Zero: This instance is equal to obj. /// Greater than zero: This instance is greater than obj or obj is null. /// public int CompareTo(UFraction32 obj) @@ -1314,7 +1314,7 @@ namespace Umbraco.Core.Media.Exif /// s is null. /// s is not formatted as numerator/denominator. /// - /// s represents numbers less than System.UInt32.MinValue or greater than + /// s represents numbers less than System.UInt32.MinValue or greater than /// System.UInt32.MaxValue. /// private static UFraction32 FromString(string s) diff --git a/src/Umbraco.Core/Media/Exif/TIFFHeader.cs b/src/Umbraco.Core/Media/Exif/TIFFHeader.cs index 82959900be..89b0c02c2e 100644 --- a/src/Umbraco.Core/Media/Exif/TIFFHeader.cs +++ b/src/Umbraco.Core/Media/Exif/TIFFHeader.cs @@ -14,7 +14,7 @@ /// public byte ID; /// - /// The offset to the first IFD section from the + /// The offset to the first IFD section from the /// start of the TIFF header. /// public uint IFDOffset; @@ -28,7 +28,7 @@ /// /// The byte order. /// The TIFF ID. This value should always be 42. - /// The offset to the first IFD section from the + /// The offset to the first IFD section from the /// start of the TIFF header. /// The byte order of the TIFF header itself. public TIFFHeader(BitConverterEx.ByteOrder byteOrder, byte id, uint ifdOffset, BitConverterEx.ByteOrder headerByteOrder) diff --git a/src/Umbraco.Core/Media/IEmbedProvider.cs b/src/Umbraco.Core/Media/IEmbedProvider.cs index 73232d4637..3a95f2c58b 100644 --- a/src/Umbraco.Core/Media/IEmbedProvider.cs +++ b/src/Umbraco.Core/Media/IEmbedProvider.cs @@ -6,4 +6,4 @@ string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Media/IEmbedSettingProvider.cs b/src/Umbraco.Core/Media/IEmbedSettingProvider.cs index ab1979184b..18377fd6da 100644 --- a/src/Umbraco.Core/Media/IEmbedSettingProvider.cs +++ b/src/Umbraco.Core/Media/IEmbedSettingProvider.cs @@ -3,7 +3,7 @@ namespace Umbraco.Core.Media { public interface IEmbedSettingProvider - { + { object GetSetting(XmlNode settingNode); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Media/IImageUrlProvider.cs b/src/Umbraco.Core/Media/IImageUrlProvider.cs index 29c0ae34ed..4a51edd412 100644 --- a/src/Umbraco.Core/Media/IImageUrlProvider.cs +++ b/src/Umbraco.Core/Media/IImageUrlProvider.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Umbraco.Core.Media { // note: because this interface is obsolete is is *not* IDiscoverable, and in case the - // PluginManager is asked to find types implementing this interface it will fall back + // TypeLoader is asked to find types implementing this interface it will fall back // to a complete scan. [Obsolete("IImageUrlProvider is no longer used and will be removed in future versions")] @@ -14,4 +14,4 @@ namespace Umbraco.Core.Media string GetImageUrlFromMedia(int mediaId, IDictionary parameters); string GetImageUrlFromFileName(string specifiedSrc, IDictionary parameters); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Media/IThumbnailProvider.cs b/src/Umbraco.Core/Media/IThumbnailProvider.cs index 18b8453324..1e5a59bb39 100644 --- a/src/Umbraco.Core/Media/IThumbnailProvider.cs +++ b/src/Umbraco.Core/Media/IThumbnailProvider.cs @@ -3,11 +3,11 @@ namespace Umbraco.Core.Media { // note: because this interface is obsolete is is *not* IDiscoverable, and in case the - // PluginManager is asked to find types implementing this interface it will fall back + // TypeLoader is asked to find types implementing this interface it will fall back // to a complete scan. [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")] - public interface IThumbnailProvider // : IDiscoverable + public interface IThumbnailProvider // : IDiscoverable { bool CanProvideThumbnail(string fileUrl); string GetThumbnailUrl(string fileUrl); diff --git a/src/Umbraco.Core/Media/ProviderSetting.cs b/src/Umbraco.Core/Media/ProviderSetting.cs index bfd06203d4..d26ad6e616 100644 --- a/src/Umbraco.Core/Media/ProviderSetting.cs +++ b/src/Umbraco.Core/Media/ProviderSetting.cs @@ -5,4 +5,4 @@ namespace Umbraco.Core.Media public class ProviderSetting : Attribute { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Media/Result.cs b/src/Umbraco.Core/Media/Result.cs index e8e3c6995c..ee86c02ea5 100644 --- a/src/Umbraco.Core/Media/Result.cs +++ b/src/Umbraco.Core/Media/Result.cs @@ -8,4 +8,4 @@ public bool SupportsDimensions { get; set; } public string Markup { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Media/Status.cs b/src/Umbraco.Core/Media/Status.cs index c0f448156e..5a815c3cf8 100644 --- a/src/Umbraco.Core/Media/Status.cs +++ b/src/Umbraco.Core/Media/Status.cs @@ -8,4 +8,4 @@ Error, Success } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs index bf6f7c59ad..3fdaf7b3d3 100644 --- a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs +++ b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs @@ -32,8 +32,8 @@ namespace Umbraco.Core.Media /// The auto-fill configuration for the specified property alias, or null. public IImagingAutoFillUploadField GetConfig(string propertyTypeAlias) { - var autoFillConfigs = _contentSettings.ImageAutoFillProperties; - return autoFillConfigs == null ? null : autoFillConfigs.FirstOrDefault(x => x.Alias == propertyTypeAlias); + var autoFillConfigs = _contentSettings.ImageAutoFillProperties; + return autoFillConfigs?.FirstOrDefault(x => x.Alias == propertyTypeAlias); } /// @@ -41,17 +41,19 @@ namespace Umbraco.Core.Media /// /// The content item. /// The property type alias. - public void Reset(IContentBase content, string propertyTypeAlias) + /// Variation language. + /// Variation segment. + public void Reset(IContentBase content, string propertyTypeAlias, int? languageId, string segment) { - if (content == null) throw new ArgumentNullException("content"); - if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias"); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (propertyTypeAlias == null) throw new ArgumentNullException(nameof(propertyTypeAlias)); // get the config, no config = nothing to do var autoFillConfig = GetConfig(propertyTypeAlias); if (autoFillConfig == null) return; // nothing // reset - Reset(content, autoFillConfig); + Reset(content, autoFillConfig, languageId, segment); } /// @@ -59,12 +61,14 @@ namespace Umbraco.Core.Media /// /// The content item. /// The auto-fill configuration. - public void Reset(IContentBase content, IImagingAutoFillUploadField autoFillConfig) + /// Variation language. + /// Variation segment. + public void Reset(IContentBase content, IImagingAutoFillUploadField autoFillConfig, int? languageId, string segment) { - if (content == null) throw new ArgumentNullException("content"); - if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig"); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); - ResetProperties(content, autoFillConfig); + ResetProperties(content, autoFillConfig, languageId, segment); } /// @@ -73,10 +77,12 @@ namespace Umbraco.Core.Media /// The content item. /// The property type alias. /// The filesystem-relative filepath, or null to clear properties. - public void Populate(IContentBase content, string propertyTypeAlias, string filepath) + /// Variation language. + /// Variation segment. + public void Populate(IContentBase content, string propertyTypeAlias, string filepath, int? languageId, string segment) { - if (content == null) throw new ArgumentNullException("content"); - if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias"); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (propertyTypeAlias == null) throw new ArgumentNullException(nameof(propertyTypeAlias)); // no property = nothing to do if (content.Properties.Contains(propertyTypeAlias) == false) return; @@ -86,7 +92,7 @@ namespace Umbraco.Core.Media if (autoFillConfig == null) return; // nothing // populate - Populate(content, autoFillConfig, filepath); + Populate(content, autoFillConfig, filepath, languageId, segment); } /// @@ -96,10 +102,12 @@ namespace Umbraco.Core.Media /// The property type alias. /// The filesystem-relative filepath, or null to clear properties. /// The stream containing the file data. - public void Populate(IContentBase content, string propertyTypeAlias, string filepath, Stream filestream) + /// Variation language. + /// Variation segment. + public void Populate(IContentBase content, string propertyTypeAlias, string filepath, Stream filestream, int? languageId, string segment) { - if (content == null) throw new ArgumentNullException("content"); - if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias"); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (propertyTypeAlias == null) throw new ArgumentNullException(nameof(propertyTypeAlias)); // no property = nothing to do if (content.Properties.Contains(propertyTypeAlias) == false) return; @@ -109,7 +117,7 @@ namespace Umbraco.Core.Media if (autoFillConfig == null) return; // nothing // populate - Populate(content, autoFillConfig, filepath, filestream); + Populate(content, autoFillConfig, filepath, filestream, languageId, segment); } /// @@ -119,15 +127,17 @@ namespace Umbraco.Core.Media /// The auto-fill configuration. /// The filesystem path to the uploaded file. /// The parameter is the path relative to the filesystem. - public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath) + /// Variation language. + /// Variation segment. + public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath, int? languageId, string segment) { - if (content == null) throw new ArgumentNullException("content"); - if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig"); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); // no file = reset, file = auto-fill if (filepath.IsNullOrWhiteSpace()) { - ResetProperties(content, autoFillConfig); + ResetProperties(content, autoFillConfig, languageId, segment); } else { @@ -138,14 +148,13 @@ namespace Umbraco.Core.Media { var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); var size = _mediaFileSystem.IsImageFile(extension) ? (Size?) _mediaFileSystem.GetDimensions(filestream) : null; - SetProperties(content, autoFillConfig, size, filestream.Length, extension); + SetProperties(content, autoFillConfig, size, filestream.Length, extension, languageId, segment); } } catch (Exception ex) { - _logger.Error(typeof(UploadAutoFillProperties), "Could not populate upload auto-fill properties for file \"" - + filepath + "\".", ex); - ResetProperties(content, autoFillConfig); + _logger.Error(typeof(UploadAutoFillProperties), $"Could not populate upload auto-fill properties for file \"{filepath}\".", ex); + ResetProperties(content, autoFillConfig, languageId, segment); } } } @@ -157,58 +166,60 @@ namespace Umbraco.Core.Media /// /// The filesystem-relative filepath, or null to clear properties. /// The stream containing the file data. - public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath, Stream filestream) + /// Variation language. + /// Variation segment. + public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath, Stream filestream, int? languageId, string segment) { - if (content == null) throw new ArgumentNullException("content"); - if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig"); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); // no file = reset, file = auto-fill if (filepath.IsNullOrWhiteSpace() || filestream == null) { - ResetProperties(content, autoFillConfig); + ResetProperties(content, autoFillConfig, languageId, segment); } else { var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); var size = _mediaFileSystem.IsImageFile(extension) ? (Size?)_mediaFileSystem.GetDimensions(filestream) : null; - SetProperties(content, autoFillConfig, size, filestream.Length, extension); + SetProperties(content, autoFillConfig, size, filestream.Length, extension, languageId, segment); } } - private static void SetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig, Size? size, long length, string extension) + private static void SetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig, Size? size, long length, string extension, int? languageId, string segment) { - if (content == null) throw new ArgumentNullException("content"); - if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig"); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); if (content.Properties.Contains(autoFillConfig.WidthFieldAlias)) - content.Properties[autoFillConfig.WidthFieldAlias].Value = size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty; + content.Properties[autoFillConfig.WidthFieldAlias].SetValue(size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty, languageId, segment); if (content.Properties.Contains(autoFillConfig.HeightFieldAlias)) - content.Properties[autoFillConfig.HeightFieldAlias].Value = size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty; + content.Properties[autoFillConfig.HeightFieldAlias].SetValue(size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty, languageId, segment); if (content.Properties.Contains(autoFillConfig.LengthFieldAlias)) - content.Properties[autoFillConfig.LengthFieldAlias].Value = length; + content.Properties[autoFillConfig.LengthFieldAlias].SetValue(length, languageId, segment); if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) - content.Properties[autoFillConfig.ExtensionFieldAlias].Value = extension; -} + content.Properties[autoFillConfig.ExtensionFieldAlias].SetValue(extension, languageId, segment); + } - private static void ResetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig) + private static void ResetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig, int? languageId, string segment) { - if (content == null) throw new ArgumentNullException("content"); - if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig"); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); if (content.Properties.Contains(autoFillConfig.WidthFieldAlias)) - content.Properties[autoFillConfig.WidthFieldAlias].Value = string.Empty; + content.Properties[autoFillConfig.WidthFieldAlias].SetValue(string.Empty, languageId, segment); if (content.Properties.Contains(autoFillConfig.HeightFieldAlias)) - content.Properties[autoFillConfig.HeightFieldAlias].Value = string.Empty; + content.Properties[autoFillConfig.HeightFieldAlias].SetValue(string.Empty, languageId, segment); if (content.Properties.Contains(autoFillConfig.LengthFieldAlias)) - content.Properties[autoFillConfig.LengthFieldAlias].Value = string.Empty; + content.Properties[autoFillConfig.LengthFieldAlias].SetValue(string.Empty, languageId, segment); if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) - content.Properties[autoFillConfig.ExtensionFieldAlias].Value = string.Empty; + content.Properties[autoFillConfig.ExtensionFieldAlias].SetValue(string.Empty, languageId, segment); } } } diff --git a/src/Umbraco.Core/Persistence/Migrations/DataLossException.cs b/src/Umbraco.Core/Migrations/DataLossException.cs similarity index 82% rename from src/Umbraco.Core/Persistence/Migrations/DataLossException.cs rename to src/Umbraco.Core/Migrations/DataLossException.cs index cb4ea9120a..6ff332f626 100644 --- a/src/Umbraco.Core/Persistence/Migrations/DataLossException.cs +++ b/src/Umbraco.Core/Migrations/DataLossException.cs @@ -1,6 +1,6 @@ -using System; +using System; -namespace Umbraco.Core.Persistence.Migrations +namespace Umbraco.Core.Migrations { /// /// Used if a migration has executed but the whole process has failed and cannot be rolled back @@ -10,13 +10,13 @@ namespace Umbraco.Core.Persistence.Migrations public DataLossException(string msg) : base(msg) { - + } public DataLossException(string msg, Exception inner) : base(msg, inner) { - + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/AlterBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/AlterBuilder.cs new file mode 100644 index 0000000000..91144774bf --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/AlterBuilder.cs @@ -0,0 +1,26 @@ +using NPoco; +using Umbraco.Core.Migrations.Expressions.Alter.Expressions; +using Umbraco.Core.Migrations.Expressions.Alter.Table; + +namespace Umbraco.Core.Migrations.Expressions.Alter +{ + /// + /// Implements . + /// + public class AlterBuilder : IAlterBuilder + { + private readonly IMigrationContext _context; + + public AlterBuilder(IMigrationContext context) + { + _context = context; + } + + /// + public IAlterTableBuilder Table(string tableName) + { + var expression = new AlterTableExpression(_context) { TableName = tableName }; + return new AlterTableBuilder(_context, expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs new file mode 100644 index 0000000000..f19fc94c97 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs @@ -0,0 +1,27 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Alter.Expressions +{ + public class AlterColumnExpression : MigrationExpressionBase + { + + public AlterColumnExpression(IMigrationContext context) + : base(context) + { + Column = new ColumnDefinition { ModificationType = ModificationType.Alter }; + } + + public virtual string SchemaName { get; set; } + public virtual string TableName { get; set; } + public virtual ColumnDefinition Column { get; set; } + + protected override string GetSql() + { + + return string.Format(SqlSyntax.AlterColumn, + SqlSyntax.GetQuotedTableName(TableName), + SqlSyntax.Format(Column)); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterDefaultConstraintExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterDefaultConstraintExpression.cs new file mode 100644 index 0000000000..c69b9390cf --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterDefaultConstraintExpression.cs @@ -0,0 +1,28 @@ +using NPoco; + +namespace Umbraco.Core.Migrations.Expressions.Alter.Expressions +{ + public class AlterDefaultConstraintExpression : MigrationExpressionBase + { + public AlterDefaultConstraintExpression(IMigrationContext context) + : base(context) + { } + + public virtual string TableName { get; set; } + + public virtual string ColumnName { get; set; } + + public virtual string ConstraintName { get; set; } + + public virtual object DefaultValue { get; set; } + + protected override string GetSql() + { + //NOTE Should probably investigate if Deleting a Default Constraint is different from deleting a 'regular' constraint + + return string.Format(SqlSyntax.DeleteConstraint, + SqlSyntax.GetQuotedTableName(TableName), + SqlSyntax.GetQuotedName(ConstraintName)); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterTableExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterTableExpression.cs new file mode 100644 index 0000000000..062ceaf8c7 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterTableExpression.cs @@ -0,0 +1,18 @@ +using NPoco; + +namespace Umbraco.Core.Migrations.Expressions.Alter.Expressions +{ + public class AlterTableExpression : MigrationExpressionBase + { + public AlterTableExpression(IMigrationContext context) + : base(context) + { } + + public virtual string TableName { get; set; } + + protected override string GetSql() + { + return string.Empty; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/IAlterBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/IAlterBuilder.cs new file mode 100644 index 0000000000..80a519d449 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/IAlterBuilder.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Migrations.Expressions.Alter.Table; + +namespace Umbraco.Core.Migrations.Expressions.Alter +{ + /// + /// Builds an Alter expression. + /// + public interface IAlterBuilder : IFluentBuilder + { + /// + /// Specifies the table to alter. + /// + IAlterTableBuilder Table(string tableName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs new file mode 100644 index 0000000000..989ce95002 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs @@ -0,0 +1,255 @@ +using System.Data; +using NPoco; +using Umbraco.Core.Migrations.Expressions.Alter.Expressions; +using Umbraco.Core.Migrations.Expressions.Common.Expressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Alter.Table +{ + public class AlterTableBuilder : ExpressionBuilderBase, + IAlterTableColumnTypeBuilder, + IAlterTableColumnOptionForeignKeyCascadeBuilder + { + private readonly IMigrationContext _context; + + public AlterTableBuilder(IMigrationContext context, AlterTableExpression expression) + : base(expression) + { + _context = context; + } + + public void Do() => Expression.Execute(); + + public ColumnDefinition CurrentColumn { get; set; } + + public ForeignKeyDefinition CurrentForeignKey { get; set; } + + public override ColumnDefinition GetColumnForType() + { + return CurrentColumn; + } + + public IAlterTableColumnOptionBuilder WithDefault(SystemMethods method) + { + CurrentColumn.DefaultValue = method; + return this; + } + + public IAlterTableColumnOptionBuilder WithDefaultValue(object value) + { + if (CurrentColumn.ModificationType == ModificationType.Alter) + { + var dc = new AlterDefaultConstraintExpression(_context) + { + TableName = Expression.TableName, + ColumnName = CurrentColumn.Name, + DefaultValue = value + }; + + Expression.Expressions.Add(dc); + } + + CurrentColumn.DefaultValue = value; + return this; + } + + public IAlterTableColumnOptionBuilder Identity() + { + CurrentColumn.IsIdentity = true; + return this; + } + + public IAlterTableColumnOptionBuilder Indexed() + { + return Indexed(null); + } + + public IAlterTableColumnOptionBuilder Indexed(string indexName) + { + CurrentColumn.IsIndexed = true; + + var index = new CreateIndexExpression(_context, new IndexDefinition + { + Name = indexName, + TableName = Expression.TableName + }); + + index.Index.Columns.Add(new IndexColumnDefinition + { + Name = CurrentColumn.Name + }); + + Expression.Expressions.Add(index); + + return this; + } + + public IAlterTableColumnOptionBuilder PrimaryKey() + { + CurrentColumn.IsPrimaryKey = true; + return this; + } + + public IAlterTableColumnOptionBuilder PrimaryKey(string primaryKeyName) + { + CurrentColumn.IsPrimaryKey = true; + CurrentColumn.PrimaryKeyName = primaryKeyName; + return this; + } + + public IAlterTableColumnOptionBuilder Nullable() + { + CurrentColumn.IsNullable = true; + return this; + } + + public IAlterTableColumnOptionBuilder NotNullable() + { + CurrentColumn.IsNullable = false; + return this; + } + + public IAlterTableColumnOptionBuilder Unique() + { + return Unique(null); + } + + public IAlterTableColumnOptionBuilder Unique(string indexName) + { + CurrentColumn.IsUnique = true; + + var index = new CreateIndexExpression(_context, new IndexDefinition + { + Name = indexName, + TableName = Expression.TableName, + IsUnique = true + }); + + index.Index.Columns.Add(new IndexColumnDefinition + { + Name = CurrentColumn.Name + }); + + Expression.Expressions.Add(index); + + return this; + } + + public IAlterTableColumnOptionForeignKeyCascadeBuilder ForeignKey(string primaryTableName, string primaryColumnName) + { + return ForeignKey(null, null, primaryTableName, primaryColumnName); + } + + public IAlterTableColumnOptionForeignKeyCascadeBuilder ForeignKey(string foreignKeyName, string primaryTableName, + string primaryColumnName) + { + return ForeignKey(foreignKeyName, null, primaryTableName, primaryColumnName); + } + + public IAlterTableColumnOptionForeignKeyCascadeBuilder ForeignKey(string foreignKeyName, string primaryTableSchema, + string primaryTableName, string primaryColumnName) + { + CurrentColumn.IsForeignKey = true; + + var fk = new CreateForeignKeyExpression(_context, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = primaryTableName, + PrimaryTableSchema = primaryTableSchema, + ForeignTable = Expression.TableName + }); + + fk.ForeignKey.PrimaryColumns.Add(primaryColumnName); + fk.ForeignKey.ForeignColumns.Add(CurrentColumn.Name); + + Expression.Expressions.Add(fk); + CurrentForeignKey = fk.ForeignKey; + return this; + } + + public IAlterTableColumnOptionForeignKeyCascadeBuilder ForeignKey() + { + CurrentColumn.IsForeignKey = true; + return this; + } + + public IAlterTableColumnOptionForeignKeyCascadeBuilder ReferencedBy(string foreignTableName, string foreignColumnName) + { + return ReferencedBy(null, null, foreignTableName, foreignColumnName); + } + + public IAlterTableColumnOptionForeignKeyCascadeBuilder ReferencedBy(string foreignKeyName, string foreignTableName, + string foreignColumnName) + { + return ReferencedBy(foreignKeyName, null, foreignTableName, foreignColumnName); + } + + public IAlterTableColumnOptionForeignKeyCascadeBuilder ReferencedBy(string foreignKeyName, string foreignTableSchema, + string foreignTableName, string foreignColumnName) + { + var fk = new CreateForeignKeyExpression(_context, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = Expression.TableName, + ForeignTable = foreignTableName, + ForeignTableSchema = foreignTableSchema + }); + + fk.ForeignKey.PrimaryColumns.Add(CurrentColumn.Name); + fk.ForeignKey.ForeignColumns.Add(foreignColumnName); + + Expression.Expressions.Add(fk); + CurrentForeignKey = fk.ForeignKey; + return this; + } + + public IAlterTableColumnTypeBuilder AddColumn(string name) + { + var column = new ColumnDefinition { Name = name, ModificationType = ModificationType.Create }; + var createColumn = new CreateColumnExpression(_context) + { + Column = column, + TableName = Expression.TableName + }; + + CurrentColumn = column; + + Expression.Expressions.Add(createColumn); + return this; + } + + public IAlterTableColumnTypeBuilder AlterColumn(string name) + { + var column = new ColumnDefinition { Name = name, ModificationType = ModificationType.Alter }; + var alterColumn = new AlterColumnExpression(_context) + { + Column = column, + TableName = Expression.TableName + }; + + CurrentColumn = column; + + Expression.Expressions.Add(alterColumn); + return this; + } + + public IAlterTableColumnOptionForeignKeyCascadeBuilder OnDelete(Rule rule) + { + CurrentForeignKey.OnDelete = rule; + return this; + } + + public IAlterTableColumnOptionForeignKeyCascadeBuilder OnUpdate(Rule rule) + { + CurrentForeignKey.OnUpdate = rule; + return this; + } + + public IAlterTableColumnOptionBuilder OnDeleteOrUpdate(Rule rule) + { + OnDelete(rule); + OnUpdate(rule); + return this; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableBuilder.cs new file mode 100644 index 0000000000..04537e48db --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableBuilder.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Migrations.Expressions.Alter.Table +{ + /// + /// Builds an Alter Table expression. + /// + public interface IAlterTableBuilder : IFluentBuilder + { + /// + /// Specifies a column to add. + /// + IAlterTableColumnTypeBuilder AddColumn(string name); + + /// + /// Specifies a column to alter. + /// + IAlterTableColumnTypeBuilder AlterColumn(string name); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableColumnOptionBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableColumnOptionBuilder.cs new file mode 100644 index 0000000000..0c323b0699 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableColumnOptionBuilder.cs @@ -0,0 +1,8 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Alter.Table +{ + public interface IAlterTableColumnOptionBuilder : IColumnOptionBuilder, + IAlterTableBuilder, IExecutableBuilder + { } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableColumnOptionForeignKeyCascadeBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableColumnOptionForeignKeyCascadeBuilder.cs new file mode 100644 index 0000000000..8099deaa56 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableColumnOptionForeignKeyCascadeBuilder.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Alter.Table +{ + public interface IAlterTableColumnOptionForeignKeyCascadeBuilder : + IAlterTableColumnOptionBuilder, + IForeignKeyCascadeBuilder + { } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableColumnTypeBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableColumnTypeBuilder.cs new file mode 100644 index 0000000000..61fdec4ca0 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/IAlterTableColumnTypeBuilder.cs @@ -0,0 +1,7 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Alter.Table +{ + public interface IAlterTableColumnTypeBuilder : IColumnTypeBuilder + { } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Common/ExecutableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Common/ExecutableBuilder.cs new file mode 100644 index 0000000000..6e93d7bcc1 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Common/ExecutableBuilder.cs @@ -0,0 +1,15 @@ +namespace Umbraco.Core.Migrations.Expressions.Common +{ + public class ExecutableBuilder : IExecutableBuilder + { + private readonly IMigrationExpression _expression; + + public ExecutableBuilder(IMigrationExpression expression) + { + _expression = expression; + } + + /// + public void Do() => _expression.Execute(); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Common/Expressions/CreateColumnExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Common/Expressions/CreateColumnExpression.cs new file mode 100644 index 0000000000..91317167f8 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Common/Expressions/CreateColumnExpression.cs @@ -0,0 +1,27 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Common.Expressions +{ + public class CreateColumnExpression : MigrationExpressionBase + { + public CreateColumnExpression(IMigrationContext context) + : base(context) + { + Column = new ColumnDefinition { ModificationType = ModificationType.Create }; + } + + public string TableName { get; set; } + public ColumnDefinition Column { get; set; } + + protected override string GetSql() + { + if (string.IsNullOrEmpty(Column.TableName)) + Column.TableName = TableName; + + return string.Format(SqlSyntax.AddColumn, + SqlSyntax.GetQuotedTableName(Column.TableName), + SqlSyntax.Format(Column)); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Common/Expressions/CreateForeignKeyExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Common/Expressions/CreateForeignKeyExpression.cs new file mode 100644 index 0000000000..2f35338eb0 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Common/Expressions/CreateForeignKeyExpression.cs @@ -0,0 +1,27 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Common.Expressions +{ + public class CreateForeignKeyExpression : MigrationExpressionBase + { + public CreateForeignKeyExpression(IMigrationContext context, ForeignKeyDefinition fkDef) + : base(context) + { + ForeignKey = fkDef; + } + + public CreateForeignKeyExpression(IMigrationContext context) + : base(context) + { + ForeignKey = new ForeignKeyDefinition(); + } + + public ForeignKeyDefinition ForeignKey { get; set; } + + protected override string GetSql() + { + return SqlSyntax.Format(ForeignKey); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Common/Expressions/CreateIndexExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Common/Expressions/CreateIndexExpression.cs new file mode 100644 index 0000000000..3d07937fa7 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Common/Expressions/CreateIndexExpression.cs @@ -0,0 +1,28 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Common.Expressions +{ + public class CreateIndexExpression : MigrationExpressionBase + { + + public CreateIndexExpression(IMigrationContext context, IndexDefinition index) + : base(context) + { + Index = index; + } + + public CreateIndexExpression(IMigrationContext context) + : base(context) + { + Index = new IndexDefinition(); + } + + public IndexDefinition Index { get; set; } + + protected override string GetSql() + { + return SqlSyntax.Format(Index); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Common/IColumnOptionBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Common/IColumnOptionBuilder.cs new file mode 100644 index 0000000000..06e00211a0 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Common/IColumnOptionBuilder.cs @@ -0,0 +1,31 @@ +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Common +{ + public interface IColumnOptionBuilder : IFluentBuilder + where TNext : IFluentBuilder + where TNextFk : IFluentBuilder + { + TNext WithDefault(SystemMethods method); + TNext WithDefaultValue(object value); + TNext Identity(); + TNext Indexed(); + TNext Indexed(string indexName); + + TNext PrimaryKey(); + TNext PrimaryKey(string primaryKeyName); + TNext Nullable(); + TNext NotNullable(); + TNext Unique(); + TNext Unique(string indexName); + + TNextFk ForeignKey(string primaryTableName, string primaryColumnName); + TNextFk ForeignKey(string foreignKeyName, string primaryTableName, string primaryColumnName); + TNextFk ForeignKey(string foreignKeyName, string primaryTableSchema, string primaryTableName, string primaryColumnName); + TNextFk ForeignKey(); + + TNextFk ReferencedBy(string foreignTableName, string foreignColumnName); + TNextFk ReferencedBy(string foreignKeyName, string foreignTableName, string foreignColumnName); + TNextFk ReferencedBy(string foreignKeyName, string foreignTableSchema, string foreignTableName, string foreignColumnName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Common/IColumnTypeBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Common/IColumnTypeBuilder.cs new file mode 100644 index 0000000000..431b6c34c4 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Common/IColumnTypeBuilder.cs @@ -0,0 +1,35 @@ +namespace Umbraco.Core.Migrations.Expressions.Common +{ + /// + /// Builds a column type expression. + /// + public interface IColumnTypeBuilder : IFluentBuilder + where TNext : IFluentBuilder + { + TNext AsAnsiString(); + TNext AsAnsiString(int size); + TNext AsBinary(); + TNext AsBinary(int size); + TNext AsBoolean(); + TNext AsByte(); + TNext AsCurrency(); + TNext AsDate(); + TNext AsDateTime(); + TNext AsDecimal(); + TNext AsDecimal(int size, int precision); + TNext AsDouble(); + TNext AsGuid(); + TNext AsFixedLengthString(int size); + TNext AsFixedLengthAnsiString(int size); + TNext AsFloat(); + TNext AsInt16(); + TNext AsInt32(); + TNext AsInt64(); + TNext AsString(); + TNext AsString(int size); + TNext AsTime(); + TNext AsXml(); + TNext AsXml(int size); + TNext AsCustom(string customType); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Common/IExecutableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Common/IExecutableBuilder.cs new file mode 100644 index 0000000000..b56ff776cb --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Common/IExecutableBuilder.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Migrations.Expressions.Common +{ + public interface IExecutableBuilder + { + /// + /// Executes. + /// + void Do(); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Common/IForeignKeyCascadeBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Common/IForeignKeyCascadeBuilder.cs new file mode 100644 index 0000000000..746dea64d4 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Common/IForeignKeyCascadeBuilder.cs @@ -0,0 +1,24 @@ +using System.Data; + +namespace Umbraco.Core.Migrations.Expressions.Common +{ + public interface IForeignKeyCascadeBuilder : IFluentBuilder + where TNext : IFluentBuilder + where TNextFk : IFluentBuilder + { + /// + /// Specifies a rule on deletes. + /// + TNextFk OnDelete(Rule rule); + + /// + /// Specifies a rule on updates. + /// + TNextFk OnUpdate(Rule rule); + + /// + /// Specifies a rule on deletes and updates. + /// + TNext OnDeleteOrUpdate(Rule rule); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Column/CreateColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Column/CreateColumnBuilder.cs new file mode 100644 index 0000000000..28de5aef14 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Column/CreateColumnBuilder.cs @@ -0,0 +1,216 @@ +using System.Data; +using NPoco; +using Umbraco.Core.Migrations.Expressions.Common.Expressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Create.Column +{ + public class CreateColumnBuilder : ExpressionBuilderBase, + ICreateColumnOnTableBuilder, + ICreateColumnTypeBuilder, + ICreateColumnOptionForeignKeyCascadeBuilder + { + private readonly IMigrationContext _context; + + public CreateColumnBuilder(IMigrationContext context, CreateColumnExpression expression) + : base(expression) + { + _context = context; + } + + public void Do() => Expression.Execute(); + + public ForeignKeyDefinition CurrentForeignKey { get; set; } + + public override ColumnDefinition GetColumnForType() + { + return Expression.Column; + } + + public ICreateColumnTypeBuilder OnTable(string name) + { + Expression.TableName = name; + return this; + } + + public ICreateColumnOptionBuilder WithDefault(SystemMethods method) + { + Expression.Column.DefaultValue = method; + return this; + } + + public ICreateColumnOptionBuilder WithDefaultValue(object value) + { + Expression.Column.DefaultValue = value; + return this; + } + + public ICreateColumnOptionBuilder Identity() + { + return Indexed(null); + } + + public ICreateColumnOptionBuilder Indexed() + { + return Indexed(null); + } + + public ICreateColumnOptionBuilder Indexed(string indexName) + { + Expression.Column.IsIndexed = true; + + var index = new CreateIndexExpression(_context, new IndexDefinition + { + Name = indexName, + TableName = Expression.TableName + }); + + index.Index.Columns.Add(new IndexColumnDefinition + { + Name = Expression.Column.Name + }); + + Expression.Expressions.Add(index); + + return this; + } + + public ICreateColumnOptionBuilder PrimaryKey() + { + Expression.Column.IsPrimaryKey = true; + return this; + } + + public ICreateColumnOptionBuilder PrimaryKey(string primaryKeyName) + { + Expression.Column.IsPrimaryKey = true; + Expression.Column.PrimaryKeyName = primaryKeyName; + return this; + } + + public ICreateColumnOptionBuilder Nullable() + { + Expression.Column.IsNullable = true; + return this; + } + + public ICreateColumnOptionBuilder NotNullable() + { + Expression.Column.IsNullable = false; + return this; + } + + public ICreateColumnOptionBuilder Unique() + { + return Unique(null); + } + + public ICreateColumnOptionBuilder Unique(string indexName) + { + Expression.Column.IsUnique = true; + + var index = new CreateIndexExpression(_context, new IndexDefinition + { + Name = indexName, + TableName = Expression.TableName, + IsUnique = true + }); + + index.Index.Columns.Add(new IndexColumnDefinition + { + Name = Expression.Column.Name + }); + + Expression.Expressions.Add(index); + + return this; + } + + public ICreateColumnOptionForeignKeyCascadeBuilder ForeignKey(string primaryTableName, string primaryColumnName) + { + return ForeignKey(null, null, primaryTableName, primaryColumnName); + } + + public ICreateColumnOptionForeignKeyCascadeBuilder ForeignKey(string foreignKeyName, string primaryTableName, + string primaryColumnName) + { + return ForeignKey(foreignKeyName, null, primaryTableName, primaryColumnName); + } + + public ICreateColumnOptionForeignKeyCascadeBuilder ForeignKey(string foreignKeyName, string primaryTableSchema, + string primaryTableName, string primaryColumnName) + { + Expression.Column.IsForeignKey = true; + + var fk = new CreateForeignKeyExpression(_context, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = primaryTableName, + PrimaryTableSchema = primaryTableSchema, + ForeignTable = Expression.TableName + }); + + fk.ForeignKey.PrimaryColumns.Add(primaryColumnName); + fk.ForeignKey.ForeignColumns.Add(Expression.Column.Name); + + Expression.Expressions.Add(fk); + CurrentForeignKey = fk.ForeignKey; + return this; + } + + public ICreateColumnOptionForeignKeyCascadeBuilder ForeignKey() + { + Expression.Column.IsForeignKey = true; + return this; + } + + public ICreateColumnOptionForeignKeyCascadeBuilder ReferencedBy(string foreignTableName, string foreignColumnName) + { + return ReferencedBy(null, null, foreignTableName, foreignColumnName); + } + + public ICreateColumnOptionForeignKeyCascadeBuilder ReferencedBy(string foreignKeyName, string foreignTableName, + string foreignColumnName) + { + return ReferencedBy(foreignKeyName, null, foreignTableName, foreignColumnName); + } + + public ICreateColumnOptionForeignKeyCascadeBuilder ReferencedBy(string foreignKeyName, string foreignTableSchema, + string foreignTableName, string foreignColumnName) + { + var fk = new CreateForeignKeyExpression(_context, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = Expression.TableName, + ForeignTable = foreignTableName, + ForeignTableSchema = foreignTableSchema + }); + + fk.ForeignKey.PrimaryColumns.Add(Expression.Column.Name); + fk.ForeignKey.ForeignColumns.Add(foreignColumnName); + + Expression.Expressions.Add(fk); + CurrentForeignKey = fk.ForeignKey; + return this; + } + + public ICreateColumnOptionForeignKeyCascadeBuilder OnDelete(Rule rule) + { + CurrentForeignKey.OnDelete = rule; + return this; + } + + public ICreateColumnOptionForeignKeyCascadeBuilder OnUpdate(Rule rule) + { + CurrentForeignKey.OnUpdate = rule; + return this; + } + + public ICreateColumnOptionBuilder OnDeleteOrUpdate(Rule rule) + { + OnDelete(rule); + OnUpdate(rule); + return this; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnOnTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnOnTableBuilder.cs new file mode 100644 index 0000000000..fbd7387cda --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnOnTableBuilder.cs @@ -0,0 +1,12 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Create.Column +{ + public interface ICreateColumnOnTableBuilder : IColumnTypeBuilder + { + /// + /// Specifies the name of the table. + /// + ICreateColumnTypeBuilder OnTable(string name); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnOptionBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnOptionBuilder.cs new file mode 100644 index 0000000000..eccaf26d89 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnOptionBuilder.cs @@ -0,0 +1,8 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Create.Column +{ + public interface ICreateColumnOptionBuilder : IColumnOptionBuilder + , IExecutableBuilder + { } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnOptionForeignKeyCascadeBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnOptionForeignKeyCascadeBuilder.cs new file mode 100644 index 0000000000..59e714e73c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnOptionForeignKeyCascadeBuilder.cs @@ -0,0 +1,8 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Create.Column +{ + public interface ICreateColumnOptionForeignKeyCascadeBuilder : ICreateColumnOptionBuilder, + IForeignKeyCascadeBuilder + { } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnTypeBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnTypeBuilder.cs new file mode 100644 index 0000000000..f9c077e838 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Column/ICreateColumnTypeBuilder.cs @@ -0,0 +1,7 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Create.Column +{ + public interface ICreateColumnTypeBuilder : IColumnTypeBuilder + { } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Constraint/CreateConstraintBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Constraint/CreateConstraintBuilder.cs new file mode 100644 index 0000000000..48690f4aa6 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Constraint/CreateConstraintBuilder.cs @@ -0,0 +1,36 @@ +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Create.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Create.Constraint +{ + public class CreateConstraintBuilder : ExpressionBuilderBase, + ICreateConstraintOnTableBuilder, + ICreateConstraintColumnsBuilder + { + public CreateConstraintBuilder(CreateConstraintExpression expression) + : base(expression) + { } + + /// + public ICreateConstraintColumnsBuilder OnTable(string tableName) + { + Expression.Constraint.TableName = tableName; + return this; + } + + /// + public IExecutableBuilder Column(string columnName) + { + Expression.Constraint.Columns.Add(columnName); + return new ExecutableBuilder(Expression); + } + + /// + public IExecutableBuilder Columns(string[] columnNames) + { + foreach (var columnName in columnNames) + Expression.Constraint.Columns.Add(columnName); + return new ExecutableBuilder(Expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Constraint/ICreateConstraintColumnsBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Constraint/ICreateConstraintColumnsBuilder.cs new file mode 100644 index 0000000000..fc2d2116e1 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Constraint/ICreateConstraintColumnsBuilder.cs @@ -0,0 +1,17 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Create.Constraint +{ + public interface ICreateConstraintColumnsBuilder : IFluentBuilder + { + /// + /// Specifies the constraint column. + /// + IExecutableBuilder Column(string columnName); + + /// + /// Specifies the constraint columns. + /// + IExecutableBuilder Columns(string[] columnNames); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Constraint/ICreateConstraintOnTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Constraint/ICreateConstraintOnTableBuilder.cs new file mode 100644 index 0000000000..d6a6c9b2a8 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Constraint/ICreateConstraintOnTableBuilder.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Migrations.Expressions.Create.Constraint +{ + public interface ICreateConstraintOnTableBuilder : IFluentBuilder + { + /// + /// Specifies the table name. + /// + ICreateConstraintColumnsBuilder OnTable(string tableName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/CreateBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/CreateBuilder.cs new file mode 100644 index 0000000000..017c652be3 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/CreateBuilder.cs @@ -0,0 +1,123 @@ +using System; +using NPoco; +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Common.Expressions; +using Umbraco.Core.Migrations.Expressions.Create.Column; +using Umbraco.Core.Migrations.Expressions.Create.Constraint; +using Umbraco.Core.Migrations.Expressions.Create.Expressions; +using Umbraco.Core.Migrations.Expressions.Create.ForeignKey; +using Umbraco.Core.Migrations.Expressions.Create.Index; +using Umbraco.Core.Migrations.Expressions.Create.KeysAndIndexes; +using Umbraco.Core.Migrations.Expressions.Create.Table; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Create +{ + public class CreateBuilder : ICreateBuilder + { + private readonly IMigrationContext _context; + + public CreateBuilder(IMigrationContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + /// + public IExecutableBuilder Table(bool withoutKeysAndIndexes = false) + { + return new CreateTableOfDtoBuilder(_context) { TypeOfDto = typeof(TDto), WithoutKeysAndIndexes = withoutKeysAndIndexes }; + } + + /// + public IExecutableBuilder KeysAndIndexes() + { + return new CreateKeysAndIndexesBuilder(_context) { TypeOfDto = typeof(TDto) }; + } + + /// + public IExecutableBuilder KeysAndIndexes(Type typeOfDto) + { + return new CreateKeysAndIndexesBuilder(_context) { TypeOfDto = typeOfDto }; + } + + /// + public ICreateTableWithColumnBuilder Table(string tableName) + { + var expression = new CreateTableExpression(_context) { TableName = tableName }; + return new CreateTableBuilder(_context, expression); + } + + /// + public ICreateColumnOnTableBuilder Column(string columnName) + { + var expression = new CreateColumnExpression(_context) { Column = { Name = columnName } }; + return new CreateColumnBuilder(_context, expression); + } + + /// + public ICreateForeignKeyFromTableBuilder ForeignKey() + { + var expression = new CreateForeignKeyExpression(_context); + return new CreateForeignKeyBuilder(expression); + } + + /// + public ICreateForeignKeyFromTableBuilder ForeignKey(string foreignKeyName) + { + var expression = new CreateForeignKeyExpression(_context) { ForeignKey = { Name = foreignKeyName } }; + return new CreateForeignKeyBuilder(expression); + } + + /// + public ICreateIndexForTableBuilder Index() + { + var expression = new CreateIndexExpression(_context); + return new CreateIndexBuilder(expression); + } + + /// + public ICreateIndexForTableBuilder Index(string indexName) + { + var expression = new CreateIndexExpression(_context) { Index = { Name = indexName } }; + return new CreateIndexBuilder(expression); + } + + /// + public ICreateConstraintOnTableBuilder PrimaryKey() + { + var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey); + return new CreateConstraintBuilder(expression); + } + + /// + public ICreateConstraintOnTableBuilder PrimaryKey(string primaryKeyName) + { + var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey); + expression.Constraint.ConstraintName = primaryKeyName; + return new CreateConstraintBuilder(expression); + } + + /// + public ICreateConstraintOnTableBuilder UniqueConstraint() + { + var expression = new CreateConstraintExpression(_context, ConstraintType.Unique); + return new CreateConstraintBuilder(expression); + } + + /// + public ICreateConstraintOnTableBuilder UniqueConstraint(string constraintName) + { + var expression = new CreateConstraintExpression(_context, ConstraintType.Unique); + expression.Constraint.ConstraintName = constraintName; + return new CreateConstraintBuilder(expression); + } + + /// + public ICreateConstraintOnTableBuilder Constraint(string constraintName) + { + var expression = new CreateConstraintExpression(_context, ConstraintType.NonUnique); + expression.Constraint.ConstraintName = constraintName; + return new CreateConstraintBuilder(expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Expressions/CreateConstraintExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Expressions/CreateConstraintExpression.cs new file mode 100644 index 0000000000..b977f5f71f --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Expressions/CreateConstraintExpression.cs @@ -0,0 +1,41 @@ +using System.Linq; +using NPoco; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Create.Expressions +{ + public class CreateConstraintExpression : MigrationExpressionBase + { + public CreateConstraintExpression(IMigrationContext context, ConstraintType constraint) + : base(context) + { + Constraint = new ConstraintDefinition(constraint); + } + + public ConstraintDefinition Constraint { get; } + + protected override string GetSql() + { + var constraintType = (Constraint.IsPrimaryKeyConstraint) ? "PRIMARY KEY" : "UNIQUE"; + + if (Constraint.IsPrimaryKeyConstraint && SqlSyntax.SupportsClustered()) + constraintType += " CLUSTERED"; + + if (Constraint.IsNonUniqueConstraint) + constraintType = string.Empty; + + var columns = new string[Constraint.Columns.Count]; + + for (var i = 0; i < Constraint.Columns.Count; i++) + { + columns[i] = SqlSyntax.GetQuotedColumnName(Constraint.Columns.ElementAt(i)); + } + + return string.Format(SqlSyntax.CreateConstraint, + SqlSyntax.GetQuotedTableName(Constraint.TableName), + SqlSyntax.GetQuotedName(Constraint.ConstraintName), + constraintType, + string.Join(", ", columns)); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Expressions/CreateTableExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Expressions/CreateTableExpression.cs new file mode 100644 index 0000000000..d194fcc01d --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Expressions/CreateTableExpression.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using NPoco; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Create.Expressions +{ + public class CreateTableExpression : MigrationExpressionBase + { + public CreateTableExpression(IMigrationContext context) + : base(context) + { + Columns = new List(); + } + + public virtual string SchemaName { get; set; } + public virtual string TableName { get; set; } + public virtual IList Columns { get; set; } + + protected override string GetSql() + { + var table = new TableDefinition { Name = TableName, SchemaName = SchemaName, Columns = Columns }; + + return string.Format(SqlSyntax.Format(table)); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/CreateForeignKeyBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/CreateForeignKeyBuilder.cs new file mode 100644 index 0000000000..3529e12187 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/CreateForeignKeyBuilder.cs @@ -0,0 +1,87 @@ +using System.Data; +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Common.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Create.ForeignKey +{ + public class CreateForeignKeyBuilder : ExpressionBuilderBase, + ICreateForeignKeyFromTableBuilder, + ICreateForeignKeyForeignColumnBuilder, + ICreateForeignKeyToTableBuilder, + ICreateForeignKeyPrimaryColumnBuilder, + ICreateForeignKeyCascadeBuilder + { + public CreateForeignKeyBuilder(CreateForeignKeyExpression expression) + : base(expression) + { } + + /// + public void Do() => Expression.Execute(); + + /// + public ICreateForeignKeyForeignColumnBuilder FromTable(string table) + { + Expression.ForeignKey.ForeignTable = table; + return this; + } + + /// + public ICreateForeignKeyToTableBuilder ForeignColumn(string column) + { + Expression.ForeignKey.ForeignColumns.Add(column); + return this; + } + + /// + public ICreateForeignKeyToTableBuilder ForeignColumns(params string[] columns) + { + foreach (var column in columns) + Expression.ForeignKey.ForeignColumns.Add(column); + return this; + } + + /// + public ICreateForeignKeyPrimaryColumnBuilder ToTable(string table) + { + Expression.ForeignKey.PrimaryTable = table; + return this; + } + + /// + public ICreateForeignKeyCascadeBuilder PrimaryColumn(string column) + { + Expression.ForeignKey.PrimaryColumns.Add(column); + return this; + } + + /// + public ICreateForeignKeyCascadeBuilder PrimaryColumns(params string[] columns) + { + foreach (var column in columns) + Expression.ForeignKey.PrimaryColumns.Add(column); + return this; + } + + /// + public ICreateForeignKeyCascadeBuilder OnDelete(Rule rule) + { + Expression.ForeignKey.OnDelete = rule; + return this; + } + + /// + public ICreateForeignKeyCascadeBuilder OnUpdate(Rule rule) + { + Expression.ForeignKey.OnUpdate = rule; + return this; + } + + /// + public IExecutableBuilder OnDeleteOrUpdate(Rule rule) + { + Expression.ForeignKey.OnDelete = rule; + Expression.ForeignKey.OnUpdate = rule; + return new ExecutableBuilder(Expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyCascadeBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyCascadeBuilder.cs new file mode 100644 index 0000000000..bb4d8fa248 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyCascadeBuilder.cs @@ -0,0 +1,12 @@ +using System.Data; +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Create.ForeignKey +{ + public interface ICreateForeignKeyCascadeBuilder : IFluentBuilder, IExecutableBuilder + { + ICreateForeignKeyCascadeBuilder OnDelete(Rule rule); + ICreateForeignKeyCascadeBuilder OnUpdate(Rule rule); + IExecutableBuilder OnDeleteOrUpdate(Rule rule); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyForeignColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyForeignColumnBuilder.cs new file mode 100644 index 0000000000..2abebc513e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyForeignColumnBuilder.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Migrations.Expressions.Create.ForeignKey +{ + public interface ICreateForeignKeyForeignColumnBuilder : IFluentBuilder + { + ICreateForeignKeyToTableBuilder ForeignColumn(string column); + ICreateForeignKeyToTableBuilder ForeignColumns(params string[] columns); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyFromTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyFromTableBuilder.cs new file mode 100644 index 0000000000..bb6ea987b5 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyFromTableBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Migrations.Expressions.Create.ForeignKey +{ + public interface ICreateForeignKeyFromTableBuilder : IFluentBuilder + { + ICreateForeignKeyForeignColumnBuilder FromTable(string table); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyPrimaryColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyPrimaryColumnBuilder.cs new file mode 100644 index 0000000000..39ad38cd7d --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyPrimaryColumnBuilder.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Migrations.Expressions.Create.ForeignKey +{ + public interface ICreateForeignKeyPrimaryColumnBuilder : IFluentBuilder + { + ICreateForeignKeyCascadeBuilder PrimaryColumn(string column); + ICreateForeignKeyCascadeBuilder PrimaryColumns(params string[] columns); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyToTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyToTableBuilder.cs new file mode 100644 index 0000000000..0c415cd0ac --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/ForeignKey/ICreateForeignKeyToTableBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Migrations.Expressions.Create.ForeignKey +{ + public interface ICreateForeignKeyToTableBuilder : IFluentBuilder + { + ICreateForeignKeyPrimaryColumnBuilder ToTable(string table); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/ICreateBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/ICreateBuilder.cs new file mode 100644 index 0000000000..ee4361d745 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/ICreateBuilder.cs @@ -0,0 +1,86 @@ +using System; +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Create.Column; +using Umbraco.Core.Migrations.Expressions.Create.Constraint; +using Umbraco.Core.Migrations.Expressions.Create.ForeignKey; +using Umbraco.Core.Migrations.Expressions.Create.Index; +using Umbraco.Core.Migrations.Expressions.Create.Table; + +namespace Umbraco.Core.Migrations.Expressions.Create +{ + /// + /// Builds a Create expression. + /// + public interface ICreateBuilder : IFluentBuilder + { + /// + /// Builds a Create Table expresion, and executes. + /// + IExecutableBuilder Table(bool withoutKeysAndIndexes = false); + + /// + /// Builds a Create Keys and Indexes expresion, and executes. + /// + IExecutableBuilder KeysAndIndexes(); + + /// + /// Builds a Create Keys and Indexes expresion, and executes. + /// + IExecutableBuilder KeysAndIndexes(Type typeOfDto); + + /// + /// Builds a Create Table expression. + /// + ICreateTableWithColumnBuilder Table(string tableName); + + /// + /// Builds a Create Column expression. + /// + ICreateColumnOnTableBuilder Column(string columnName); + + /// + /// Builds a Create Foreign Key expression. + /// + ICreateForeignKeyFromTableBuilder ForeignKey(); + + /// + /// Builds a Create Foreign Key expression. + /// + ICreateForeignKeyFromTableBuilder ForeignKey(string foreignKeyName); + + /// + /// Builds a Create Index expression. + /// + ICreateIndexForTableBuilder Index(); + + /// + /// Builds a Create Index expression. + /// + ICreateIndexForTableBuilder Index(string indexName); + + /// + /// Builds a Create Primary Key expression. + /// + ICreateConstraintOnTableBuilder PrimaryKey(); + + /// + /// Builds a Create Primary Key expression. + /// + ICreateConstraintOnTableBuilder PrimaryKey(string primaryKeyName); + + /// + /// Builds a Create Unique Constraint expression. + /// + ICreateConstraintOnTableBuilder UniqueConstraint(); + + /// + /// Builds a Create Unique Constraint expression. + /// + ICreateConstraintOnTableBuilder UniqueConstraint(string constraintName); + + /// + /// Builds a Create Constraint expression. + /// + ICreateConstraintOnTableBuilder Constraint(string constraintName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Index/CreateIndexBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Index/CreateIndexBuilder.cs new file mode 100644 index 0000000000..ad8ac7f22d --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Index/CreateIndexBuilder.cs @@ -0,0 +1,97 @@ +using Umbraco.Core.Migrations.Expressions.Common.Expressions; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Create.Index +{ + public class CreateIndexBuilder : ExpressionBuilderBase, + ICreateIndexForTableBuilder, + ICreateIndexOnColumnBuilder, + ICreateIndexColumnOptionsBuilder, + ICreateIndexOptionsBuilder + { + public CreateIndexBuilder(CreateIndexExpression expression) + : base(expression) + { } + + /// + public void Do() => Expression.Execute(); + + public IndexColumnDefinition CurrentColumn { get; set; } + + /// + public ICreateIndexOnColumnBuilder OnTable(string tableName) + { + Expression.Index.TableName = tableName; + return this; + } + + /// + public ICreateIndexColumnOptionsBuilder OnColumn(string columnName) + { + CurrentColumn = new IndexColumnDefinition { Name = columnName }; + Expression.Index.Columns.Add(CurrentColumn); + return this; + } + + /// + public ICreateIndexOptionsBuilder WithOptions() + { + return this; + } + + /// + public ICreateIndexOnColumnBuilder Ascending() + { + CurrentColumn.Direction = Direction.Ascending; + return this; + } + + /// + public ICreateIndexOnColumnBuilder Descending() + { + CurrentColumn.Direction = Direction.Descending; + return this; + } + + /// + ICreateIndexOnColumnBuilder ICreateIndexColumnOptionsBuilder.Unique() + { + Expression.Index.IsUnique = true; + //if it is Unique then it must be unique nonclustered and set the other flags + Expression.Index.IndexType = IndexTypes.UniqueNonClustered; + Expression.Index.IsClustered = false; + return this; + } + + /// + public ICreateIndexOnColumnBuilder NonClustered() + { + Expression.Index.IndexType = IndexTypes.NonClustered; + Expression.Index.IsClustered = false; + Expression.Index.IndexType = IndexTypes.NonClustered; + Expression.Index.IsUnique = false; + return this; + } + + /// + public ICreateIndexOnColumnBuilder Clustered() + { + Expression.Index.IndexType = IndexTypes.Clustered; + Expression.Index.IsClustered = true; + //if it is clustered then we have to change the index type set the other flags + Expression.Index.IndexType = IndexTypes.Clustered; + Expression.Index.IsClustered = true; + Expression.Index.IsUnique = false; + return this; + } + + /// + ICreateIndexOnColumnBuilder ICreateIndexOptionsBuilder.Unique() + { + Expression.Index.IndexType = IndexTypes.UniqueNonClustered; + Expression.Index.IsUnique = true; + return this; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexColumnOptionsBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexColumnOptionsBuilder.cs new file mode 100644 index 0000000000..fbf873236d --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexColumnOptionsBuilder.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Migrations.Expressions.Create.Index +{ + public interface ICreateIndexColumnOptionsBuilder : IFluentBuilder + { + ICreateIndexOnColumnBuilder Ascending(); + ICreateIndexOnColumnBuilder Descending(); + ICreateIndexOnColumnBuilder Unique(); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexForTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexForTableBuilder.cs new file mode 100644 index 0000000000..f22cedab16 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexForTableBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Migrations.Expressions.Create.Index +{ + public interface ICreateIndexForTableBuilder : IFluentBuilder + { + ICreateIndexOnColumnBuilder OnTable(string tableName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexOnColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexOnColumnBuilder.cs new file mode 100644 index 0000000000..b904632cc5 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexOnColumnBuilder.cs @@ -0,0 +1,17 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Create.Index +{ + public interface ICreateIndexOnColumnBuilder : IFluentBuilder, IExecutableBuilder + { + /// + /// Specifies the index column. + /// + ICreateIndexColumnOptionsBuilder OnColumn(string columnName); + + /// + /// Specifies options. + /// + ICreateIndexOptionsBuilder WithOptions(); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexOptionsBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexOptionsBuilder.cs new file mode 100644 index 0000000000..d3b9c51cd7 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Index/ICreateIndexOptionsBuilder.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Migrations.Expressions.Create.Index +{ + public interface ICreateIndexOptionsBuilder : IFluentBuilder + { + ICreateIndexOnColumnBuilder Unique(); + ICreateIndexOnColumnBuilder NonClustered(); + ICreateIndexOnColumnBuilder Clustered(); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/KeysAndIndexes/CreateKeysAndIndexesBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/KeysAndIndexes/CreateKeysAndIndexesBuilder.cs new file mode 100644 index 0000000000..b11ef7e2c8 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/KeysAndIndexes/CreateKeysAndIndexesBuilder.cs @@ -0,0 +1,41 @@ +using System; +using NPoco; +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Execute.Expressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Create.KeysAndIndexes +{ + public class CreateKeysAndIndexesBuilder : IExecutableBuilder + { + private readonly IMigrationContext _context; + private readonly DatabaseType[] _supportedDatabaseTypes; + + public CreateKeysAndIndexesBuilder(IMigrationContext context, params DatabaseType[] supportedDatabaseTypes) + { + _context = context; + _supportedDatabaseTypes = supportedDatabaseTypes; + } + + public Type TypeOfDto { get; set; } + + /// + public void Do() + { + var syntax = _context.SqlContext.SqlSyntax; + var tableDefinition = DefinitionFactory.GetTableDefinition(TypeOfDto, syntax); + + ExecuteSql(syntax.FormatPrimaryKey(tableDefinition)); + foreach (var sql in syntax.Format(tableDefinition.Indexes)) + ExecuteSql(sql); + foreach (var sql in syntax.Format(tableDefinition.ForeignKeys)) + ExecuteSql(sql); + } + + private void ExecuteSql(string sql) + { + new ExecuteSqlStatementExpression(_context) { SqlStatement = sql } + .Execute(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Table/CreateTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Table/CreateTableBuilder.cs new file mode 100644 index 0000000000..10836fd228 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Table/CreateTableBuilder.cs @@ -0,0 +1,292 @@ +using System.Data; +using NPoco; +using Umbraco.Core.Migrations.Expressions.Common.Expressions; +using Umbraco.Core.Migrations.Expressions.Create.Expressions; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Create.Table +{ + public class CreateTableBuilder : ExpressionBuilderBase, + ICreateTableColumnAsTypeBuilder, + ICreateTableColumnOptionForeignKeyCascadeBuilder + { + private readonly IMigrationContext _context; + + public CreateTableBuilder(IMigrationContext context, CreateTableExpression expression) + : base(expression) + { + _context = context; + } + + /// + public void Do() => Expression.Execute(); + + public ColumnDefinition CurrentColumn { get; set; } + + public ForeignKeyDefinition CurrentForeignKey { get; set; } + + public override ColumnDefinition GetColumnForType() + { + return CurrentColumn; + } + + /// + public ICreateTableColumnAsTypeBuilder WithColumn(string name) + { + var column = new ColumnDefinition { Name = name, TableName = Expression.TableName, ModificationType = ModificationType.Create }; + Expression.Columns.Add(column); + CurrentColumn = column; + return this; + } + + /// + public ICreateTableColumnOptionBuilder WithDefault(SystemMethods method) + { + CurrentColumn.DefaultValue = method; + return this; + } + + public ICreateTableColumnOptionBuilder WithDefaultValue(object value) + { + CurrentColumn.DefaultValue = value; + return this; + } + + /// + public ICreateTableColumnOptionBuilder Identity() + { + CurrentColumn.IsIdentity = true; + return this; + } + + /// + public ICreateTableColumnOptionBuilder Indexed() + { + return Indexed(null); + } + + /// + public ICreateTableColumnOptionBuilder Indexed(string indexName) + { + CurrentColumn.IsIndexed = true; + + var index = new CreateIndexExpression(_context, new IndexDefinition + { + Name = indexName, + SchemaName = Expression.SchemaName, + TableName = Expression.TableName + }); + + index.Index.Columns.Add(new IndexColumnDefinition + { + Name = CurrentColumn.Name + }); + + Expression.Expressions.Add(index); + + return this; + } + + /// + public ICreateTableColumnOptionBuilder PrimaryKey() + { + CurrentColumn.IsPrimaryKey = true; + + //For MySQL, the PK will be created WITH the create table expression, however for + // SQL Server, the PK get's created in a different Alter table expression afterwords. + // MySQL will choke if the same constraint is again added afterword + // TODO: This is a super hack, I'd rather not add another property like 'CreatesPkInCreateTableDefinition' to check + // for this, but I don't see another way around. MySQL doesn't support checking for a constraint before creating + // it... except in a very strange way but it doesn't actually provider error feedback if it doesn't work so we cannot use + // it. For now, this is what I'm doing + if (Expression.DatabaseType.IsMySql() == false) + { + var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey) + { + Constraint = + { + TableName = CurrentColumn.TableName, + Columns = new[] { CurrentColumn.Name } + } + }; + Expression.Expressions.Add(expression); + } + + return this; + } + + /// + public ICreateTableColumnOptionBuilder PrimaryKey(string primaryKeyName) + { + CurrentColumn.IsPrimaryKey = true; + CurrentColumn.PrimaryKeyName = primaryKeyName; + + //For MySQL, the PK will be created WITH the create table expression, however for + // SQL Server, the PK get's created in a different Alter table expression afterwords. + // MySQL will choke if the same constraint is again added afterword + // TODO: This is a super hack, I'd rather not add another property like 'CreatesPkInCreateTableDefinition' to check + // for this, but I don't see another way around. MySQL doesn't support checking for a constraint before creating + // it... except in a very strange way but it doesn't actually provider error feedback if it doesn't work so we cannot use + // it. For now, this is what I'm doing + + if (Expression.DatabaseType.IsMySql() == false) + { + var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey) + { + Constraint = + { + ConstraintName = primaryKeyName, + TableName = CurrentColumn.TableName, + Columns = new[] { CurrentColumn.Name } + } + }; + Expression.Expressions.Add(expression); + } + + return this; + } + + /// + public ICreateTableColumnOptionBuilder Nullable() + { + CurrentColumn.IsNullable = true; + return this; + } + + /// + public ICreateTableColumnOptionBuilder NotNullable() + { + CurrentColumn.IsNullable = false; + return this; + } + + /// + public ICreateTableColumnOptionBuilder Unique() + { + return Unique(null); + } + + /// + public ICreateTableColumnOptionBuilder Unique(string indexName) + { + CurrentColumn.IsUnique = true; + + var index = new CreateIndexExpression(_context, new IndexDefinition + { + Name = indexName, + SchemaName = Expression.SchemaName, + TableName = Expression.TableName, + IsUnique = true + }); + + index.Index.Columns.Add(new IndexColumnDefinition + { + Name = CurrentColumn.Name + }); + + Expression.Expressions.Add(index); + + return this; + } + + /// + public ICreateTableColumnOptionForeignKeyCascadeBuilder ForeignKey(string primaryTableName, string primaryColumnName) + { + return ForeignKey(null, null, primaryTableName, primaryColumnName); + } + + /// + public ICreateTableColumnOptionForeignKeyCascadeBuilder ForeignKey(string foreignKeyName, string primaryTableName, + string primaryColumnName) + { + return ForeignKey(foreignKeyName, null, primaryTableName, primaryColumnName); + } + + /// + public ICreateTableColumnOptionForeignKeyCascadeBuilder ForeignKey(string foreignKeyName, string primaryTableSchema, + string primaryTableName, string primaryColumnName) + { + CurrentColumn.IsForeignKey = true; + + var fk = new CreateForeignKeyExpression(_context, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = primaryTableName, + PrimaryTableSchema = primaryTableSchema, + ForeignTable = Expression.TableName, + ForeignTableSchema = Expression.SchemaName + }); + + fk.ForeignKey.PrimaryColumns.Add(primaryColumnName); + fk.ForeignKey.ForeignColumns.Add(CurrentColumn.Name); + + Expression.Expressions.Add(fk); + CurrentForeignKey = fk.ForeignKey; + return this; + } + + /// + public ICreateTableColumnOptionForeignKeyCascadeBuilder ForeignKey() + { + CurrentColumn.IsForeignKey = true; + return this; + } + + /// + public ICreateTableColumnOptionForeignKeyCascadeBuilder ReferencedBy(string foreignTableName, string foreignColumnName) + { + return ReferencedBy(null, null, foreignTableName, foreignColumnName); + } + + /// + public ICreateTableColumnOptionForeignKeyCascadeBuilder ReferencedBy(string foreignKeyName, string foreignTableName, + string foreignColumnName) + { + return ReferencedBy(foreignKeyName, null, foreignTableName, foreignColumnName); + } + + /// + public ICreateTableColumnOptionForeignKeyCascadeBuilder ReferencedBy(string foreignKeyName, string foreignTableSchema, + string foreignTableName, string foreignColumnName) + { + var fk = new CreateForeignKeyExpression(_context, new ForeignKeyDefinition + { + Name = foreignKeyName, + PrimaryTable = Expression.TableName, + PrimaryTableSchema = Expression.SchemaName, + ForeignTable = foreignTableName, + ForeignTableSchema = foreignTableSchema + }); + + fk.ForeignKey.PrimaryColumns.Add(CurrentColumn.Name); + fk.ForeignKey.ForeignColumns.Add(foreignColumnName); + + Expression.Expressions.Add(fk); + CurrentForeignKey = fk.ForeignKey; + return this; + } + + /// + public ICreateTableColumnOptionForeignKeyCascadeBuilder OnDelete(Rule rule) + { + CurrentForeignKey.OnDelete = rule; + return this; + } + + /// + public ICreateTableColumnOptionForeignKeyCascadeBuilder OnUpdate(Rule rule) + { + CurrentForeignKey.OnUpdate = rule; + return this; + } + + /// + public ICreateTableColumnOptionBuilder OnDeleteOrUpdate(Rule rule) + { + OnDelete(rule); + OnUpdate(rule); + return this; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Table/CreateTableOfDtoBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Table/CreateTableOfDtoBuilder.cs new file mode 100644 index 0000000000..4b73e9435d --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Table/CreateTableOfDtoBuilder.cs @@ -0,0 +1,47 @@ +using System; +using NPoco; +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Execute.Expressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Create.Table +{ + public class CreateTableOfDtoBuilder : IExecutableBuilder + { + private readonly IMigrationContext _context; + private readonly DatabaseType[] _supportedDatabaseTypes; + + public CreateTableOfDtoBuilder(IMigrationContext context, params DatabaseType[] supportedDatabaseTypes) + { + _context = context; + _supportedDatabaseTypes = supportedDatabaseTypes; + } + + public Type TypeOfDto { get; set; } + + public bool WithoutKeysAndIndexes { get; set; } + + /// + public void Do() + { + var syntax = _context.SqlContext.SqlSyntax; + var tableDefinition = DefinitionFactory.GetTableDefinition(TypeOfDto, syntax); + + ExecuteSql(syntax.Format(tableDefinition)); + if (WithoutKeysAndIndexes) + return; + + ExecuteSql(syntax.FormatPrimaryKey(tableDefinition)); + foreach (var sql in syntax.Format(tableDefinition.ForeignKeys)) + ExecuteSql(sql); + foreach (var sql in syntax.Format(tableDefinition.Indexes)) + ExecuteSql(sql); + } + + private void ExecuteSql(string sql) + { + new ExecuteSqlStatementExpression(_context) { SqlStatement = sql } + .Execute(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableColumnAsTypeBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableColumnAsTypeBuilder.cs new file mode 100644 index 0000000000..31511ddbb9 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableColumnAsTypeBuilder.cs @@ -0,0 +1,7 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Create.Table +{ + public interface ICreateTableColumnAsTypeBuilder : IColumnTypeBuilder + { } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableColumnOptionBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableColumnOptionBuilder.cs new file mode 100644 index 0000000000..542c08c978 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableColumnOptionBuilder.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Create.Table +{ + public interface ICreateTableColumnOptionBuilder : + IColumnOptionBuilder, + ICreateTableWithColumnBuilder + { } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableColumnOptionForeignKeyCascadeBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableColumnOptionForeignKeyCascadeBuilder.cs new file mode 100644 index 0000000000..e2b7a18b68 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableColumnOptionForeignKeyCascadeBuilder.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Create.Table +{ + public interface ICreateTableColumnOptionForeignKeyCascadeBuilder : + ICreateTableColumnOptionBuilder, + IForeignKeyCascadeBuilder + { } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableWithColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableWithColumnBuilder.cs new file mode 100644 index 0000000000..0292028f2a --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Create/Table/ICreateTableWithColumnBuilder.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Create.Table +{ + public interface ICreateTableWithColumnBuilder : IFluentBuilder, IExecutableBuilder + { + ICreateTableColumnAsTypeBuilder WithColumn(string name); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Column/DeleteColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Column/DeleteColumnBuilder.cs new file mode 100644 index 0000000000..696cda1506 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Column/DeleteColumnBuilder.cs @@ -0,0 +1,27 @@ +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Delete.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Column +{ + public class DeleteColumnBuilder : ExpressionBuilderBase, + IDeleteColumnBuilder + { + public DeleteColumnBuilder(DeleteColumnExpression expression) + : base(expression) + { } + + /// + public IExecutableBuilder FromTable(string tableName) + { + Expression.TableName = tableName; + return new ExecutableBuilder(Expression); + } + + /// + public IDeleteColumnBuilder Column(string columnName) + { + Expression.ColumnNames.Add(columnName); + return this; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Column/IDeleteColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Column/IDeleteColumnBuilder.cs new file mode 100644 index 0000000000..76da05f524 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Column/IDeleteColumnBuilder.cs @@ -0,0 +1,20 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Column +{ + /// + /// Builds a Delete Column expression. + /// + public interface IDeleteColumnBuilder : IFluentBuilder + { + /// + /// Specifies the table of the column to delete. + /// + IExecutableBuilder FromTable(string tableName); + + /// + /// Specifies the column to delete. + /// + IDeleteColumnBuilder Column(string columnName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Constraint/DeleteConstraintBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Constraint/DeleteConstraintBuilder.cs new file mode 100644 index 0000000000..af0b0e5498 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Constraint/DeleteConstraintBuilder.cs @@ -0,0 +1,20 @@ +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Delete.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Constraint +{ + public class DeleteConstraintBuilder : ExpressionBuilderBase, + IDeleteConstraintBuilder + { + public DeleteConstraintBuilder(DeleteConstraintExpression expression) + : base(expression) + { } + + /// + public IExecutableBuilder FromTable(string tableName) + { + Expression.Constraint.TableName = tableName; + return new ExecutableBuilder(Expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Constraint/IDeleteConstraintBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Constraint/IDeleteConstraintBuilder.cs new file mode 100644 index 0000000000..cdb107e1ab --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Constraint/IDeleteConstraintBuilder.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Constraint +{ + /// + /// Builds a Delete Constraint expression. + /// + public interface IDeleteConstraintBuilder : IFluentBuilder + { + /// + /// Specifies the table of the constraint to delete. + /// + IExecutableBuilder FromTable(string tableName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Data/DeleteDataBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Data/DeleteDataBuilder.cs new file mode 100644 index 0000000000..635456b7a6 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Data/DeleteDataBuilder.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.ComponentModel; +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Delete.Expressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Data +{ + public class DeleteDataBuilder : ExpressionBuilderBase, + IDeleteDataBuilder + { + public DeleteDataBuilder(DeleteDataExpression expression) + : base(expression) + { } + + /// + public IExecutableBuilder IsNull(string columnName) + { + Expression.Rows.Add(new DeletionDataDefinition { new KeyValuePair(columnName, null) }); + return this; + } + + /// + public IDeleteDataBuilder Row(object dataAsAnonymousType) + { + Expression.Rows.Add(GetData(dataAsAnonymousType)); + return this; + } + + /// + public IExecutableBuilder AllRows() + { + Expression.IsAllRows = true; + return this; + } + + /// + public void Do() + { + Expression.Execute(); + } + + private static DeletionDataDefinition GetData(object dataAsAnonymousType) + { + var properties = TypeDescriptor.GetProperties(dataAsAnonymousType); + + var data = new DeletionDataDefinition(); + foreach (PropertyDescriptor property in properties) + data.Add(new KeyValuePair(property.Name, property.GetValue(dataAsAnonymousType))); + return data; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Data/IDeleteDataBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Data/IDeleteDataBuilder.cs new file mode 100644 index 0000000000..043b900e75 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Data/IDeleteDataBuilder.cs @@ -0,0 +1,25 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Data +{ + /// + /// Builds a Delete expression. + /// + public interface IDeleteDataBuilder : IFluentBuilder, IExecutableBuilder + { + /// + /// Specifies a row to be deleted. + /// + IDeleteDataBuilder Row(object dataAsAnonymousType); + + /// + /// Specifies that all rows must be deleted. + /// + IExecutableBuilder AllRows(); + + /// + /// Specifies that rows with a specified column being null must be deleted. + /// + IExecutableBuilder IsNull(string columnName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs new file mode 100644 index 0000000000..373b375fa8 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs @@ -0,0 +1,31 @@ +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Delete.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Delete.DefaultConstraint +{ + /// + /// Implements , . + /// + public class DeleteDefaultConstraintBuilder : ExpressionBuilderBase, + IDeleteDefaultConstraintOnTableBuilder, + IDeleteDefaultConstraintOnColumnBuilder + { + public DeleteDefaultConstraintBuilder(DeleteDefaultConstraintExpression expression) + : base(expression) + { } + + /// + public IDeleteDefaultConstraintOnColumnBuilder OnTable(string tableName) + { + Expression.TableName = tableName; + return this; + } + + /// + public IExecutableBuilder OnColumn(string columnName) + { + Expression.ColumnName = columnName; + return new ExecutableBuilder(Expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/IDeleteDefaultConstraintOnColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/IDeleteDefaultConstraintOnColumnBuilder.cs new file mode 100644 index 0000000000..2ab4a32185 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/IDeleteDefaultConstraintOnColumnBuilder.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Delete.DefaultConstraint +{ + /// + /// Builds a Delete expression. + /// + public interface IDeleteDefaultConstraintOnColumnBuilder : IFluentBuilder + { + /// + /// Specifies the column of the constraint to delete. + /// + IExecutableBuilder OnColumn(string columnName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/IDeleteDefaultConstraintOnTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/IDeleteDefaultConstraintOnTableBuilder.cs new file mode 100644 index 0000000000..ff288cd6c8 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/IDeleteDefaultConstraintOnTableBuilder.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Migrations.Expressions.Delete.DefaultConstraint +{ + /// + /// Builds a Delete expression. + /// + public interface IDeleteDefaultConstraintOnTableBuilder : IFluentBuilder + { + /// + /// Specifies the table of the constraint to delete. + /// + IDeleteDefaultConstraintOnColumnBuilder OnTable(string tableName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs new file mode 100644 index 0000000000..d081305cde --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs @@ -0,0 +1,106 @@ +using NPoco; +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Delete.Column; +using Umbraco.Core.Migrations.Expressions.Delete.Constraint; +using Umbraco.Core.Migrations.Expressions.Delete.Data; +using Umbraco.Core.Migrations.Expressions.Delete.DefaultConstraint; +using Umbraco.Core.Migrations.Expressions.Delete.Expressions; +using Umbraco.Core.Migrations.Expressions.Delete.ForeignKey; +using Umbraco.Core.Migrations.Expressions.Delete.Index; +using Umbraco.Core.Migrations.Expressions.Delete.KeysAndIndexes; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Delete +{ + public class DeleteBuilder : IDeleteBuilder + { + private readonly IMigrationContext _context; + + public DeleteBuilder(IMigrationContext context) + { + _context = context; + } + + /// + public IExecutableBuilder Table(string tableName) + { + var expression = new DeleteTableExpression(_context) { TableName = tableName }; + return new ExecutableBuilder(expression); + } + + /// + public IExecutableBuilder KeysAndIndexes(string tableName = null) + { + return new DeleteKeysAndIndexesBuilder(_context) { TableName = tableName }; + } + + /// + public IDeleteColumnBuilder Column(string columnName) + { + var expression = new DeleteColumnExpression(_context) {ColumnNames = {columnName}}; + return new DeleteColumnBuilder(expression); + } + + /// + public IDeleteForeignKeyFromTableBuilder ForeignKey() + { + var expression = new DeleteForeignKeyExpression(_context); + return new DeleteForeignKeyBuilder(expression); + } + + /// + public IDeleteForeignKeyOnTableBuilder ForeignKey(string foreignKeyName) + { + var expression = new DeleteForeignKeyExpression(_context) {ForeignKey = {Name = foreignKeyName}}; + return new DeleteForeignKeyBuilder(expression); + } + + /// + public IDeleteDataBuilder FromTable(string tableName) + { + var expression = new DeleteDataExpression(_context) { TableName = tableName }; + return new DeleteDataBuilder(expression); + } + + /// + public IDeleteIndexForTableBuilder Index() + { + var expression = new DeleteIndexExpression(_context); + return new DeleteIndexBuilder(expression); + } + + /// + public IDeleteIndexForTableBuilder Index(string indexName) + { + var expression = new DeleteIndexExpression(_context) { Index = { Name = indexName } }; + return new DeleteIndexBuilder(expression); + } + + /// + public IDeleteConstraintBuilder PrimaryKey(string primaryKeyName) + { + var expression = new DeleteConstraintExpression(_context, ConstraintType.PrimaryKey) + { + Constraint = { ConstraintName = primaryKeyName } + }; + return new DeleteConstraintBuilder(expression); + } + + /// + public IDeleteConstraintBuilder UniqueConstraint(string constraintName) + { + var expression = new DeleteConstraintExpression(_context, ConstraintType.Unique) + { + Constraint = { ConstraintName = constraintName } + }; + return new DeleteConstraintBuilder(expression); + } + + /// + public IDeleteDefaultConstraintOnTableBuilder DefaultConstraint() + { + var expression = new DeleteDefaultConstraintExpression(_context); + return new DeleteDefaultConstraintBuilder(expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteColumnExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteColumnExpression.cs new file mode 100644 index 0000000000..9df810bd12 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteColumnExpression.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Text; +using NPoco; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Expressions +{ + public class DeleteColumnExpression : MigrationExpressionBase + { + public DeleteColumnExpression(IMigrationContext context) + : base(context) + { + ColumnNames = new List(); + } + + public virtual string TableName { get; set; } + public ICollection ColumnNames { get; set; } + + protected override string GetSql() + { + var stmts = new StringBuilder(); + foreach (var columnName in ColumnNames) + { + stmts.AppendFormat(SqlSyntax.DropColumn, SqlSyntax.GetQuotedTableName(TableName), SqlSyntax.GetQuotedColumnName(columnName)); + AppendStatementSeparator(stmts); + } + return stmts.ToString(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteConstraintExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteConstraintExpression.cs new file mode 100644 index 0000000000..25777c5541 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteConstraintExpression.cs @@ -0,0 +1,34 @@ +using NPoco; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Expressions +{ + public class DeleteConstraintExpression : MigrationExpressionBase + { + public DeleteConstraintExpression(IMigrationContext context, ConstraintType type) + : base(context) + { + Constraint = new ConstraintDefinition(type); + } + + public ConstraintDefinition Constraint { get; } + + protected override string GetSql() + { + return DatabaseType.IsMySql() + ? GetMySql() + : string.Format(SqlSyntax.DeleteConstraint, + SqlSyntax.GetQuotedTableName(Constraint.TableName), + SqlSyntax.GetQuotedName(Constraint.ConstraintName)); + } + + private string GetMySql() + { + return string.Format(SqlSyntax.DeleteConstraint, + SqlSyntax.GetQuotedTableName(Constraint.TableName), + Constraint.IsPrimaryKeyConstraint ? "PRIMARY KEY" : "FOREIGN KEY", + ""); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDataExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDataExpression.cs new file mode 100644 index 0000000000..1775f0b53e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDataExpression.cs @@ -0,0 +1,39 @@ +using System.Linq; +using System.Collections.Generic; +using System.Text; +using NPoco; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Expressions +{ + public class DeleteDataExpression : MigrationExpressionBase + { + public DeleteDataExpression(IMigrationContext context) + : base(context) + { } + + public string TableName { get; set; } + public virtual bool IsAllRows { get; set; } + + public List Rows { get; } = new List(); + + protected override string GetSql() + { + if (IsAllRows) + return string.Format(SqlSyntax.DeleteData, SqlSyntax.GetQuotedTableName(TableName), "(1=1)"); + + var stmts = new StringBuilder(); + foreach (var row in Rows) + { + var whereClauses = row.Select(kvp => $"{SqlSyntax.GetQuotedColumnName(kvp.Key)} {(kvp.Value == null ? "IS" : "=")} {GetQuotedValue(kvp.Value)}"); + + stmts.Append(string.Format(SqlSyntax.DeleteData, + SqlSyntax.GetQuotedTableName(TableName), + string.Join(" AND ", whereClauses))); + + AppendStatementSeparator(stmts); + } + return stmts.ToString(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDefaultConstraintExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDefaultConstraintExpression.cs new file mode 100644 index 0000000000..8b0b20c0e2 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDefaultConstraintExpression.cs @@ -0,0 +1,21 @@ +using NPoco; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Expressions +{ + public class DeleteDefaultConstraintExpression : MigrationExpressionBase + { + public DeleteDefaultConstraintExpression(IMigrationContext context) + : base(context) + { } + + public virtual string TableName { get; set; } + public virtual string ColumnName { get; set; } + + protected override string GetSql() + { + return string.Format(SqlSyntax.DeleteDefaultConstraint, + SqlSyntax.GetQuotedTableName(TableName), + SqlSyntax.GetQuotedColumnName(ColumnName)); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteForeignKeyExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteForeignKeyExpression.cs new file mode 100644 index 0000000000..037b5eac89 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteForeignKeyExpression.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using NPoco; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Expressions +{ + public class DeleteForeignKeyExpression : MigrationExpressionBase + { + public DeleteForeignKeyExpression(IMigrationContext context) + : base(context) + { + ForeignKey = new ForeignKeyDefinition(); + } + + public ForeignKeyDefinition ForeignKey { get; set; } + + protected override string GetSql() + { + if (ForeignKey.ForeignTable == null) + throw new ArgumentNullException("Table name not specified, ensure you have appended the OnTable extension. Format should be Delete.ForeignKey(KeyName).OnTable(TableName)"); + + if (DatabaseType.IsMySql()) + return GetMySql(); + + if (string.IsNullOrEmpty(ForeignKey.Name)) + { + ForeignKey.Name = $"FK_{ForeignKey.ForeignTable}_{ForeignKey.PrimaryTable}_{ForeignKey.PrimaryColumns.First()}"; + } + + return string.Format(SqlSyntax.DeleteConstraint, + SqlSyntax.GetQuotedTableName(ForeignKey.ForeignTable), + SqlSyntax.GetQuotedName(ForeignKey.Name)); + } + + private string GetMySql() + { + // MySql naming "convention" for foreignkeys, which aren't explicitly named + if (string.IsNullOrEmpty(ForeignKey.Name)) + ForeignKey.Name = $"{ForeignKey.ForeignTable.ToLower()}_ibfk_1"; + + return string.Format(SqlSyntax.DeleteConstraint, + SqlSyntax.GetQuotedTableName(ForeignKey.ForeignTable), + "FOREIGN KEY", + SqlSyntax.GetQuotedName(ForeignKey.Name)); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteIndexExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteIndexExpression.cs new file mode 100644 index 0000000000..f5c21085b8 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteIndexExpression.cs @@ -0,0 +1,29 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Expressions +{ + public class DeleteIndexExpression : MigrationExpressionBase + { + public DeleteIndexExpression(IMigrationContext context) + : base(context) + { + Index = new IndexDefinition(); + } + + public DeleteIndexExpression(IMigrationContext context, IndexDefinition index) + : base(context) + { + Index = index; + } + + public IndexDefinition Index { get; } + + protected override string GetSql() + { + return string.Format(SqlSyntax.DropIndex, + SqlSyntax.GetQuotedName(Index.Name), + SqlSyntax.GetQuotedTableName(Index.TableName)); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteTableExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteTableExpression.cs new file mode 100644 index 0000000000..1843648c45 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteTableExpression.cs @@ -0,0 +1,19 @@ +using NPoco; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Expressions +{ + public class DeleteTableExpression : MigrationExpressionBase + { + public DeleteTableExpression(IMigrationContext context) + : base(context) + { } + + public virtual string TableName { get; set; } + + protected override string GetSql() + { + return string.Format(SqlSyntax.DropTable, + SqlSyntax.GetQuotedTableName(TableName)); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/DeleteForeignKeyBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/DeleteForeignKeyBuilder.cs new file mode 100644 index 0000000000..9850e914ea --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/DeleteForeignKeyBuilder.cs @@ -0,0 +1,72 @@ +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Delete.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Delete.ForeignKey +{ + /// + /// Implements IDeleteForeignKey... + /// + public class DeleteForeignKeyBuilder : ExpressionBuilderBase, + IDeleteForeignKeyFromTableBuilder, + IDeleteForeignKeyForeignColumnBuilder, + IDeleteForeignKeyToTableBuilder, + IDeleteForeignKeyPrimaryColumnBuilder, + IDeleteForeignKeyOnTableBuilder + { + public DeleteForeignKeyBuilder(DeleteForeignKeyExpression expression) + : base(expression) + { } + + /// + public IDeleteForeignKeyForeignColumnBuilder FromTable(string foreignTableName) + { + Expression.ForeignKey.ForeignTable = foreignTableName; + return this; + } + + /// + public IDeleteForeignKeyToTableBuilder ForeignColumn(string column) + { + Expression.ForeignKey.ForeignColumns.Add(column); + return this; + } + + /// + public IDeleteForeignKeyToTableBuilder ForeignColumns(params string[] columns) + { + foreach (var column in columns) + Expression.ForeignKey.ForeignColumns.Add(column); + + return this; + } + + /// + public IDeleteForeignKeyPrimaryColumnBuilder ToTable(string table) + { + Expression.ForeignKey.PrimaryTable = table; + return this; + } + + /// + public IExecutableBuilder PrimaryColumn(string column) + { + Expression.ForeignKey.PrimaryColumns.Add(column); + return new ExecutableBuilder(Expression); + } + + /// + public IExecutableBuilder PrimaryColumns(params string[] columns) + { + foreach (var column in columns) + Expression.ForeignKey.PrimaryColumns.Add(column); + return new ExecutableBuilder(Expression); + } + + /// + public IExecutableBuilder OnTable(string foreignTableName) + { + Expression.ForeignKey.ForeignTable = foreignTableName; + return new ExecutableBuilder(Expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyForeignColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyForeignColumnBuilder.cs new file mode 100644 index 0000000000..b682e68a8f --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyForeignColumnBuilder.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Migrations.Expressions.Delete.ForeignKey +{ + /// + /// Builds a Delete expression. + /// + public interface IDeleteForeignKeyForeignColumnBuilder : IFluentBuilder + { + /// + /// Specifies the foreign column. + /// + IDeleteForeignKeyToTableBuilder ForeignColumn(string column); + + /// + /// Specifies the foreign columns. + /// + IDeleteForeignKeyToTableBuilder ForeignColumns(params string[] columns); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyFromTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyFromTableBuilder.cs new file mode 100644 index 0000000000..581254a250 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyFromTableBuilder.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Migrations.Expressions.Delete.ForeignKey +{ + /// + /// Builds a Delete expression. + /// + public interface IDeleteForeignKeyFromTableBuilder : IFluentBuilder + { + /// + /// Specifies the source table of the foreign key. + /// + IDeleteForeignKeyForeignColumnBuilder FromTable(string foreignTableName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyOnTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyOnTableBuilder.cs new file mode 100644 index 0000000000..6a16dd9687 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyOnTableBuilder.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Delete.ForeignKey +{ + /// + /// Builds a Delete expression. + /// + public interface IDeleteForeignKeyOnTableBuilder : IFluentBuilder + { + /// + /// Specifies the table of the foreign key. + /// + IExecutableBuilder OnTable(string foreignTableName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyPrimaryColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyPrimaryColumnBuilder.cs new file mode 100644 index 0000000000..c579759871 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyPrimaryColumnBuilder.cs @@ -0,0 +1,20 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Delete.ForeignKey +{ + /// + /// Builds a Delete expression. + /// + public interface IDeleteForeignKeyPrimaryColumnBuilder : IFluentBuilder + { + /// + /// Specifies the target primary column. + /// + IExecutableBuilder PrimaryColumn(string column); + + /// + /// Specifies the target primary columns. + /// + IExecutableBuilder PrimaryColumns(params string[] columns); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyToTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyToTableBuilder.cs new file mode 100644 index 0000000000..8ae7a65f9a --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/ForeignKey/IDeleteForeignKeyToTableBuilder.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Migrations.Expressions.Delete.ForeignKey +{ + /// + /// Builds a Delete expression. + /// + public interface IDeleteForeignKeyToTableBuilder : IFluentBuilder + { + /// + /// Specifies the target table of the foreign key. + /// + IDeleteForeignKeyPrimaryColumnBuilder ToTable(string table); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/IDeleteBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/IDeleteBuilder.cs new file mode 100644 index 0000000000..07faf5028e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/IDeleteBuilder.cs @@ -0,0 +1,73 @@ +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Delete.Column; +using Umbraco.Core.Migrations.Expressions.Delete.Constraint; +using Umbraco.Core.Migrations.Expressions.Delete.Data; +using Umbraco.Core.Migrations.Expressions.Delete.DefaultConstraint; +using Umbraco.Core.Migrations.Expressions.Delete.ForeignKey; +using Umbraco.Core.Migrations.Expressions.Delete.Index; + +namespace Umbraco.Core.Migrations.Expressions.Delete +{ + /// + /// Builds a Delete expression. + /// + public interface IDeleteBuilder : IFluentBuilder + { + /// + /// Specifies the table to delete. + /// + IExecutableBuilder Table(string tableName); + + /// + /// Specifies the table to delete keys and indexes for. + /// + IExecutableBuilder KeysAndIndexes(string tableName = null); + + /// + /// Specifies the column to delete. + /// + IDeleteColumnBuilder Column(string columnName); + + /// + /// Specifies the foreign key to delete. + /// + IDeleteForeignKeyFromTableBuilder ForeignKey(); + + /// + /// Specifies the foreign key to delete. + /// + IDeleteForeignKeyOnTableBuilder ForeignKey(string foreignKeyName); + + /// + /// Specifies the table to delete data from. + /// + /// + /// + IDeleteDataBuilder FromTable(string tableName); + + /// + /// Specifies the index to delete. + /// + IDeleteIndexForTableBuilder Index(); + + /// + /// Specifies the index to delete. + /// + IDeleteIndexForTableBuilder Index(string indexName); + + /// + /// Specifies the primary key to delete. + /// + IDeleteConstraintBuilder PrimaryKey(string primaryKeyName); + + /// + /// Specifies the unique constraint to delete. + /// + IDeleteConstraintBuilder UniqueConstraint(string constraintName); + + /// + /// Specifies the default constraint to delete. + /// + IDeleteDefaultConstraintOnTableBuilder DefaultConstraint(); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Index/DeleteIndexBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Index/DeleteIndexBuilder.cs new file mode 100644 index 0000000000..b3cacb2f95 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Index/DeleteIndexBuilder.cs @@ -0,0 +1,41 @@ +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Delete.Expressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Index +{ + public class DeleteIndexBuilder : ExpressionBuilderBase, + IDeleteIndexForTableBuilder, IDeleteIndexOnColumnBuilder + { + public DeleteIndexBuilder(DeleteIndexExpression expression) + : base(expression) + { } + + /// + public void Do() => Expression.Execute(); + + public IndexColumnDefinition CurrentColumn { get; set; } + + public IDeleteIndexOnColumnBuilder OnTable(string tableName) + { + Expression.Index.TableName = tableName; + return this; + } + + /// + public IExecutableBuilder OnColumn(string columnName) + { + var column = new IndexColumnDefinition { Name = columnName }; + Expression.Index.Columns.Add(column); + return new ExecutableBuilder(Expression); + } + + /// + public IExecutableBuilder OnColumns(params string[] columnNames) + { + foreach (string columnName in columnNames) + Expression.Index.Columns.Add(new IndexColumnDefinition { Name = columnName }); + return new ExecutableBuilder(Expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Index/IDeleteIndexForTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Index/IDeleteIndexForTableBuilder.cs new file mode 100644 index 0000000000..8501c6b02f --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Index/IDeleteIndexForTableBuilder.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Migrations.Expressions.Delete.Index +{ + /// + /// Builds a Delete expression. + /// + public interface IDeleteIndexForTableBuilder : IFluentBuilder + { + /// + /// Specifies the table of the index to delete. + /// + IDeleteIndexOnColumnBuilder OnTable(string tableName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Index/IDeleteIndexOnColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Index/IDeleteIndexOnColumnBuilder.cs new file mode 100644 index 0000000000..3aa877bf8e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Index/IDeleteIndexOnColumnBuilder.cs @@ -0,0 +1,23 @@ +using System; +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Index +{ + /// + /// Builds a Delete expression. + /// + public interface IDeleteIndexOnColumnBuilder : IFluentBuilder, IExecutableBuilder + { + /// + /// Specifies the column of the index. + /// + [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] + IExecutableBuilder OnColumn(string columnName); + + /// + /// Specifies the column of the index. + /// + [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] + IExecutableBuilder OnColumns(params string[] columnNames); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs new file mode 100644 index 0000000000..1c595d9103 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs @@ -0,0 +1,56 @@ +using System.Linq; +using NPoco; +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Expressions.Delete.KeysAndIndexes +{ + public class DeleteKeysAndIndexesBuilder : IExecutableBuilder + { + private readonly IMigrationContext _context; + private readonly DatabaseType[] _supportedDatabaseTypes; + + public DeleteKeysAndIndexesBuilder(IMigrationContext context, params DatabaseType[] supportedDatabaseTypes) + { + _context = context; + _supportedDatabaseTypes = supportedDatabaseTypes; + } + + public string TableName { get; set; } + + /// + public void Do() + { + if (TableName == null) + { + // drop keys + var keys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToArray(); + foreach (var key in keys.Where(x => x.Item2.StartsWith("FK_"))) + Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do(); + foreach (var key in keys.Where(x => x.Item2.StartsWith("PK_"))) + Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do(); + + // drop indexes + var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToArray(); + foreach (var index in indexes) + Delete.Index(index.IndexName).OnTable(index.TableName).Do(); + } + else + { + // drop keys + var keys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToArray(); + foreach (var key in keys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("FK_"))) + Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do(); + foreach (var key in keys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("PK_"))) + Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do(); + + // drop indexes + var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToArray(); + foreach (var index in indexes.Where(x => x.TableName == TableName)) + Delete.Index(index.IndexName).OnTable(index.TableName).Do(); + } + } + + private IDeleteBuilder Delete => new DeleteBuilder(_context); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Execute/ExecuteBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Execute/ExecuteBuilder.cs new file mode 100644 index 0000000000..0637d2e597 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Execute/ExecuteBuilder.cs @@ -0,0 +1,24 @@ +using NPoco; +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Execute.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Execute +{ + public class ExecuteBuilder : ExpressionBuilderBase, + IExecuteBuilder, IExecutableBuilder + { + public ExecuteBuilder(IMigrationContext context) + : base(new ExecuteSqlStatementExpression(context)) + { } + + /// + public void Do() => Expression.Execute(); + + /// + public IExecutableBuilder Sql(string sqlStatement) + { + Expression.SqlStatement = sqlStatement; + return this; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Execute/Expressions/ExecuteSqlStatementExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Execute/Expressions/ExecuteSqlStatementExpression.cs new file mode 100644 index 0000000000..b5c1fbdf6b --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Execute/Expressions/ExecuteSqlStatementExpression.cs @@ -0,0 +1,18 @@ +using NPoco; + +namespace Umbraco.Core.Migrations.Expressions.Execute.Expressions +{ + public class ExecuteSqlStatementExpression : MigrationExpressionBase + { + public ExecuteSqlStatementExpression(IMigrationContext context) + : base(context) + { } + + public virtual string SqlStatement { get; set; } + + protected override string GetSql() + { + return SqlStatement; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Execute/IExecuteBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Execute/IExecuteBuilder.cs new file mode 100644 index 0000000000..5747eb2c1a --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Execute/IExecuteBuilder.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Execute +{ + /// + /// Builds and executes an Sql statement. + /// + /// Deals with multi-statements Sql. + public interface IExecuteBuilder : IFluentBuilder + { + /// + /// Specifies the Sql statement to execute. + /// + IExecutableBuilder Sql(string sqlStatement); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/ExpressionBuilderBase.cs b/src/Umbraco.Core/Migrations/Expressions/ExpressionBuilderBase.cs new file mode 100644 index 0000000000..e491cf30c7 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/ExpressionBuilderBase.cs @@ -0,0 +1,22 @@ +namespace Umbraco.Core.Migrations.Expressions +{ + /// + /// Provides a base class for expression builders. + /// + public abstract class ExpressionBuilderBase + where TExpression : IMigrationExpression + { + /// + /// Initializes a new instance of the class. + /// + protected ExpressionBuilderBase(TExpression expression) + { + Expression = expression; + } + + /// + /// Gets the expression. + /// + public TExpression Expression { get; } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/ExpressionBuilderBaseOfNext.cs b/src/Umbraco.Core/Migrations/Expressions/ExpressionBuilderBaseOfNext.cs new file mode 100644 index 0000000000..91cf471b0a --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/ExpressionBuilderBaseOfNext.cs @@ -0,0 +1,185 @@ +using System.Data; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions +{ + /// + /// Provides a base class for expression builders. + /// + public abstract class ExpressionBuilderBase : ExpressionBuilderBase + where TExpression : IMigrationExpression + where TNext : IFluentBuilder + { + /// + /// Initializes a new instance of the class. + /// + protected ExpressionBuilderBase(TExpression expression) + : base(expression) + { } + + // FIXME WTF is this and what is TNext here?! + + public abstract ColumnDefinition GetColumnForType(); + + private ColumnDefinition Column => GetColumnForType(); + + public TNext AsAnsiString() + { + Column.Type = DbType.AnsiString; + return (TNext)(object)this; + } + + public TNext AsAnsiString(int size) + { + Column.Type = DbType.AnsiString; + Column.Size = size; + return (TNext)(object)this; + } + + public TNext AsBinary() + { + Column.Type = DbType.Binary; + return (TNext)(object)this; + } + + public TNext AsBinary(int size) + { + Column.Type = DbType.Binary; + Column.Size = size; + return (TNext)(object)this; + } + + public TNext AsBoolean() + { + Column.Type = DbType.Boolean; + return (TNext)(object)this; + } + + public TNext AsByte() + { + Column.Type = DbType.Byte; + return (TNext)(object)this; + } + + public TNext AsCurrency() + { + Column.Type = DbType.Currency; + return (TNext)(object)this; + } + + public TNext AsDate() + { + Column.Type = DbType.Date; + return (TNext)(object)this; + } + + public TNext AsDateTime() + { + Column.Type = DbType.DateTime; + return (TNext)(object)this; + } + + public TNext AsDecimal() + { + Column.Type = DbType.Decimal; + return (TNext)(object)this; + } + + public TNext AsDecimal(int size, int precision) + { + Column.Type = DbType.Decimal; + Column.Size = size; + Column.Precision = precision; + return (TNext)(object)this; + } + + public TNext AsDouble() + { + Column.Type = DbType.Double; + return (TNext)(object)this; + } + + public TNext AsFixedLengthString(int size) + { + Column.Type = DbType.StringFixedLength; + Column.Size = size; + return (TNext)(object)this; + } + + public TNext AsFixedLengthAnsiString(int size) + { + Column.Type = DbType.AnsiStringFixedLength; + Column.Size = size; + return (TNext)(object)this; + } + + public TNext AsFloat() + { + Column.Type = DbType.Single; + return (TNext)(object)this; + } + + public TNext AsGuid() + { + Column.Type = DbType.Guid; + return (TNext)(object)this; + } + + public TNext AsInt16() + { + Column.Type = DbType.Int16; + return (TNext)(object)this; + } + + public TNext AsInt32() + { + Column.Type = DbType.Int32; + return (TNext)(object)this; + } + + public TNext AsInt64() + { + Column.Type = DbType.Int64; + return (TNext)(object)this; + } + + public TNext AsString() + { + Column.Type = DbType.String; + return (TNext)(object)this; + } + + public TNext AsString(int size) + { + Column.Type = DbType.String; + Column.Size = size; + return (TNext)(object)this; + } + + public TNext AsTime() + { + Column.Type = DbType.Time; + return (TNext)(object)this; + } + + public TNext AsXml() + { + Column.Type = DbType.Xml; + return (TNext)(object)this; + } + + public TNext AsXml(int size) + { + Column.Type = DbType.Xml; + Column.Size = size; + return (TNext)(object)this; + } + + public TNext AsCustom(string customType) + { + Column.Type = null; + Column.CustomType = customType; + return (TNext)(object)this; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/IFluentBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/IFluentBuilder.cs new file mode 100644 index 0000000000..6d947ef410 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/IFluentBuilder.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Migrations.Expressions +{ + public interface IFluentBuilder + { } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Insert/Expressions/InsertDataExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Insert/Expressions/InsertDataExpression.cs new file mode 100644 index 0000000000..7efe457402 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Insert/Expressions/InsertDataExpression.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Text; +using System.Linq; +using NPoco; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Insert.Expressions +{ + public class InsertDataExpression : MigrationExpressionBase + { + public InsertDataExpression(IMigrationContext context) + : base(context) + { } + + public string TableName { get; set; } + public bool EnabledIdentityInsert { get; set; } + + public List Rows { get; } = new List(); + + protected override string GetSql() + { + var stmts = new StringBuilder(); + + if (EnabledIdentityInsert && SqlSyntax.SupportsIdentityInsert()) + { + stmts.AppendLine($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(TableName)} ON"); + AppendStatementSeparator(stmts); + } + + try + { + foreach (var item in Rows) + { + var cols = new StringBuilder(); + var vals = new StringBuilder(); + var first = true; + foreach (var keyVal in item) + { + if (first) + { + first = false; + } + else + { + cols.Append(","); + vals.Append(","); + } + cols.Append(SqlSyntax.GetQuotedColumnName(keyVal.Key)); + vals.Append(GetQuotedValue(keyVal.Value)); + } + + var sql = string.Format(SqlSyntax.InsertData, SqlSyntax.GetQuotedTableName(TableName), cols, vals); + + stmts.Append(sql); + AppendStatementSeparator(stmts); + } + } + finally + { + if (EnabledIdentityInsert && SqlSyntax.SupportsIdentityInsert()) + { + stmts.AppendLine($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(TableName)} OFF"); + AppendStatementSeparator(stmts); + } + } + + return stmts.ToString(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Insert/IInsertBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Insert/IInsertBuilder.cs new file mode 100644 index 0000000000..ad8adeb5c3 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Insert/IInsertBuilder.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Migrations.Expressions.Insert +{ + /// + /// Builds an Insert expression. + /// + public interface IInsertBuilder : IFluentBuilder + { + /// + /// Specifies the table to insert into. + /// + IInsertIntoBuilder IntoTable(string tableName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Insert/IInsertIntoBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Insert/IInsertIntoBuilder.cs new file mode 100644 index 0000000000..f1b901382e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Insert/IInsertIntoBuilder.cs @@ -0,0 +1,20 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Insert +{ + /// + /// Builds an Insert Into expression. + /// + public interface IInsertIntoBuilder : IFluentBuilder, IExecutableBuilder + { + /// + /// Enables identity insert. + /// + IInsertIntoBuilder EnableIdentityInsert(); + + /// + /// Specifies a row to be inserted. + /// + IInsertIntoBuilder Row(object dataAsAnonymousType); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Insert/InsertBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Insert/InsertBuilder.cs new file mode 100644 index 0000000000..3e315e7ea8 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Insert/InsertBuilder.cs @@ -0,0 +1,25 @@ +using NPoco; +using Umbraco.Core.Migrations.Expressions.Insert.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Insert +{ + /// + /// Implements . + /// + public class InsertBuilder : IInsertBuilder + { + private readonly IMigrationContext _context; + + public InsertBuilder(IMigrationContext context) + { + _context = context; + } + + /// + public IInsertIntoBuilder IntoTable(string tableName) + { + var expression = new InsertDataExpression(_context) { TableName = tableName }; + return new InsertIntoBuilder(expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Insert/InsertIntoBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Insert/InsertIntoBuilder.cs new file mode 100644 index 0000000000..07524eac54 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Insert/InsertIntoBuilder.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.ComponentModel; +using Umbraco.Core.Migrations.Expressions.Insert.Expressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Migrations.Expressions.Insert +{ + /// + /// Implements . + /// + public class InsertIntoBuilder : ExpressionBuilderBase, + IInsertIntoBuilder + { + public InsertIntoBuilder(InsertDataExpression expression) + : base(expression) + { } + + /// + public void Do() => Expression.Execute(); + + /// + public IInsertIntoBuilder EnableIdentityInsert() + { + Expression.EnabledIdentityInsert = true; + return this; + } + + /// + public IInsertIntoBuilder Row(object dataAsAnonymousType) + { + Expression.Rows.Add(GetData(dataAsAnonymousType)); + return this; + } + + private static InsertionDataDefinition GetData(object dataAsAnonymousType) + { + var properties = TypeDescriptor.GetProperties(dataAsAnonymousType); + + var data = new InsertionDataDefinition(); + foreach (PropertyDescriptor property in properties) + data.Add(new KeyValuePair(property.Name, property.GetValue(dataAsAnonymousType))); + return data; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Rename/Column/IRenameColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Rename/Column/IRenameColumnBuilder.cs new file mode 100644 index 0000000000..dd2ccb889f --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Rename/Column/IRenameColumnBuilder.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Migrations.Expressions.Rename.Column +{ + /// + /// Builds a Rename Column expression. + /// + public interface IRenameColumnBuilder : IFluentBuilder + { + /// + /// Specifies the table name. + /// + IRenameColumnToBuilder OnTable(string tableName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Rename/Column/IRenameColumnToBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Rename/Column/IRenameColumnToBuilder.cs new file mode 100644 index 0000000000..630c813abc --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Rename/Column/IRenameColumnToBuilder.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Rename.Column +{ + /// + /// Builds a Rename Column expression. + /// + public interface IRenameColumnToBuilder : IFluentBuilder + { + /// + /// Specifies the new name of the column. + /// + IExecutableBuilder To(string name); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Rename/Column/RenameColumnBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Rename/Column/RenameColumnBuilder.cs new file mode 100644 index 0000000000..1f4e808e32 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Rename/Column/RenameColumnBuilder.cs @@ -0,0 +1,30 @@ +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Rename.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Rename.Column +{ + public class RenameColumnBuilder : ExpressionBuilderBase, + IRenameColumnToBuilder, IRenameColumnBuilder, IExecutableBuilder + { + public RenameColumnBuilder(RenameColumnExpression expression) + : base(expression) + { } + + /// + public void Do() => Expression.Execute(); + + /// + public IExecutableBuilder To(string name) + { + Expression.NewName = name; + return this; + } + + /// + public IRenameColumnToBuilder OnTable(string tableName) + { + Expression.TableName = tableName; + return this; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Rename/Expressions/RenameColumnExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Rename/Expressions/RenameColumnExpression.cs new file mode 100644 index 0000000000..0f73c06f15 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Rename/Expressions/RenameColumnExpression.cs @@ -0,0 +1,50 @@ +using NPoco; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Migrations.Expressions.Rename.Expressions +{ + public class RenameColumnExpression : MigrationExpressionBase + { + public RenameColumnExpression(IMigrationContext context) + : base(context) + { } + + public virtual string TableName { get; set; } + public virtual string OldName { get; set; } + public virtual string NewName { get; set; } + + public override string Process(IMigrationContext context) + => GetSql(); + + /// + protected override string GetSql() + { + return DatabaseType.IsMySql() + ? GetMySql() + : GetBaseSql(); + } + + private string GetBaseSql() + { + return SqlSyntax.FormatColumnRename(TableName, OldName, NewName); + } + + private string GetMySql() + { + var columnDefinitionSql = $@" +SELECT CONCAT( + CAST(COLUMN_TYPE AS CHAR), + IF(ISNULL(CHARACTER_SET_NAME), '', CONCAT(' CHARACTER SET ', CHARACTER_SET_NAME)), + IF(ISNULL(COLLATION_NAME), '', CONCAT(' COLLATE ', COLLATION_NAME)), + ' ', + IF(IS_NULLABLE = 'NO', 'NOT NULL ', ''), + IF(IS_NULLABLE = 'NO' AND COLUMN_DEFAULT IS NULL, '', CONCAT('DEFAULT ', QUOTE(COLUMN_DEFAULT), ' ')), + UPPER(extra)) +FROM INFORMATION_SCHEMA.COLUMNS +WHERE TABLE_NAME = '{TableName}' AND COLUMN_NAME = '{OldName}'"; + + var columnDefinition = Database.ExecuteScalar(columnDefinitionSql); + return GetBaseSql() + " " + columnDefinition; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Rename/Expressions/RenameTableExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Rename/Expressions/RenameTableExpression.cs new file mode 100644 index 0000000000..cdd3367b92 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Rename/Expressions/RenameTableExpression.cs @@ -0,0 +1,31 @@ +using NPoco; + +namespace Umbraco.Core.Migrations.Expressions.Rename.Expressions +{ + /// + /// Represents a Rename Table expression. + /// + public class RenameTableExpression : MigrationExpressionBase + { + public RenameTableExpression(IMigrationContext context) + : base(context) + { } + + /// + /// Gets or sets the source name. + /// + public virtual string OldName { get; set; } + + /// + /// Gets or sets the target name. + /// + public virtual string NewName { get; set; } + + /// + /// + protected override string GetSql() + { + return SqlSyntax.FormatTableRename(OldName, NewName); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Rename/IRenameBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Rename/IRenameBuilder.cs new file mode 100644 index 0000000000..ac93158508 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Rename/IRenameBuilder.cs @@ -0,0 +1,21 @@ +using Umbraco.Core.Migrations.Expressions.Rename.Column; +using Umbraco.Core.Migrations.Expressions.Rename.Table; + +namespace Umbraco.Core.Migrations.Expressions.Rename +{ + /// + /// Builds a Rename expression. + /// + public interface IRenameBuilder : IFluentBuilder + { + /// + /// Specifies the table to rename. + /// + IRenameTableBuilder Table(string oldName); + + /// + /// Specifies the column to rename. + /// + IRenameColumnBuilder Column(string oldName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Rename/RenameBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Rename/RenameBuilder.cs new file mode 100644 index 0000000000..bb9950e354 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Rename/RenameBuilder.cs @@ -0,0 +1,31 @@ +using NPoco; +using Umbraco.Core.Migrations.Expressions.Rename.Column; +using Umbraco.Core.Migrations.Expressions.Rename.Expressions; +using Umbraco.Core.Migrations.Expressions.Rename.Table; + +namespace Umbraco.Core.Migrations.Expressions.Rename +{ + public class RenameBuilder : IRenameBuilder + { + private readonly IMigrationContext _context; + + public RenameBuilder(IMigrationContext context) + { + _context = context; + } + + /// + public IRenameTableBuilder Table(string oldName) + { + var expression = new RenameTableExpression(_context) { OldName = oldName }; + return new RenameTableBuilder(expression); + } + + /// + public IRenameColumnBuilder Column(string oldName) + { + var expression = new RenameColumnExpression(_context) { OldName = oldName }; + return new RenameColumnBuilder(expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Rename/Table/IRenameTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Rename/Table/IRenameTableBuilder.cs new file mode 100644 index 0000000000..6309dd0abf --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Rename/Table/IRenameTableBuilder.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Rename.Table +{ + /// + /// Builds a Rename Table expression. + /// + public interface IRenameTableBuilder : IFluentBuilder + { + /// + /// Specifies the new name of the table. + /// + IExecutableBuilder To(string name); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Rename/Table/RenameTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Rename/Table/RenameTableBuilder.cs new file mode 100644 index 0000000000..2a7f7a446a --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Rename/Table/RenameTableBuilder.cs @@ -0,0 +1,23 @@ +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Rename.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Rename.Table +{ + public class RenameTableBuilder : ExpressionBuilderBase, + IRenameTableBuilder, IExecutableBuilder + { + public RenameTableBuilder(RenameTableExpression expression) + : base(expression) + { } + + /// + public void Do() => Expression.Execute(); + + /// + public IExecutableBuilder To(string name) + { + Expression.NewName = name; + return this; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Update/Expressions/UpdateDataExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Update/Expressions/UpdateDataExpression.cs new file mode 100644 index 0000000000..26d96bbdb9 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Update/Expressions/UpdateDataExpression.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using NPoco; +using System.Linq; + +namespace Umbraco.Core.Migrations.Expressions.Update.Expressions +{ + public class UpdateDataExpression : MigrationExpressionBase + { + public UpdateDataExpression(IMigrationContext context) + : base(context) + { } + + public string TableName { get; set; } + + public List> Set { get; set; } + public List> Where { get; set; } + public bool IsAllRows { get; set; } + + protected override string GetSql() + { + var updateItems = Set.Select(x => $"{SqlSyntax.GetQuotedColumnName(x.Key)} = {GetQuotedValue(x.Value)}"); + var whereClauses = IsAllRows + ? null + : Where.Select(x => $"{SqlSyntax.GetQuotedColumnName(x.Key)} {(x.Value == null ? "IS" : "=")} {GetQuotedValue(x.Value)}"); + + var whereClause = whereClauses == null + ? "(1=1)" + : string.Join(" AND ", whereClauses.ToArray()); + + return string.Format(SqlSyntax.UpdateData, + SqlSyntax.GetQuotedTableName(TableName), + string.Join(", ", updateItems), + whereClause); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Update/IUpdateBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Update/IUpdateBuilder.cs new file mode 100644 index 0000000000..152dda4012 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Update/IUpdateBuilder.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Migrations.Expressions.Update +{ + /// + /// Builds an Update expression. + /// + public interface IUpdateBuilder : IFluentBuilder + { + /// + /// Specifies the table to update. + /// + IUpdateTableBuilder Table(string tableName); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Update/IUpdateTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Update/IUpdateTableBuilder.cs new file mode 100644 index 0000000000..023a62113c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Update/IUpdateTableBuilder.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Migrations.Expressions.Update +{ + /// + /// Builds an Update expression. + /// + public interface IUpdateTableBuilder + { + /// + /// Specifies the data. + /// + IUpdateWhereBuilder Set(object dataAsAnonymousType); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Update/IUpdateWhereBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Update/IUpdateWhereBuilder.cs new file mode 100644 index 0000000000..dc722f2f5a --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Update/IUpdateWhereBuilder.cs @@ -0,0 +1,20 @@ +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Update +{ + /// + /// Builds an Update expression. + /// + public interface IUpdateWhereBuilder + { + /// + /// Specifies rows to update. + /// + IExecutableBuilder Where(object dataAsAnonymousType); + + /// + /// Specifies that all rows must be updated. + /// + IExecutableBuilder AllRows(); + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Update/UpdateBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Update/UpdateBuilder.cs new file mode 100644 index 0000000000..05546b5d37 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Update/UpdateBuilder.cs @@ -0,0 +1,22 @@ +using NPoco; +using Umbraco.Core.Migrations.Expressions.Update.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Update +{ + public class UpdateBuilder : IUpdateBuilder + { + private readonly IMigrationContext _context; + + public UpdateBuilder(IMigrationContext context) + { + _context = context; + } + + /// + public IUpdateTableBuilder Table(string tableName) + { + var expression = new UpdateDataExpression(_context) { TableName = tableName }; + return new UpdateDataBuilder(expression); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Expressions/Update/UpdateDataBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Update/UpdateDataBuilder.cs new file mode 100644 index 0000000000..7e327db2d7 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Expressions/Update/UpdateDataBuilder.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.ComponentModel; +using Umbraco.Core.Migrations.Expressions.Common; +using Umbraco.Core.Migrations.Expressions.Update.Expressions; + +namespace Umbraco.Core.Migrations.Expressions.Update +{ + public class UpdateDataBuilder : ExpressionBuilderBase, + IUpdateTableBuilder, IUpdateWhereBuilder, IExecutableBuilder + { + public UpdateDataBuilder(UpdateDataExpression expression) + : base(expression) + { } + + /// + public void Do() => Expression.Execute(); + + /// + public IUpdateWhereBuilder Set(object dataAsAnonymousType) + { + Expression.Set = GetData(dataAsAnonymousType); + return this; + } + + /// + public IExecutableBuilder Where(object dataAsAnonymousType) + { + Expression.Where = GetData(dataAsAnonymousType); + return this; + } + + /// + public IExecutableBuilder AllRows() + { + Expression.IsAllRows = true; + return this; + } + + private static List> GetData(object dataAsAnonymousType) + { + var properties = TypeDescriptor.GetProperties(dataAsAnonymousType); + + var data = new List>(); + foreach (PropertyDescriptor property in properties) + data.Add(new KeyValuePair(property.Name, property.GetValue(dataAsAnonymousType))); + return data; + } + } +} diff --git a/src/Umbraco.Core/Migrations/IMigration.cs b/src/Umbraco.Core/Migrations/IMigration.cs new file mode 100644 index 0000000000..53b7874b3a --- /dev/null +++ b/src/Umbraco.Core/Migrations/IMigration.cs @@ -0,0 +1,12 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Migrations +{ + /// + /// Represents a migration. + /// + public interface IMigration : IDiscoverable + { + void Migrate(); + } +} diff --git a/src/Umbraco.Core/Migrations/IMigrationBuilder.cs b/src/Umbraco.Core/Migrations/IMigrationBuilder.cs new file mode 100644 index 0000000000..3d78d825a7 --- /dev/null +++ b/src/Umbraco.Core/Migrations/IMigrationBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Core.Migrations +{ + public interface IMigrationBuilder + { + IMigration Build(Type migrationType, IMigrationContext context); + } +} diff --git a/src/Umbraco.Core/Migrations/IMigrationContext.cs b/src/Umbraco.Core/Migrations/IMigrationContext.cs new file mode 100644 index 0000000000..4db1b07b63 --- /dev/null +++ b/src/Umbraco.Core/Migrations/IMigrationContext.cs @@ -0,0 +1,31 @@ +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Migrations +{ + /// + /// Provides context to migrations. + /// + public interface IMigrationContext + { + /// + /// Gets the logger. + /// + ILogger Logger { get; } + + /// + /// Gets the database instance. + /// + IUmbracoDatabase Database { get; } + + /// + /// Gets the Sql context. + /// + ISqlContext SqlContext { get; } + + /// + /// Gets the expression index. + /// + int Index { get; set; } + } +} diff --git a/src/Umbraco.Core/Migrations/IMigrationExpression.cs b/src/Umbraco.Core/Migrations/IMigrationExpression.cs new file mode 100644 index 0000000000..8f950ab301 --- /dev/null +++ b/src/Umbraco.Core/Migrations/IMigrationExpression.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Core.Migrations +{ + /// + /// Marker interface for migration expressions + /// + public interface IMigrationExpression + { + string Process(IMigrationContext context); // fixme kill + void Execute(); + } +} diff --git a/src/Umbraco.Core/Migrations/IPostMigration.cs b/src/Umbraco.Core/Migrations/IPostMigration.cs new file mode 100644 index 0000000000..827a09e882 --- /dev/null +++ b/src/Umbraco.Core/Migrations/IPostMigration.cs @@ -0,0 +1,11 @@ +using Semver; +using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Migrations +{ + public interface IPostMigration + { + void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger); + } +} diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs new file mode 100644 index 0000000000..a3fd35df44 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -0,0 +1,689 @@ +using System; +using System.Configuration; +using System.Data.SqlServerCe; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Exceptions; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Upgrade; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Migrations.Install +{ + /// + /// Supports building and configuring the database. + /// + public class DatabaseBuilder + { + private readonly IUmbracoDatabaseFactory _databaseFactory; + private readonly IScopeProvider _scopeProvider; + private readonly IGlobalSettings _globalSettings; + private readonly IRuntimeState _runtime; + private readonly IMigrationBuilder _migrationBuilder; + private readonly IKeyValueService _keyValueService; + private readonly PostMigrationCollection _postMigrations; + private readonly ILogger _logger; + + private DatabaseSchemaResult _databaseSchemaValidationResult; + + public DatabaseBuilder(IScopeProvider scopeProvider, IGlobalSettings globalSettings, IUmbracoDatabaseFactory databaseFactory, IRuntimeState runtime, ILogger logger, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, PostMigrationCollection postMigrations) + { + _scopeProvider = scopeProvider; + _globalSettings = globalSettings; + _databaseFactory = databaseFactory; + _runtime = runtime; + _logger = logger; + _migrationBuilder = migrationBuilder; + _keyValueService = keyValueService; + _postMigrations = postMigrations; + } + + #region Status + + /// + /// Gets a value indicating whether the database is configured. It does not necessarily + /// mean that it is possible to connect, nor that Umbraco is installed, nor + /// up-to-date. + /// + public bool IsDatabaseConfigured => _databaseFactory.Configured; + + /// + /// Gets a value indicating whether it is possible to connect to the database. + /// + public bool CanConnect => _databaseFactory.CanConnect; + + // that method was originally created by Per in DatabaseHelper- tests the db connection for install + // fixed by Shannon to not-ignore the provider + // fixed by Stephan as part of the v8 persistence cleanup, now using provider names + SqlCe exception + // moved by Stephan to DatabaseBuilder + // probably needs to be cleaned up + + public bool CheckConnection(string databaseType, string connectionString, string server, string database, string login, string password, bool integratedAuth) + { + // we do not test SqlCE connection + if (databaseType.InvariantContains("sqlce")) + return true; + + string providerName; + + if (string.IsNullOrWhiteSpace(connectionString) == false) + { + providerName = DbConnectionExtensions.DetectProviderNameFromConnectionString(connectionString); + } + else if (integratedAuth) + { + // has to be Sql Server + providerName = Constants.DbProviderNames.SqlServer; + connectionString = GetIntegratedSecurityDatabaseConnectionString(server, database); + } + else + { + connectionString = GetDatabaseConnectionString( + server, database, login, password, + databaseType, out providerName); + } + + return DbConnectionExtensions.IsConnectionAvailable(connectionString, providerName); + } + + public bool HasSomeNonDefaultUser() + { + using (var scope = _scopeProvider.CreateScope()) + { + // look for the super user with default password + var sql = scope.Database.SqlContext.Sql() + .SelectCount() + .From() + .Where(x => x.Id == Constants.Security.SuperId && x.Password == "default"); + var result = scope.Database.ExecuteScalar(sql); + var has = result != 1; + if (has == false) + { + // found only 1 user == the default user with default password + // however this always exists on uCloud, also need to check if there are other users too + result = scope.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoUser"); + has = result != 1; + } + scope.Complete(); + return has; + } + } + + #endregion + + #region Configure Connection String + + private const string EmbeddedDatabaseConnectionString = @"Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1;"; + + /// + /// Configures a connection string for the embedded database. + /// + public void ConfigureEmbeddedDatabaseConnection() + { + ConfigureEmbeddedDatabaseConnection(_databaseFactory, _logger); + } + + private static void ConfigureEmbeddedDatabaseConnection(IUmbracoDatabaseFactory factory, ILogger logger) + { + SaveConnectionString(EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe, logger); + + var path = Path.Combine(GlobalSettings.FullPathToRoot, "App_Data", "Umbraco.sdf"); + if (File.Exists(path) == false) + { + // this should probably be in a "using (new SqlCeEngine)" clause but not sure + // of the side effects and it's been like this for quite some time now + + var engine = new SqlCeEngine(EmbeddedDatabaseConnectionString); + engine.CreateDatabase(); + } + + factory.Configure(EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe); + } + + /// + /// Configures a connection string that has been entered manually. + /// + /// A connection string. + /// Has to be either SQL Server or MySql + public void ConfigureDatabaseConnection(string connectionString) + { + var provider = DbConnectionExtensions.DetectProviderNameFromConnectionString(connectionString); + var providerName = provider.ToString().ToLower().Contains("mysql") + ? Constants.DbProviderNames.MySql + : Constants.DbProviderNames.SqlServer; + + SaveConnectionString(connectionString, providerName, _logger); + _databaseFactory.Configure(connectionString, providerName); + } + + /// + /// Configures a connection string from the installer. + /// + /// The name or address of the database server. + /// The name of the database. + /// The user name. + /// The user password. + /// The name the provider (Sql, Sql Azure, Sql Ce, MySql). + public void ConfigureDatabaseConnection(string server, string databaseName, string user, string password, string databaseProvider) + { + string providerName; + var connectionString = GetDatabaseConnectionString(server, databaseName, user, password, databaseProvider, out providerName); + + SaveConnectionString(connectionString, providerName, _logger); + _databaseFactory.Configure(connectionString, providerName); + } + + /// + /// Gets a connection string from the installer. + /// + /// The name or address of the database server. + /// The name of the database. + /// The user name. + /// The user password. + /// The name the provider (Sql, Sql Azure, Sql Ce, MySql). + /// + /// A connection string. + public static string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName) + { + providerName = Constants.DbProviderNames.SqlServer; + var test = databaseProvider.ToLower(); + if (test.Contains("mysql")) + { + providerName = Constants.DbProviderNames.MySql; + return $"Server={server}; Database={databaseName};Uid={user};Pwd={password}"; + } + if (test.Contains("azure")) + { + return GetAzureConnectionString(server, databaseName, user, password); + } + return $"server={server};database={databaseName};user id={user};password={password}"; + } + + /// + /// Configures a connection string using Microsoft SQL Server integrated security. + /// + /// The name or address of the database server. + /// The name of the database + public void ConfigureIntegratedSecurityDatabaseConnection(string server, string databaseName) + { + var connectionString = GetIntegratedSecurityDatabaseConnectionString(server, databaseName); + SaveConnectionString(connectionString, Constants.DbProviderNames.SqlServer, _logger); + _databaseFactory.Configure(connectionString, Constants.DbProviderNames.SqlServer); + } + + /// + /// Gets a connection string using Microsoft SQL Server integrated security. + /// + /// The name or address of the database server. + /// The name of the database + /// A connection string. + public static string GetIntegratedSecurityDatabaseConnectionString(string server, string databaseName) + { + return $"Server={server};Database={databaseName};Integrated Security=true"; + } + + /// + /// Gets an Azure connection string. + /// + /// The name or address of the database server. + /// The name of the database. + /// The user name. + /// The user password. + /// A connection string. + public static string GetAzureConnectionString(string server, string databaseName, string user, string password) + { + if (server.Contains(".") && ServerStartsWithTcp(server) == false) + server = $"tcp:{server}"; + + if (server.Contains(".") == false && ServerStartsWithTcp(server)) + { + string serverName = server.Contains(",") + ? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal)) + : server; + + var portAddition = string.Empty; + + if (server.Contains(",")) + portAddition = server.Substring(server.IndexOf(",", StringComparison.Ordinal)); + + server = $"{serverName}.database.windows.net{portAddition}"; + } + + if (ServerStartsWithTcp(server) == false) + server = $"tcp:{server}.database.windows.net"; + + if (server.Contains(",") == false) + server = $"{server},1433"; + + if (user.Contains("@") == false) + { + var userDomain = server; + + if (ServerStartsWithTcp(server)) + userDomain = userDomain.Substring(userDomain.IndexOf(":", StringComparison.Ordinal) + 1); + + if (userDomain.Contains(".")) + userDomain = userDomain.Substring(0, userDomain.IndexOf(".", StringComparison.Ordinal)); + + user = $"{user}@{userDomain}"; + } + + return $"Server={server};Database={databaseName};User ID={user};Password={password}"; + } + + private static bool ServerStartsWithTcp(string server) + { + return server.ToLower().StartsWith("tcp:".ToLower()); + } + + /// + /// Saves the connection string as a proper .net connection string in web.config. + /// + /// Saves the ConnectionString in the very nasty 'medium trust'-supportive way. + /// The connection string. + /// The provider name. + /// A logger. + private static void SaveConnectionString(string connectionString, string providerName, ILogger logger) + { + if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullOrEmptyException(nameof(connectionString)); + if (string.IsNullOrWhiteSpace(providerName)) throw new ArgumentNullOrEmptyException(nameof(providerName)); + + // set the connection string for the new datalayer + var connectionStringSettings = new ConnectionStringSettings(Constants.System.UmbracoConnectionName, connectionString, providerName); + + var fileName = IOHelper.MapPath($"{SystemDirectories.Root}/web.config"); + var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); + if (xml.Root == null) throw new Exception("Invalid web.config file."); + var connectionStrings = xml.Root.DescendantsAndSelf("connectionStrings").FirstOrDefault(); + if (connectionStrings == null) throw new Exception("Invalid web.config file."); + + // honour configSource, if its set, change the xml file we are saving the configuration + // to the one set in the configSource attribute + if (connectionStrings.Attribute("configSource") != null) + { + var source = connectionStrings.Attribute("configSource").Value; + var configFile = IOHelper.MapPath($"{SystemDirectories.Root}/{source}"); + logger.Info($"Storing ConnectionString in {configFile}"); + if (File.Exists(configFile)) + { + xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); + fileName = configFile; + } + connectionStrings = xml.Root.DescendantsAndSelf("connectionStrings").FirstOrDefault(); + if (connectionStrings == null) throw new Exception("Invalid web.config file."); + } + + // update connectionString if it exists, or else create a new connectionString + var setting = connectionStrings.Descendants("add").FirstOrDefault(s => s.Attribute("name").Value == Constants.System.UmbracoConnectionName); + if (setting == null) + { + connectionStrings.Add(new XElement("add", + new XAttribute("name", Constants.System.UmbracoConnectionName), + new XAttribute("connectionString", connectionStringSettings), + new XAttribute("providerName", providerName))); + } + else + { + setting.Attribute("connectionString").Value = connectionString; + setting.Attribute("providerName").Value = providerName; + } + + xml.Save(fileName, SaveOptions.DisableFormatting); + logger.Info("Configured a new ConnectionString using the '" + providerName + "' provider."); + } + + internal bool IsConnectionStringConfigured(ConnectionStringSettings databaseSettings) + { + var dbIsSqlCe = false; + if (databaseSettings?.ProviderName != null) + dbIsSqlCe = databaseSettings.ProviderName == Constants.DbProviderNames.SqlCe; + var sqlCeDatabaseExists = false; + if (dbIsSqlCe) + { + var parts = databaseSettings.ConnectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + var dataSourcePart = parts.FirstOrDefault(x => x.InvariantStartsWith("Data Source=")); + if (dataSourcePart != null) + { + var datasource = dataSourcePart.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString()); + var filePath = datasource.Replace("Data Source=", string.Empty); + sqlCeDatabaseExists = File.Exists(filePath); + } + } + + // Either the connection details are not fully specified or it's a SQL CE database that doesn't exist yet + if (databaseSettings == null + || string.IsNullOrWhiteSpace(databaseSettings.ConnectionString) || string.IsNullOrWhiteSpace(databaseSettings.ProviderName) + || (dbIsSqlCe && sqlCeDatabaseExists == false)) + { + return false; + } + + return true; + } + + #endregion + + #region Utils + + internal static void GiveLegacyAChance(IUmbracoDatabaseFactory factory, ILogger logger) + { + // look for the legacy appSettings key + var legacyConnString = ConfigurationManager.AppSettings[Constants.System.UmbracoConnectionName]; + if (string.IsNullOrWhiteSpace(legacyConnString)) return; + + var test = legacyConnString.ToLowerInvariant(); + if (test.Contains("sqlce4umbraco")) + { + // sql ce + ConfigureEmbeddedDatabaseConnection(factory, logger); + } + else if (test.Contains("tcp:")) + { + // sql azure + SaveConnectionString(legacyConnString, Constants.DbProviderNames.SqlServer, logger); + factory.Configure(legacyConnString, Constants.DbProviderNames.SqlServer); + } + else if (test.Contains("datalayer=mysql")) + { + // mysql + + // strip the datalayer part off + var connectionStringWithoutDatalayer = string.Empty; + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var variable in legacyConnString.Split(';').Where(x => x.ToLowerInvariant().StartsWith("datalayer") == false)) + connectionStringWithoutDatalayer = $"{connectionStringWithoutDatalayer}{variable};"; + + SaveConnectionString(connectionStringWithoutDatalayer, Constants.DbProviderNames.MySql, logger); + factory.Configure(connectionStringWithoutDatalayer, Constants.DbProviderNames.MySql); + } + else + { + // sql server + SaveConnectionString(legacyConnString, Constants.DbProviderNames.SqlServer, logger); + factory.Configure(legacyConnString, Constants.DbProviderNames.SqlServer); + } + + // remove the legacy connection string, so we don't end up in a loop if something goes wrong + GlobalSettings.RemoveSetting(Constants.System.UmbracoConnectionName); + } + + #endregion + + #region Database Schema + + internal DatabaseSchemaResult ValidateDatabaseSchema() + { + using (var scope = _scopeProvider.CreateScope()) + { + var result = ValidateDatabaseSchema(scope); + scope.Complete(); + return result; + } + } + + private DatabaseSchemaResult ValidateDatabaseSchema(IScope scope) + { + if (_databaseFactory.Configured == false) + return new DatabaseSchemaResult(_databaseFactory.SqlContext.SqlSyntax); + + if (_databaseSchemaValidationResult != null) + return _databaseSchemaValidationResult; + + var database = scope.Database; + var dbSchema = new DatabaseSchemaCreator(database, _logger); + _databaseSchemaValidationResult = dbSchema.ValidateSchema(); + scope.Complete(); + return _databaseSchemaValidationResult; + } + + internal Result CreateDatabaseSchemaAndData() + { + using (var scope = _scopeProvider.CreateScope()) + { + var result = CreateDatabaseSchemaAndData(scope); + scope.Complete(); + return result; + } + } + + private Result CreateDatabaseSchemaAndData(IScope scope) + { + try + { + var readyForInstall = CheckReadyForInstall(); + if (readyForInstall.Success == false) + { + return readyForInstall.Result; + } + + _logger.Info("Database configuration status: Started"); + + var database = scope.Database; + + // If MySQL, we're going to ensure that database calls are maintaining proper casing as to remove the necessity for checks + // for case insensitive queries. In an ideal situation (which is what we're striving for), all calls would be case sensitive. + + /* + var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database); + if (supportsCaseInsensitiveQueries == false) + { + message = "

 

The database you're trying to use does not support case insensitive queries.
We currently do not support these types of databases.

" + + "

You can fix this by changing the following setting in your my.ini file in your MySQL installation directory:

" + + "
lower_case_table_names=1

" + + "

Note: Make sure to check with your hosting provider if they support case insensitive queries as well.

" + + "

For more technical information on case sensitivity in MySQL, have a look at " + + "the documentation on the subject

"; + + return new Result { Message = message, Success = false, Percentage = "15" }; + } + */ + + var message = GetResultMessageForMySql(); + var schemaResult = ValidateDatabaseSchema(); + var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); + + //If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing + if (string.IsNullOrEmpty(_globalSettings.ConfigurationStatus) && installedSchemaVersion.Equals(new Version(0, 0, 0))) + { + if (_runtime.Level == RuntimeLevel.Run) + throw new Exception("Umbraco is already configured!"); + + var creator = new DatabaseSchemaCreator(database, _logger); + creator.InitializeDatabaseSchema(); + + message = message + "

Installation completed!

"; + + //now that everything is done, we need to determine the version of SQL server that is executing + _logger.Info("Database configuration status: " + message); + return new Result { Message = message, Success = true, Percentage = "100" }; + } + + //we need to do an upgrade so return a new status message and it will need to be done during the next step + _logger.Info("Database requires upgrade"); + message = "

Upgrading database, this may take some time...

"; + return new Result + { + RequiresUpgrade = true, + Message = message, + Success = true, + Percentage = "30" + }; + } + catch (Exception ex) + { + return HandleInstallException(ex); + } + } + + // This assumes all of the previous checks are done! + internal Result UpgradeSchemaAndData() + { + try + { + var readyForInstall = CheckReadyForInstall(); + if (readyForInstall.Success == false) + { + return readyForInstall.Result; + } + + _logger.Info("Database upgrade started"); + + //var database = scope.Database; + //var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database); + + var message = GetResultMessageForMySql(); + + // fixme - remove this code + //var schemaResult = ValidateDatabaseSchema(); + // + //var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion()); + //var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService); + //var targetVersion = UmbracoVersion.Current; + // + ////In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will + //// return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the + //// migrations are run for the target version + //if (installedMigrationVersion == new SemVersion(new Version(0, 0, 0)) && installedSchemaVersion > new SemVersion(new Version(0, 0, 0))) + //{ + // //set the installedMigrationVersion to be one less than the target so the latest migrations are guaranteed to execute + // installedMigrationVersion = new SemVersion(targetVersion.SubtractRevision()); + //} + // + ////Figure out what our current installed version is. If the web.config doesn't have a version listed, then we'll use the minimum + //// version detected between the schema installed and the migrations listed in the migration table. + //// If there is a version in the web.config, we'll take the minimum between the listed migration in the db and what + //// is declared in the web.config. + // + //var currentInstalledVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) + // //Take the minimum version between the detected schema version and the installed migration version + // ? new[] { installedSchemaVersion, installedMigrationVersion }.Min() + // //Take the minimum version between the installed migration version and the version specified in the config + // : new[] { SemVersion.Parse(GlobalSettings.ConfigurationStatus), installedMigrationVersion }.Min(); + // + ////Ok, another edge case here. If the current version is a pre-release, + //// then we want to ensure all migrations for the current release are executed. + //if (currentInstalledVersion.Prerelease.IsNullOrWhiteSpace() == false) + //{ + // currentInstalledVersion = new SemVersion(currentInstalledVersion.GetVersion().SubtractRevision()); + //} + + // upgrade + var upgrader = new UmbracoUpgrader(_scopeProvider, _migrationBuilder, _keyValueService, _postMigrations, _logger); + upgrader.Execute(); + + // fixme remove this code + //var runner = new MigrationRunner(_scopeProvider, builder, migrationEntryService, _logger, currentInstalledVersion, UmbracoVersion.SemanticVersion, Constants.System.UmbracoMigrationName); + //var upgraded = runner.Execute(/*upgrade:true*/); + //if (upgraded == false) + //{ + // throw new ApplicationException("Upgrading failed, either an error occurred during the upgrade process or an event canceled the upgrade process, see log for full details"); + //} + + message = message + "

Upgrade completed!

"; + + //now that everything is done, we need to determine the version of SQL server that is executing + + _logger.Info("Database configuration status: " + message); + + return new Result { Message = message, Success = true, Percentage = "100" }; + } + catch (Exception ex) + { + return HandleInstallException(ex); + } + } + + private string GetResultMessageForMySql() + { + if (_databaseFactory.GetType() == typeof(MySqlSyntaxProvider)) + { + return "

 

Congratulations, the database step ran successfully!

" + + "

Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.

" + + "

However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries

" + + "

Make sure to check with your hosting provider if they support case insensitive queries as well.

" + + "

They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:

" + + "
lower_case_table_names=1

" + + "

For more technical information on case sensitivity in MySQL, have a look at " + + "the documentation on the subject

"; + } + return string.Empty; + } + + /* + private string GetResultMessageForMySql(bool? supportsCaseInsensitiveQueries) + { + if (supportsCaseInsensitiveQueries == null) + { + return "

 

Warning! Could not check if your database type supports case insensitive queries.
We currently do not support these databases that do not support case insensitive queries.

" + + "

You can check this by looking for the following setting in your my.ini file in your MySQL installation directory:

" + + "
lower_case_table_names=1

" + + "

Note: Make sure to check with your hosting provider if they support case insensitive queries as well.

" + + "

For more technical information on case sensitivity in MySQL, have a look at " + + "the documentation on the subject

"; + } + if (SqlSyntax.GetType() == typeof(MySqlSyntaxProvider)) + { + return "

 

Congratulations, the database step ran successfully!

" + + "

Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.

" + + "

However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries

" + + "

Make sure to check with your hosting provider if they support case insensitive queries as well.

" + + "

They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:

" + + "
lower_case_table_names=1

" + + "

For more technical information on case sensitivity in MySQL, have a look at " + + "the documentation on the subject

"; + } + return string.Empty; + }*/ + + private Attempt CheckReadyForInstall() + { + if (_databaseFactory.Configured == false) + { + return Attempt.Fail(new Result + { + Message = "Database configuration is invalid. Please check that the entered database exists and" + + " that the provided username and password has write access to the database.", + Success = false, + Percentage = "10" + }); + } + return Attempt.Succeed(); + } + + private Result HandleInstallException(Exception ex) + { + _logger.Error("Database configuration failed", ex); + + if (_databaseSchemaValidationResult != null) + { + _logger.Info("The database schema validation produced the following summary: \n" + _databaseSchemaValidationResult.GetSummary()); + } + + return new Result + { + Message = + "The database configuration failed with the following message: " + ex.Message + + "\n Please check log file for additional information (can be found in '/App_Data/Logs/UmbracoTraceLog.txt')", + Success = false, + Percentage = "90" + }; + } + + internal class Result + { + public bool RequiresUpgrade { get; set; } + public string Message { get; set; } + public bool Success { get; set; } + public string Percentage { get; set; } + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs new file mode 100644 index 0000000000..77bd1f6280 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -0,0 +1,300 @@ +using System; +using NPoco; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Upgrade; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Install +{ + /// + /// Creates the initial database data during install. + /// + internal class DatabaseDataCreator + { + private readonly IDatabase _database; + private readonly ILogger _logger; + + public DatabaseDataCreator(IDatabase database, ILogger logger) + { + _database = database; + _logger = logger; + } + + /// + /// Initialize the base data creation by inserting the data foundation for umbraco + /// specific to a table + /// + /// Name of the table to create base data for + public void InitializeBaseData(string tableName) + { + _logger.Info($"Creating data in table {tableName}"); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.Node)) + CreateNodeData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.Lock)) + CreateLockData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.ContentType)) + CreateContentTypeData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.User)) + CreateUserData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.UserGroup)) + CreateUserGroupData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.User2UserGroup)) + CreateUser2UserGroupData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.UserGroup2App)) + CreateUserGroup2AppData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.PropertyTypeGroup)) + CreatePropertyTypeGroupData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.PropertyType)) + CreatePropertyTypeData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.Language)) + CreateLanguageData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.ContentChildType)) + CreateContentChildTypeData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.DataType)) + CreateDataTypeData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.RelationType)) + CreateRelationTypeData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.TaskType)) + CreateTaskTypeData(); + + if (tableName.Equals(Constants.DatabaseSchema.Tables.KeyValue)) + CreateKeyValueData(); + + _logger.Info($"Done creating table {tableName} data."); + } + + private void CreateNodeData() + { + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = Constants.ObjectTypes.SystemRoot, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = Constants.ObjectTypes.ContentRecycleBin, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = Constants.ObjectTypes.MediaRecycleBin, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -92, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-92", SortOrder = 35, UniqueId = new Guid("f0bc4bfb-b499-40d6-ba86-058885a5178c"), Text = "Label", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "True/false", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = new Guid("fbaf13a8-4036-41f2-93a3-974f678c312a"), Text = "Checkbox list", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -42, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-42", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = Constants.ObjectTypes.MemberType, CreateDate = DateTime.Now }); + + //New UDI pickers with newer Ids + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Related Links", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + + //TODO: We're not creating these for 7.0 + //_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1039, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1039", SortOrder = 2, UniqueId = new Guid("06f349a9-c949-4b6a-8660-59c10451af42"), Text = "Ultimate Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + //_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1038, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1038", SortOrder = 2, UniqueId = new Guid("1251c96c-185c-4e9b-93f4-b48205573cbd"), Text = "Simple Editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + //_database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1042, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1042", SortOrder = 2, UniqueId = new Guid("0a452bd5-83f9-4bc3-8403-1286e13fb77e"), Text = "Macro Container", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + } + + private void CreateLockData() + { + // all lock objects + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.ContentTypes, Name = "ContentTypes" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.ContentTree, Name = "ContentTree" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MediaTypes, Name = "MediaTypes" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MediaTree, Name = "MediaTree" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTypes, Name = "MemberTypes" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTree, Name = "MemberTree" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" }); + } + + private void CreateContentTypeData() + { + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user", Variations = (byte) ContentVariation.InvariantNeutral }); + } + + private void CreateUserData() + { + _database.Insert(Constants.DatabaseSchema.Tables.User, "id", false, new UserDto { Id = Constants.Security.SuperId, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en-US", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); + } + + private void CreateUserGroupData() + { + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); + _database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); + } + + private void CreateUser2UserGroupData() + { + _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = Constants.Security.SuperId }); // add super to admins + _database.Insert(new User2UserGroupDto { UserGroupId = 5, UserId = Constants.Security.SuperId }); // add super to sensitive data + } + + private void CreateUserGroup2AppData() + { + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Content }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Media }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Members }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Forms }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Content }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Media }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Forms }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 4, AppAlias = Constants.Applications.Translation }); + } + + private void CreatePropertyTypeGroupData() + { + _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); + //membership property group + _database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); + } + + private void CreatePropertyTypeData() + { + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.DataTypes.DefaultMediaListView, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + //membership property types + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + + } + + private void CreateLanguageData() + { + _database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "en-US" }); + } + + private void CreateContentChildTypeData() + { + _database.Insert(Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1031 }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1032 }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentChildType, "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1033 }); + } + + private void CreateDataTypeData() + { + //layouts for the list view + const string cardLayout = "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": 1, \"selected\": true}"; + const string listLayout = "{\"name\": \"List\",\"path\": \"views/propertyeditors/listview/layouts/list/list.html\",\"icon\": \"icon-list\", \"isSystem\": 1,\"selected\": true}"; + const string layouts = "[" + cardLayout + "," + listLayout + "]"; + + //TODO Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors. + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -49, EditorAlias = Constants.PropertyEditors.Aliases.Boolean, DbType = "Integer" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -51, EditorAlias = Constants.PropertyEditors.Aliases.Integer, DbType = "Integer" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -87, EditorAlias = Constants.PropertyEditors.Aliases.TinyMce, DbType = "Ntext", + Configuration = "{\"value\":\",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|\"}" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -88, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -89, EditorAlias = Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -90, EditorAlias = Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -92, EditorAlias = Constants.PropertyEditors.Aliases.NoEdit, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -36, EditorAlias = Constants.PropertyEditors.Aliases.DateTime, DbType = "Date" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -37, EditorAlias = Constants.PropertyEditors.Aliases.ColorPicker, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -39, EditorAlias = Constants.PropertyEditors.Aliases.DropDownListMultiple, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -40, EditorAlias = Constants.PropertyEditors.Aliases.RadioButtonList, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = Constants.PropertyEditors.Aliases.Date, DbType = "Date" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -42, EditorAlias = Constants.PropertyEditors.Aliases.DropDownList, DbType = "Integer" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -43, EditorAlias = Constants.PropertyEditors.Aliases.CheckBoxList, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1041, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext", + Configuration = "{\"group\":\"default\"}" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1043, EditorAlias = Constants.PropertyEditors.Aliases.ImageCropper, DbType = "Ntext" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultContentListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", + Configuration = "{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" + layouts + ", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]}" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultMediaListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", + Configuration = "{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" + layouts + ", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]}" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultMembersListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", + Configuration = "{\"pageSize\":10, \"orderBy\":\"username\", \"orderDirection\":\"asc\", \"includeProperties\":[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]}" }); + + //New UDI pickers with newer Ids + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1046, EditorAlias = Constants.PropertyEditors.Aliases.ContentPicker, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1047, EditorAlias = Constants.PropertyEditors.Aliases.MemberPicker, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1048, EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1049, EditorAlias = Constants.PropertyEditors.Aliases.MediaPicker, DbType = "Ntext", + Configuration = "{\"multiPicker\":1}" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1050, EditorAlias = Constants.PropertyEditors.Aliases.RelatedLinks, DbType = "Ntext" }); + + //TODO: We're not creating these for 7.0 + //_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { DataTypeId = 1038, PropertyEditorAlias = Constants.PropertyEditors.MarkdownEditorAlias, DbType = "Ntext" }); + //_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { DataTypeId = 1039, PropertyEditorAlias = Constants.PropertyEditors.UltimatePickerAlias, DbType = "Ntext" }); + //_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { DataTypeId = 1042, PropertyEditorAlias = Constants.PropertyEditors.MacroContainerAlias, DbType = "Ntext" }); + } + + private void CreateRelationTypeData() + { + var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); + relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType); + } + + private void CreateTaskTypeData() + { + _database.Insert(Constants.DatabaseSchema.Tables.TaskType, "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); + } + + private void CreateKeyValueData() + { + // on install, initialize the umbraco migration plan with the final state + + var plan = new UmbracoPlan(); + var stateValueKey = Upgrader.GetStateValueKey(plan); + var finalState = plan.FinalState; + + _database.Insert(Constants.DatabaseSchema.Tables.KeyValue, "key", false, new KeyValueDto { Key = stateValueKey, Value = finalState, Updated = DateTime.Now }); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs new file mode 100644 index 0000000000..cc9c0f3893 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -0,0 +1,446 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NPoco; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Install +{ + /// + /// Creates the initial database schema during install. + /// + internal class DatabaseSchemaCreator + { + private readonly IUmbracoDatabase _database; + private readonly ILogger _logger; + + public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger) + { + _database = database; + _logger = logger; + } + + private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax; + + // all tables, in order + public static readonly List OrderedTables = new List + { + typeof (NodeDto), + typeof (ContentTypeDto), + typeof (TemplateDto), + typeof (ContentDto), + typeof (ContentVersionDto), + typeof (MediaVersionDto), + typeof (DocumentDto), + typeof (ContentTypeTemplateDto), + typeof (DataTypeDto), + typeof (DictionaryDto), + typeof (LanguageDto), + typeof (LanguageTextDto), + typeof (DomainDto), + typeof (LogDto), + typeof (MacroDto), + typeof (MacroPropertyDto), + typeof (MemberTypeDto), + typeof (MemberDto), + typeof (Member2MemberGroupDto), + typeof (ContentXmlDto), + typeof (PreviewXmlDto), + typeof (PropertyTypeGroupDto), + typeof (PropertyTypeDto), + typeof (PropertyDataDto), + typeof (RelationTypeDto), + typeof (RelationDto), + typeof (TagDto), + typeof (TagRelationshipDto), + typeof (UserDto), + typeof (TaskTypeDto), + typeof (TaskDto), + typeof (ContentType2ContentTypeDto), + typeof (ContentTypeAllowedContentTypeDto), + typeof (User2NodeNotifyDto), + typeof (ServerRegistrationDto), + typeof (AccessDto), + typeof (AccessRuleDto), + typeof (CacheInstructionDto), + typeof (ExternalLoginDto), + typeof (RedirectUrlDto), + typeof (LockDto), + typeof (UserGroupDto), + typeof (User2UserGroupDto), + typeof (UserGroup2NodePermissionDto), + typeof (UserGroup2AppDto), + typeof (UserStartNodeDto), + typeof (ContentNuDto), + typeof (DocumentVersionDto), + typeof (KeyValueDto), + typeof (UserLoginDto), + typeof (ConsentDto), + typeof (AuditEntryDto) + }; + + /// + /// Drops all Umbraco tables in the db. + /// + internal void UninstallDatabaseSchema() + { + _logger.Info("Start UninstallDatabaseSchema"); + + foreach (var table in OrderedTables.AsEnumerable().Reverse()) + { + var tableNameAttribute = table.FirstAttribute(); + var tableName = tableNameAttribute == null ? table.Name : tableNameAttribute.Value; + + _logger.Info("Uninstall" + tableName); + + try + { + if (TableExists(tableName)) + DropTable(tableName); + } + catch (Exception ex) + { + //swallow this for now, not sure how best to handle this with diff databases... though this is internal + // and only used for unit tests. If this fails its because the table doesn't exist... generally! + _logger.Error("Could not drop table " + tableName, ex); + } + } + } + + /// + /// Initializes the database by creating the umbraco db schema. + /// + public void InitializeDatabaseSchema() + { + var e = new DatabaseCreationEventArgs(); + FireBeforeCreation(e); + + if (e.Cancel == false) + { + var dataCreation = new DatabaseDataCreator(_database, _logger); + foreach (var table in OrderedTables) + CreateTable(false, table, dataCreation); + } + + FireAfterCreation(e); + } + + /// + /// Validates the schema of the current database. + /// + public DatabaseSchemaResult ValidateSchema() + { + var result = new DatabaseSchemaResult(SqlSyntax); + + //get the db index defs + result.DbIndexDefinitions = SqlSyntax.GetDefinedIndexes(_database) + .Select(x => new DbIndexDefinition + { + TableName = x.Item1, + IndexName = x.Item2, + ColumnName = x.Item3, + IsUnique = x.Item4 + }).ToArray(); + + result.TableDefinitions.AddRange(OrderedTables + .Select(x => DefinitionFactory.GetTableDefinition(x, SqlSyntax))); + + ValidateDbTables(result); + ValidateDbColumns(result); + ValidateDbIndexes(result); + ValidateDbConstraints(result); + + return result; + } + + private void ValidateDbConstraints(DatabaseSchemaResult result) + { + //MySql doesn't conform to the "normal" naming of constraints, so there is currently no point in doing these checks. + //TODO: At a later point we do other checks for MySql, but ideally it should be necessary to do special checks for different providers. + // ALso note that to get the constraints for MySql we have to open a connection which we currently have not. + if (SqlSyntax is MySqlSyntaxProvider) + return; + + //Check constraints in configured database against constraints in schema + var constraintsInDatabase = SqlSyntax.GetConstraintsPerColumn(_database).DistinctBy(x => x.Item3).ToList(); + var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("FK_")).Select(x => x.Item3).ToList(); + var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("PK_")).Select(x => x.Item3).ToList(); + var indexesInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("IX_")).Select(x => x.Item3).ToList(); + var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); + var unknownConstraintsInDatabase = + constraintsInDatabase.Where( + x => + x.Item3.InvariantStartsWith("FK_") == false && x.Item3.InvariantStartsWith("PK_") == false && + x.Item3.InvariantStartsWith("IX_") == false).Select(x => x.Item3).ToList(); + var foreignKeysInSchema = result.TableDefinitions.SelectMany(x => x.ForeignKeys.Select(y => y.Name)).ToList(); + var primaryKeysInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => y.PrimaryKeyName)) + .Where(x => x.IsNullOrWhiteSpace() == false).ToList(); + + //Add valid and invalid foreign key differences to the result object + // We'll need to do invariant contains with case insensitivity because foreign key, primary key, and even index naming w/ MySQL is not standardized + // In theory you could have: FK_ or fk_ ...or really any standard that your development department (or developer) chooses to use. + foreach (var unknown in unknownConstraintsInDatabase) + { + if (foreignKeysInSchema.InvariantContains(unknown) || primaryKeysInSchema.InvariantContains(unknown) || indexesInSchema.InvariantContains(unknown)) + { + result.ValidConstraints.Add(unknown); + } + else + { + result.Errors.Add(new Tuple("Unknown", unknown)); + } + } + + //Foreign keys: + + var validForeignKeyDifferences = foreignKeysInDatabase.Intersect(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var foreignKey in validForeignKeyDifferences) + { + result.ValidConstraints.Add(foreignKey); + } + var invalidForeignKeyDifferences = + foreignKeysInDatabase.Except(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(foreignKeysInSchema.Except(foreignKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var foreignKey in invalidForeignKeyDifferences) + { + result.Errors.Add(new Tuple("Constraint", foreignKey)); + } + + + //Primary keys: + + //Add valid and invalid primary key differences to the result object + var validPrimaryKeyDifferences = primaryKeysInDatabase.Intersect(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var primaryKey in validPrimaryKeyDifferences) + { + result.ValidConstraints.Add(primaryKey); + } + var invalidPrimaryKeyDifferences = + primaryKeysInDatabase.Except(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(primaryKeysInSchema.Except(primaryKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var primaryKey in invalidPrimaryKeyDifferences) + { + result.Errors.Add(new Tuple("Constraint", primaryKey)); + } + + //Constaints: + + //NOTE: SD: The colIndex checks above should really take care of this but I need to keep this here because it was here before + // and some schema validation checks might rely on this data remaining here! + //Add valid and invalid index differences to the result object + var validIndexDifferences = indexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var index in validIndexDifferences) + { + result.ValidConstraints.Add(index); + } + var invalidIndexDifferences = + indexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(indexesInSchema.Except(indexesInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var index in invalidIndexDifferences) + { + result.Errors.Add(new Tuple("Constraint", index)); + } + } + + private void ValidateDbColumns(DatabaseSchemaResult result) + { + //Check columns in configured database against columns in schema + var columnsInDatabase = SqlSyntax.GetColumnsInSchema(_database); + var columnsPerTableInDatabase = columnsInDatabase.Select(x => string.Concat(x.TableName, ",", x.ColumnName)).ToList(); + var columnsPerTableInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => string.Concat(y.TableName, ",", y.Name))).ToList(); + //Add valid and invalid column differences to the result object + var validColumnDifferences = columnsPerTableInDatabase.Intersect(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var column in validColumnDifferences) + { + result.ValidColumns.Add(column); + } + + var invalidColumnDifferences = + columnsPerTableInDatabase.Except(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(columnsPerTableInSchema.Except(columnsPerTableInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var column in invalidColumnDifferences) + { + result.Errors.Add(new Tuple("Column", column)); + } + } + + private void ValidateDbTables(DatabaseSchemaResult result) + { + //Check tables in configured database against tables in schema + var tablesInDatabase = SqlSyntax.GetTablesInSchema(_database).ToList(); + var tablesInSchema = result.TableDefinitions.Select(x => x.Name).ToList(); + //Add valid and invalid table differences to the result object + var validTableDifferences = tablesInDatabase.Intersect(tablesInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var tableName in validTableDifferences) + { + result.ValidTables.Add(tableName); + } + + var invalidTableDifferences = + tablesInDatabase.Except(tablesInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(tablesInSchema.Except(tablesInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var tableName in invalidTableDifferences) + { + result.Errors.Add(new Tuple("Table", tableName)); + } + } + + private void ValidateDbIndexes(DatabaseSchemaResult result) + { + //These are just column indexes NOT constraints or Keys + //var colIndexesInDatabase = result.DbIndexDefinitions.Where(x => x.IndexName.InvariantStartsWith("IX_")).Select(x => x.IndexName).ToList(); + var colIndexesInDatabase = result.DbIndexDefinitions.Select(x => x.IndexName).ToList(); + var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); + + //Add valid and invalid index differences to the result object + var validColIndexDifferences = colIndexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var index in validColIndexDifferences) + { + result.ValidIndexes.Add(index); + } + + var invalidColIndexDifferences = + colIndexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(indexesInSchema.Except(colIndexesInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var index in invalidColIndexDifferences) + { + result.Errors.Add(new Tuple("Index", index)); + } + } + + #region Events + + /// + /// The save event handler + /// + internal delegate void DatabaseEventHandler(DatabaseCreationEventArgs e); + + /// + /// Occurs when [before save]. + /// + internal static event DatabaseEventHandler BeforeCreation; + /// + /// Raises the event. + /// + /// The instance containing the event data. + internal virtual void FireBeforeCreation(DatabaseCreationEventArgs e) + { + BeforeCreation?.Invoke(e); + } + + /// + /// Occurs when [after save]. + /// + internal static event DatabaseEventHandler AfterCreation; + /// + /// Raises the event. + /// + /// The instance containing the event data. + internal virtual void FireAfterCreation(DatabaseCreationEventArgs e) + { + AfterCreation?.Invoke(e); + } + + #endregion + + #region Utilities + + public bool TableExists(string tableName) + { + return SqlSyntax.DoesTableExist(_database, tableName); + } + + // this is used in tests + internal void CreateTable(bool overwrite = false) + where T : new() + { + var tableType = typeof(T); + CreateTable(overwrite, tableType, new DatabaseDataCreator(_database, _logger)); + } + + public void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation) + { + var tableDefinition = DefinitionFactory.GetTableDefinition(modelType, SqlSyntax); + var tableName = tableDefinition.Name; + + var createSql = SqlSyntax.Format(tableDefinition); + var createPrimaryKeySql = SqlSyntax.FormatPrimaryKey(tableDefinition); + var foreignSql = SqlSyntax.Format(tableDefinition.ForeignKeys); + var indexSql = SqlSyntax.Format(tableDefinition.Indexes); + + var tableExist = TableExists(tableName); + if (overwrite && tableExist) + { + DropTable(tableName); + tableExist = false; + } + + if (tableExist == false) + { + using (var transaction = _database.GetTransaction()) + { + //Execute the Create Table sql + var created = _database.Execute(new Sql(createSql)); + _logger.Info($"Create Table '{tableName}' ({created}):\n {createSql}"); + + //If any statements exists for the primary key execute them here + if (string.IsNullOrEmpty(createPrimaryKeySql) == false) + { + var createdPk = _database.Execute(new Sql(createPrimaryKeySql)); + _logger.Info($"Create Primary Key ({createdPk}):\n {createPrimaryKeySql}"); + } + + //Turn on identity insert if db provider is not mysql + if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) + _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} ON ")); + + //Call the NewTable-event to trigger the insert of base/default data + //OnNewTable(tableName, _db, e, _logger); + + dataCreation.InitializeBaseData(tableName); + + //Turn off identity insert if db provider is not mysql + if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) + _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} OFF;")); + + //Special case for MySql + if (SqlSyntax is MySqlSyntaxProvider && tableName.Equals("umbracoUser")) + { + _database.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); + } + + //Loop through index statements and execute sql + foreach (var sql in indexSql) + { + var createdIndex = _database.Execute(new Sql(sql)); + _logger.Info($"Create Index ({createdIndex}):\n {sql}"); + } + + //Loop through foreignkey statements and execute sql + foreach (var sql in foreignSql) + { + var createdFk = _database.Execute(new Sql(sql)); + _logger.Info($"Create Foreign Key ({createdFk}):\n {sql}"); + } + + transaction.Complete(); + } + } + + _logger.Info($"Created table '{tableName}'"); + } + + public void DropTable(string tableName) + { + var sql = new Sql(string.Format(SqlSyntax.DropTable, SqlSyntax.GetQuotedTableName(tableName))); + _database.Execute(sql); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs similarity index 83% rename from src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs rename to src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs index cd2426357d..0ec27cf0b1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs @@ -1,217 +1,211 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Semver; -using Umbraco.Core.Configuration; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Persistence.Migrations.Initial -{ - public class DatabaseSchemaResult - { - public DatabaseSchemaResult() - { - Errors = new List>(); - TableDefinitions = new List(); - ValidTables = new List(); - ValidColumns = new List(); - ValidConstraints = new List(); - ValidIndexes = new List(); - } - - public List> Errors { get; set; } - - public List TableDefinitions { get; set; } - - public List ValidTables { get; set; } - - public List ValidColumns { get; set; } - - public List ValidConstraints { get; set; } - - public List ValidIndexes { get; set; } - - internal IEnumerable DbIndexDefinitions { get; set; } - - /// - /// Checks in the db which version is installed based on the migrations that have been run - /// - /// - /// - public SemVersion DetermineInstalledVersionByMigrations(IMigrationEntryService migrationEntryService) - { - SemVersion mostrecent = null; - - if (ValidTables.Any(x => x.InvariantEquals("umbracoMigration"))) - { - var allMigrations = migrationEntryService.GetAll(Constants.System.UmbracoMigrationName); - mostrecent = allMigrations.OrderByDescending(x => x.Version).Select(x => x.Version).FirstOrDefault(); - } - - return mostrecent ?? new SemVersion(new Version(0, 0, 0)); - } - - /// - /// Determines the version of the currently installed database by detecting the current database structure - /// - /// - /// A with Major and Minor values for - /// non-empty database, otherwise "0.0.0" for empty databases. - /// - public Version DetermineInstalledVersion() - { - //If (ValidTables.Count == 0) database is empty and we return -> new Version(0, 0, 0); - if (ValidTables.Count == 0) - return new Version(0, 0, 0); - - //If Errors is empty or if TableDefinitions tables + columns correspond to valid tables + columns then we're at current version - if (Errors.Any() == false || - (TableDefinitions.All(x => ValidTables.Contains(x.Name)) - && TableDefinitions.SelectMany(definition => definition.Columns).All(x => ValidColumns.Contains(x.Name)))) - return UmbracoVersion.Current; - - //If Errors contains umbracoApp or umbracoAppTree its pre-6.0.0 -> new Version(4, 10, 0); - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoApp") || x.Item2.InvariantEquals("umbracoAppTree")))) - { - //If Errors contains umbracoUser2app or umbracoAppTree foreignkey to umbracoApp exists its pre-4.8.0 -> new Version(4, 7, 0); - if (Errors.Any(x => - x.Item1.Equals("Constraint") - && (x.Item2.InvariantContains("umbracoUser2app_umbracoApp") - || x.Item2.InvariantContains("umbracoAppTree_umbracoApp")))) - { - return new Version(4, 7, 0); - } - - return new Version(4, 8, 0); - } - - //if the error is for umbracoServer - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoServer")))) - { - return new Version(6, 0, 0); - } - - //if the error indicates a problem with the column cmsMacroProperty.macroPropertyType then it is not version 7 - // since these columns get removed in v7 - if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMacroProperty,macroPropertyType")))) - { - //if the error is for this IX_umbracoNodeTrashed which is added in 6.2 AND in 7.1 but we do not have the above columns - // then it must mean that we aren't on 6.2 so must be 6.1 - if (Errors.Any(x => x.Item1.Equals("Index") && (x.Item2.InvariantEquals("IX_umbracoNodeTrashed")))) - { - return new Version(6, 1, 0); - } - else - { - //if there are no errors for that index, then the person must have 6.2 installed - return new Version(6, 2, 0); - } - } - - //if the error indicates a problem with the constraint FK_cmsContent_cmsContentType_nodeId then it is not version 7.2 - // since this gets added in 7.2.0 so it must be the previous version - if (Errors.Any(x => x.Item1.Equals("Constraint") && (x.Item2.InvariantEquals("FK_cmsContent_cmsContentType_nodeId")))) - { - return new Version(7, 0, 0); - } - - //if the error is for umbracoAccess it must be the previous version to 7.3 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoAccess")))) - { - return new Version(7, 2, 0); - } - - //if the error is for umbracoDeployChecksum it must be the previous version to 7.4 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoDeployChecksum")))) - { - return new Version(7, 3, 0); - } - - //if the error is for umbracoRedirectUrl it must be the previous version to 7.5 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoRedirectUrl")))) - { - return new Version(7, 4, 0); - } - - //if the error indicates a problem with the column cmsMacroProperty.uniquePropertyId then it is not version 7.6 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMacroProperty,uniquePropertyId")))) - { - return new Version(7, 5, 0); - } - - //if the error is for umbracoUserGroup it must be the previous version to 7.7 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoUserStartNode")))) - { - return new Version(7, 6, 0); - } - - //if the error is for cmsMedia it must be the previous version to 7.8 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("cmsMedia")))) - { - return new Version(7, 7, 0); - } - - return UmbracoVersion.Current; - } - - /// - /// Gets a summary of the schema validation result - /// - /// A string containing a human readable string with a summary message - public string GetSummary() - { - var sb = new StringBuilder(); - if (Errors.Any() == false) - { - sb.AppendLine("The database schema validation didn't find any errors."); - return sb.ToString(); - } - - //Table error summary - if (Errors.Any(x => x.Item1.Equals("Table"))) - { - sb.AppendLine("The following tables were found in the database, but are not in the current schema:"); - sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Table")).Select(x => x.Item2))); - sb.AppendLine(" "); - } - //Column error summary - if (Errors.Any(x => x.Item1.Equals("Column"))) - { - sb.AppendLine("The following columns were found in the database, but are not in the current schema:"); - sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Column")).Select(x => x.Item2))); - sb.AppendLine(" "); - } - //Constraint error summary - if (Errors.Any(x => x.Item1.Equals("Constraint"))) - { - sb.AppendLine("The following constraints (Primary Keys, Foreign Keys and Indexes) were found in the database, but are not in the current schema:"); - sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Constraint")).Select(x => x.Item2))); - sb.AppendLine(" "); - } - //Index error summary - if (Errors.Any(x => x.Item1.Equals("Index"))) - { - sb.AppendLine("The following indexes were found in the database, but are not in the current schema:"); - sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Index")).Select(x => x.Item2))); - sb.AppendLine(" "); - } - //Unknown constraint error summary - if (Errors.Any(x => x.Item1.Equals("Unknown"))) - { - sb.AppendLine("The following unknown constraints (Primary Keys, Foreign Keys and Indexes) were found in the database, but are not in the current schema:"); - sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Unknown")).Select(x => x.Item2))); - sb.AppendLine(" "); - } - - if (SqlSyntaxContext.SqlSyntaxProvider is MySqlSyntaxProvider) - { - sb.AppendLine("Please note that the constraints could not be validated because the current dataprovider is MySql."); - } - - return sb.ToString(); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Umbraco.Core.Configuration; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Install +{ + public class DatabaseSchemaResult + { + private readonly ISqlSyntaxProvider _sqlSyntax; + + public DatabaseSchemaResult(ISqlSyntaxProvider sqlSyntax) + { + _sqlSyntax = sqlSyntax; + Errors = new List>(); + TableDefinitions = new List(); + ValidTables = new List(); + ValidColumns = new List(); + ValidConstraints = new List(); + ValidIndexes = new List(); + } + + public List> Errors { get; set; } + + public List TableDefinitions { get; set; } + + public List ValidTables { get; set; } + + public List ValidColumns { get; set; } + + public List ValidConstraints { get; set; } + + public List ValidIndexes { get; set; } + + internal IEnumerable DbIndexDefinitions { get; set; } + + /// + /// Determines the version of the currently installed database by detecting the current database structure + /// + /// + /// A with Major and Minor values for + /// non-empty database, otherwise "0.0.0" for empty databases. + /// + public Version DetermineInstalledVersion() + { + // v8 = kill versions older than 7 + + //If (ValidTables.Count == 0) database is empty and we return -> new Version(0, 0, 0); + if (ValidTables.Count == 0) + return new Version(0, 0, 0); + + // FIXME - but the whole detection is borked really + return new Version(8, 0, 0); + + //If Errors is empty or if TableDefinitions tables + columns correspond to valid tables + columns then we're at current version + if (Errors.Any() == false || + (TableDefinitions.All(x => ValidTables.Contains(x.Name)) + && TableDefinitions.SelectMany(definition => definition.Columns).All(x => ValidColumns.Contains(x.Name)))) + return UmbracoVersion.Current; + + //If Errors contains umbracoApp or umbracoAppTree its pre-6.0.0 -> new Version(4, 10, 0); + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoApp") || x.Item2.InvariantEquals("umbracoAppTree")))) + { + //If Errors contains umbracoUser2app or umbracoAppTree foreignkey to umbracoApp exists its pre-4.8.0 -> new Version(4, 7, 0); + if (Errors.Any(x => + x.Item1.Equals("Constraint") + && (x.Item2.InvariantContains("umbracoUser2app_umbracoApp") + || x.Item2.InvariantContains("umbracoAppTree_umbracoApp")))) + { + return new Version(4, 7, 0); + } + + return new Version(4, 8, 0); + } + + //if the error is for umbracoServer + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoServer")))) + { + return new Version(6, 0, 0); + } + + //if the error indicates a problem with the column cmsMacroProperty.macroPropertyType then it is not version 7 + // since these columns get removed in v7 + if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMacroProperty,macroPropertyType")))) + { + //if the error is for this IX_umbracoNodeTrashed which is added in 6.2 AND in 7.1 but we do not have the above columns + // then it must mean that we aren't on 6.2 so must be 6.1 + if (Errors.Any(x => x.Item1.Equals("Index") && (x.Item2.InvariantEquals("IX_umbracoNodeTrashed")))) + { + return new Version(6, 1, 0); + } + else + { + //if there are no errors for that index, then the person must have 6.2 installed + return new Version(6, 2, 0); + } + } + + //if the error indicates a problem with the constraint FK_cms-OBSOLETE-Content_cmsContentType_nodeId then it is not version 7.2 + // since this gets added in 7.2.0 so it must be the previous version + if (Errors.Any(x => x.Item1.Equals("Constraint") && (x.Item2.InvariantEquals("FK_cms-OBSOLETE-Content_cmsContentType_nodeId")))) + { + return new Version(7, 0, 0); + } + + //if the error is for umbracoAccess it must be the previous version to 7.3 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoAccess")))) + { + return new Version(7, 2, 0); + } + + //if the error is for cms-OBSOLETE-PropertyData.dataDecimal it must be the previous version to 7.4 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cms-OBSOLETE-PropertyData,dataDecimal")))) + { + return new Version(7, 3, 0); + } + + //if the error is for umbracoRedirectUrl it must be the previous version to 7.5 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoRedirectUrl")))) + { + return new Version(7, 4, 0); + } + + //if the error indicates a problem with the column cmsMacroProperty.uniquePropertyId then it is not version 7.6 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMacroProperty,uniquePropertyId")))) + { + return new Version(7, 5, 0); + } + + //if the error is for umbracoUserGroup it must be the previous version to 7.7 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoUserStartNode")))) + { + return new Version(7, 6, 0); + } + + //if the error is for cmsMedia it must be the previous version to 7.8 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoMedia")))) + { + return new Version(7, 7, 0); + } + + //if the error is for isSensitive column it must be the previous version to 7.9 since that is when it is added + if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMemberType,isSensitive")))) + { + return new Version(7, 8, 0); + } + + return UmbracoVersion.Current; + } + + /// + /// Gets a summary of the schema validation result + /// + /// A string containing a human readable string with a summary message + public string GetSummary() + { + var sb = new StringBuilder(); + if (Errors.Any() == false) + { + sb.AppendLine("The database schema validation didn't find any errors."); + return sb.ToString(); + } + + //Table error summary + if (Errors.Any(x => x.Item1.Equals("Table"))) + { + sb.AppendLine("The following tables were found in the database, but are not in the current schema:"); + sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Table")).Select(x => x.Item2))); + sb.AppendLine(" "); + } + //Column error summary + if (Errors.Any(x => x.Item1.Equals("Column"))) + { + sb.AppendLine("The following columns were found in the database, but are not in the current schema:"); + sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Column")).Select(x => x.Item2))); + sb.AppendLine(" "); + } + //Constraint error summary + if (Errors.Any(x => x.Item1.Equals("Constraint"))) + { + sb.AppendLine("The following constraints (Primary Keys, Foreign Keys and Indexes) were found in the database, but are not in the current schema:"); + sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Constraint")).Select(x => x.Item2))); + sb.AppendLine(" "); + } + //Index error summary + if (Errors.Any(x => x.Item1.Equals("Index"))) + { + sb.AppendLine("The following indexes were found in the database, but are not in the current schema:"); + sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Index")).Select(x => x.Item2))); + sb.AppendLine(" "); + } + //Unknown constraint error summary + if (Errors.Any(x => x.Item1.Equals("Unknown"))) + { + sb.AppendLine("The following unknown constraints (Primary Keys, Foreign Keys and Indexes) were found in the database, but are not in the current schema:"); + sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Unknown")).Select(x => x.Item2))); + sb.AppendLine(" "); + } + + if (_sqlSyntax is MySqlSyntaxProvider) + { + sb.AppendLine("Please note that the constraints could not be validated because the current dataprovider is MySql."); + } + + return sb.ToString(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/MigrationBase.cs b/src/Umbraco.Core/Migrations/MigrationBase.cs new file mode 100644 index 0000000000..9fbee0ed92 --- /dev/null +++ b/src/Umbraco.Core/Migrations/MigrationBase.cs @@ -0,0 +1,103 @@ +using System; +using NPoco; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Expressions.Alter; +using Umbraco.Core.Migrations.Expressions.Create; +using Umbraco.Core.Migrations.Expressions.Delete; +using Umbraco.Core.Migrations.Expressions.Execute; +using Umbraco.Core.Migrations.Expressions.Insert; +using Umbraco.Core.Migrations.Expressions.Rename; +using Umbraco.Core.Migrations.Expressions.Update; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations +{ + /// + /// Provides a base class to all migrations. + /// + public abstract partial class MigrationBase : IMigration + { + /// + /// Initializes a new instance of the class. + /// + /// A migration context. + protected MigrationBase(IMigrationContext context) + { + Context = context; + } + + /// + /// Gets the migration context. + /// + protected IMigrationContext Context { get; } + + /// + /// Gets the logger. + /// + protected ILogger Logger => Context.Logger; + + /// + /// Gets the Sql syntax. + /// + protected ISqlSyntaxProvider SqlSyntax => Context.SqlContext.SqlSyntax; + + /// + /// Gets the database instance. + /// + protected IUmbracoDatabase Database => Context.Database; + + /// + /// Gets the database type. + /// + protected DatabaseType DatabaseType => Context.Database.DatabaseType; + + /// + /// Creates a new Sql statement. + /// + protected Sql Sql() => Context.SqlContext.Sql(); + + /// + /// Creates a new Sql statement with arguments. + /// + protected Sql Sql(string sql, params object[] args) => Context.SqlContext.Sql(sql, args); + + /// + public abstract void Migrate(); + + /// + /// Builds an Alter expression. + /// + public IAlterBuilder Alter => new AlterBuilder(Context); + + /// + /// Builds a Create expression. + /// + public ICreateBuilder Create => new CreateBuilder(Context); + + /// + /// Builds a Delete expression. + /// + public IDeleteBuilder Delete => new DeleteBuilder(Context); + + /// + /// Builds an Execute expression. + /// + public IExecuteBuilder Execute => new ExecuteBuilder(Context); + + /// + /// Builds an Insert expression. + /// + public IInsertBuilder Insert => new InsertBuilder(Context); + + /// + /// Builds a Rename expression. + /// + public IRenameBuilder Rename => new RenameBuilder(Context); + + /// + /// Builds an Update expression. + /// + public IUpdateBuilder Update => new UpdateBuilder(Context); + } +} diff --git a/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs new file mode 100644 index 0000000000..b1b405bcf4 --- /dev/null +++ b/src/Umbraco.Core/Migrations/MigrationBase_Extra.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations +{ + /// + /// Provides a base class to all migrations. + /// + public abstract partial class MigrationBase + { + // provides extra methods for migrations + + protected void AddColumn(string columnName) + { + var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, table.Name, columnName); + } + + protected void AddColumn(string tableName, string columnName) + { + var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, tableName, columnName); + } + + private void AddColumn(TableDefinition table, string tableName, string columnName) + { + if (ColumnExists(tableName, columnName)) return; + + var column = table.Columns.First(x => x.Name == columnName); + var createSql = SqlSyntax.Format(column); + + Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do(); + } + + protected void AddColumn(string columnName, out IEnumerable sqls) + { + var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, table.Name, columnName, out sqls); + } + + protected void AddColumn(string tableName, string columnName, out IEnumerable sqls) + { + var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + AddColumn(table, tableName, columnName, out sqls); + } + + private void AddColumn(TableDefinition table, string tableName, string columnName, out IEnumerable sqls) + { + if (ColumnExists(tableName, columnName)) + { + sqls = Enumerable.Empty(); + return; + } + + var column = table.Columns.First(x => x.Name == columnName); + var createSql = SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out sqls); + Execute.Sql(string.Format(SqlSyntax.AddColumn, SqlSyntax.GetQuotedTableName(tableName), createSql)).Do(); + } + + protected void AlterColumn(string tableName, string columnName) + { + var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + var column = table.Columns.First(x => x.Name == columnName); + SqlSyntax.Format(column, SqlSyntax.GetQuotedTableName(tableName), out var sqls); + foreach (var sql in sqls) + Execute.Sql(sql).Do(); + } + + protected void ReplaceColumn(string tableName, string currentName, string newName) + { + AddColumn(tableName, newName, out var sqls); + Execute.Sql($"UPDATE {SqlSyntax.GetQuotedTableName(tableName)} SET {SqlSyntax.GetQuotedColumnName(newName)}={SqlSyntax.GetQuotedColumnName(currentName)}").Do(); + foreach (var sql in sqls) Execute.Sql(sql).Do(); + Delete.Column(currentName).FromTable(tableName).Do(); + } + + protected bool TableExists(string tableName) + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database); + return tables.Any(x => x.InvariantEquals(tableName)); + } + + protected bool ColumnExists(string tableName, string columnName) + { + // that's ok even on MySql + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + return columns.Any(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName)); + } + + protected string ColumnType(string tableName, string columnName) + { + // that's ok even on MySql + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + var column = columns.FirstOrDefault(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName)); + return column?.DataType; + } + } +} diff --git a/src/Umbraco.Core/Migrations/MigrationBuilder.cs b/src/Umbraco.Core/Migrations/MigrationBuilder.cs new file mode 100644 index 0000000000..3d8c88c771 --- /dev/null +++ b/src/Umbraco.Core/Migrations/MigrationBuilder.cs @@ -0,0 +1,36 @@ +using System; +using LightInject; + +namespace Umbraco.Core.Migrations +{ + public class MigrationBuilder : IMigrationBuilder + { + private readonly IServiceContainer _container; + + public MigrationBuilder(IServiceContainer container) + { + _container = container; + + // because the builder should be "per container" this ctor should run only once per container. + // + // note: constructor dependencies do NOT work with lifetimes other than transient + // see https://github.com/seesharper/LightInject/issues/294 + // + // resolve ctor dependency from GetInstance() runtimeArguments, if possible - 'factory' is + // the container, 'info' describes the ctor argument, and 'args' contains the args that + // were passed to GetInstance() - use first arg if it is the right type. + // + // for IMigrationContext + container.RegisterConstructorDependency((factory, info, args) => args.Length > 0 ? args[0] as IMigrationContext : null); + } + + public IMigration Build(Type migrationType, IMigrationContext context) + { + // LightInject .Create() is a shortcut for .Register() + .GetInstance() + // but it does not support parameters, so we do it ourselves here + + _container.Register(migrationType); + return (IMigration) _container.GetInstance(migrationType, new object[] { context }); + } + } +} diff --git a/src/Umbraco.Core/Migrations/MigrationContext.cs b/src/Umbraco.Core/Migrations/MigrationContext.cs new file mode 100644 index 0000000000..d0802c813d --- /dev/null +++ b/src/Umbraco.Core/Migrations/MigrationContext.cs @@ -0,0 +1,23 @@ +using System; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Migrations +{ + internal class MigrationContext : IMigrationContext + { + public MigrationContext(IUmbracoDatabase database, ILogger logger) + { + Database = database ?? throw new ArgumentNullException(nameof(database)); + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public ILogger Logger { get; } + + public IUmbracoDatabase Database { get; } + + public ISqlContext SqlContext => Database.SqlContext; + + public int Index { get; set; } + } +} diff --git a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs new file mode 100644 index 0000000000..351d9e9f8a --- /dev/null +++ b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using NPoco; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations +{ + /// + /// Provides a base class for migration expressions. + /// + public abstract class MigrationExpressionBase : IMigrationExpression + { + private bool _executed; + private List _expressions; + + protected MigrationExpressionBase(IMigrationContext context) + { + Context = context ?? throw new ArgumentNullException(nameof(context)); + } + + protected IMigrationContext Context { get; } + + protected ILogger Logger => Context.Logger; + + protected ISqlSyntaxProvider SqlSyntax => Context.Database.SqlContext.SqlSyntax; + + protected IUmbracoDatabase Database => Context.Database; + + public DatabaseType DatabaseType => Context.Database.DatabaseType; + + public List Expressions => _expressions ?? (_expressions = new List()); + + public virtual string Process(IMigrationContext context) + { + return ToString(); + } + + protected virtual string GetSql() + { + return ToString(); + } + + public void Execute() + { + if (_executed) + throw new InvalidOperationException("This expression has already been executed."); + _executed = true; + + var sql = GetSql(); + + if (string.IsNullOrWhiteSpace(sql)) + { + Logger.Info(GetType(), $"SQL [{Context.Index}]: "); + } + else + { + // split multiple statements - required for SQL CE + // http://stackoverflow.com/questions/13665491/sql-ce-inconsistent-with-multiple-statements + var stmtBuilder = new StringBuilder(); + using (var reader = new StringReader(sql)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + if (line.Trim().Equals("GO", StringComparison.OrdinalIgnoreCase)) + ExecuteStatement(stmtBuilder); + else + stmtBuilder.Append(line); + } + + if (stmtBuilder.Length > 0) + ExecuteStatement(stmtBuilder); + } + } + + Context.Index++; + + if (_expressions == null) + return; + + foreach (var expression in _expressions) + expression.Execute(); + } + + private void ExecuteStatement(StringBuilder stmtBuilder) + { + var stmt = stmtBuilder.ToString(); + Logger.Info(GetType(), $"SQL [{Context.Index}]: {stmt}"); + Database.Execute(stmt); + stmtBuilder.Clear(); + } + + protected void AppendStatementSeparator(StringBuilder stmtBuilder) + { + stmtBuilder.AppendLine(";"); + if (DatabaseType.IsSqlServerOrCe()) + stmtBuilder.AppendLine("GO"); + } + + /// + /// This might be useful in the future if we add it to the interface, but for now it's used to hack the DeleteAppTables & DeleteForeignKeyExpression + /// to ensure they are not executed twice. + /// + internal string Name { get; set; } + + protected string GetQuotedValue(object val) + { + if (val == null) return "NULL"; + + var type = val.GetType(); + + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + return ((bool)val) ? "1" : "0"; + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + return val.ToString(); + case TypeCode.DateTime: + return SqlSyntax.GetQuotedValue(SqlSyntax.FormatDateTime((DateTime) val)); + default: + return SqlSyntax.GetQuotedValue(val.ToString()); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/MigrationPlan.cs b/src/Umbraco.Core/Migrations/MigrationPlan.cs new file mode 100644 index 0000000000..4ca900aecd --- /dev/null +++ b/src/Umbraco.Core/Migrations/MigrationPlan.cs @@ -0,0 +1,252 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Migrations +{ + /// + /// Represents a migration plan. + /// + public class MigrationPlan + { + private readonly IMigrationBuilder _migrationBuilder; + private readonly ILogger _logger; + private readonly Dictionary _transitions = new Dictionary(); + + private string _prevState; + private string _finalState; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the plan. + /// The plan cannot be executed. Use this constructor e.g. when only validating the plan, + /// or trying to get its final state, without actually needing to execute it. + public MigrationPlan(string name) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + Name = name; + + // ReSharper disable once VirtualMemberCallInConstructor + // (accepted) + DefinePlan(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the plan. + /// A migration builder. + /// A logger. + /// The plan can be executed. + public MigrationPlan(string name, IMigrationBuilder migrationBuilder, ILogger logger) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + Name = name; + _migrationBuilder = migrationBuilder ?? throw new ArgumentNullException(nameof(migrationBuilder)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + // ReSharper disable once VirtualMemberCallInConstructor + // (accepted) + DefinePlan(); + } + + /// + /// Defines the plan. + /// + protected virtual void DefinePlan() { } + + /// + /// Gets the name of the plan. + /// + public string Name { get; } + + /// + /// Adds an empty migration from source to target state. + /// + public MigrationPlan Add(string sourceState, string targetState) + => Add(sourceState, targetState); + + /// + /// Adds a migration from source to target state. + /// + public MigrationPlan Add(string sourceState, string targetState) + where TMigration : IMigration + => Add(sourceState, targetState, typeof(TMigration)); + + /// + /// Adds a migration from source to target state. + /// + public MigrationPlan Add(string sourceState, string targetState, Type migration) + { + if (sourceState == null) throw new ArgumentNullException(nameof(sourceState)); + if (string.IsNullOrWhiteSpace(targetState)) throw new ArgumentNullOrEmptyException(nameof(targetState)); + if (sourceState == targetState) throw new ArgumentException("Source and target state cannot be identical."); + if (migration == null) throw new ArgumentNullException(nameof(migration)); + if (!migration.Implements()) throw new ArgumentException($"Type {migration.Name} does not implement IMigration.", nameof(migration)); + + sourceState = sourceState.Trim(); + targetState = targetState.Trim(); + + // throw if we already have a transition for that state which is not null, + // null is used to keep track of the last step of the chain + if (_transitions.ContainsKey(sourceState) && _transitions[sourceState] != null) + throw new InvalidOperationException($"A transition from state \"{sourceState}\" has already been defined."); + + // register the transition + _transitions[sourceState] = new Transition + { + SourceState = sourceState, + TargetState = targetState, + MigrationType = migration + }; + + // register the target state if we don't know it already + // this is how we keep track of the final state - because + // transitions could be defined in any order, that might + // be overriden afterwards. + if (!_transitions.ContainsKey(targetState)) + _transitions.Add(targetState, null); + + _prevState = targetState; + _finalState = null; + + return this; + } + + /// + /// Chains an empty migration from chain to target state. + /// + public MigrationPlan Chain(string targetState) + => Chain(targetState); + + /// + /// Chains a migration from chain to target state. + /// + public MigrationPlan Chain(string targetState) + where TMigration : IMigration + => Chain(targetState, typeof(TMigration)); + + /// + /// Chains a migration from chain to target state. + /// + public MigrationPlan Chain(string targetState, Type migration) + => Add(_prevState, targetState, migration); + + /// + /// Sets the chain state. + /// + public MigrationPlan From(string sourceState) + { + _prevState = sourceState ?? throw new ArgumentNullException(nameof(sourceState)); + return this; + } + + /// + /// Gets the initial state. + /// + /// The initial state is the state when no entry for the plan + /// could be found in the database (i.e. the plan has never run). + public virtual string InitialState => string.Empty; + + /// + /// Gets the final state. + /// + public string FinalState => _finalState ?? (_finalState = Validate()); + + /// + /// Validates the plan. + /// + /// The plan's final state. + public string Validate() + { + // quick check for dead ends - a dead end is a transition that has a target state + // that is not null and does not match any source state. such a target state has + // been registered as a source state with a null transition. so there should be only + // one. + string finalState = null; + foreach (var kvp in _transitions.Where(x => x.Value == null)) + { + if (finalState == null) + finalState = kvp.Key; + else + throw new Exception("Multiple final states have been detected."); + } + + // now check for loops + var verified = new List(); + foreach (var transition in _transitions.Values) + { + if (transition == null || verified.Contains(transition.SourceState)) continue; + + var visited = new List { transition.SourceState }; + var nextTransition = _transitions[transition.TargetState]; + while (nextTransition != null && !verified.Contains(nextTransition.SourceState)) + { + if (visited.Contains(nextTransition.SourceState)) + throw new Exception("A loop has been detected."); + visited.Add(nextTransition.SourceState); + nextTransition = _transitions[nextTransition.TargetState]; + } + verified.AddRange(visited); + } + + return finalState; + } + + /// + /// Executes the plan. + /// + /// A scope. + /// The state to start execution at. + /// The final state. + /// The plan executes within the scope, which must then be completed. + public string Execute(IScope scope, string fromState) + { + Validate(); + + if (_migrationBuilder == null || _logger == null) + throw new InvalidOperationException("Cannot execute a non-executing plan."); + + _logger.Info($"Starting \"{Name}\"..."); + var origState = fromState ?? string.Empty; + var info = "At " + (string.IsNullOrWhiteSpace(origState) ? "origin" : ("\"" + origState + "\"")) + "."; + info = info.Replace("{", "{{").Replace("}", "}}"); // stupid log4net + _logger.Info(info); + + if (!_transitions.TryGetValue(origState, out var transition)) + throw new Exception($"Unknown state \"{origState}\"."); + + var context = new MigrationContext(scope.Database, _logger); + + while (transition != null) + { + var migration = _migrationBuilder.Build(transition.MigrationType, context); + migration.Migrate(); + + var nextState = transition.TargetState; + origState = nextState; + + _logger.Info($"At \"{origState}\"."); + + if (!_transitions.TryGetValue(origState, out transition)) + throw new Exception($"Unknown state \"{origState}\"."); + } + + _logger.Info("Done (pending scope completion)."); + + // fixme - what about post-migrations? + return origState; + } + + private class Transition + { + public string SourceState { get; set; } + public string TargetState { get; set; } + public Type MigrationType { get; set; } + } + } +} diff --git a/src/Umbraco.Core/Migrations/NoopMigration.cs b/src/Umbraco.Core/Migrations/NoopMigration.cs new file mode 100644 index 0000000000..7a2a7d5875 --- /dev/null +++ b/src/Umbraco.Core/Migrations/NoopMigration.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Migrations +{ + public class NoopMigration : IMigration + { + public void Migrate() + { + // nop + } + } +} diff --git a/src/Umbraco.Core/Migrations/PostMigrationCollection.cs b/src/Umbraco.Core/Migrations/PostMigrationCollection.cs new file mode 100644 index 0000000000..488bdfd72d --- /dev/null +++ b/src/Umbraco.Core/Migrations/PostMigrationCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Migrations +{ + public class PostMigrationCollection : BuilderCollectionBase + { + public PostMigrationCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs b/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs new file mode 100644 index 0000000000..63cdaf4454 --- /dev/null +++ b/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs @@ -0,0 +1,16 @@ +using LightInject; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Migrations +{ + public class PostMigrationCollectionBuilder : LazyCollectionBuilderBase + { + public PostMigrationCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override PostMigrationCollectionBuilder This => this; + + protected override ILifetime CollectionLifetime => null; // transient + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs new file mode 100644 index 0000000000..a23be132fd --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -0,0 +1,208 @@ +using System; +using System.Configuration; +using Semver; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Upgrade.V_7_10_0; +using Umbraco.Core.Migrations.Upgrade.V_7_5_0; +using Umbraco.Core.Migrations.Upgrade.V_7_5_5; +using Umbraco.Core.Migrations.Upgrade.V_7_6_0; +using Umbraco.Core.Migrations.Upgrade.V_7_7_0; +using Umbraco.Core.Migrations.Upgrade.V_7_8_0; +using Umbraco.Core.Migrations.Upgrade.V_7_9_0; +using Umbraco.Core.Migrations.Upgrade.V_8_0_0; + +namespace Umbraco.Core.Migrations.Upgrade +{ + /// + /// Represents Umbraco's migration plan. + /// + public class UmbracoPlan : MigrationPlan + { + /// + /// Initializes a new instance of the class. + /// + public UmbracoPlan() + : base(Constants.System.UmbracoUpgradePlanName) + { } + + /// + /// Initializes a new instance of the class. + /// + public UmbracoPlan(IMigrationBuilder migrationBuilder, ILogger logger) + : base(Constants.System.UmbracoUpgradePlanName, migrationBuilder, logger) + { } + + /// + /// + /// The default initial state in plans is string.Empty. + /// When upgrading from version 7, we want to use specific initial states + /// that are e.g. "{orig-7.9.3}", "{orig-7.11.1}", etc. so we can chain the proper + /// migrations. + /// This is also where we detect the current version, and reject invalid + /// upgrades (from a tool old version, or going back in time, etc). + /// + public override string InitialState + { + get + { + // no state in database yet - assume we have something in web.config that makes some sense + if (!SemVersion.TryParse(ConfigurationManager.AppSettings["umbracoConfigurationStatus"], out var currentVersion)) + throw new InvalidOperationException("Could not get current version from web.config umbracoConfigurationStatus appSetting."); + + // must be at least 7.? - fixme adjust when releasing + if (currentVersion < new SemVersion(7, 10)) + throw new InvalidOperationException($"Version {currentVersion} cannot be upgraded to {UmbracoVersion.SemanticVersion}."); + + // cannot go back in time + if (currentVersion > UmbracoVersion.SemanticVersion) + throw new InvalidOperationException($"Version {currentVersion} cannot be downgraded to {UmbracoVersion.SemanticVersion}."); + + switch (currentVersion.Major) + { + case 7: + // upgrading from version 7 + return "{orig-" + currentVersion + "}"; + case 8: // fixme remove when releasing + // upgrading from version 8 + // should never happen, this is very temp and for my own website - zpqrtbnk + return "{04F54303-3055-4700-8F76-35A37F232FF5}"; // right before the variants migration + default: + throw new InvalidOperationException($"Version {currentVersion} is not supported by the migration plan."); + } + + } + } + + /// + protected override void DefinePlan() + { + // NOTE: MODIFYING THE PLAN + // + // Please take great care when modifying the plan! + // + // * Creating a migration for version 8: + // Append the migration to the main version 8 chain, using a new guid. + // Update the final state (see end of file) to that guid. + // Append the migration to version 7 upgrade chains. + // * Porting a migration from version 7: + // Append the migration to the main version 8 chain, using a new guid. + // Update the final state (see end of file) to that guid. + // Update all init-7.x.y chains. + + + // UPGRADE FROM 7, OLDEST + // + // When upgrading from version 7, the state is automatically set to {init-7.x.y} where + // 7.x.y is the version. We need to define a chain starting at that state and taking + // us to version 8. And we need such a chain for each 7.x.y version that can be upgraded + // to version 8, bearing in mind that new releases of version 7 will probably be + // created *after* the first released of version 8. + // + // fixme adjust when releasing the first public (alpha?) version + + // we don't support upgrading from versions older than 7.? + // and then we only need to run v8 migrations + // + From("{init-7.10.0}"); + Chain("{7C447271-CA3F-4A6A-A913-5D77015655CB}"); + Chain("{CBFF58A2-7B50-4F75-8E98-249920DB0F37}"); + Chain("{3D18920C-E84D-405C-A06A-B7CEE52FE5DD}"); + + Chain("{FB0A5429-587E-4BD0-8A67-20F0E7E62FF7}"); + Chain("{F0C42457-6A3B-4912-A7EA-F27ED85A2092}"); + Chain("{8640C9E4-A1C0-4C59-99BB-609B4E604981}"); + Chain("{DD1B99AF-8106-4E00-BAC7-A43003EA07F8}"); + Chain("{9DF05B77-11D1-475C-A00A-B656AF7E0908}"); + Chain("{6FE3EF34-44A0-4992-B379-B40BC4EF1C4D}"); + Chain("{7F59355A-0EC9-4438-8157-EB517E6D2727}"); + + // must chain to v8 final state (see at end of file) + Chain("{79591E91-01EA-43F7-AC58-7BD286DB1E77}"); + + + // UPGRADE FROM 7, MORE RECENT + // + // handle more recent versions - not yet + + // for more recent versions... + // 7.10.x = same as 7.10.0? + //From("{init-7.10.1}").Chain("{init-7.10.0}"); + + + // VERSION 8 PLAN + // + // this is the master Umbraco migration plan, starting from the very first version 8 + // release, which was a pre-pre-alpha, years ago. It contains migrations created + // for version 8, along with migrations ported from version 7 as version 7 evolves. + // It is therefore *normal* that some pure version 8 migrations are mixed with + // migrations merged from version 7. + // + // new migrations should always be *appended* to the *end* of the chain. + + // 8.0.0 + From("{init-origin}"); // "origin" was 7.4.something + Chain("{98347B5E-65BF-4DD7-BB43-A09CB7AF4FCA}"); + Chain("{1E8165C4-942D-40DC-AC76-C5FF8831E400}"); + Chain("{39E15568-7AAD-4D54-81D0-758CCFC529F8}"); + Chain("{55C3F97D-BDA7-4FB1-A743-B0456B56EAA3}"); + + // merging from 7.5.0 + Chain("{287F9E39-F673-42F7-908C-21659AB13B13}"); + Chain("{2D08588A-AD90-479C-9F6E-A99B60BA7226}"); + Chain("{2D917FF8-AC81-4C00-A407-1F4B1DF6089C}"); + + // merging from 7.5.5 + Chain("{44484C32-EEB3-4A12-B1CB-11E02CE22AB2}"); + + // merging from 7.6.0 + Chain("{3586E4E9-2922-49EB-8E2A-A530CE6DBDE0}"); + Chain("{D4A5674F-654D-4CC7-85E5-CFDBC533A318}"); + Chain("{7F828EDD-6622-4A8D-AD80-EEAF46C11680}"); + Chain("{F30AC223-D277-4D1F-B2AB-F0F0D3546CE1}"); + Chain("{7C27E310-CF48-4637-A22E-8D87355161C1}"); + Chain("{7D2ABA16-EE48-4569-8827-E81370FC4871}"); + Chain("{02879EDF-13A8-43AF-87A5-DD85723D0016}"); + Chain("{5496C6CC-3AE0-4789-AF49-5BB4E28FA424}"); + Chain("{8995332B-085E-4C0C-849E-9A77E79F4293}"); + + // merging from 7.7.0 + Chain("{74319856-7681-46B1-AA0D-F7E896FBE6A1}"); + Chain("{0427B0A2-994A-4AB4-BFF3-31B20614F6C9}"); + Chain("{F0D6F782-E432-46DE-A3A7-2AF06DB8853B}"); + Chain("{AEB2BA2B-71E4-4B1B-AB6C-CEFB7F06FEEB}"); + Chain("{B5A6C799-B91E-496F-A1FE-7B4FE98BF6AB}"); + Chain("{04F54303-3055-4700-8F76-35A37F232FF5}"); + + // 8.0.0 + Chain("{6550C7E8-77B7-4DE3-9B58-E31C81CB9504}"); + Chain("{E3388F73-89FA-45FE-A539-C7FACC8D63DD}"); + Chain("{82C4BA1D-7720-46B1-BBD7-07F3F73800E6}"); + Chain("{139F26D7-7E08-48E3-81D9-E50A21A72F67}"); + Chain("{CC1B1201-1328-443C-954A-E0BBB8CCC1B5}"); + Chain("{CA7DB949-3EF4-403D-8464-F9BA36A52E87}"); + Chain("{7F0BF916-F64E-4B25-864A-170D6E6B68E5}"); + + // merging from 7.8.0 + Chain("{FDCB727A-EFB6-49F3-89E4-A346503AB849}"); + Chain("{2A796A08-4FE4-4783-A1A5-B8A6C8AA4A92}"); + Chain("{1A46A98B-2AAB-4C8E-870F-A2D55A97FD1F}"); + Chain("{0AE053F6-2683-4234-87B2-E963F8CE9498}"); + Chain("{D454541C-15C5-41CF-8109-937F26A78E71}"); + + // merging from 7.9.0 + Chain("{89A728D1-FF4C-4155-A269-62CC09AD2131}"); + Chain("{FD8631BC-0388-425C-A451-5F58574F6F05}"); + Chain("{2821F53E-C58B-4812-B184-9CD240F990D7}"); + Chain("{8918450B-3DA0-4BB7-886A-6FA8B7E4186E}"); + + // mergin from 7.10.0 + Chain("{79591E91-01EA-43F7-AC58-7BD286DB1E77}"); + + // FINAL STATE - MUST MATCH LAST ONE ABOVE ! + // whenever this changes, update all references in this file! + + Add(string.Empty, "{79591E91-01EA-43F7-AC58-7BD286DB1E77}"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoUpgrader.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoUpgrader.cs new file mode 100644 index 0000000000..b24ad2a20e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoUpgrader.cs @@ -0,0 +1,31 @@ +using System; +using System.Configuration; +using Semver; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Migrations.Upgrade +{ + public class UmbracoUpgrader : Upgrader + { + public UmbracoUpgrader(IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, PostMigrationCollection postMigrations, ILogger logger) + : base(scopeProvider, migrationBuilder, keyValueService, postMigrations, logger) + { } + + protected override MigrationPlan GetPlan() + { + return new UmbracoPlan(MigrationBuilder, Logger); + } + + protected override (SemVersion, SemVersion) GetVersions() + { + // assume we have something in web.config that makes some sense + if (!SemVersion.TryParse(ConfigurationManager.AppSettings["umbracoConfigurationStatus"], out var currentVersion)) + throw new InvalidOperationException("Could not get current version from web.config umbracoConfigurationStatus appSetting."); + + return (currentVersion, UmbracoVersion.SemanticVersion); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/Upgrader.cs b/src/Umbraco.Core/Migrations/Upgrade/Upgrader.cs new file mode 100644 index 0000000000..7481444bd8 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/Upgrader.cs @@ -0,0 +1,77 @@ +using System; +using Semver; +using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Migrations.Upgrade +{ + public abstract class Upgrader + { + private readonly IKeyValueService _keyValueService; + private readonly PostMigrationCollection _postMigrations; + private MigrationPlan _plan; + + protected Upgrader(IScopeProvider scopeProvider, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, PostMigrationCollection postMigrations, ILogger logger) + { + ScopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); + MigrationBuilder = migrationBuilder ?? throw new ArgumentNullException(nameof(migrationBuilder)); + _keyValueService = keyValueService ?? throw new ArgumentNullException(nameof(keyValueService)); + _postMigrations = postMigrations ?? throw new ArgumentNullException(nameof(postMigrations)); + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public string Name => Plan.Name; + + public string StateValueKey => GetStateValueKey(Plan); + + protected IScopeProvider ScopeProvider { get; } + + protected IMigrationBuilder MigrationBuilder { get; } + + protected ILogger Logger { get; } + + protected MigrationPlan Plan => _plan ?? (_plan = GetPlan()); + + protected abstract MigrationPlan GetPlan(); + protected abstract (SemVersion, SemVersion) GetVersions(); + + public void Execute() + { + var plan = Plan; + + using (var scope = ScopeProvider.CreateScope()) + { + // read current state + var currentState = _keyValueService.GetValue(StateValueKey); + var forceState = false; + + if (currentState == null) + { + currentState = plan.InitialState; + forceState = true; + } + + // execute plan + var state = plan.Execute(scope, currentState); + if (string.IsNullOrWhiteSpace(state)) + throw new Exception("Plan execution returned an invalid null or empty state."); + + // save new state + if (forceState) + _keyValueService.SetValue(StateValueKey, state); + else + _keyValueService.SetValue(StateValueKey, currentState, state); + + // run post-migrations + (var originVersion, var targetVersion) = GetVersions(); + foreach (var postMigration in _postMigrations) + postMigration.Execute(Name, scope, originVersion, targetVersion, Logger); + + scope.Complete(); + } + } + + public static string GetStateValueKey(MigrationPlan plan) => "Umbraco.Core.Upgrader.State+" + plan.Name; + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_10_0/RenamePreviewFolder.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_10_0/RenamePreviewFolder.cs new file mode 100644 index 0000000000..a73fb8a325 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_10_0/RenamePreviewFolder.cs @@ -0,0 +1,36 @@ +using System.IO; +using Umbraco.Core.IO; +using File = System.IO.File; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_10_0 +{ + /// + /// Renames the preview folder containing static html files to ensure it does not interfere with the MVC route + /// that is now supposed to render these views dynamically. We don't want to delete as people may have made + /// customizations to these files that would need to be migrated to the new .cshtml view files. + /// + public class RenamePreviewFolder : MigrationBase + { + public RenamePreviewFolder(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var previewFolderPath = IOHelper.MapPath(SystemDirectories.Umbraco + "/preview"); + if (Directory.Exists(previewFolderPath)) + { + var newPath = previewFolderPath.Replace("preview", "preview.old"); + if (Directory.Exists(newPath) == false) + { + Directory.Move(previewFolderPath, newPath); + var readmeText = + $"Static html files used for preview and canvas editing functionality no longer live in this directory.\r\n" + + $"Instead they have been recreated as MVC views and can now be found in '~/Umbraco/Views/Preview'.\r\n" + + $"See issue: http://issues.umbraco.org/issue/U4-11090"; + File.WriteAllText(Path.Combine(newPath, "readme.txt"), readmeText); + } + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/AddRedirectUrlTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/AddRedirectUrlTable.cs new file mode 100644 index 0000000000..7040874a74 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/AddRedirectUrlTable.cs @@ -0,0 +1,50 @@ +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_5_0 +{ + public class AddRedirectUrlTable : MigrationBase + { + public AddRedirectUrlTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var database = Database; + var umbracoRedirectUrlTableName = "umbracoRedirectUrl"; + + var tables = SqlSyntax.GetTablesInSchema(database).ToArray(); + + if (tables.InvariantContains(umbracoRedirectUrlTableName)) + { + var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); + if (columns.Any(x => x.TableName.InvariantEquals(umbracoRedirectUrlTableName) && x.ColumnName.InvariantEquals("id") && x.DataType == "uniqueidentifier")) + return; + Delete.Table(umbracoRedirectUrlTableName).Do(); + } + + Create.Table(umbracoRedirectUrlTableName) + .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_" + umbracoRedirectUrlTableName) + .WithColumn("createDateUtc").AsDateTime().NotNullable() + .WithColumn("url").AsString(2048).NotNullable() + .WithColumn("contentKey").AsGuid().NotNullable() + .WithColumn("urlHash").AsString(40).NotNullable() + .Do(); + + Create.Index("IX_" + umbracoRedirectUrlTableName).OnTable(umbracoRedirectUrlTableName) + .OnColumn("urlHash") + .Ascending() + .OnColumn("contentKey") + .Ascending() + .OnColumn("createDateUtc") + .Descending() + .WithOptions().NonClustered() + .Do(); + + Create.ForeignKey("FK_" + umbracoRedirectUrlTableName) + .FromTable(umbracoRedirectUrlTableName).ForeignColumn("contentKey") + .ToTable("umbracoNode").PrimaryColumn("uniqueID") + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/RemoveStylesheetDataAndTablesAgain.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/RemoveStylesheetDataAndTablesAgain.cs new file mode 100644 index 0000000000..77fb24ddbc --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/RemoveStylesheetDataAndTablesAgain.cs @@ -0,0 +1,35 @@ +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_5_0 +{ + /// + /// This is here to re-remove these tables, we dropped them in 7.3 but new installs created them again so we're going to re-drop them + /// + public class RemoveStylesheetDataAndTablesAgain : MigrationBase + { + public RemoveStylesheetDataAndTablesAgain(IMigrationContext context) + : base(context) + { + } + + public override void Migrate() + { + //Clear all stylesheet data if the tables exist + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("cmsStylesheetProperty")) + { + Delete.FromTable("cmsStylesheetProperty").AllRows().Do(); + Delete.FromTable("umbracoNode").Row(new { nodeObjectType = Constants.ObjectTypes.StylesheetProperty }).Do(); + + Delete.Table("cmsStylesheetProperty").Do(); + } + if (tables.InvariantContains("cmsStylesheet")) + { + Delete.FromTable("cmsStylesheet").AllRows().Do(); + Delete.FromTable("umbracoNode").Row(new { nodeObjectType = Constants.ObjectTypes.Stylesheet }).Do(); + + Delete.Table("cmsStylesheet").Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/UpdateUniqueIndexOnPropertyData.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/UpdateUniqueIndexOnPropertyData.cs new file mode 100644 index 0000000000..73f03e96c6 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/UpdateUniqueIndexOnPropertyData.cs @@ -0,0 +1,62 @@ +using System.Linq; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_5_0 +{ + /// + /// See: http://issues.umbraco.org/issue/U4-8522 + /// + public class UpdateUniqueIndexOnPropertyData : MigrationBase + { + public UpdateUniqueIndexOnPropertyData(IMigrationContext context) + : base(context) + { + } + + public override void Migrate() + { + //Clear all stylesheet data if the tables exist + //tuple = tablename, indexname, columnname, unique + var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); + var found = indexes.FirstOrDefault( + x => x.Item1.InvariantEquals("cmsPropertyData") + && x.Item2.InvariantEquals("IX_cmsPropertyData_1") + //we're searching for the old index which is not unique + && x.Item4 == false); + + if (found != null) + { + //Check for MySQL + if (DatabaseType.IsMySql()) + { + //Use the special double nested sub query for MySQL since that is the only + //way delete sub queries works + var delPropQry = SqlSyntax.GetDeleteSubquery( + "cmsPropertyData", + "id", + Sql("SELECT MIN(id) FROM cmsPropertyData GROUP BY nodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL"), + WhereInType.NotIn); + Database.Execute(delPropQry.SQL); + } + else + { + //NOTE: Even though the above will work for MSSQL, we are not going to execute the + // nested delete sub query logic since it will be slower and there could be a ton of property + // data here so needs to be as fast as possible. + Database.Execute("DELETE FROM cmsPropertyData WHERE id NOT IN (SELECT MIN(id) FROM cmsPropertyData GROUP BY nodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL)"); + } + + //we need to re create this index + Delete.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData").Do(); + Create.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData") + .OnColumn("nodeId").Ascending() + .OnColumn("versionId").Ascending() + .OnColumn("propertytypeid").Ascending() + .WithOptions().NonClustered() + .WithOptions().Unique() + .Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_5/UpdateAllowedMediaTypesAtRoot.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_5/UpdateAllowedMediaTypesAtRoot.cs new file mode 100644 index 0000000000..362b8e251c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_5/UpdateAllowedMediaTypesAtRoot.cs @@ -0,0 +1,17 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_7_5_5 +{ + /// + /// See: http://issues.umbraco.org/issue/U4-4196 + /// + public class UpdateAllowedMediaTypesAtRoot : MigrationBase + { + public UpdateAllowedMediaTypesAtRoot(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + Database.Execute("UPDATE cmsContentType SET allowAtRoot = 1 WHERE nodeId = 1032 OR nodeId = 1033"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToCmsMemberLoginName.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToCmsMemberLoginName.cs new file mode 100644 index 0000000000..05abc5ec83 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToCmsMemberLoginName.cs @@ -0,0 +1,43 @@ +using System.Linq; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + public class AddIndexToCmsMemberLoginName : MigrationBase + { + public AddIndexToCmsMemberLoginName(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var database = Database; + + //Now we need to check if we can actually d6 this because we won't be able to if there's data in there that is too long + //http://issues.umbraco.org/issue/U4-9758 + + var colLen = SqlSyntax is MySqlSyntaxProvider + ? database.ExecuteScalar("select max(LENGTH(LoginName)) from cmsMember") + : database.ExecuteScalar("select max(datalength(LoginName)) from cmsMember"); + + if (colLen < 900 == false && colLen != null) + { + return; + } + + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")) == false) + { + //we can apply the index + Create.Index("IX_cmsMember_LoginName").OnTable("cmsMember") + .OnColumn("LoginName") + .Ascending() + .WithOptions() + .NonClustered() + .Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUmbracoNodePath.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUmbracoNodePath.cs new file mode 100644 index 0000000000..191f410b47 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUmbracoNodePath.cs @@ -0,0 +1,28 @@ +using System.Linq; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + public class AddIndexToUmbracoNodePath : MigrationBase + { + public AddIndexToUmbracoNodePath(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodePath")) == false) + { + Create.Index("IX_umbracoNodePath").OnTable("umbracoNode") + .OnColumn("path") + .Ascending() + .WithOptions() + .NonClustered() + .Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUser2NodePermission.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUser2NodePermission.cs new file mode 100644 index 0000000000..87b392b09c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUser2NodePermission.cs @@ -0,0 +1,28 @@ +using System.Linq; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + public class AddIndexToUser2NodePermission : MigrationBase + { + public AddIndexToUser2NodePermission(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoUser2NodePermission_nodeId")) == false) + { + Create.Index("IX_umbracoUser2NodePermission_nodeId").OnTable("umbracoUser2NodePermission") + .OnColumn("nodeId") + .Ascending() + .WithOptions() + .NonClustered() + .Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexesToUmbracoRelationTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexesToUmbracoRelationTables.cs new file mode 100644 index 0000000000..17dd060376 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexesToUmbracoRelationTables.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + public class AddIndexesToUmbracoRelationTables : MigrationBase + { + public AddIndexesToUmbracoRelationTables(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database).ToArray(); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelation_parentChildType")) == false) + { + //This will remove any corrupt/duplicate data in the relation table before the index is applied + //Ensure this executes in a defered block which will be done inside of the migration transaction + var database = Database; + + //We need to check if this index has corrupted data and then clear that data + var duplicates = database.Fetch("SELECT parentId,childId,relType FROM umbracoRelation GROUP BY parentId,childId,relType HAVING COUNT(*) > 1"); + if (duplicates.Count > 0) + { + //need to fix this there cannot be duplicates so we'll take the latest entries, it's really not going to matter though + foreach (var duplicate in duplicates) + { + var ids = database.Fetch("SELECT id FROM umbracoRelation WHERE parentId=@parentId AND childId=@childId AND relType=@relType ORDER BY datetime DESC", + new { parentId = duplicate.parentId, childId = duplicate.childId, relType = duplicate.relType }); + + if (ids.Count == 1) + { + //this is just a safety check, this should absolutely never happen + throw new InvalidOperationException("Duplicates were detected but could not be discovered"); + } + + //delete the others + ids = ids.Skip(0).ToList(); + + //iterate in groups of 2000 to avoid the max sql parameter limit + foreach (var idGroup in ids.InGroupsOf(2000)) + { + database.Execute("DELETE FROM umbracoRelation WHERE id IN (@ids)", new { ids = idGroup }); + } + } + } + + //unique index to prevent duplicates - and for better perf + Create.Index("IX_umbracoRelation_parentChildType").OnTable("umbracoRelation") + .OnColumn("parentId").Ascending() + .OnColumn("childId").Ascending() + .OnColumn("relType").Ascending() + .WithOptions() + .Unique() + .Do(); + } + + //need indexes on alias and name for relation type since these are queried against + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_alias")) == false) + { + Create.Index("IX_umbracoRelationType_alias").OnTable("umbracoRelationType") + .OnColumn("alias") + .Ascending() + .WithOptions() + .NonClustered() + .Do(); + } + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_name")) == false) + { + Create.Index("IX_umbracoRelationType_name").OnTable("umbracoRelationType") + .OnColumn("name") + .Ascending() + .WithOptions() + .NonClustered() + .Do(); + } + + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockObjects.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockObjects.cs new file mode 100644 index 0000000000..2cc62cd2f9 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockObjects.cs @@ -0,0 +1,27 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + public class AddLockObjects : MigrationBase + { + public AddLockObjects(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + EnsureLockObject(Constants.Locks.Servers, "Servers"); + } + + private void EnsureLockObject(int id, string name) + { + var db = Database; + var exists = db.Exists(id); + if (exists) return; + // be safe: delete old umbracoNode lock objects if any + db.Execute("DELETE FROM umbracoNode WHERE id=@id;", new { id }); + // then create umbracoLock object + db.Execute("INSERT umbracoLock (id, name, value) VALUES (@id, @name, 1);", new { id, name }); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockTable.cs new file mode 100644 index 0000000000..369bda0758 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockTable.cs @@ -0,0 +1,24 @@ +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + public class AddLockTable : MigrationBase + { + public AddLockTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("umbracoLock") == false) + { + Create.Table("umbracoLock") + .WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock") + .WithColumn("value").AsInt32().NotNullable() + .WithColumn("name").AsString(64).NotNullable() + .Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddMacroUniqueIdColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddMacroUniqueIdColumn.cs new file mode 100644 index 0000000000..82888ab970 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddMacroUniqueIdColumn.cs @@ -0,0 +1,68 @@ +using System; +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + public class AddMacroUniqueIdColumn : MigrationBase + { + public AddMacroUniqueIdColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("cmsMacro") && x.ColumnName.InvariantEquals("uniqueId")) == false) + { + Create.Column("uniqueId").OnTable("cmsMacro").AsGuid().Nullable().Do(); + UpdateMacroGuids(); + Alter.Table("cmsMacro").AlterColumn("uniqueId").AsGuid().NotNullable().Do(); + Create.Index("IX_cmsMacro_UniqueId").OnTable("cmsMacro").OnColumn("uniqueId") + .Ascending() + .WithOptions().NonClustered() + .WithOptions().Unique() + .Do(); + + } + + if (columns.Any(x => x.TableName.InvariantEquals("cmsMacroProperty") && x.ColumnName.InvariantEquals("uniquePropertyId")) == false) + { + Create.Column("uniquePropertyId").OnTable("cmsMacroProperty").AsGuid().Nullable().Do(); + UpdateMacroPropertyGuids(); + Alter.Table("cmsMacroProperty").AlterColumn("uniquePropertyId").AsGuid().NotNullable().Do(); + Create.Index("IX_cmsMacroProperty_UniquePropertyId").OnTable("cmsMacroProperty").OnColumn("uniquePropertyId") + .Ascending() + .WithOptions().NonClustered() + .WithOptions().Unique() + .Do(); + } + } + + private void UpdateMacroGuids() + { + var database = Database; + + var updates = database.Query("SELECT id, macroAlias FROM cmsMacro") + .Select(macro => Tuple.Create((int) macro.id, ("macro____" + (string) macro.macroAlias).ToGuid())) + .ToList(); + + foreach (var update in updates) + database.Execute("UPDATE cmsMacro set uniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); + } + + private void UpdateMacroPropertyGuids() + { + var database = Database; + + var updates = database.Query(@"SELECT cmsMacroProperty.id id, macroPropertyAlias propertyAlias, cmsMacro.macroAlias macroAlias +FROM cmsMacroProperty +JOIN cmsMacro ON cmsMacroProperty.macro=cmsMacro.id") + .Select(prop => Tuple.Create((int) prop.id, ("macro____" + (string) prop.macroAlias + "____" + (string) prop.propertyAlias).ToGuid())) + .ToList(); + + foreach (var update in updates) + database.Execute("UPDATE cmsMacroProperty set uniquePropertyId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddRelationTypeUniqueIdColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddRelationTypeUniqueIdColumn.cs new file mode 100644 index 0000000000..e166a4582d --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddRelationTypeUniqueIdColumn.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + public class AddRelationTypeUniqueIdColumn : MigrationBase + { + public AddRelationTypeUniqueIdColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals("umbracoRelationType") && x.ColumnName.InvariantEquals("typeUniqueId")) == false) + { + Create.Column("typeUniqueId").OnTable("umbracoRelationType").AsGuid().Nullable().Do(); + UpdateRelationTypeGuids(); + Alter.Table("umbracoRelationType").AlterColumn("typeUniqueId").AsGuid().NotNullable().Do(); + Create.Index("IX_umbracoRelationType_UniqueId").OnTable("umbracoRelationType").OnColumn("typeUniqueId") + .Ascending() + .WithOptions().NonClustered() + .WithOptions().Unique() + .Do(); + } + } + + private void UpdateRelationTypeGuids() + { + var database = Database; + var updates = database.Query("SELECT id, alias, name FROM umbracoRelationType") + .Select(relationType => Tuple.Create((int) relationType.id, ("relationType____" + (string) relationType.alias + "____" + (string) relationType.name).ToGuid())) + .ToList(); + + foreach (var update in updates) + database.Execute("UPDATE umbracoRelationType set typeUniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/NormalizeTemplateGuids.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/NormalizeTemplateGuids.cs new file mode 100644 index 0000000000..9e8c739104 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/NormalizeTemplateGuids.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + public class NormalizeTemplateGuids : MigrationBase + { + public NormalizeTemplateGuids(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var database = Database; + + // we need this migration because ppl running pre-7.6 on Cloud and Courier have templates in different + // environments having different GUIDs (Courier does not sync template GUIDs) and we need to normalize + // these GUIDs so templates with the same alias on different environments have the same GUID. + // however, if already running a prerelease version of 7.6, we do NOT want to normalize the GUIDs as quite + // probably, we are already running Deploy and the GUIDs are OK. assuming noone is running a prerelease + // of 7.6 on Courier. + // so... testing if we already have a 7.6.0 version installed. not pretty but...? + // + var version = database.FirstOrDefault("SELECT version FROM umbracoMigration WHERE name=@name ORDER BY version DESC", new { name = Constants.System.UmbracoUpgradePlanName }); + if (version != null && version.StartsWith("7.6.0")) return; + + var updates = database.Query(@"SELECT umbracoNode.id, cmsTemplate.alias FROM umbracoNode +JOIN cmsTemplate ON umbracoNode.id=cmsTemplate.nodeId +WHERE nodeObjectType = @guid", new { guid = Constants.ObjectTypes.TemplateType }) + .Select(template => Tuple.Create((int) template.id, ("template____" + (string) template.alias).ToGuid())) + .ToList(); + + foreach (var update in updates) + database.Execute("UPDATE umbracoNode set uniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/ReduceLoginNameColumnsSize.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/ReduceLoginNameColumnsSize.cs new file mode 100644 index 0000000000..30ee91300d --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/ReduceLoginNameColumnsSize.cs @@ -0,0 +1,43 @@ +using System.Linq; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + public class ReduceLoginNameColumnsSize : MigrationBase + { + public ReduceLoginNameColumnsSize(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + //Now we need to check if we can actually d6 this because we won't be able to if there's data in there that is too long + //http://issues.umbraco.org/issue/U4-9758 + + var database = Database; + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(database); + + var colLen = (SqlSyntax is MySqlSyntaxProvider) + ? database.ExecuteScalar("select max(LENGTH(LoginName)) from cmsMember") + : database.ExecuteScalar("select max(datalength(LoginName)) from cmsMember"); + + if (colLen < 900 == false) return; + + //if an index exists on this table we need to drop it. Normally we'd check via index name but in some odd cases (i.e. Our) + //the index name is something odd (starts with "mi_"). In any case, the index cannot exist if we want to alter the column + //so we'll drop whatever index is there and add one with the correct name after. + var loginNameIndex = dbIndexes.FirstOrDefault(x => x.TableName.InvariantEquals("cmsMember") && x.ColumnName.InvariantEquals("LoginName")); + if (loginNameIndex != null) + { + Delete.Index(loginNameIndex.IndexName).OnTable("cmsMember").Do(); + } + + //we can apply the col length change + Alter.Table("cmsMember") + .AlterColumn("LoginName") + .AsString(225) + .NotNullable() + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemovePropertyDataIdIndex.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemovePropertyDataIdIndex.cs new file mode 100644 index 0000000000..804714d1ff --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemovePropertyDataIdIndex.cs @@ -0,0 +1,29 @@ +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + /// + /// See: http://issues.umbraco.org/issue/U4-9188 + /// + public class UpdateUniqueIndexOnPropertyData : MigrationBase + { + public UpdateUniqueIndexOnPropertyData(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + //tuple = tablename, indexname, columnname, unique + var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); + var found = indexes.FirstOrDefault( + x => x.Item1.InvariantEquals("cmsPropertyData") + && x.Item2.InvariantEquals("IX_cmsPropertyData")); + + if (found != null) + { + //drop the index + Delete.Index("IX_cmsPropertyData").OnTable("cmsPropertyData").Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemoveUmbracoDeployTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemoveUmbracoDeployTables.cs new file mode 100644 index 0000000000..744b441b32 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemoveUmbracoDeployTables.cs @@ -0,0 +1,30 @@ +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 +{ + public class RemoveUmbracoDeployTables : MigrationBase + { + public RemoveUmbracoDeployTables(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + // there are two versions of umbracoDeployDependency, + // 1. one created by 7.4 and never used, we need to remove it (has a sourceId column) + // 2. one created by Deploy itself, we need to keep it (has a sourceUdi column) + if (tables.InvariantContains("umbracoDeployDependency")) + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + if (columns.Any(x => x.TableName.InvariantEquals("umbracoDeployDependency") && x.ColumnName.InvariantEquals("sourceId"))) + Delete.Table("umbracoDeployDependency").Do(); + } + + // always remove umbracoDeployChecksum + if (tables.InvariantContains("umbracoDeployChecksum")) + Delete.Table("umbracoDeployChecksum").Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddIndexToDictionaryKeyColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddIndexToDictionaryKeyColumn.cs new file mode 100644 index 0000000000..700702204c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddIndexToDictionaryKeyColumn.cs @@ -0,0 +1,40 @@ +using System.Linq; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 +{ + public class AddIndexToDictionaryKeyColumn : MigrationBase + { + public AddIndexToDictionaryKeyColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var database = Database; + //Now we need to check if we can actually do this because we won't be able to if there's data in there that is too long + var colLen = (SqlSyntax is MySqlSyntaxProvider) + ? database.ExecuteScalar(string.Format("select max(LENGTH({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))) + : database.ExecuteScalar(string.Format("select max(datalength({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))); + + if (colLen < 900 == false && colLen != null) + { + return; + } + + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDictionary_key")) == false) + { + //we can apply the index + Create.Index("IX_cmsDictionary_key").OnTable("cmsDictionary") + .OnColumn("key") + .Ascending() + .WithOptions() + .NonClustered() + .Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserGroupTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserGroupTables.cs new file mode 100644 index 0000000000..35df178672 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserGroupTables.cs @@ -0,0 +1,368 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.SqlSyntax; +using ColumnInfo = Umbraco.Core.Persistence.SqlSyntax.ColumnInfo; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 +{ + public class AddUserGroupTables : MigrationBase + { + private readonly string _collateSyntax; + + public AddUserGroupTables(IMigrationContext context) + : base(context) + { + //For some of the migration data inserts we require to use a special MSSQL collate expression since + //some databases may have a custom collation specified and if that is the case, when we compare strings + //in dynamic SQL it will try to compare strings in different collations and this will yield errors. + _collateSyntax = SqlSyntax is MySqlSyntaxProvider || SqlSyntax is SqlCeSyntaxProvider + ? string.Empty + : "COLLATE DATABASE_DEFAULT"; + } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToList(); + var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + //In some very rare cases, there might alraedy be user group tables that we'll need to remove first + //but of course we don't want to remove the tables we will be creating below if they already exist so + //need to do some checks first since these old rare tables have a different schema + RemoveOldTablesIfExist(tables, columns); + + if (AddNewTables(tables)) + { + MigrateUserPermissions(); + MigrateUserTypesToGroups(); + DeleteOldTables(tables, constraints); + SetDefaultIcons(); + } + else + { + //if we aren't adding the tables, make sure that the umbracoUserGroup table has the correct FKs - these + //were added after the beta release so we need to do some cleanup + //if the FK doesn't exist + if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUserGroup") + && x.Item2.InvariantEquals("startContentId") + && x.Item3.InvariantEquals("FK_startContentId_umbracoNode_id")) == false) + { + //before we add any foreign key we need to make sure there's no stale data in there which would have happened in the beta + //release if a start node was assigned and then that start node was deleted. + Database.Execute(@"UPDATE umbracoUserGroup SET startContentId = NULL WHERE startContentId NOT IN (SELECT id FROM umbracoNode)"); + + Create.ForeignKey("FK_startContentId_umbracoNode_id") + .FromTable("umbracoUserGroup") + .ForeignColumn("startContentId") + .ToTable("umbracoNode") + .PrimaryColumn("id") + .OnDelete(Rule.None) + .OnUpdate(Rule.None) + .Do(); + } + + if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUserGroup") + && x.Item2.InvariantEquals("startMediaId") + && x.Item3.InvariantEquals("FK_startMediaId_umbracoNode_id")) == false) + { + //before we add any foreign key we need to make sure there's no stale data in there which would have happened in the beta + //release if a start node was assigned and then that start node was deleted. + Database.Execute(@"UPDATE umbracoUserGroup SET startMediaId = NULL WHERE startMediaId NOT IN (SELECT id FROM umbracoNode)"); + + Create.ForeignKey("FK_startMediaId_umbracoNode_id") + .FromTable("umbracoUserGroup") + .ForeignColumn("startMediaId") + .ToTable("umbracoNode") + .PrimaryColumn("id") + .OnDelete(Rule.None) + .OnUpdate(Rule.None) + .Do(); + } + } + } + + /// + /// In some very rare cases, there might alraedy be user group tables that we'll need to remove first + /// but of course we don't want to remove the tables we will be creating below if they already exist so + /// need to do some checks first since these old rare tables have a different schema + /// + /// + /// + private void RemoveOldTablesIfExist(List tables, ColumnInfo[] columns) + { + if (tables.Contains("umbracoUser2userGroup", StringComparer.InvariantCultureIgnoreCase)) + { + //this column doesn't exist in the 7.7 schema, so if it's there, then this is a super old table + var foundOldColumn = columns + .FirstOrDefault(x => + x.ColumnName.Equals("user", StringComparison.InvariantCultureIgnoreCase) + && x.TableName.Equals("umbracoUser2userGroup", StringComparison.InvariantCultureIgnoreCase)); + if (foundOldColumn != null) + { + Delete.Table("umbracoUser2userGroup").Do(); + //remove from the tables list since this will be re-checked in further logic + tables.Remove("umbracoUser2userGroup"); + } + } + + if (tables.Contains("umbracoUserGroup", StringComparer.InvariantCultureIgnoreCase)) + { + //The new schema has several columns, the super old one for this table only had 2 so if it's 2 get rid of it + var countOfCols = columns + .Count(x => x.TableName.Equals("umbracoUserGroup", StringComparison.InvariantCultureIgnoreCase)); + if (countOfCols == 2) + { + Delete.Table("umbracoUserGroup").Do(); + //remove from the tables list since this will be re-checked in further logic + tables.Remove("umbracoUserGroup"); + } + } + } + + private void SetDefaultIcons() + { + Database.Execute($"UPDATE umbracoUserGroup SET icon = \'\' WHERE userGroupAlias = \'{Constants.Security.AdminGroupAlias}\'"); + Database.Execute("UPDATE umbracoUserGroup SET icon = \'icon-edit\' WHERE userGroupAlias = \'writer\'"); + Database.Execute("UPDATE umbracoUserGroup SET icon = \'icon-tools\' WHERE userGroupAlias = \'editor\'"); + Database.Execute("UPDATE umbracoUserGroup SET icon = \'icon-globe\' WHERE userGroupAlias = \'translator\'"); + } + + private bool AddNewTables(List tables) + { + var updated = false; + if (tables.InvariantContains("umbracoUserGroup") == false) + { + Create.Table().Do(); + updated = true; + } + + if (tables.InvariantContains("umbracoUser2UserGroup") == false) + { + Create.Table().Do(); + updated = true; + } + + if (tables.InvariantContains("umbracoUserGroup2App") == false) + { + Create.Table().Do(); + updated = true; + } + + if (tables.InvariantContains("umbracoUserGroup2NodePermission") == false) + { + Create.Table().Do(); + updated = true; + } + + return updated; + } + + private void MigrateUserTypesToGroups() + { + // Create a user group for each user type + Database.Execute(@"INSERT INTO umbracoUserGroup (userGroupAlias, userGroupName, userGroupDefaultPermissions) + SELECT userTypeAlias, userTypeName, userTypeDefaultPermissions + FROM umbracoUserType"); + + // Add each user to the group created from their type + Database.Execute(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId) + SELECT u.id, ug.id + FROM umbracoUser u + INNER JOIN umbracoUserType ut ON ut.id = u.userType + INNER JOIN umbracoUserGroup ug ON ug.userGroupAlias {0} = ut.userTypeAlias {0}", _collateSyntax)); + + // Add the built-in administrator account to all apps + // this will lookup all of the apps that the admin currently has access to in order to assign the sections + // instead of use statically assigning since there could be extra sections we don't know about. + Database.Execute(@"INSERT INTO umbracoUserGroup2app (userGroupId,app) + SELECT ug.id, app + FROM umbracoUserGroup ug + INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id + INNER JOIN umbracoUser u ON u.id = u2ug.userId + INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id + WHERE u.id = 0"); + + // Add the default section access to the other built-in accounts + // writer: + Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) + SELECT ug.id, 'content' as app + FROM umbracoUserGroup ug + WHERE ug.userGroupAlias {0} = 'writer' {0}", _collateSyntax)); + // editor + Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) + SELECT ug.id, 'content' as app + FROM umbracoUserGroup ug + WHERE ug.userGroupAlias {0} = 'editor' {0}", _collateSyntax)); + Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) + SELECT ug.id, 'media' as app + FROM umbracoUserGroup ug + WHERE ug.userGroupAlias {0} = 'editor' {0}", _collateSyntax)); + // translator + Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) + SELECT ug.id, 'translation' as app + FROM umbracoUserGroup ug + WHERE ug.userGroupAlias {0} = 'translator' {0}", _collateSyntax)); + + //We need to lookup all distinct combinations of section access and create a group for each distinct collection + //and assign groups accordingly. We'll perform the lookup 'now' to then create the queued SQL migrations. + var userAppsData = Context.Database.Query(@"SELECT u.id, u2a.app FROM umbracoUser u + INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id + ORDER BY u.id, u2a.app"); + var usersWithApps = new Dictionary>(); + foreach (var userApps in userAppsData) + { + if (usersWithApps.TryGetValue(userApps.id, out List apps) == false) + { + apps = new List {userApps.app}; + usersWithApps.Add(userApps.id, apps); + } + else + { + apps.Add(userApps.app); + } + } + //At this stage we have a dictionary of users with a collection of their apps which are sorted + //and we need to determine the unique/distinct app collections for each user to create groups with. + //We can do this by creating a hash value of all of the app values and since they are already sorted we can get a distinct + //collection by this hash. + var distinctApps = usersWithApps + .Select(x => new {appCollection = x.Value, appsHash = string.Join("", x.Value).GenerateHash()}) + .DistinctBy(x => x.appsHash) + .ToArray(); + //Now we need to create user groups for each of these distinct app collections, and then assign the corresponding users to those groups + for (var i = 0; i < distinctApps.Length; i++) + { + //create the group + var alias = "MigratedSectionAccessGroup_" + (i + 1); + Insert.IntoTable("umbracoUserGroup").Row(new + { + userGroupAlias = "MigratedSectionAccessGroup_" + (i + 1), + userGroupName = "Migrated Section Access Group " + (i + 1) + }).Do(); + //now assign the apps + var distinctApp = distinctApps[i]; + foreach (var app in distinctApp.appCollection) + { + Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) + SELECT ug.id, '" + app + @"' as app + FROM umbracoUserGroup ug + WHERE ug.userGroupAlias {0} = '" + alias + "' {0}", _collateSyntax)); + } + //now assign the corresponding users to this group + foreach (var userWithApps in usersWithApps) + { + //check if this user's groups hash matches the current groups hash + var hash = string.Join("", userWithApps.Value).GenerateHash(); + if (hash == distinctApp.appsHash) + { + //it matches so assign the user to this group + Database.Execute(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId) + SELECT " + userWithApps.Key + @", ug.id + FROM umbracoUserGroup ug + WHERE ug.userGroupAlias {0} = '" + alias + "' {0}", _collateSyntax)); + } + } + } + + // Rename some groups for consistency (plural form) + Database.Execute("UPDATE umbracoUserGroup SET userGroupName = 'Writers' WHERE userGroupAlias = 'writer'"); + Database.Execute("UPDATE umbracoUserGroup SET userGroupName = 'Translators' WHERE userGroupAlias = 'translator'"); + + //Ensure all built in groups have a start node of -1 + Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'editor'"); + Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'editor'"); + Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'writer'"); + Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'writer'"); + Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'translator'"); + Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'translator'"); + Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'admin'"); + Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'admin'"); + } + + private void MigrateUserPermissions() + { + // Create user group records for all non-admin users that have specific permissions set + Database.Execute(@"INSERT INTO umbracoUserGroup(userGroupAlias, userGroupName) + SELECT 'permissionGroupFor' + userLogin, 'Migrated Permission Group for ' + userLogin + FROM umbracoUser + WHERE (id IN ( + SELECT userid + FROM umbracoUser2NodePermission + )) + AND id > 0"); + + // Associate those groups with the users + Database.Execute(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId) + SELECT u.id, ug.id + FROM umbracoUser u + INNER JOIN umbracoUserGroup ug ON ug.userGroupAlias {0} = 'permissionGroupFor' + userLogin {0}", _collateSyntax)); + + // Create node permissions on the groups + Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2NodePermission (userGroupId,nodeId,permission) + SELECT ug.id, nodeId, permission + FROM umbracoUserGroup ug + INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id + INNER JOIN umbracoUser u ON u.id = u2ug.userId + INNER JOIN umbracoUser2NodePermission u2np ON u2np.userId = u.id + WHERE ug.userGroupAlias {0} NOT IN ( + SELECT userTypeAlias {0} + FROM umbracoUserType + )", _collateSyntax)); + + // Create app permissions on the groups + Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId,app) + SELECT ug.id, app + FROM umbracoUserGroup ug + INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id + INNER JOIN umbracoUser u ON u.id = u2ug.userId + INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id + WHERE ug.userGroupAlias {0} NOT IN ( + SELECT userTypeAlias {0} + FROM umbracoUserType + )", _collateSyntax)); + } + + private void DeleteOldTables(List tables, Tuple[] constraints) + { + if (tables.InvariantContains("umbracoUser2App")) + { + Delete.Table("umbracoUser2App").Do(); + } + + if (tables.InvariantContains("umbracoUser2NodePermission")) + { + Delete.Table("umbracoUser2NodePermission").Do(); + } + + if (tables.InvariantContains("umbracoUserType") && tables.InvariantContains("umbracoUser")) + { + if (DatabaseType.IsMySql()) + { + //In MySql, this will drop the FK according to it's special naming rules + Delete.ForeignKey().FromTable("umbracoUser").ForeignColumn("userType").ToTable("umbracoUserType").PrimaryColumn("id"); + } + else + { + //Delete the FK if it exists before dropping the column + if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_umbracoUser_umbracoUserType_id"))) + { + Delete.ForeignKey("FK_umbracoUser_umbracoUserType_id").OnTable("umbracoUser").Do(); + } + + //This is the super old constraint name of the FK for user type so check this one too + if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_user_userType"))) + { + Delete.ForeignKey("FK_user_userType").OnTable("umbracoUser").Do(); + } + } + + Delete.Column("userType").FromTable("umbracoUser").Do(); + Delete.Table("umbracoUserType").Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserStartNodeTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserStartNodeTable.cs new file mode 100644 index 0000000000..54545e06d3 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserStartNodeTable.cs @@ -0,0 +1,41 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 +{ + public class AddUserStartNodeTable : MigrationBase + { + public AddUserStartNodeTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + if (tables.InvariantContains("umbracoUserStartNode")) return; + + Create.Table().Do(); + + MigrateUserStartNodes(); + + //now remove the old columns + + Delete.Column("startStructureID").FromTable("umbracoUser").Do(); + Delete.Column("startMediaID").FromTable("umbracoUser").Do(); + } + + private void MigrateUserStartNodes() + { + Database.Execute(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) + SELECT id, startStructureID, 1 + FROM umbracoUser + WHERE startStructureID IS NOT NULL AND startStructureID > 0 AND startStructureID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Document + "')"); + + Database.Execute(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) + SELECT id, startMediaID, 2 + FROM umbracoUser + WHERE startMediaID IS NOT NULL AND startMediaID > 0 AND startMediaID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Media + "')"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/EnsureContentTemplatePermissions.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/EnsureContentTemplatePermissions.cs new file mode 100644 index 0000000000..1f7e2faf33 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/EnsureContentTemplatePermissions.cs @@ -0,0 +1,37 @@ +using System.Linq; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 +{ + /// + /// Ensures the built-in user groups have the blueprint permission by default on upgrade + /// + public class EnsureContentTemplatePermissions : MigrationBase + { + public EnsureContentTemplatePermissions(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var database = Database; + var userGroups = database.Fetch( + Context.SqlContext.Sql().Select("*") + .From() + .Where(x => x.Alias == "admin" || x.Alias == "editor")); + + foreach (var userGroup in userGroups) + { + if (userGroup.DefaultPermissions.Contains('�') == false) + { + userGroup.DefaultPermissions += "�"; + Update.Table("umbracoUserGroup") + .Set(new { userGroupDefaultPermissions = userGroup.DefaultPermissions }) + .Where(new { id = userGroup.Id }) + .Do(); + } + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/ReduceDictionaryKeyColumnsSize.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/ReduceDictionaryKeyColumnsSize.cs new file mode 100644 index 0000000000..9f84320620 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/ReduceDictionaryKeyColumnsSize.cs @@ -0,0 +1,39 @@ +using System.Linq; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 +{ + public class ReduceDictionaryKeyColumnsSize : MigrationBase + { + public ReduceDictionaryKeyColumnsSize(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + //Now we need to check if we can actually do this because we won't be able to if there's data in there that is too long + + var database = Database; + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(database); + + var colLen = SqlSyntax is MySqlSyntaxProvider + ? database.ExecuteScalar(string.Format("select max(LENGTH({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))) + : database.ExecuteScalar(string.Format("select max(datalength({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))); + + if (colLen < 900 == false) return; + + //if it exists we need to drop it first + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDictionary_key"))) + { + Delete.Index("IX_cmsDictionary_key").OnTable("cmsDictionary").Do(); + } + + //we can apply the col length change + Alter.Table("cmsDictionary") + .AlterColumn("key") + .AsString(450) + .NotNullable() + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/UpdateUserTables.cs similarity index 75% rename from src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs rename to src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/UpdateUserTables.cs index b3fa51c0b3..3714427cd7 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/UpdateUserTables.cs @@ -1,55 +1,48 @@ -using System.Linq; +using System.Linq; using System.Web.Security; using Newtonsoft.Json; -using Umbraco.Core.Logging; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Security; -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero +namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 { - [Migration("7.7.0", 0, Constants.System.UmbracoMigrationName)] public class UpdateUserTables : MigrationBase { - public UpdateUserTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } + public UpdateUserTables(IMigrationContext context) + : base(context) + { } - public override void Up() + public override void Migrate() { //Don't exeucte if the column is already there var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("createDate")) == false) - Create.Column("createDate").OnTable("umbracoUser").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime); + Create.Column("createDate").OnTable("umbracoUser").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime).Do(); if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("updateDate")) == false) - Create.Column("updateDate").OnTable("umbracoUser").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime); + Create.Column("updateDate").OnTable("umbracoUser").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime).Do(); if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("emailConfirmedDate")) == false) - Create.Column("emailConfirmedDate").OnTable("umbracoUser").AsDateTime().Nullable(); + Create.Column("emailConfirmedDate").OnTable("umbracoUser").AsDateTime().Nullable().Do(); if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("invitedDate")) == false) - Create.Column("invitedDate").OnTable("umbracoUser").AsDateTime().Nullable(); + Create.Column("invitedDate").OnTable("umbracoUser").AsDateTime().Nullable().Do(); if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("avatar")) == false) - Create.Column("avatar").OnTable("umbracoUser").AsString(500).Nullable(); + Create.Column("avatar").OnTable("umbracoUser").AsString(500).Nullable().Do(); if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("passwordConfig")) == false) { - Create.Column("passwordConfig").OnTable("umbracoUser").AsString(500).Nullable(); + Create.Column("passwordConfig").OnTable("umbracoUser").AsString(500).Nullable().Do(); //Check if we have a known config, we only want to store config for hashing var membershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider(); if (membershipProvider.PasswordFormat == MembershipPasswordFormat.Hashed) { var json = JsonConvert.SerializeObject(new { hashAlgorithm = Membership.HashAlgorithmType }); - Execute.Sql("UPDATE umbracoUser SET passwordConfig = '" + json + "'"); + Database.Execute("UPDATE umbracoUser SET passwordConfig = '" + json + "'"); } } } - - public override void Down() - { - } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs new file mode 100644 index 0000000000..ddb084a609 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs @@ -0,0 +1,27 @@ +using System.Linq; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 +{ + internal class AddIndexToPropertyTypeAliasColumn : MigrationBase + { + public AddIndexToPropertyTypeAliasColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); + + //make sure it doesn't already exist + if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsPropertyTypeAlias")) == false) + { + //we can apply the index + Create.Index("IX_cmsPropertyTypeAlias").OnTable(Constants.DatabaseSchema.Tables.PropertyType) + .OnColumn("Alias") + .Ascending().WithOptions().NonClustered() + .Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs new file mode 100644 index 0000000000..0ce2c91f2e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs @@ -0,0 +1,20 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 +{ + internal class AddInstructionCountColumn : MigrationBase + { + public AddInstructionCountColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.CacheInstruction) && x.ColumnName.InvariantEquals("instructionCount")) == false) + AddColumn("instructionCount"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs new file mode 100644 index 0000000000..b4c0062770 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Factories; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 +{ + internal class AddMediaVersionTable : MigrationBase + { + public AddMediaVersionTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + if (tables.InvariantContains(Constants.DatabaseSchema.Tables.MediaVersion)) return; + + Create.Table().Do(); + MigrateMediaPaths(); + } + + private void MigrateMediaPaths() + { + // this may not be the most efficient way to do it, compared to how it's done in v7, but this + // migration should only run for v8 sites that are being developed, before v8 is released, so + // no big sites and performances don't matter here - keep it simple + + var sql = Sql() + .Select(x => x.VarcharValue, x => x.TextValue) + .AndSelect(x => Alias(x.Id, "versionId")) + .From() + .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) + .InnerJoin().On((left, right) => left.VersionId == right.Id) + .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + .Where(x => x.Alias == "umbracoFile") + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media); + + var paths = new List(); + + //using QUERY = a db cursor, we won't load this all into memory first, just row by row + foreach (var row in Database.Query(sql)) + { + // if there's values then ensure there's a media path match and extract it + string mediaPath = null; + if ( + (row.varcharValue != null && ContentBaseFactory.TryMatch((string) row.varcharValue, out mediaPath)) + || (row.textValue != null && ContentBaseFactory.TryMatch((string) row.textValue, out mediaPath))) + { + paths.Add(new MediaVersionDto + { + Id = (int) row.versionId, + Path = mediaPath + }); + } + } + + // bulk insert + Database.BulkInsertRecords(paths); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs new file mode 100644 index 0000000000..cd2678205f --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs @@ -0,0 +1,21 @@ +using System.Linq; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 +{ + internal class AddTourDataUserColumn : MigrationBase + { + public AddTourDataUserColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.User) && x.ColumnName.InvariantEquals("tourData")) == false) + AddColumn("tourData"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs new file mode 100644 index 0000000000..7a55362072 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs @@ -0,0 +1,22 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 +{ + internal class AddUserLoginTable : MigrationBase + { + public AddUserLoginTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + if (tables.InvariantContains(Constants.DatabaseSchema.Tables.UserLogin) == false) + { + Create.Table().Do(); + } + } + } +} 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 new file mode 100644 index 0000000000..4e1a7d1470 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs @@ -0,0 +1,20 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 +{ + internal class AddIsSensitiveMemberTypeColumn : MigrationBase + { + public AddIsSensitiveMemberTypeColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); + + if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.MemberType) && x.ColumnName.InvariantEquals("isSensitive")) == false) + AddColumn("isSensitive"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs new file mode 100644 index 0000000000..e7880dfc73 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs @@ -0,0 +1,22 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 +{ + internal class AddUmbracoAuditTable : MigrationBase + { + public AddUmbracoAuditTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + if (tables.InvariantContains(Constants.DatabaseSchema.Tables.AuditEntry)) + return; + + Create.Table().Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs new file mode 100644 index 0000000000..e3656f69ac --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs @@ -0,0 +1,22 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 +{ + internal class AddUmbracoConsentTable : MigrationBase + { + public AddUmbracoConsentTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + + if (tables.InvariantContains(Constants.DatabaseSchema.Tables.Consent)) + return; + + Create.Table().Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs new file mode 100644 index 0000000000..a3749f7be5 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs @@ -0,0 +1,27 @@ +using System; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 +{ + internal class CreateSensitiveDataUserGroup : MigrationBase + { + public CreateSensitiveDataUserGroup(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var sql = Sql() + .SelectCount() + .From() + .Where(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); + + var exists = Database.ExecuteScalar(sql) > 0; + if (exists) return; + + var groupId = Database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", new UserGroupDto { StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); + Database.Insert(new User2UserGroupDto { UserGroupId = Convert.ToInt32(groupId), UserId = Constants.Security.SuperId }); // add super to sensitive data + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentNuTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentNuTable.cs new file mode 100644 index 0000000000..efa54a0f59 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentNuTable.cs @@ -0,0 +1,42 @@ +using System.Data; +using System.Linq; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + class AddContentNuTable : MigrationBase + { + public AddContentNuTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("cmsContentNu")) return; + + var textType = SqlSyntax.GetSpecialDbType(SpecialDbTypes.NTEXT); + + Create.Table("cmsContentNu") + .WithColumn("nodeId").AsInt32().NotNullable() + .WithColumn("published").AsBoolean().NotNullable() + .WithColumn("data").AsCustom(textType).NotNullable() + .WithColumn("rv").AsInt64().NotNullable().WithDefaultValue(0) + .Do(); + + Create.PrimaryKey("PK_cmsContentNu") + .OnTable("cmsContentNu") + .Columns(new[] { "nodeId", "published" }) + .Do(); + + Create.ForeignKey("FK_cmsContentNu_umbracoNode_id") + .FromTable("cmsContentNu") + .ForeignColumn("nodeId") + .ToTable("umbracoNode") + .PrimaryColumn("id") + .OnDelete(Rule.Cascade) + .OnUpdate(Rule.None) + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs new file mode 100644 index 0000000000..d595c70fa0 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs @@ -0,0 +1,35 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class AddLockObjects : MigrationBase + { + public AddLockObjects(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // some may already exist, just ensure everything we need is here + EnsureLockObject(Constants.Locks.Servers, "Servers"); + EnsureLockObject(Constants.Locks.ContentTypes, "ContentTypes"); + EnsureLockObject(Constants.Locks.ContentTree, "ContentTree"); + EnsureLockObject(Constants.Locks.MediaTree, "MediaTree"); + EnsureLockObject(Constants.Locks.MemberTree, "MemberTree"); + EnsureLockObject(Constants.Locks.MediaTypes, "MediaTypes"); + EnsureLockObject(Constants.Locks.MemberTypes, "MemberTypes"); + EnsureLockObject(Constants.Locks.Domains, "Domains"); + } + + private void EnsureLockObject(int id, string name) + { + var db = Database; + var exists = db.Exists(id); + if (exists) return; + // be safe: delete old umbracoNode lock objects if any + db.Execute($"DELETE FROM umbracoNode WHERE id={id};"); + // then create umbracoLock object + db.Execute($"INSERT umbracoLock (id, name, value) VALUES ({id}, '{name}', 1);"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockTable.cs new file mode 100644 index 0000000000..aa14bc81c8 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockTable.cs @@ -0,0 +1,24 @@ +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class AddLockTable : MigrationBase + { + public AddLockTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + if (tables.InvariantContains("umbracoLock") == false) + { + Create.Table("umbracoLock") + .WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock") + .WithColumn("value").AsInt32().NotNullable() + .WithColumn("name").AsString(64).NotNullable() + .Do(); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs new file mode 100644 index 0000000000..eb39f37112 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; +using NPoco; +using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class DataTypeMigration : MigrationBase + { + public DataTypeMigration(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // delete *all* keys and indexes - because of FKs + Delete.KeysAndIndexes().Do(); + + // drop and create columns + Delete.Column("pk").FromTable("cmsDataType").Do(); + + // rename the table + Rename.Table("cmsDataType").To(Constants.DatabaseSchema.Tables.DataType).Do(); + + // create column + AddColumn(Constants.DatabaseSchema.Tables.DataType, "config"); + Execute.Sql(Sql().Update(u => u.Set(x => x.Configuration, string.Empty)).SQL).Do(); + + // re-create *all* keys and indexes + foreach (var x in DatabaseSchemaCreator.OrderedTables) + Create.KeysAndIndexes(x).Do(); + + // renames + Execute.Sql(Sql() + .Update(u => u.Set(x => x.EditorAlias, "Umbraco.ColorPicker")) + .Where(x => x.EditorAlias == "Umbraco.ColorPickerAlias").SQL).Do(); + + // from preValues to configuration... + var sql = Sql() + .Select() + .AndSelect(x => x.Alias, x => x.SortOrder, x => x.Value) + .From() + .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + .OrderBy(x => x.NodeId) + .AndBy(x => x.SortOrder); + + var dtos = Database.Fetch(sql).GroupBy(x => x.NodeId); + + foreach (var group in dtos) + { + var dataType = Database.Fetch(Sql() + .Select() + .From() + .Where(x => x.NodeId == group.Key)).First(); + + var aliases = group.Select(x => x.Alias).Distinct().ToArray(); + if (aliases.Length == 1 && string.IsNullOrWhiteSpace(aliases[0])) + { + // array-based prevalues + var values = new Dictionary { ["values"] = group.OrderBy(x => x.SortOrder).Select(x => x.Value).ToArray() }; + dataType.Configuration = JsonConvert.SerializeObject(values); + } + else + { + // assuming we don't want to fall back to array + if (aliases.Length != group.Count() || aliases.Any(string.IsNullOrWhiteSpace)) + throw new InvalidOperationException($"Cannot migrate datatype w/ id={dataType.NodeId} preValues: duplicate or null/empty alias."); + + // dictionary-base prevalues + var values = group.ToDictionary(x => x.Alias, x => x.Value); + dataType.Configuration = JsonConvert.SerializeObject(values); + } + + Database.Update(dataType); + } + + // drop preValues table + // FIXME keep it around for now + //Delete.Table("cmsDataTypePreValues"); + } + + [TableName("cmsDataTypePreValues")] + [ExplicitColumns] + public class PreValueDto + { + [Column("datatypeNodeId")] + public int NodeId { get; set; } + + [Column("alias")] + public string Alias { get; set; } + + [Column("sortorder")] + public int SortOrder { get; set; } + + [Column("value")] + public string Value { get; set; } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropMigrationsTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropMigrationsTable.cs new file mode 100644 index 0000000000..26668361bd --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropMigrationsTable.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class DropMigrationsTable : MigrationBase + { + public DropMigrationsTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + Delete.Table("umbracoMigration").Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/LanguageColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/LanguageColumns.cs new file mode 100644 index 0000000000..e8a1197d37 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/LanguageColumns.cs @@ -0,0 +1,17 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class LanguageColumns : MigrationBase + { + public LanguageColumns(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + AddColumn(Constants.DatabaseSchema.Tables.Language, "isDefaultVariantLang"); + AddColumn(Constants.DatabaseSchema.Tables.Language, "mandatory"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs new file mode 100644 index 0000000000..ee439088be --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs @@ -0,0 +1,42 @@ +using System; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class PropertyEditorsMigration : MigrationBase + { + public PropertyEditorsMigration(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + RenameDataType(Constants.PropertyEditors.Aliases.ContentPicker + "2", Constants.PropertyEditors.Aliases.ContentPicker); + RenameDataType(Constants.PropertyEditors.Aliases.MediaPicker + "2", Constants.PropertyEditors.Aliases.MediaPicker); + RenameDataType(Constants.PropertyEditors.Aliases.MemberPicker + "2", Constants.PropertyEditors.Aliases.MemberPicker); + RenameDataType(Constants.PropertyEditors.Aliases.MultiNodeTreePicker + "2", Constants.PropertyEditors.Aliases.MultiNodeTreePicker); + RenameDataType(Constants.PropertyEditors.Aliases.RelatedLinks + "2", Constants.PropertyEditors.Aliases.RelatedLinks); + RenameDataType("Umbraco.TextboxMultiple", Constants.PropertyEditors.Aliases.TextArea, false); + RenameDataType("Umbraco.Textbox", Constants.PropertyEditors.Aliases.TextBox, false); + } + + private void RenameDataType(string fromAlias, string toAlias, bool checkCollision = true) + { + if (checkCollision) + { + var oldCount = Database.ExecuteScalar(Sql() + .SelectCount() + .From() + .Where(x => x.EditorAlias == toAlias)); + + if (oldCount > 0) + throw new InvalidOperationException($"Cannot rename datatype alias \"{fromAlias}\" to \"{toAlias}\" because the target alias is already used."); + } + + Database.Execute(Sql() + .Update(u => u.Set(x => x.EditorAlias, toAlias)) + .Where(x => x.EditorAlias == fromAlias)); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorXmlColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorXmlColumns.cs new file mode 100644 index 0000000000..8b85d6992e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorXmlColumns.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class RefactorXmlColumns : MigrationBase + { + public RefactorXmlColumns(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + if (ColumnExists("cmsContentXml", "Rv") == false) + Alter.Table("cmsContentXml").AddColumn("Rv").AsInt64().NotNullable().WithDefaultValue(0).Do(); + + if (ColumnExists("cmsPreviewXml", "Rv") == false) + Alter.Table("cmsPreviewXml").AddColumn("Rv").AsInt64().NotNullable().WithDefaultValue(0).Do(); + + // remove the any PK_ and the FK_ to cmsContentVersion.VersionId + if (DatabaseType.IsMySql()) + { + Delete.PrimaryKey("PK_cmsPreviewXml").FromTable("cmsPreviewXml").Do(); + + Delete.ForeignKey().FromTable("cmsPreviewXml").ForeignColumn("VersionId") + .ToTable("cmsContentVersion").PrimaryColumn("VersionId") + .Do(); + } + else + { + var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); + var dups = new List(); + foreach (var c in constraints.Where(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantStartsWith("PK_"))) + { + var keyName = c.Item3.ToLowerInvariant(); + if (dups.Contains(keyName)) + { + Logger.Warn("Duplicate constraint " + c.Item3); + continue; + } + dups.Add(keyName); + Delete.PrimaryKey(c.Item3).FromTable(c.Item1).Do(); + } + foreach (var c in constraints.Where(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantStartsWith("FK_cmsPreviewXml_cmsContentVersion"))) + { + Delete.ForeignKey().FromTable("cmsPreviewXml").ForeignColumn("VersionId") + .ToTable("cmsContentVersion").PrimaryColumn("VersionId") + .Do(); + } + } + + if (ColumnExists("cmsPreviewXml", "Timestamp")) + Delete.Column("Timestamp").FromTable("cmsPreviewXml").Do(); + + if (ColumnExists("cmsPreviewXml", "VersionId")) + { + RemoveDuplicates(); + Delete.Column("VersionId").FromTable("cmsPreviewXml").Do(); + } + + // re-create the primary key + Create.PrimaryKey("PK_cmsPreviewXml") + .OnTable("cmsPreviewXml") + .Columns(new[] { "nodeId" }) + .Do(); + } + + private bool ColumnExists(string tableName, string columnName) + { + // that's ok even on MySql + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); + return columns.Any(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName)); + } + + private void RemoveDuplicates() + { + const string sql = @"delete from cmsPreviewXml where versionId in ( +select cmsPreviewXml.versionId from cmsPreviewXml +join cmsDocument on cmsPreviewXml.versionId=cmsDocument.versionId +where cmsDocument.newest <> 1)"; + + Context.Database.Execute(sql); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs new file mode 100644 index 0000000000..ba29880e79 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs @@ -0,0 +1,32 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class SuperZero : MigrationBase + { + public SuperZero(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var exists = Database.Fetch("select id from umbracoUser where id=-1;").Count > 0; + if (exists) return; + + Database.Execute("update umbracoUser set userLogin = userLogin + '__' where userId=0"); + + Database.Execute("set identity_insert umbracoUser on;"); + Database.Execute(@" + insert into umbracoUser select -1, + userDisabled, userNoConsole, userName, substring(userLogin, 1, len(userLogin) - 2), userPassword, passwordConfig, + userEmail, userLanguage, securityStampToken, failedLoginAttempts, lastLockoutDate, + lastPasswordChangeDate, lastLoginDate, emailConfirmedDate, invitedDate, + createDate, updateDate, avatar + from umbracoUser where id=0;"); + Database.Execute("set identity_insert umbracoUser off;"); + + Database.Execute("update umbracoUser2UserGroup set userId=-1 where userId=0;"); + Database.Execute("update umbracoNode set nodeUser=-1 where nodeUser=0;"); + Database.Execute($"update {Constants.DatabaseSchema.Tables.ContentVersion} set userId=-1 where userId=0;"); + Database.Execute("delete from umbracoUser where id=0;"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TagsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TagsMigration.cs new file mode 100644 index 0000000000..89cb7e74ac --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TagsMigration.cs @@ -0,0 +1,24 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class TagsMigration : MigrationBase + { + public TagsMigration(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // alter columns => non-null + AlterColumn(Constants.DatabaseSchema.Tables.Tag, "group"); + AlterColumn(Constants.DatabaseSchema.Tables.Tag, "tag"); + + //AddColumn(Constants.DatabaseSchema.Tables.Tag, "key"); + + // kill unused parentId column + Delete.ForeignKey("FK_cmsTags_cmsTags").OnTable(Constants.DatabaseSchema.Tables.Tag).Do(); + Delete.Column("ParentId").FromTable(Constants.DatabaseSchema.Tables.Tag); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs new file mode 100644 index 0000000000..174404b1b9 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class VariantsMigration : MigrationBase + { + public VariantsMigration(IMigrationContext context) + : base(context) + { } + + // notes + // do NOT use Rename.Column as it's borked on SQLCE - use ReplaceColumn instead + // not sure it all runs on MySql, needs to test + + public override void Migrate() + { + // delete *all* keys and indexes - because of FKs + Delete.KeysAndIndexes().Do(); + + MigratePropertyData(); + MigrateContentAndPropertyTypes(); + MigrateContent(); + MigrateVersions(); + + if (Database.Fetch($@"SELECT {Constants.DatabaseSchema.Tables.ContentVersion}.nodeId, COUNT({Constants.DatabaseSchema.Tables.ContentVersion}.id) +FROM {Constants.DatabaseSchema.Tables.ContentVersion} +JOIN {Constants.DatabaseSchema.Tables.DocumentVersion} ON {Constants.DatabaseSchema.Tables.ContentVersion}.id={Constants.DatabaseSchema.Tables.DocumentVersion}.id +WHERE {Constants.DatabaseSchema.Tables.DocumentVersion}.published=1 +GROUP BY {Constants.DatabaseSchema.Tables.ContentVersion}.nodeId +HAVING COUNT({Constants.DatabaseSchema.Tables.ContentVersion}.id) > 1").Any()) + { + Debugger.Break(); + throw new Exception("Migration failed: duplicate 'published' document versions."); + } + + if (Database.Fetch($@"SELECT v1.nodeId, v1.id, COUNT(v2.id) +FROM {Constants.DatabaseSchema.Tables.ContentVersion} v1 +LEFT JOIN {Constants.DatabaseSchema.Tables.ContentVersion} v2 ON v1.nodeId=v2.nodeId AND v2.[current]=1 +GROUP BY v1.nodeId, v1.id +HAVING COUNT(v2.id) <> 1").Any()) + { + Debugger.Break(); + throw new Exception("Migration failed: missing or duplicate 'current' content versions."); + } + + // re-create *all* keys and indexes + foreach (var x in DatabaseSchemaCreator.OrderedTables) + Create.KeysAndIndexes(x).Do(); + } + + private void MigratePropertyData() + { + // if the table has already been renamed, we're done + if (TableExists(Constants.DatabaseSchema.Tables.PropertyData)) + return; + + // add columns + if (!ColumnExists(PreTables.PropertyData, "languageId")) + AddColumn(PreTables.PropertyData, "languageId"); + if (!ColumnExists(PreTables.PropertyData, "segment")) + AddColumn(PreTables.PropertyData, "segment"); + + // rename columns + if (ColumnExists(PreTables.PropertyData, "dataNtext")) + ReplaceColumn(PreTables.PropertyData, "dataNtext", "textValue"); + if (ColumnExists(PreTables.PropertyData, "dataNvarchar")) + ReplaceColumn(PreTables.PropertyData, "dataNvarchar", "varcharValue"); + if (ColumnExists(PreTables.PropertyData, "dataDecimal")) + ReplaceColumn(PreTables.PropertyData, "dataDecimal", "decimalValue"); + if (ColumnExists(PreTables.PropertyData, "dataInt")) + ReplaceColumn(PreTables.PropertyData, "dataInt", "intValue"); + if (ColumnExists(PreTables.PropertyData, "dataDate")) + ReplaceColumn(PreTables.PropertyData, "dataDate", "dateValue"); + + // transform column versionId from guid to integer (contentVersion.id) + if (ColumnType(PreTables.PropertyData, "versionId") == "uniqueidentifier") + { + Database.Execute($"ALTER TABLE {PreTables.PropertyData} ADD COLUMN versionId2 INT NULL;"); + // SQLCE does not support UPDATE...FROM + var temp = Database.Fetch($"SELECT id, versionId FROM {PreTables.ContentVersion}"); + foreach (var t in temp) + Database.Execute($"UPDATE {PreTables.PropertyData} SET versionId2=@v2 WHERE versionId=@v1", new { v1 = t.versionId, v2 = t.id }); + Delete.Column("versionId").FromTable(PreTables.PropertyData).Do(); + ReplaceColumn(PreTables.PropertyData, "versionId2", "versionId"); + } + + // drop column + if (ColumnExists(PreTables.PropertyData, "contentNodeId")) + Delete.Column("contentNodeId").FromTable(PreTables.PropertyData).Do(); + + // rename table + Rename.Table(PreTables.PropertyData).To(Constants.DatabaseSchema.Tables.PropertyData).Do(); + } + + private void MigrateContentAndPropertyTypes() + { + if (!ColumnExists(PreTables.ContentType, "variations")) + AddColumn(PreTables.ContentType, "variations"); + if (!ColumnExists(PreTables.PropertyType, "variations")) + AddColumn(PreTables.PropertyType, "variations"); + } + + private void MigrateContent() + { + // if the table has already been renamed, we're done + if (TableExists(Constants.DatabaseSchema.Tables.Content)) + return; + + // rename columns + if (ColumnExists(PreTables.Content, "contentType")) + ReplaceColumn(PreTables.Content, "contentType", "contentTypeId"); + + // drop columns + if (ColumnExists(PreTables.Content, "pk")) + Delete.Column("pk").FromTable(PreTables.Content).Do(); + + // rename table + Rename.Table(PreTables.Content).To(Constants.DatabaseSchema.Tables.Content).Do(); + } + + private void MigrateVersions() + { + // if the table has already been renamed, we're done + if (TableExists(Constants.DatabaseSchema.Tables.ContentVersion)) + return; + + // if the table already exists, we're done + if (TableExists(Constants.DatabaseSchema.Tables.DocumentVersion)) + return; + + // if the table has already been renamed, we're done + if (TableExists(Constants.DatabaseSchema.Tables.Document)) + return; + + // do it all at once + + // add contentVersion columns + if (!ColumnExists(PreTables.ContentVersion, "text")) + AddColumn(PreTables.ContentVersion, "text"); + if (!ColumnExists(PreTables.ContentVersion, "current")) + { + AddColumn(PreTables.ContentVersion, "current", out var sqls); + Database.Execute($@"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} SET {SqlSyntax.GetQuotedColumnName("current")}=0"); + foreach (var sql in sqls) Database.Execute(sql); + } + if (!ColumnExists(PreTables.ContentVersion, "userId")) + { + AddColumn(PreTables.ContentVersion, "userId", out var sqls); + Database.Execute($@"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} SET userId=0"); + foreach (var sql in sqls) Database.Execute(sql); + } + + // rename contentVersion contentId column + if (ColumnExists(PreTables.ContentVersion, "ContentId")) + ReplaceColumn(PreTables.ContentVersion, "ContentId", "nodeId"); + + // populate contentVersion text, current and userId columns for documents + // SQLCE does not support UPDATE...FROM + var temp1 = Database.Fetch($"SELECT versionId, text, newest, documentUser FROM {PreTables.Document}"); + foreach (var t in temp1) + Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=@current, userId=@userId WHERE versionId=@versionId", + new { text = t.text, current = t.newest, userId=t.documentUser, versionId=t.versionId }); + + // populate contentVersion text and current columns for non-documents, userId is default + // SQLCE does not support UPDATE...FROM + var temp2 = Database.Fetch($@"SELECT cver.versionId, n.text +FROM {PreTables.ContentVersion} cver +JOIN {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.Node)} n ON cver.nodeId=n.id +WHERE cver.versionId NOT IN (SELECT versionId FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)})"); + + foreach (var t in temp2) + Database.Execute($@"UPDATE {PreTables.ContentVersion} SET text=@text, {SqlSyntax.GetQuotedColumnName("current")}=1, userId=0 WHERE versionId=@versionId", + new { text = t.text, versionId=t.versionId }); + + // create table + Create.Table(withoutKeysAndIndexes: true).Do(); + + // every document row becomes a document version + Database.Execute($@"INSERT INTO {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DocumentVersion)} (id, templateId, published) +SELECT cver.id, doc.templateId, doc.published +FROM {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver +JOIN {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc ON doc.nodeId=cver.nodeId AND doc.versionId=cver.versionId"); + + // need to add extra rows for where published=newest + // 'cos INSERT above has inserted the 'published' document version + // and v8 always has a 'edited' document version too + var temp3 = Database.Fetch($@"SELECT doc.nodeId, doc.updateDate, doc.documentUser, doc.text, doc.templateId, cver.id versionId +FROM {SqlSyntax.GetQuotedTableName(PreTables.Document)} doc +JOIN {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} cver ON doc.nodeId=cver.nodeId AND doc.versionId=cver.versionId +WHERE doc.newest=1 AND doc.published=1"); + var getIdentity = Database.SqlContext.DatabaseType.IsMySql() + ? "LAST_INSERT_ID()" + : "@@@@IDENTITY"; + foreach (var t in temp3) + { + Database.Execute($@"INSERT INTO {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} (nodeId, versionId, versionDate, userId, {SqlSyntax.GetQuotedColumnName("current")}, text) +VALUES (@nodeId, @versionId, @versionDate, @userId, 1, @text)", new { nodeId=t.nodeId, versionId=Guid.NewGuid(), versionDate=t.updateDate, userId=t.documentUser, text=t.text }); + var id = Database.ExecuteScalar("SELECT " + getIdentity); + Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.ContentVersion)} SET {SqlSyntax.GetQuotedColumnName("current")}=0 WHERE nodeId=@0 AND id<>@1", (int) t.nodeId, id); + Database.Execute($@"INSERT INTO {SqlSyntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DocumentVersion)} (id, templateId, published) +VALUES (@id, @templateId, 0)", new { id=id, templateId=t.templateId }); + + var versionId = (int) t.versionId; + var pdatas = Database.Fetch(Sql().Select().From().Where(x => x.VersionId == versionId)); + foreach (var pdata in pdatas) + { + pdata.VersionId = id; + Database.Insert(pdata); + } + } + + // reduce document to 1 row per content + Database.Execute($@"DELETE FROM {PreTables.Document} +WHERE versionId NOT IN (SELECT (versionId) FROM {PreTables.ContentVersion} WHERE {SqlSyntax.GetQuotedColumnName("current")} = 1) AND (published<>1 OR newest<>1)"); + + // drop some document columns + Delete.Column("text").FromTable(PreTables.Document).Do(); + Delete.Column("templateId").FromTable(PreTables.Document).Do(); + Delete.Column("documentUser").FromTable(PreTables.Document).Do(); + Delete.Column("updateDate").FromTable(PreTables.Document).Do(); + Delete.Column("versionId").FromTable(PreTables.Document).Do(); + Delete.Column("newest").FromTable(PreTables.Document).Do(); + + // add and populate edited column + if (!ColumnExists(PreTables.Document, "edited")) + { + AddColumn(PreTables.Document, "edited", out var sqls); + Database.Execute($"UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=0"); + foreach (var sql in sqls) Database.Execute(sql); + } + + // set 'edited' to true whenever a 'non-published' property data is != a published one + // cannot compare NTEXT values in TSQL + // cannot cast NTEXT to NVARCHAR(MAX) in SQLCE + // ... bah ... + var temp = Database.Fetch($@"SELECT n.id, +v1.intValue intValue1, v1.decimalValue decimalValue1, v1.dateValue dateValue1, v1.varcharValue varcharValue1, v1.textValue textValue1, +v2.intValue intValue2, v2.decimalValue decimalValue2, v2.dateValue dateValue2, v2.varcharValue varcharValue2, v2.textValue textValue2 +FROM {Constants.DatabaseSchema.Tables.Node} n +JOIN {PreTables.ContentVersion} cv1 ON n.id=cv1.nodeId AND cv1.{SqlSyntax.GetQuotedColumnName("current")}=1 +JOIN {Constants.DatabaseSchema.Tables.PropertyData} v1 ON cv1.id=v1.versionId +JOIN {PreTables.ContentVersion} cv2 ON n.id=cv2.nodeId +JOIN {Constants.DatabaseSchema.Tables.DocumentVersion} dv ON cv2.id=dv.id AND dv.published=1 +JOIN {Constants.DatabaseSchema.Tables.PropertyData} v2 ON cv2.id=v2.versionId +WHERE v1.propertyTypeId=v2.propertyTypeId AND v1.languageId=v2.languageId AND v1.segment=v2.segment"); + + foreach (var t in temp) + if (t.intValue1 != t.intValue2 || t.decimalValue1 != t.decimalValue2 || t.dateValue1 != t.dateValue2 || t.varcharValue1 != t.varcharValue2 || t.textValue1 != t.textValue2) + Database.Execute("UPDATE {SqlSyntax.GetQuotedTableName(PreTables.Document)} SET edited=1 WHERE nodeId=@nodeIdd", new { t.id }); + + // drop more columns + Delete.Column("versionId").FromTable(PreTables.ContentVersion).Do(); + + // rename tables + Rename.Table(PreTables.ContentVersion).To(Constants.DatabaseSchema.Tables.ContentVersion).Do(); + Rename.Table(PreTables.Document).To(Constants.DatabaseSchema.Tables.Document).Do(); + } + + private static class PreTables + { + // ReSharper disable UnusedMember.Local + public const string Lock = "umbracoLock"; + public const string Log = "umbracoLog"; + + public const string Node = "umbracoNode"; + public const string NodeData = "cmsContentNu"; + public const string NodeXml = "cmsContentXml"; + public const string NodePreviewXml = "cmsPreviewXml"; + + public const string ContentType = "cmsContentType"; + public const string ContentChildType = "cmsContentTypeAllowedContentType"; + public const string DocumentType = "cmsDocumentType"; + public const string ElementTypeTree = "cmsContentType2ContentType"; + public const string DataType = "cmsDataType"; + public const string DataTypePreValue = "cmsDataTypePreValues"; + public const string Template = "cmsTemplate"; + + public const string Content = "cmsContent"; + public const string ContentVersion = "cmsContentVersion"; + public const string Document = "cmsDocument"; + + public const string PropertyType = "cmsPropertyType"; + public const string PropertyTypeGroup = "cmsPropertyTypeGroup"; + public const string PropertyData = "cmsPropertyData"; + + public const string RelationType = "umbracoRelationType"; + public const string Relation = "umbracoRelation"; + + public const string Domain = "umbracoDomains"; + public const string Language = "umbracoLanguage"; + public const string DictionaryEntry = "cmsDictionary"; + public const string DictionaryValue = "cmsLanguageText"; + + public const string User = "umbracoUser"; + public const string UserGroup = "umbracoUserGroup"; + public const string UserStartNode = "umbracoUserStartNode"; + public const string User2UserGroup = "umbracoUser2UserGroup"; + public const string User2NodeNotify = "umbracoUser2NodeNotify"; + public const string UserGroup2App = "umbracoUserGroup2App"; + public const string UserGroup2NodePermission = "umbracoUserGroup2NodePermission"; + public const string ExternalLogin = "umbracoExternalLogin"; + + public const string Macro = "cmsMacro"; + public const string MacroProperty = "cmsMacroProperty"; + + public const string Member = "cmsMember"; + public const string MemberType = "cmsMemberType"; + public const string Member2MemberGroup = "cmsMember2MemberGroup"; + + public const string Access = "umbracoAccess"; + public const string AccessRule = "umbracoAccessRule"; + public const string RedirectUrl = "umbracoRedirectUrl"; + + public const string CacheInstruction = "umbracoCacheInstruction"; + public const string Migration = "umbracoMigration"; + public const string Server = "umbracoServer"; + + public const string Tag = "cmsTags"; + public const string TagRelationship = "cmsTagRelationship"; + + public const string Task = "cmsTask"; + public const string TaskType = "cmsTaskType"; + // ReSharper restore UnusedMember.Local + } + } +} diff --git a/src/Umbraco.Core/ModelMapperHelper.cs b/src/Umbraco.Core/ModelMapperHelper.cs deleted file mode 100644 index a76b68e7dd..0000000000 --- a/src/Umbraco.Core/ModelMapperHelper.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; - -namespace Umbraco.Core -{ - /// - /// Helper class for static model mapping with automapper - /// - internal static class ModelMapperHelper - { - internal static IMappingExpression SelfMap(this IConfiguration config) - { - return config.CreateMap(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ApplicationTree.cs b/src/Umbraco.Core/Models/ApplicationTree.cs index 92002f1e99..53873b8b90 100644 --- a/src/Umbraco.Core/Models/ApplicationTree.cs +++ b/src/Umbraco.Core/Models/ApplicationTree.cs @@ -7,12 +7,13 @@ namespace Umbraco.Core.Models [DebuggerDisplay("Tree - {Title} ({ApplicationAlias})")] public class ApplicationTree { + private static readonly ConcurrentDictionary ResolvedTypes = new ConcurrentDictionary(); + /// /// Initializes a new instance of the class. /// public ApplicationTree() { } - /// /// Initializes a new instance of the class. /// @@ -26,14 +27,14 @@ namespace Umbraco.Core.Models /// The tree type. public ApplicationTree(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) { - this.Initialize = initialize; - this.SortOrder = sortOrder; - this.ApplicationAlias = applicationAlias; - this.Alias = alias; - this.Title = title; - this.IconClosed = iconClosed; - this.IconOpened = iconOpened; - this.Type = type; + Initialize = initialize; + SortOrder = sortOrder; + ApplicationAlias = applicationAlias; + Alias = alias; + Title = title; + IconClosed = iconClosed; + IconOpened = iconOpened; + Type = type; } /// @@ -52,13 +53,13 @@ namespace Umbraco.Core.Models /// Gets the application alias. /// /// The application alias. - public string ApplicationAlias { get; private set; } + public string ApplicationAlias { get; } /// /// Gets the tree alias. /// /// The alias. - public string Alias { get; private set; } + public string Alias { get; } /// /// Gets or sets the tree title. @@ -85,18 +86,14 @@ namespace Umbraco.Core.Models public string Type { get; set; } private Type _runtimeType; - + /// /// Returns the CLR type based on it's assembly name stored in the config /// /// public Type GetRuntimeType() { - if (_runtimeType != null) - return _runtimeType; - - _runtimeType = TryGetType(Type); - return _runtimeType; + return _runtimeType ?? (_runtimeType = System.Type.GetType(Type)); } /// @@ -117,21 +114,18 @@ namespace Umbraco.Core.Models } //we need to implement a bit of a hack here due to some trees being renamed and backwards compat - var parts = type.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length == 2) - { - if (parts[1].Trim() == "umbraco" && parts[0].StartsWith("Umbraco.Web.Trees") && parts[0].EndsWith("Controller") == false) - { - //if it's one of our controllers but it's not suffixed with "Controller" then add it and try again - var tempType = parts[0] + "Controller, umbraco"; + var parts = type.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length != 2) + throw new InvalidOperationException("Could not resolve type"); + if (parts[1].Trim() != "Umbraco.Web" || parts[0].StartsWith("Umbraco.Web.Trees") == false || parts[0].EndsWith("Controller")) + throw new InvalidOperationException("Could not resolve type"); - result = System.Type.GetType(tempType); - if (result != null) - { - return result; - } - } - } + //if it's one of our controllers but it's not suffixed with "Controller" then add it and try again + var tempType = parts[0] + "Controller, Umbraco.Web"; + + result = System.Type.GetType(tempType); + if (result != null) + return result; throw new InvalidOperationException("Could not resolve type"); }); @@ -139,10 +133,9 @@ namespace Umbraco.Core.Models catch (InvalidOperationException) { //swallow, this is our own exception, couldn't find the type + // fixme bad use of exceptions here! return null; } } - - private static readonly ConcurrentDictionary ResolvedTypes = new ConcurrentDictionary(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/AuditEntry.cs b/src/Umbraco.Core/Models/AuditEntry.cs new file mode 100644 index 0000000000..2076e5328c --- /dev/null +++ b/src/Umbraco.Core/Models/AuditEntry.cs @@ -0,0 +1,94 @@ +using System; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents an audited event. + /// + [Serializable] + [DataContract(IsReference = true)] + internal class AuditEntry : EntityBase, IAuditEntry + { + private static PropertySelectors _selectors; + + private int _performingUserId; + private string _performingDetails; + private string _performingIp; + private int _affectedUserId; + private string _affectedDetails; + private string _eventType; + private string _eventDetails; + + private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); + + private class PropertySelectors + { + public readonly PropertyInfo PerformingUserId = ExpressionHelper.GetPropertyInfo(x => x.PerformingUserId); + public readonly PropertyInfo PerformingDetails = ExpressionHelper.GetPropertyInfo(x => x.PerformingDetails); + public readonly PropertyInfo PerformingIp = ExpressionHelper.GetPropertyInfo(x => x.PerformingIp); + public readonly PropertyInfo AffectedUserId = ExpressionHelper.GetPropertyInfo(x => x.AffectedUserId); + public readonly PropertyInfo AffectedDetails = ExpressionHelper.GetPropertyInfo(x => x.AffectedDetails); + public readonly PropertyInfo EventType = ExpressionHelper.GetPropertyInfo(x => x.EventType); + public readonly PropertyInfo EventDetails = ExpressionHelper.GetPropertyInfo(x => x.EventDetails); + } + + /// + public int PerformingUserId + { + get => _performingUserId; + set => SetPropertyValueAndDetectChanges(value, ref _performingUserId, Selectors.PerformingUserId); + } + + /// + public string PerformingDetails + { + get => _performingDetails; + set => SetPropertyValueAndDetectChanges(value, ref _performingDetails, Selectors.PerformingDetails); + } + + /// + public string PerformingIp + { + get => _performingIp; + set => SetPropertyValueAndDetectChanges(value, ref _performingIp, Selectors.PerformingIp); + } + + /// + public DateTime EventDateUtc + { + get => CreateDate; + set => CreateDate = value; + } + + /// + public int AffectedUserId + { + get => _affectedUserId; + set => SetPropertyValueAndDetectChanges(value, ref _affectedUserId, Selectors.AffectedUserId); + } + + /// + public string AffectedDetails + { + get => _affectedDetails; + set => SetPropertyValueAndDetectChanges(value, ref _affectedDetails, Selectors.AffectedDetails); + } + + /// + public string EventType + { + get => _eventType; + set => SetPropertyValueAndDetectChanges(value, ref _eventType, Selectors.EventType); + } + + /// + public string EventDetails + { + get => _eventDetails; + set => SetPropertyValueAndDetectChanges(value, ref _eventDetails, Selectors.EventDetails); + } + } +} diff --git a/src/Umbraco.Core/Models/AuditItem.cs b/src/Umbraco.Core/Models/AuditItem.cs index 7fb38c4ae3..6bfe32bd77 100644 --- a/src/Umbraco.Core/Models/AuditItem.cs +++ b/src/Umbraco.Core/Models/AuditItem.cs @@ -1,8 +1,8 @@ -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { - public sealed class AuditItem : Entity, IAuditItem + public sealed class AuditItem : EntityBase, IAuditItem { /// /// Constructor for creating an item to be created @@ -23,9 +23,8 @@ namespace Umbraco.Core.Models EnableChangeTracking(); } - public string Comment { get; private set; } - public AuditType AuditType { get; private set; } - public int UserId { get; private set; } - + public string Comment { get; } + public AuditType AuditType { get; } + public int UserId { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/AuditType.cs b/src/Umbraco.Core/Models/AuditType.cs index 7174bd28a2..da2c8e5d8e 100644 --- a/src/Umbraco.Core/Models/AuditType.cs +++ b/src/Umbraco.Core/Models/AuditType.cs @@ -82,4 +82,4 @@ /// Custom } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Consent.cs b/src/Umbraco.Core/Models/Consent.cs new file mode 100644 index 0000000000..87dd9767a0 --- /dev/null +++ b/src/Umbraco.Core/Models/Consent.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a consent. + /// + [Serializable] + [DataContract(IsReference = true)] + internal class Consent : EntityBase, IConsent + { + private static PropertySelectors _selector; + + private bool _current; + private string _source; + private string _context; + private string _action; + private ConsentState _state; + private string _comment; + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo Current = ExpressionHelper.GetPropertyInfo(x => x.Current); + public readonly PropertyInfo Source = ExpressionHelper.GetPropertyInfo(x => x.Source); + public readonly PropertyInfo Context = ExpressionHelper.GetPropertyInfo(x => x.Context); + public readonly PropertyInfo Action = ExpressionHelper.GetPropertyInfo(x => x.Action); + public readonly PropertyInfo State = ExpressionHelper.GetPropertyInfo(x => x.State); + public readonly PropertyInfo Comment = ExpressionHelper.GetPropertyInfo(x => x.Comment); + } + + private static PropertySelectors Selectors => _selector ?? (_selector = new PropertySelectors()); + + /// + public bool Current + { + get => _current; + set => SetPropertyValueAndDetectChanges(value, ref _current, Selectors.Current); + } + + /// + public string Source + { + get => _source; + set + { + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); + SetPropertyValueAndDetectChanges(value, ref _source, Selectors.Source); + } + } + + /// + public string Context + { + get => _context; + set + { + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); + SetPropertyValueAndDetectChanges(value, ref _context, Selectors.Context); + } + } + + /// + public string Action + { + get => _action; + set + { + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); + SetPropertyValueAndDetectChanges(value, ref _action, Selectors.Action); + } + } + + /// + public ConsentState State + { + get => _state; + // note: we probably should validate the state here, but since the + // enum is [Flags] with many combinations, this could be expensive + set => SetPropertyValueAndDetectChanges(value, ref _state, Selectors.State); + } + + /// + public string Comment + { + get => _comment; + set => SetPropertyValueAndDetectChanges(value, ref _comment, Selectors.Comment); + } + + /// + public IEnumerable History => HistoryInternal; + + /// + /// Gets the previous states of this consent. + /// + internal List HistoryInternal { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/ConsentExtensions.cs b/src/Umbraco.Core/Models/ConsentExtensions.cs new file mode 100644 index 0000000000..fabeaf5809 --- /dev/null +++ b/src/Umbraco.Core/Models/ConsentExtensions.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Models +{ + /// + /// Provides extension methods for the interface. + /// + public static class ConsentExtensions + { + /// + /// Determines whether the consent is granted. + /// + public static bool IsGranted(this IConsent consent) => (consent.State & ConsentState.Granted) > 0; + + /// + /// Determines whether the consent is revoked. + /// + public static bool IsRevoked(this IConsent consent) => (consent.State & ConsentState.Revoked) > 0; + } +} diff --git a/src/Umbraco.Core/Models/ConsentState.cs b/src/Umbraco.Core/Models/ConsentState.cs new file mode 100644 index 0000000000..ed370823f3 --- /dev/null +++ b/src/Umbraco.Core/Models/ConsentState.cs @@ -0,0 +1,38 @@ +using System; + +namespace Umbraco.Core.Models +{ + /// + /// Represents the state of a consent. + /// + [Flags] + public enum ConsentState // : int + { + // note - this is a [Flags] enumeration + // on can create detailed flags such as: + //GrantedOptIn = Granted | 0x0001 + //GrandedByForce = Granted | 0x0002 + // + // 16 situations for each Pending/Granted/Revoked should be ok + + /// + /// There is no consent. + /// + None = 0, + + /// + /// Consent is pending and has not been granted yet. + /// + Pending = 0x10000, + + /// + /// Consent has been granted. + /// + Granted = 0x20000, + + /// + /// Consent has been revoked. + /// + Revoked = 0x40000 + } +} diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index ef16a78248..f468d8be90 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; @@ -15,12 +16,13 @@ namespace Umbraco.Core.Models private IContentType _contentType; private ITemplate _template; private bool _published; - private string _language; + private PublishedState _publishedState; private DateTime? _releaseDate; private DateTime? _expireDate; - private int _writer; private string _nodeName;//NOTE Once localization is introduced this will be the non-localized Node Name. + private static readonly Lazy Ps = new Lazy(); + /// /// Constructor for creating a Content object /// @@ -28,9 +30,8 @@ namespace Umbraco.Core.Models /// Parent object /// ContentType for the current Content object public Content(string name, IContent parent, IContentType contentType) - : this(name, parent, contentType, new PropertyCollection()) - { - } + : this(name, parent, contentType, new PropertyCollection()) + { } /// /// Constructor for creating a Content object @@ -39,13 +40,13 @@ namespace Umbraco.Core.Models /// Parent object /// ContentType for the current Content object /// Collection of properties - public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties) - : base(name, parent, contentType, properties) - { - Mandate.ParameterNotNull(contentType, "contentType"); - - _contentType = contentType; - } + public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties) + : base(name, parent, contentType, properties) + { + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + _publishedState = PublishedState.Unpublished; + PublishedVersionId = 0; + } /// /// Constructor for creating a Content object @@ -55,8 +56,7 @@ namespace Umbraco.Core.Models /// ContentType for the current Content object public Content(string name, int parentId, IContentType contentType) : this(name, parentId, contentType, new PropertyCollection()) - { - } + { } /// /// Constructor for creating a Content object @@ -66,23 +66,20 @@ namespace Umbraco.Core.Models /// ContentType for the current Content object /// Collection of properties public Content(string name, int parentId, IContentType contentType, PropertyCollection properties) - : base(name, parentId, contentType, properties) + : base(name, parentId, contentType, properties) { - Mandate.ParameterNotNull(contentType, "contentType"); - - _contentType = contentType; + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + _publishedState = PublishedState.Unpublished; + PublishedVersionId = 0; } - private static readonly Lazy Ps = new Lazy(); - + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template); public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.Published); - public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ReleaseDate); public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo(x => x.ExpireDate); - public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId); public readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeName); } @@ -97,8 +94,8 @@ namespace Umbraco.Core.Models [DataMember] public virtual ITemplate Template { - get { return _template; } - set { SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector); } + get => _template ?? _contentType.DefaultTemplate; + set => SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector); } /// @@ -126,38 +123,53 @@ namespace Umbraco.Core.Models } /// - /// Boolean indicating whether this Content is Published or not + /// Gets or sets a value indicating whether this content item is published or not. /// - /// - /// Setting Published to true/false should be private or internal and should ONLY be used for wiring up the value - /// from the db or modifying it based on changing the published state. - /// [DataMember] public bool Published { - get { return _published; } - internal set { SetPropertyValueAndDetectChanges(value, ref _published, Ps.Value.PublishedSelector); } + get => _published; + + // the setter is internal and should only be invoked from + // - the ContentFactory when creating a content entity from a dto + // - the ContentRepository when updating a content entity + internal set + { + SetPropertyValueAndDetectChanges(value, ref _published, Ps.Value.PublishedSelector); + _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; + } } /// - /// Language of the data contained within this Content object. + /// Gets the published state of the content item. /// - [Obsolete("This is not used and will be removed from the codebase in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - public string Language + /// The state should be Published or Unpublished, depending on whether Published + /// is true or false, but can also temporarily be Publishing or Unpublishing when the + /// content item is about to be saved. + [DataMember] + public PublishedState PublishedState { - get { return _language; } - set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); } + get => _publishedState; + set + { + if (value != PublishedState.Publishing && value != PublishedState.Unpublishing) + throw new ArgumentException("Invalid state, only Publishing and Unpublishing are accepted."); + _publishedState = value; + } } + [IgnoreDataMember] + public bool Edited { get; internal set; } + + /// /// The date this Content should be released and thus be published /// [DataMember] public DateTime? ReleaseDate { - get { return _releaseDate; } - set { SetPropertyValueAndDetectChanges(value, ref _releaseDate, Ps.Value.ReleaseDateSelector); } + get => _releaseDate; + set => SetPropertyValueAndDetectChanges(value, ref _releaseDate, Ps.Value.ReleaseDateSelector); } /// @@ -166,18 +178,8 @@ namespace Umbraco.Core.Models [DataMember] public DateTime? ExpireDate { - get { return _expireDate; } - set { SetPropertyValueAndDetectChanges(value, ref _expireDate, Ps.Value.ExpireDateSelector); } - } - - /// - /// Id of the user who wrote/updated this Content - /// - [DataMember] - public virtual int WriterId - { - get { return _writer; } - set { SetPropertyValueAndDetectChanges(value, ref _writer, Ps.Value.WriterSelector); } + get => _expireDate; + set => SetPropertyValueAndDetectChanges(value, ref _expireDate, Ps.Value.ExpireDateSelector); } /// @@ -189,18 +191,199 @@ namespace Umbraco.Core.Models [DataMember] internal string NodeName { - get { return _nodeName; } - set { SetPropertyValueAndDetectChanges(value, ref _nodeName, Ps.Value.NodeNameSelector); } + get => _nodeName; + set => SetPropertyValueAndDetectChanges(value, ref _nodeName, Ps.Value.NodeNameSelector); } - /// /// Gets the ContentType used by this content object /// [IgnoreDataMember] - public IContentType ContentType + public IContentType ContentType => _contentType; + + [IgnoreDataMember] + public DateTime? PublishDate { get; internal set; } + + [IgnoreDataMember] + public int? PublisherId { get; internal set; } + + [IgnoreDataMember] + public ITemplate PublishTemplate { get; internal set; } + + [IgnoreDataMember] + public string PublishName { get; internal set; } + + [IgnoreDataMember] + public int PublishedVersionId { get; internal set; } + + [DataMember] + public bool Blueprint { get; internal set; } + + /// + public virtual bool PublishAllValues() { - get { return _contentType; } + if (ValidateAll().Any()) + return false; + + foreach (var property in Properties) + property.PublishAllValues(); + _publishedState = PublishedState.Publishing; + return true; + } + + /// + public virtual bool PublishValues(int? languageId = null, string segment = null) + { + if (Validate(languageId, segment).Any()) + return false; + + foreach (var property in Properties) + property.PublishValue(languageId, segment); + _publishedState = PublishedState.Publishing; + return true; + } + + /// + public virtual bool PublishCultureValues(int? languageId = null) + { + if (ValidateCulture(languageId).Any()) + return false; + + foreach (var property in Properties) + property.PublishCultureValues(languageId); + _publishedState = PublishedState.Publishing; + return true; + } + + /// + public virtual void ClearAllPublishedValues() + { + foreach (var property in Properties) + property.ClearPublishedAllValues(); + _publishedState = PublishedState.Publishing; + } + + /// + public virtual void ClearPublishedValues(int? languageId = null, string segment = null) + { + foreach (var property in Properties) + property.ClearPublishedValue(languageId, segment); + _publishedState = PublishedState.Publishing; + } + + /// + public virtual void ClearCulturePublishedValues(int? languageId = null) + { + foreach (var property in Properties) + property.ClearPublishedCultureValues(languageId); + _publishedState = PublishedState.Publishing; + } + + private bool CopyingFromSelf(IContent other) + { + // copying from the same Id and VersionPk + return Id == other.Id && VersionId == other.VersionId; + } + + /// + public virtual void CopyAllValues(IContent other) + { + if (other.ContentTypeId != ContentTypeId) + throw new InvalidOperationException("Cannot copy values from a different content type."); + + // we could copy from another document entirely, + // or from another version of the same document, + // in which case there is a special case. + var published = CopyingFromSelf(other); + + // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails + + // clear all existing properties + foreach (var property in Properties) + foreach (var pvalue in property.Values) + if (property.PropertyType.ValidateVariation(pvalue.LanguageId, pvalue.Segment, false)) + property.SetValue(null, pvalue.LanguageId, pvalue.Segment); + + // copy other properties + var otherProperties = other.Properties; + foreach (var otherProperty in otherProperties) + { + var alias = otherProperty.PropertyType.Alias; + foreach (var pvalue in otherProperty.Values) + { + if (!otherProperty.PropertyType.ValidateVariation(pvalue.LanguageId, pvalue.Segment, false)) + continue; + var value = published ? pvalue.PublishedValue : pvalue.EditedValue; + SetValue(alias, value, pvalue.LanguageId, pvalue.Segment); + } + } + } + + /// + public virtual void CopyValues(IContent other, int? languageId = null, string segment = null) + { + if (other.ContentTypeId != ContentTypeId) + throw new InvalidOperationException("Cannot copy values from a different content type."); + + var published = CopyingFromSelf(other); + + // segment is invariant in comparisons + segment = segment?.ToLowerInvariant(); + + // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails + + // clear all existing properties + foreach (var property in Properties) + { + if (!property.PropertyType.ValidateVariation(languageId, segment, false)) + continue; + + foreach (var pvalue in property.Values) + if (pvalue.LanguageId == languageId && pvalue.Segment == segment) + property.SetValue(null, pvalue.LanguageId, pvalue.Segment); + } + + // copy other properties + var otherProperties = other.Properties; + foreach (var otherProperty in otherProperties) + { + if (!otherProperty.PropertyType.ValidateVariation(languageId, segment, false)) + continue; + + var alias = otherProperty.PropertyType.Alias; + SetValue(alias, otherProperty.GetValue(languageId, segment, published), languageId, segment); + } + } + + /// + public virtual void CopyCultureValues(IContent other, int? languageId = null) + { + if (other.ContentTypeId != ContentTypeId) + throw new InvalidOperationException("Cannot copy values from a different content type."); + + var published = CopyingFromSelf(other); + + // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails + + // clear all existing properties + foreach (var property in Properties) + foreach (var pvalue in property.Values) + if (pvalue.LanguageId == languageId && property.PropertyType.ValidateVariation(pvalue.LanguageId, pvalue.Segment, false)) + property.SetValue(null, pvalue.LanguageId, pvalue.Segment); + + // copy other properties + var otherProperties = other.Properties; + foreach (var otherProperty in otherProperties) + { + var alias = otherProperty.PropertyType.Alias; + foreach (var pvalue in otherProperty.Values) + { + if (pvalue.LanguageId != languageId || !otherProperty.PropertyType.ValidateVariation(pvalue.LanguageId, pvalue.Segment, false)) + continue; + var value = published ? pvalue.PublishedValue : pvalue.EditedValue; + SetValue(alias, value, pvalue.LanguageId, pvalue.Segment); + } + } } /// @@ -238,70 +421,12 @@ namespace Umbraco.Core.Models ChangeContentType(contentType); } - /// - /// Changes the Published state of the content object - /// - public void ChangePublishedState(PublishedState state) + public override void ResetDirtyProperties(bool rememberDirty) { - Published = state == PublishedState.Published; - PublishedState = state; - } + base.ResetDirtyProperties(rememberDirty); - [DataMember] - internal PublishedState PublishedState { get; set; } - - /// - /// Gets or sets the unique identifier of the published version, if any. - /// - [IgnoreDataMember] - public Guid PublishedVersionGuid { get; internal set; } - - /// - /// Gets a value indicating whether the content has a published version. - /// - public bool HasPublishedVersion { get { return PublishedVersionGuid != default(Guid); } } - - [IgnoreDataMember] - internal DateTime PublishedDate { get; set; } - - [DataMember] - public bool IsBlueprint { get; internal set; } - - /// - /// Changes the Trashed state of the content object - /// - /// Boolean indicating whether content is trashed (true) or not trashed (false) - /// - public override void ChangeTrashedState(bool isTrashed, int parentId = -20) - { - Trashed = isTrashed; - ParentId = parentId; - - //If the content is trashed and is published it should be marked as unpublished - if (isTrashed && Published) - { - ChangePublishedState(PublishedState.Unpublished); - } - } - - /// - /// Method to call when Entity is being updated - /// - /// Modified Date is set and a new Version guid is set - internal override void UpdatingEntity() - { - base.UpdatingEntity(); - Version = Guid.NewGuid(); - } - - /// - /// Creates a deep clone of the current entity with its identity and it's property identities reset - /// - /// - [Obsolete("Use DeepCloneWithResetIdentities instead")] - public IContent Clone() - { - return DeepCloneWithResetIdentities(); + // take care of the published state + _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; } /// @@ -312,21 +437,18 @@ namespace Umbraco.Core.Models { var clone = (Content)DeepClone(); clone.Key = Guid.Empty; - clone.Version = Guid.NewGuid(); + clone.VersionId = clone.PublishedVersionId = 0; clone.ResetIdentity(); foreach (var property in clone.Properties) - { property.ResetIdentity(); - property.Version = clone.Version; - } return clone; } public override object DeepClone() { - var clone = (Content)base.DeepClone(); + var clone = (Content) base.DeepClone(); //turn off change tracking clone.DisableChangeTracking(); //need to manually clone this since it's not settable @@ -340,4 +462,4 @@ namespace Umbraco.Core.Models } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index ebbb3fa80e..a1df720a83 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Web; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -17,83 +17,61 @@ namespace Umbraco.Core.Models [Serializable] [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")] - public abstract class ContentBase : Entity, IContentBase + public abstract class ContentBase : TreeEntityBase, IContentBase { - protected IContentTypeComposition ContentTypeBase; - - private Lazy _parentId; - private string _name;//NOTE Once localization is introduced this will be the localized Name of the Content/Media. - private int _sortOrder; - private int _level; - private string _path; - private int _creatorId; - private bool _trashed; + private static readonly Lazy Ps = new Lazy(); + private int _contentTypeId; + protected IContentTypeComposition ContentTypeBase; + private int _writerId; + + // fixme need to deal with localized names, how? + // for the time being, this is the node text = unique + private string _name; + private PropertyCollection _properties; - private readonly List _lastInvalidProperties = new List(); /// - /// Protected constructor for ContentBase (Base for Content and Media) + /// Initializes a new instance of the class. /// - /// Localized Name of the entity - /// - /// - /// protected ContentBase(string name, int parentId, IContentTypeComposition contentType, PropertyCollection properties) + : this(name, contentType, properties) { - Mandate.ParameterCondition(parentId != 0, "parentId"); - Mandate.ParameterNotNull(contentType, "contentType"); - Mandate.ParameterNotNull(properties, "properties"); - - ContentTypeBase = contentType; - Version = Guid.NewGuid(); - - _parentId = new Lazy(() => parentId); - _name = name; - _contentTypeId = contentType.Id; - _properties = properties; - _properties.EnsurePropertyTypes(PropertyTypes); - _additionalData = new Dictionary(); + if (parentId == 0) throw new ArgumentOutOfRangeException(nameof(parentId)); + ParentId = parentId; } /// - /// Protected constructor for ContentBase (Base for Content and Media) + /// Initializes a new instance of the class. /// - /// Localized Name of the entity - /// - /// - /// protected ContentBase(string name, IContentBase parent, IContentTypeComposition contentType, PropertyCollection properties) - { - Mandate.ParameterNotNull(parent, "parent"); - Mandate.ParameterNotNull(contentType, "contentType"); - Mandate.ParameterNotNull(properties, "properties"); + : this(name, contentType, properties) + { + if (parent == null) throw new ArgumentNullException(nameof(parent)); + SetParent(parent); + } - ContentTypeBase = contentType; - Version = Guid.NewGuid(); + private ContentBase(string name, IContentTypeComposition contentType, PropertyCollection properties) + { + ContentTypeBase = contentType ?? throw new ArgumentNullException(nameof(contentType)); - _parentId = new Lazy(() => parent.Id); - _name = name; - _contentTypeId = contentType.Id; - _properties = properties; - _properties.EnsurePropertyTypes(PropertyTypes); - _additionalData = new Dictionary(); - } + // initially, all new instances have + Id = 0; // no identity + VersionId = 0; // no versions - private static readonly Lazy Ps = new Lazy(); + Name = _name = name; + _contentTypeId = contentType.Id; + _properties = properties ?? throw new ArgumentNullException(nameof(properties)); + _properties.EnsurePropertyTypes(PropertyTypes); + } + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); - } + public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId); + } protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) { @@ -101,94 +79,17 @@ namespace Umbraco.Core.Models } /// - /// Gets or sets the Id of the Parent entity + /// Id of the user who wrote/updated this entity /// [DataMember] - public virtual int ParentId + public virtual int WriterId { - get - { - var val = _parentId.Value; - if (val == 0) - { - throw new InvalidOperationException("The ParentId cannot have a value of 0. Perhaps the parent object used to instantiate this object has not been persisted to the data store."); - } - return val; - } - set - { - _parentId = new Lazy(() => value); - OnPropertyChanged(Ps.Value.ParentIdSelector); - } + get => _writerId; + set => SetPropertyValueAndDetectChanges(value, ref _writerId, Ps.Value.WriterSelector); } - /// - /// Gets or sets the name of the entity - /// - [DataMember] - public virtual string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// Gets or sets the sort order of the content entity - /// - [DataMember] - public virtual int SortOrder - { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } - } - - /// - /// Gets or sets the level of the content entity - /// - [DataMember] - public virtual int Level - { - get { return _level; } - set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } - } - - /// - /// Gets or sets the path - /// - [DataMember] - public virtual string Path //Setting this value should be handled by the class not the user - { - get { return _path; } - set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } - } - - /// - /// Profile of the user who created this Content - /// - [DataMember] - public virtual int CreatorId - { - get { return _creatorId; } - set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } - } - - /// - /// Boolean indicating whether this Content is Trashed or not. - /// If Content is Trashed it will be located in the Recyclebin. - /// - /// When content is trashed it should be unpublished - [DataMember] - public virtual bool Trashed //Setting this value should be handled by the class not the user - { - get { return _trashed; } - internal set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } - } - - /// - /// Guid Id of the curent Version - /// - [DataMember] - public Guid Version { get; internal set; } + [IgnoreDataMember] + public int VersionId { get; internal set; } /// /// Integer Id of the default ContentType @@ -206,16 +107,16 @@ namespace Umbraco.Core.Models } return _contentTypeId; } - protected set { SetPropertyValueAndDetectChanges(value, ref _contentTypeId, Ps.Value.DefaultContentTypeIdSelector); } + protected set => SetPropertyValueAndDetectChanges(value, ref _contentTypeId, Ps.Value.DefaultContentTypeIdSelector); } /// - /// Collection of properties, which make up all the data available for this Content object + /// Gets or sets the collection of properties for the entity. /// [DataMember] public virtual PropertyCollection Properties { - get { return _properties; } + get => _properties; set { _properties = value; @@ -223,327 +124,184 @@ namespace Umbraco.Core.Models } } - private readonly IDictionary _additionalData; - /// - /// Some entities may expose additional data that other's might not, this custom data will be available in this collection - /// - [EditorBrowsable(EditorBrowsableState.Never)] - IDictionary IUmbracoEntity.AdditionalData - { - get { return _additionalData; } - } - - /// - /// List of PropertyGroups available on this Content object + /// Gets the enumeration of property groups for the entity. + /// fixme is a proxy, kill this /// [IgnoreDataMember] - public IEnumerable PropertyGroups { get { return ContentTypeBase.CompositionPropertyGroups; } } + public IEnumerable PropertyGroups => ContentTypeBase.CompositionPropertyGroups; /// - /// List of PropertyTypes available on this Content object + /// Gets the numeration of property types for the entity. + /// fixme is a proxy, kill this /// [IgnoreDataMember] - public IEnumerable PropertyTypes { get { return ContentTypeBase.CompositionPropertyTypes; } } + public IEnumerable PropertyTypes => ContentTypeBase.CompositionPropertyTypes; - /// - /// Indicates whether the content object has a property with the supplied alias - /// - /// Alias of the PropertyType - /// True if Property with given alias exists, otherwise False + #region Has, Get, Set, Publish Property Value + + /// public virtual bool HasProperty(string propertyTypeAlias) + => Properties.Contains(propertyTypeAlias); + + /// + public virtual object GetValue(string propertyTypeAlias, int? languageId = null, string segment = null, bool published = false) { - return Properties.Contains(propertyTypeAlias); + return Properties.TryGetValue(propertyTypeAlias, out var property) + ? property.GetValue(languageId, segment, published) + : null; } - /// - /// Gets the value of a Property - /// - /// Alias of the PropertyType - /// Value as an - public virtual object GetValue(string propertyTypeAlias) + /// + public virtual TValue GetValue(string propertyTypeAlias, int? languageId = null, string segment = null, bool published = false) { - return Properties.Contains(propertyTypeAlias) ? Properties[propertyTypeAlias].Value : null; + if (!Properties.TryGetValue(propertyTypeAlias, out var property)) + return default; + + var convertAttempt = property.GetValue(languageId, segment, published).TryConvertTo(); + return convertAttempt.Success ? convertAttempt.Result : default; } - /// - /// Gets the value of a Property - /// - /// Type of the value to return - /// Alias of the PropertyType - /// Value as a - public virtual TPassType GetValue(string propertyTypeAlias) - { - if (Properties.Contains(propertyTypeAlias) == false) - { - return default(TPassType); - } - - var convertAttempt = Properties[propertyTypeAlias].Value.TryConvertTo(); - return convertAttempt.Success ? convertAttempt.Result : default(TPassType); - } - - /// - /// Sets the value of a Property - /// - /// Alias of the PropertyType - /// Value to set for the Property - public virtual void SetValue(string propertyTypeAlias, object value) - { - if (value == null) - { - SetValueOnProperty(propertyTypeAlias, value); - return; - } - - // .NET magic to call one of the 'SetPropertyValue' handlers with matching signature - ((dynamic)this).SetPropertyValue(propertyTypeAlias, (dynamic)value); - } - - /// - /// Sets the value of a Property - /// - /// Alias of the PropertyType - /// Value to set for the Property - public virtual void SetPropertyValue(string propertyTypeAlias, string value) - { - SetValueOnProperty(propertyTypeAlias, value); - } - - /// - /// Sets the value of a Property - /// - /// Alias of the PropertyType - /// Value to set for the Property - public virtual void SetPropertyValue(string propertyTypeAlias, int value) - { - SetValueOnProperty(propertyTypeAlias, value); - } - - /// - /// Sets the value of a Property - /// - /// Alias of the PropertyType - /// Value to set for the Property - public virtual void SetPropertyValue(string propertyTypeAlias, long value) - { - SetValueOnProperty(propertyTypeAlias, value); - } - - /// - /// Sets the value of a Property - /// - /// Alias of the PropertyType - /// Value to set for the Property - public virtual void SetPropertyValue(string propertyTypeAlias, decimal value) - { - SetValueOnProperty(propertyTypeAlias, value); - } - - /// - /// Sets the value of a Property - /// - /// Alias of the PropertyType - /// Value to set for the Property - public virtual void SetPropertyValue(string propertyTypeAlias, double value) - { - SetValueOnProperty(propertyTypeAlias, value); - } - - /// - /// Sets the value of a Property - /// - /// Alias of the PropertyType - /// Value to set for the Property - public virtual void SetPropertyValue(string propertyTypeAlias, bool value) - { - int val = Convert.ToInt32(value); - SetValueOnProperty(propertyTypeAlias, val); - } - - /// - /// Sets the value of a Property - /// - /// Alias of the PropertyType - /// Value to set for the Property - public virtual void SetPropertyValue(string propertyTypeAlias, DateTime value) - { - SetValueOnProperty(propertyTypeAlias, value); - } - - /// - /// Sets the value of a Property - /// - /// Alias of the PropertyType - /// Value to set for the Property - public virtual void SetPropertyValue(string propertyTypeAlias, HttpPostedFile value) - { - ContentExtensions.SetValue(this, propertyTypeAlias, value); - } - - /// - /// Sets the value of a Property - /// - /// Alias of the PropertyType - /// Value to set for the Property - public virtual void SetPropertyValue(string propertyTypeAlias, HttpPostedFileBase value) - { - ContentExtensions.SetValue(this, propertyTypeAlias, value); - } - - /// - /// Sets the value of a Property - /// - /// Alias of the PropertyType - /// Value to set for the Property - [Obsolete("There is no reason for this overload since HttpPostedFileWrapper inherits from HttpPostedFileBase")] - public virtual void SetPropertyValue(string propertyTypeAlias, HttpPostedFileWrapper value) - { - ContentExtensions.SetValue(this, propertyTypeAlias, value); - } - - /// - /// Private method to set the value of a property - /// - /// - /// - private void SetValueOnProperty(string propertyTypeAlias, object value) + /// + public virtual void SetValue(string propertyTypeAlias, object value, int? languageId = null, string segment = null) { if (Properties.Contains(propertyTypeAlias)) { - Properties[propertyTypeAlias].Value = value; + Properties[propertyTypeAlias].SetValue(value, languageId, segment); return; } var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); if (propertyType == null) - { - throw new Exception(String.Format("No PropertyType exists with the supplied alias: {0}", propertyTypeAlias)); - } - Properties.Add(propertyType.CreatePropertyFromValue(value)); + throw new InvalidOperationException($"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\"."); + + var property = propertyType.CreateProperty(); + property.SetValue(value, languageId, segment); + Properties.Add(property); + } + + // HttpPostedFileBase is the base class that can be mocked + // HttpPostedFile is what we get in ASP.NET + // HttpPostedFileWrapper wraps sealed HttpPostedFile as HttpPostedFileBase + + /// + /// Sets the posted file value of a property. + /// + public virtual void SetValue(string propertyTypeAlias, HttpPostedFile value, int? languageId = null, string segment = null) + { + ContentExtensions.SetValue(this, propertyTypeAlias, new HttpPostedFileWrapper(value), languageId, segment); } /// - /// Boolean indicating whether the content and its properties are valid + /// Sets the posted file value of a property. /// - /// True if content is valid otherwise false - public virtual bool IsValid() + public virtual void SetValue(string propertyTypeAlias, HttpPostedFileBase value, int? languageId = null, string segment = null) { - _lastInvalidProperties.Clear(); - _lastInvalidProperties.AddRange(Properties.Where(property => property.IsValid() == false)); - return _lastInvalidProperties.Any() == false; + ContentExtensions.SetValue(this, propertyTypeAlias, value, languageId, segment); } - /// - /// Returns a collection of the result of the last validation process, this collection contains all invalid properties. - /// - [IgnoreDataMember] - internal IEnumerable LastInvalidProperties + #endregion + + #region Validation + + public virtual Property[] ValidateAll() { - get { return _lastInvalidProperties; } + return Properties.Where(x => !x.IsAllValid()).ToArray(); } - public abstract void ChangeTrashedState(bool isTrashed, int parentId = -20); - - #region Dirty property handling - - /// - /// We will override this method to ensure that when we reset the dirty properties that we - /// also reset the dirty changes made to the content's Properties (user defined) - /// - /// - public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public virtual Property[] Validate(int? languageId = null, string segment = null) { - base.ResetDirtyProperties(rememberPreviouslyChangedProperties); + return Properties.Where(x => !x.IsValid(languageId, segment)).ToArray(); + } + public virtual Property[] ValidateCulture(int? languageId = null) + { + return Properties.Where(x => !x.IsCultureValid(languageId)).ToArray(); + } + + #endregion + + #region Dirty + + /// + /// Overriden to include user properties. + public override void ResetDirtyProperties(bool rememberDirty) + { + base.ResetDirtyProperties(rememberDirty); + + // also reset dirty changes made to user's properties foreach (var prop in Properties) - { - prop.ResetDirtyProperties(rememberPreviouslyChangedProperties); - } + prop.ResetDirtyProperties(rememberDirty); } - /// - /// Indicates whether the current entity is dirty. - /// - /// True if entity is dirty, otherwise False + /// + /// Overriden to include user properties. public override bool IsDirty() { return IsEntityDirty() || this.IsAnyUserPropertyDirty(); } - /// - /// Indicates whether the current entity was dirty. - /// - /// True if entity was dirty, otherwise False + /// + /// Overriden to include user properties. public override bool WasDirty() { return WasEntityDirty() || this.WasAnyUserPropertyDirty(); } /// - /// Returns true if only the entity properties are dirty + /// Gets a value indicating whether the current entity's own properties (not user) are dirty. /// - /// public bool IsEntityDirty() { return base.IsDirty(); } /// - /// Returns true if only the entity properties were dirty + /// Gets a value indicating whether the current entity's own properties (not user) were dirty. /// - /// public bool WasEntityDirty() { return base.WasDirty(); } - /// - /// Indicates whether a specific property on the current entity is dirty. - /// - /// Name of the property to check - /// - /// True if any of the class properties are dirty or - /// True if any of the user defined PropertyType properties are dirty based on their alias, - /// otherwise False - /// + /// + /// Overriden to include user properties. public override bool IsPropertyDirty(string propertyName) { - bool existsInEntity = base.IsPropertyDirty(propertyName); - if (existsInEntity) + if (base.IsPropertyDirty(propertyName)) return true; - if (Properties.Contains(propertyName)) - { - return Properties[propertyName].IsDirty(); - } - - return false; + return Properties.Contains(propertyName) && Properties[propertyName].IsDirty(); } - /// - /// Indicates whether a specific property on the current entity was changed and the changes were committed - /// - /// Name of the property to check - /// - /// True if any of the class properties are dirty or - /// True if any of the user defined PropertyType properties are dirty based on their alias, - /// otherwise False - /// + /// + /// Overriden to include user properties. public override bool WasPropertyDirty(string propertyName) { - bool existsInEntity = base.WasPropertyDirty(propertyName); - if (existsInEntity) + if (base.WasPropertyDirty(propertyName)) return true; - if (Properties.Contains(propertyName)) - { - return Properties[propertyName].WasDirty(); - } - - return false; + return Properties.Contains(propertyName) && Properties[propertyName].WasDirty(); } + /// + /// Overriden to include user properties. + public override IEnumerable GetDirtyProperties() + { + var instanceProperties = base.GetDirtyProperties(); + var propertyTypes = Properties.Where(x => x.IsDirty()).Select(x => x.Alias); + return instanceProperties.Concat(propertyTypes); + } + + /// + /// Overriden to include user properties. + public override IEnumerable GetWereDirtyProperties() + { + var instanceProperties = base.GetWereDirtyProperties(); + var propertyTypes = Properties.Where(x => x.WasDirty()).Select(x => x.Alias); + return instanceProperties.Concat(propertyTypes); + } + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index c7e6829983..047b2fa74f 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -1,20 +1,28 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Web; using System.Xml.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Core.Composing; using Umbraco.Core.IO; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; namespace Umbraco.Core.Models { public static class ContentExtensions { + // this ain't pretty + private static MediaFileSystem _mediaFileSystem; + private static MediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem); + #region IContent /// @@ -31,277 +39,127 @@ namespace Umbraco.Core.Models return dirty.WasPropertyDirty("Published") && entity.Published; } - /// - /// Determines if the item should be persisted at all - /// - /// - /// - /// - /// In one particular case, a content item shouldn't be persisted: - /// * The item exists and is published - /// * A call to ContentService.Save is made - /// * The item has not been modified whatsoever apart from changing it's published status from published to saved - /// - /// In this case, there is no reason to make any database changes at all - /// - internal static bool RequiresSaving(this IContent entity) - { - var publishedState = ((Content)entity).PublishedState; - return RequiresSaving(entity, publishedState); - } - - /// - /// Determines if the item should be persisted at all - /// - /// - /// - /// - /// - /// In one particular case, a content item shouldn't be persisted: - /// * The item exists and is published - /// * A call to ContentService.Save is made - /// * The item has not been modified whatsoever apart from changing it's published status from published to saved - /// - /// In this case, there is no reason to make any database changes at all - /// - internal static bool RequiresSaving(this IContent entity, PublishedState publishedState) - { - var publishedChanged = entity.IsPropertyDirty("Published") && publishedState != PublishedState.Unpublished; - //check if any user prop has changed - var propertyValueChanged = entity.IsAnyUserPropertyDirty(); - - //We need to know if any other property apart from Published was changed here - //don't create a new version if the published state has changed to 'Save' but no data has actually been changed - if (publishedChanged && entity.Published == false && propertyValueChanged == false) - { - //at this point we need to check if any non property value has changed that wasn't the published state - var changedProps = ((TracksChangesEntityBase)entity).GetDirtyProperties(); - if (changedProps.Any(x => x != "Published") == false) - { - return false; - } - } - - return true; - } - - /// - /// Determines if a new version should be created - /// - /// - /// - /// - /// A new version needs to be created when: - /// * The publish status is changed - /// * The language is changed - /// * The item is already published and is being published again and any property value is changed (to enable a rollback) - /// - internal static bool ShouldCreateNewVersion(this IContent entity) - { - var publishedState = ((Content)entity).PublishedState; - return ShouldCreateNewVersion(entity, publishedState); - } - - /// - /// Returns a list of all dirty user defined properties - /// - /// - public static IEnumerable GetDirtyUserProperties(this IContentBase entity) - { - return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias); - } - - public static bool IsAnyUserPropertyDirty(this IContentBase entity) - { - return entity.Properties.Any(x => x.IsDirty()); - } - - public static bool WasAnyUserPropertyDirty(this IContentBase entity) - { - return entity.Properties.Any(x => x.WasDirty()); - } - - /// - /// Determines if a new version should be created - /// - /// - /// - /// - /// - /// A new version needs to be created when: - /// * The publish status is changed - /// * The language is changed - /// * The item is already published and is being published again and any property value is changed (to enable a rollback) - /// - internal static bool ShouldCreateNewVersion(this IContent entity, PublishedState publishedState) - { - //check if the published state has changed or the language - var publishedChanged = entity.IsPropertyDirty("Published") && publishedState != PublishedState.Unpublished; - var langChanged = entity.IsPropertyDirty("Language"); - var contentChanged = publishedChanged || langChanged; - - //check if any user prop has changed - var propertyValueChanged = entity.IsAnyUserPropertyDirty(); - - //return true if published or language has changed - if (contentChanged) - { - return true; - } - - //check if any content prop has changed - var contentDataChanged = ((Content)entity).IsEntityDirty(); - - //return true if the item is published and a property has changed or if any content property has changed - return (propertyValueChanged && publishedState == PublishedState.Published) || contentDataChanged; - } - - /// - /// Determines if the published db flag should be set to true for the current entity version and all other db - /// versions should have their flag set to false. - /// - /// - /// - /// - /// This is determined by: - /// * If a new version is being created and the entity is published - /// * If the published state has changed and the entity is published OR the entity has been un-published. - /// - internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity) - { - var publishedState = ((Content)entity).PublishedState; - return entity.ShouldClearPublishedFlagForPreviousVersions(publishedState, entity.ShouldCreateNewVersion(publishedState)); - } - - /// - /// Determines if the published db flag should be set to true for the current entity version and all other db - /// versions should have their flag set to false. - /// - /// - /// - /// - /// - /// - /// This is determined by: - /// * If a new version is being created and the entity is published - /// * If the published state has changed and the entity is published OR the entity has been un-published. - /// - internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity, PublishedState publishedState, bool isCreatingNewVersion) - { - if (isCreatingNewVersion && entity.Published) - { - return true; - } - - //If Published state has changed then previous versions should have their publish state reset. - //If state has been changed to unpublished the previous versions publish state should also be reset. - if (entity.IsPropertyDirty("Published") && (entity.Published || publishedState == PublishedState.Unpublished)) - { - return true; - } - - return false; - } - /// /// Returns a list of the current contents ancestors, not including the content itself. /// /// Current content + /// /// An enumerable list of objects - public static IEnumerable Ancestors(this IContent content) + public static IEnumerable Ancestors(this IContent content, IContentService contentService) { - return ApplicationContext.Current.Services.ContentService.GetAncestors(content); + return contentService.GetAncestors(content); } /// /// Returns a list of the current contents children. /// /// Current content + /// /// An enumerable list of objects - public static IEnumerable Children(this IContent content) + public static IEnumerable Children(this IContent content, IContentService contentService) { - return ApplicationContext.Current.Services.ContentService.GetChildren(content.Id); + return contentService.GetChildren(content.Id); } /// /// Returns a list of the current contents descendants, not including the content itself. /// /// Current content + /// /// An enumerable list of objects - public static IEnumerable Descendants(this IContent content) + public static IEnumerable Descendants(this IContent content, IContentService contentService) { - return ApplicationContext.Current.Services.ContentService.GetDescendants(content); + return contentService.GetDescendants(content); } /// /// Returns the parent of the current content. /// /// Current content + /// /// An object - public static IContent Parent(this IContent content) + public static IContent Parent(this IContent content, IContentService contentService) { - return ApplicationContext.Current.Services.ContentService.GetById(content.ParentId); + return contentService.GetById(content.ParentId); } + #endregion #region IMedia + /// /// Returns a list of the current medias ancestors, not including the media itself. /// /// Current media + /// /// An enumerable list of objects + public static IEnumerable Ancestors(this IMedia media, IMediaService mediaService) + { + return mediaService.GetAncestors(media); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable Ancestors(this IMedia media) { - return ApplicationContext.Current.Services.MediaService.GetAncestors(media); + return Current.Services.MediaService.GetAncestors(media); } /// /// Returns a list of the current medias children. /// /// Current media + /// /// An enumerable list of objects + public static IEnumerable Children(this IMedia media, IMediaService mediaService) + { + return mediaService.GetChildren(media.Id); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable Children(this IMedia media) { - return ApplicationContext.Current.Services.MediaService.GetChildren(media.Id); + return Current.Services.MediaService.GetChildren(media.Id); } /// /// Returns a list of the current medias descendants, not including the media itself. /// /// Current media + /// /// An enumerable list of objects + public static IEnumerable Descendants(this IMedia media, IMediaService mediaService) + { + return mediaService.GetDescendants(media); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable Descendants(this IMedia media) { - return ApplicationContext.Current.Services.MediaService.GetDescendants(media); + return Current.Services.MediaService.GetDescendants(media); } /// /// Returns the parent of the current media. /// /// Current media + /// /// An object + public static IMedia Parent(this IMedia media, IMediaService mediaService) + { + return mediaService.GetById(media.ParentId); + } + + [Obsolete("Use the overload with the service reference instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IMedia Parent(this IMedia media) { - return ApplicationContext.Current.Services.MediaService.GetById(media.ParentId); + return Current.Services.MediaService.GetById(media.ParentId); } #endregion - internal static bool IsInRecycleBin(this IContent content) - { - return IsInRecycleBin(content, Constants.System.RecycleBinContent); - } - - internal static bool IsInRecycleBin(this IMedia media) - { - return IsInRecycleBin(media, Constants.System.RecycleBinMedia); - } - - internal static bool IsInRecycleBin(this IContentBase content, int recycleBinId) - { - return content.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Contains(recycleBinId.ToInvariantString()); - } - /// /// Removes characters that are not valide XML characters from all entity properties /// of type string. See: http://stackoverflow.com/a/961504/5018 @@ -316,10 +174,12 @@ namespace Umbraco.Core.Models entity.Name = entity.Name.ToValidXmlString(); foreach (var property in entity.Properties) { - if (property.Value is string) + foreach (var propertyValue in property.Values) { - var value = (string) property.Value; - property.Value = value.ToValidXmlString(); + if (propertyValue.EditedValue is string editString) + propertyValue.EditedValue = editString.ToValidXmlString(); + if (propertyValue.PublishedValue is string publishedString) + propertyValue.PublishedValue = publishedString.ToValidXmlString(); } } } @@ -396,64 +256,22 @@ namespace Umbraco.Core.Models .Contains(property.PropertyTypeId)); } - /// - /// Set property values by alias with an annonymous object - /// - public static void PropertyValues(this IContentBase content, object value) - { - if (value == null) - throw new Exception("No properties has been passed in"); - - var propertyInfos = value.GetType().GetProperties(); - foreach (var propertyInfo in propertyInfos) - { - //Check if a PropertyType with alias exists thus being a valid property - var propertyType = content.PropertyTypes.FirstOrDefault(x => x.Alias == propertyInfo.Name); - if (propertyType == null) - throw new Exception( - string.Format( - "The property alias {0} is not valid, because no PropertyType with this alias exists", - propertyInfo.Name)); - - //Check if a Property with the alias already exists in the collection thus being updated or inserted - var item = content.Properties.FirstOrDefault(x => x.Alias == propertyInfo.Name); - if (item != null) - { - item.Value = propertyInfo.GetValue(value, null); - //Update item with newly added value - content.Properties.Add(item); - } - else - { - //Create new Property to add to collection - var property = propertyType.CreatePropertyFromValue(propertyInfo.GetValue(value, null)); - content.Properties.Add(property); - } - } - } - public static IContentTypeComposition GetContentType(this IContentBase contentBase) { - if (contentBase == null) throw new ArgumentNullException("contentBase"); + if (contentBase == null) throw new ArgumentNullException(nameof(contentBase)); - var content = contentBase as IContent; - if (content != null) return content.ContentType; - var media = contentBase as IMedia; - if (media != null) return media.ContentType; - var member = contentBase as IMember; - if (member != null) return member.ContentType; + if (contentBase is IContent content) return content.ContentType; + if (contentBase is IMedia media) return media.ContentType; + if (contentBase is IMember member) return member.ContentType; throw new NotSupportedException("Unsupported IContentBase implementation: " + contentBase.GetType().FullName + "."); } #region SetValue for setting file contents /// - /// Stores and sets an uploaded HttpPostedFileBase as a property value. + /// Sets the posted file value of a property. /// - /// A content item. - /// The property alias. - /// The uploaded . - public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value) + public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value, int? languageId = null, string segment = null) { // ensure we get the filename without the path in IE in intranet mode // http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie @@ -470,54 +288,27 @@ namespace Umbraco.Core.Models // get a safe & clean filename filename = IOHelper.SafeFileName(filename); if (string.IsNullOrWhiteSpace(filename)) return; - filename = filename.ToLower(); + filename = filename.ToLower(); // fixme - er... why? - FileSystemProviderManager.Current.MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream); + MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream, languageId, segment); } /// - /// Stores and sets an uploaded HttpPostedFile as a property value. + /// Sets the posted file value of a property. /// - /// A content item. - /// The property alias. - /// The uploaded . - public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFile value) - { - SetValue(content, propertyTypeAlias, (HttpPostedFileBase) new HttpPostedFileWrapper(value)); - } - - /// - /// Stores and sets an uploaded HttpPostedFileWrapper as a property value. - /// - /// A content item. - /// The property alias. - /// The uploaded . - [Obsolete("There is no reason for this overload since HttpPostedFileWrapper inherits from HttpPostedFileBase")] - public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileWrapper value) - { - SetValue(content, propertyTypeAlias, (HttpPostedFileBase) value); - } - - /// - /// Stores and sets a file as a property value. - /// - /// A content item. - /// The property alias. - /// The name of the file. - /// A stream containing the file data. /// 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, string propertyTypeAlias, string filename, Stream filestream) + public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, int? languageId = null, string segment = null) { if (filename == null || filestream == null) return; // get a safe & clean filename filename = IOHelper.SafeFileName(filename); if (string.IsNullOrWhiteSpace(filename)) return; - filename = filename.ToLower(); + filename = filename.ToLower(); // fixme - er... why? - FileSystemProviderManager.Current.MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream); + MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream, languageId, segment); } /// @@ -541,20 +332,19 @@ namespace Umbraco.Core.Models var propertyType = content.GetContentType() .CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); if (propertyType == null) throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + "."); - return FileSystemProviderManager.Current.MediaFileSystem.StoreFile(content, propertyType, filename, filestream, filepath); + return MediaFileSystem.StoreFile(content, propertyType, filename, filestream, filepath); } #endregion #region User/Profile methods - /// - /// Gets the for the Creator of this media item. - /// + [Obsolete("Use the overload that declares the IUserService to use")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IProfile GetCreatorProfile(this IMedia media) { - return ApplicationContext.Current.Services.UserService.GetProfileById(media.CreatorId); + return Current.Services.UserService.GetProfileById(media.CreatorId); } /// @@ -565,13 +355,11 @@ namespace Umbraco.Core.Models return userService.GetProfileById(media.CreatorId); } - /// - /// Gets the for the Creator of this content item. - /// [Obsolete("Use the overload that declares the IUserService to use")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IProfile GetCreatorProfile(this IContentBase content) { - return ApplicationContext.Current.Services.UserService.GetProfileById(content.CreatorId); + return Current.Services.UserService.GetProfileById(content.CreatorId); } /// @@ -582,13 +370,11 @@ namespace Umbraco.Core.Models return userService.GetProfileById(content.CreatorId); } - /// - /// Gets the for the Writer of this content. - /// [Obsolete("Use the overload that declares the IUserService to use")] + [EditorBrowsable(EditorBrowsableState.Never)] public static IProfile GetWriterProfile(this IContent content) { - return ApplicationContext.Current.Services.UserService.GetProfileById(content.WriterId); + return Current.Services.UserService.GetProfileById(content.WriterId); } /// @@ -599,144 +385,12 @@ namespace Umbraco.Core.Models return userService.GetProfileById(content.WriterId); } - #endregion - /// - /// Checks whether an item has any published versions + /// Gets the for the Writer of this content. /// - /// - /// True if the content has any published versiom otherwise False - [Obsolete("Use the HasPublishedVersion property.", false)] - public static bool HasPublishedVersion(this IContent content) + public static IProfile GetWriterProfile(this IMedia content, IUserService userService) { - return content.HasPublishedVersion; - } - - #region Tag methods - - ///// - ///// Returns the tags for the given property - ///// - ///// - ///// - ///// - ///// - ///// - ///// The tags returned are only relavent for published content & saved media or members - ///// - //public static IEnumerable GetTags(this IContentBase content, string propertyTypeAlias, string tagGroup = "default") - //{ - - //} - - /// - /// Sets tags for the property - will add tags to the tags table and set the property value to be the comma delimited value of the tags. - /// - /// The content item to assign the tags to - /// The property alias to assign the tags to - /// The tags to assign - /// True to replace the tags on the current property with the tags specified or false to merge them with the currently assigned ones - /// The group/category to assign the tags, the default value is "default" - /// - public static void SetTags(this IContentBase content, string propertyTypeAlias, IEnumerable tags, bool replaceTags, string tagGroup = "default") - { - content.SetTags(TagCacheStorageType.Csv, propertyTypeAlias, tags, replaceTags, tagGroup); - } - - /// - /// Sets tags for the property - will add tags to the tags table and set the property value to be the comma delimited value of the tags. - /// - /// The content item to assign the tags to - /// The tag storage type in cache (default is csv) - /// The property alias to assign the tags to - /// The tags to assign - /// True to replace the tags on the current property with the tags specified or false to merge them with the currently assigned ones - /// The group/category to assign the tags, the default value is "default" - /// - public static void SetTags(this IContentBase content, TagCacheStorageType storageType, string propertyTypeAlias, IEnumerable tags, bool replaceTags, string tagGroup = "default") - { - var property = content.Properties[propertyTypeAlias]; - if (property == null) - { - throw new IndexOutOfRangeException("No property exists with name " + propertyTypeAlias); - } - property.SetTags(storageType, propertyTypeAlias, tags, replaceTags, tagGroup); - } - - internal static void SetTags(this Property property, TagCacheStorageType storageType, string propertyTypeAlias, IEnumerable tags, bool replaceTags, string tagGroup = "default") - { - if (property == null) throw new ArgumentNullException("property"); - - var trimmedTags = tags.Select(x => x.Trim()).ToArray(); - - property.TagSupport.Enable = true; - property.TagSupport.Tags = trimmedTags.Select(x => new Tuple(x, tagGroup)); - property.TagSupport.Behavior = replaceTags ? PropertyTagBehavior.Replace : PropertyTagBehavior.Merge; - - //ensure the property value is set to the same thing - if (replaceTags) - { - switch (storageType) - { - case TagCacheStorageType.Csv: - property.Value = string.Join(",", trimmedTags); - break; - case TagCacheStorageType.Json: - //json array - property.Value = JsonConvert.SerializeObject(trimmedTags); - break; - } - - } - else - { - switch (storageType) - { - case TagCacheStorageType.Csv: - var currTags = property.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()); - property.Value = string.Join(",", trimmedTags.Union(currTags)); - break; - case TagCacheStorageType.Json: - var currJson = JsonConvert.DeserializeObject(property.Value.ToString()); - //need to append the new ones - foreach (var tag in trimmedTags) - { - currJson.Add(tag); - } - //json array - property.Value = JsonConvert.SerializeObject(currJson); - break; - } - } - } - - /// - /// Remove any of the tags specified in the collection from the property if they are currently assigned. - /// - /// - /// - /// - /// The group/category that the tags are currently assigned to, the default value is "default" - public static void RemoveTags(this IContentBase content, string propertyTypeAlias, IEnumerable tags, string tagGroup = "default") - { - var property = content.Properties[propertyTypeAlias]; - if (property == null) - { - throw new IndexOutOfRangeException("No property exists with name " + propertyTypeAlias); - } - - var trimmedTags = tags.Select(x => x.Trim()).ToArray(); - - property.TagSupport.Behavior = PropertyTagBehavior.Remove; - property.TagSupport.Enable = true; - property.TagSupport.Tags = trimmedTags.Select(x => new Tuple(x, tagGroup)); - - //set the property value - var currTags = property.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()); - - property.Value = string.Join(",", currTags.Except(trimmedTags)); + return userService.GetProfileById(content.WriterId); } #endregion @@ -754,15 +408,11 @@ namespace Umbraco.Core.Models return packagingService.Export(content, true, raiseEvents: false); } - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Xml representation of the passed in + [Obsolete("Use the overload that declares the IPackagingService to use")] public static XElement ToXml(this IContent content) { - return ApplicationContext.Current.Services.PackagingService.Export(content, raiseEvents: false); + return Current.Services.PackagingService.Export(content, raiseEvents: false); } /// @@ -776,15 +426,10 @@ namespace Umbraco.Core.Models return packagingService.Export(content, raiseEvents: false); } - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Xml representation of the passed in [Obsolete("Use the overload that declares the IPackagingService to use")] public static XElement ToXml(this IMedia media) { - return ApplicationContext.Current.Services.PackagingService.Export(media, raiseEvents: false); + return Current.Services.PackagingService.Export(media, raiseEvents: false); } /// @@ -809,12 +454,6 @@ namespace Umbraco.Core.Models return packagingService.Export(media, true, raiseEvents: false); } - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Boolean indicating whether the xml should be generated for preview - /// Xml representation of the passed in [Obsolete("Use the overload that declares the IPackagingService to use")] public static XElement ToXml(this IContent content, bool isPreview) { @@ -837,15 +476,10 @@ namespace Umbraco.Core.Models return content.ToXml(packagingService); } - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Xml representation of the passed in [Obsolete("Use the overload that declares the IPackagingService to use")] public static XElement ToXml(this IMember member) { - return ((PackagingService)(ApplicationContext.Current.Services.PackagingService)).Export(member); + return ((PackagingService)(Current.Services.PackagingService)).Export(member); } /// @@ -859,5 +493,24 @@ namespace Umbraco.Core.Models return ((PackagingService)(packagingService)).Export(member); } #endregion + + #region Dirty + + public static IEnumerable GetDirtyUserProperties(this IContentBase entity) + { + return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias); + } + + public static bool IsAnyUserPropertyDirty(this IContentBase entity) + { + return entity.Properties.Any(x => x.IsDirty()); + } + + public static bool WasAnyUserPropertyDirty(this IContentBase entity) + { + return entity.Properties.Any(x => x.WasDirty()); + } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ContentPreviewEntity.cs b/src/Umbraco.Core/Models/ContentPreviewEntity.cs deleted file mode 100644 index 2f843cf2a7..0000000000 --- a/src/Umbraco.Core/Models/ContentPreviewEntity.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Xml.Linq; - -namespace Umbraco.Core.Models -{ - /// - /// Used content repository in order to add an entity to the persisted collection to be saved - /// in a single transaction during saving an entity - /// - internal class ContentPreviewEntity : ContentXmlEntity - where TContent : IContentBase - { - public ContentPreviewEntity(TContent content, Func xml) - : base(content, xml) - { - } - - public Guid Version - { - get { return Content.Version; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentStatus.cs b/src/Umbraco.Core/Models/ContentStatus.cs index 926a0fbc58..b31bf24f82 100644 --- a/src/Umbraco.Core/Models/ContentStatus.cs +++ b/src/Umbraco.Core/Models/ContentStatus.cs @@ -21,4 +21,4 @@ namespace Umbraco.Core.Models [EnumMember] AwaitingRelease } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ContentTagsExtensions.cs b/src/Umbraco.Core/Models/ContentTagsExtensions.cs new file mode 100644 index 0000000000..8aceaac762 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentTagsExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + /// + /// Provides extension methods for the class, to manage tags. + /// + public static class ContentTagsExtensions + { + /// + /// Assign tags. + /// + /// The content item. + /// The property alias. + /// The tags. + /// A value indicating whether to merge the tags with existing tags instead of replacing them. + /// Tags do not support variants. + public static void AssignTags(this IContentBase content, string propertyTypeAlias, IEnumerable tags, bool merge = false) + { + content.GetTagProperty(propertyTypeAlias).AssignTags(tags, merge); + } + + /// + /// Remove tags. + /// + /// The content item. + /// The property alias. + /// The tags. + /// Tags do not support variants. + public static void RemoveTags(this IContentBase content, string propertyTypeAlias, IEnumerable tags) + { + content.GetTagProperty(propertyTypeAlias).RemoveTags(tags); + } + + // gets and validates the property + private static Property GetTagProperty(this IContentBase content, string propertyTypeAlias) + { + if (content == null) throw new ArgumentNullException(nameof(content)); + + var property = content.Properties[propertyTypeAlias]; + if (property != null) return property; + + throw new IndexOutOfRangeException($"Could not find a property with alias \"{propertyTypeAlias}\"."); + } + } +} diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 568d80ccb7..a7418a1441 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -13,6 +13,9 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class ContentType : ContentTypeCompositionBase, IContentType { + private static readonly Lazy Ps = new Lazy(); + public const bool IsPublishingConst = true; + private int _defaultTemplate; private IEnumerable _allowedTemplates; @@ -48,8 +51,10 @@ namespace Umbraco.Core.Models _allowedTemplates = new List(); } - private static readonly Lazy Ps = new Lazy(); + /// + public override bool IsPublishing => IsPublishingConst; + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultTemplateId); @@ -64,7 +69,7 @@ namespace Umbraco.Core.Models /// /// Gets or sets the alias of the default Template. /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! - /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, /// we should not store direct entity /// [IgnoreDataMember] @@ -86,16 +91,16 @@ namespace Umbraco.Core.Models /// /// Gets or Sets a list of Templates which are allowed for the ContentType /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! - /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, /// we should not store direct entity /// [DataMember] public IEnumerable AllowedTemplates { - get { return _allowedTemplates; } + get => _allowedTemplates; set { - SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, Ps.Value.TemplateComparer); + SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, Ps.Value.TemplateComparer); if (_allowedTemplates.Any(x => x.Id == _defaultTemplate) == false) DefaultTemplateId = 0; @@ -150,31 +155,5 @@ namespace Umbraco.Core.Models { return DeepCloneWithResetIdentities(alias); } - - /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset - /// - /// - public IContentType DeepCloneWithResetIdentities(string alias) - { - var clone = (ContentType)DeepClone(); - clone.Alias = alias; - clone.Key = Guid.Empty; - foreach (var propertyGroup in clone.PropertyGroups) - { - propertyGroup.ResetIdentity(); - propertyGroup.ResetDirtyProperties(false); - } - foreach (var propertyType in clone.PropertyTypes) - { - propertyType.ResetIdentity(); - propertyType.ResetDirtyProperties(false); - } - - clone.ResetIdentity(); - clone.ResetDirtyProperties(false); - return clone; - } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs index b1d2b45dc5..52214a9b38 100644 --- a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs +++ b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Models +namespace Umbraco.Core.Models { /// /// Used when determining available compositions for a given content type @@ -14,4 +14,4 @@ namespace Umbraco.Core.Models public IContentTypeComposition Composition { get; private set; } public bool Allowed { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs index 653d7a10a9..b4e038c14c 100644 --- a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs +++ b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace Umbraco.Core.Models @@ -23,4 +23,4 @@ namespace Umbraco.Core.Models public IEnumerable Ancestors { get; private set; } public IEnumerable Results { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index db0bc0e900..350ae7be1f 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -6,7 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Strings; namespace Umbraco.Core.Models @@ -17,77 +17,89 @@ namespace Umbraco.Core.Models [Serializable] [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] - public abstract class ContentTypeBase : Entity, IContentTypeBase + public abstract class ContentTypeBase : TreeEntityBase, IContentTypeBase { - private Lazy _parentId; - private string _name; - private int _level; - private string _path; + //fixme this should be invariant by default but for demo purposes and until the UI is updated to support changing a property type we'll make this neutral by default + private const ContentVariation DefaultVaryBy = ContentVariation.CultureNeutral; + + private static readonly Lazy Ps = new Lazy(); + private string _alias; private string _description; - private int _sortOrder; private string _icon = "icon-folder"; private string _thumbnail = "folder.png"; - private int _creatorId; - private bool _allowedAsRoot; + private bool _allowedAsRoot; // note: only one that's not 'pure element type' private bool _isContainer; - private bool _trashed; private PropertyGroupCollection _propertyGroups; private PropertyTypeCollection _propertyTypes; private IEnumerable _allowedContentTypes; private bool _hasPropertyTypeBeenRemoved; - + private ContentVariation _variations; protected ContentTypeBase(int parentId) { - Mandate.ParameterCondition(parentId != 0, "parentId"); + if (parentId == 0) throw new ArgumentOutOfRangeException(nameof(parentId)); + ParentId = parentId; - _parentId = new Lazy(() => parentId); _allowedContentTypes = new List(); _propertyGroups = new PropertyGroupCollection(); - _propertyTypes = new PropertyTypeCollection(); + + // actually OK as IsPublishing is constant + // ReSharper disable once VirtualMemberCallInConstructor + _propertyTypes = new PropertyTypeCollection(IsPublishing); _propertyTypes.CollectionChanged += PropertyTypesChanged; - _additionalData = new Dictionary(); + + _variations = DefaultVaryBy; } - protected ContentTypeBase(IContentTypeBase parent) : this(parent, null) - { - } + protected ContentTypeBase(IContentTypeBase parent) + : this(parent, null) + { } protected ContentTypeBase(IContentTypeBase parent, string alias) { - Mandate.ParameterNotNull(parent, "parent"); + if (parent == null) throw new ArgumentNullException(nameof(parent)); + SetParent(parent); _alias = alias; - _parentId = new Lazy(() => parent.Id); _allowedContentTypes = new List(); _propertyGroups = new PropertyGroupCollection(); - _propertyTypes = new PropertyTypeCollection(); + + // actually OK as IsPublishing is constant + // ReSharper disable once VirtualMemberCallInConstructor + _propertyTypes = new PropertyTypeCollection(IsPublishing); _propertyTypes.CollectionChanged += PropertyTypesChanged; - _additionalData = new Dictionary(); + + _variations = DefaultVaryBy; } - private static readonly Lazy Ps = new Lazy(); + /// + /// Gets a value indicating whether the content type is publishing. + /// + /// + /// A publishing content type supports draft and published values for properties. + /// It is possible to retrieve either the draft (default) or published value of a property. + /// Setting the value always sets the draft value, which then needs to be published. + /// A non-publishing content type only supports one value for properties. Getting + /// the draft or published value of a property returns the same thing, and publishing + /// a value property has no effect. + /// + public abstract bool IsPublishing { get; } + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); public readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); public readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.Thumbnail); - public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); public readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo(x => x.AllowedAsRoot); public readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo(x => x.IsContainer); - public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); public readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedContentTypes); public readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyTypes); public readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPropertyTypeBeenRemoved); + public readonly PropertyInfo VaryBy = ExpressionHelper.GetPropertyInfo(x => x.Variations); //Custom comparer for enumerable public readonly DelegateEqualityComparer> ContentTypeSortComparer = @@ -96,7 +108,6 @@ namespace Umbraco.Core.Models sorts => sorts.GetHashCode()); } - protected void PropertyGroupsChanged(object sender, NotifyCollectionChangedEventArgs e) { OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector); @@ -107,73 +118,17 @@ namespace Umbraco.Core.Models OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); } - /// - /// Gets or sets the Id of the Parent entity - /// - /// Might not be necessary if handled as a relation? - [DataMember] - public virtual int ParentId - { - get - { - var val = _parentId.Value; - if (val == 0) - { - throw new InvalidOperationException("The ParentId cannot be a value of 0. Perhaps the parent object used to instantiate this object has not been persisted to the data store."); - } - return val; - } - set - { - _parentId = new Lazy(() => value); - OnPropertyChanged(Ps.Value.ParentIdSelector); - } - } - - /// - /// Gets or sets the name of the current entity - /// - [DataMember] - public virtual string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// Gets or sets the level of the content entity - /// - [DataMember] - public virtual int Level //NOTE Is this relevant for a ContentType? - { - get { return _level; } - set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } - } - - /// - /// Gets of sets the path - /// - [DataMember] - public virtual string Path //NOTE Is this relevant for a ContentType? - { - get { return _path; } - set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } - } - /// /// The Alias of the ContentType /// [DataMember] public virtual string Alias { - get { return _alias; } - set - { - SetPropertyValueAndDetectChanges( - value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), - ref _alias, - Ps.Value.AliasSelector); - } + get => _alias; + set => SetPropertyValueAndDetectChanges( + value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), + ref _alias, + Ps.Value.AliasSelector); } /// @@ -182,18 +137,8 @@ namespace Umbraco.Core.Models [DataMember] public virtual string Description { - get { return _description; } - set { SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); } - } - - /// - /// Gets or sets the sort order of the content entity - /// - [DataMember] - public virtual int SortOrder - { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } + get => _description; + set => SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); } /// @@ -202,8 +147,8 @@ namespace Umbraco.Core.Models [DataMember] public virtual string Icon { - get { return _icon; } - set { SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); } + get => _icon; + set => SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); } /// @@ -212,18 +157,8 @@ namespace Umbraco.Core.Models [DataMember] public virtual string Thumbnail { - get { return _thumbnail; } - set { SetPropertyValueAndDetectChanges(value, ref _thumbnail, Ps.Value.ThumbnailSelector); } - } - - /// - /// Gets or sets the Id of the user who created this ContentType - /// - [DataMember] - public virtual int CreatorId - { - get { return _creatorId; } - set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } + get => _thumbnail; + set => SetPropertyValueAndDetectChanges(value, ref _thumbnail, Ps.Value.ThumbnailSelector); } /// @@ -232,8 +167,8 @@ namespace Umbraco.Core.Models [DataMember] public virtual bool AllowedAsRoot { - get { return _allowedAsRoot; } - set { SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, Ps.Value.AllowedAsRootSelector); } + get => _allowedAsRoot; + set => SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, Ps.Value.AllowedAsRootSelector); } /// @@ -245,29 +180,8 @@ namespace Umbraco.Core.Models [DataMember] public virtual bool IsContainer { - get { return _isContainer; } - set { SetPropertyValueAndDetectChanges(value, ref _isContainer, Ps.Value.IsContainerSelector); } - } - - /// - /// Boolean indicating whether this ContentType is Trashed or not. - /// If ContentType is Trashed it will be located in the Recyclebin. - /// - [DataMember] - public virtual bool Trashed //NOTE Is this relevant for a ContentType? - { - get { return _trashed; } - set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } - } - - private readonly IDictionary _additionalData; - /// - /// Some entities may expose additional data that other's might not, this custom data will be available in this collection - /// - [EditorBrowsable(EditorBrowsableState.Never)] - IDictionary IUmbracoEntity.AdditionalData - { - get { return _additionalData; } + get => _isContainer; + set => SetPropertyValueAndDetectChanges(value, ref _isContainer, Ps.Value.IsContainerSelector); } /// @@ -276,12 +190,18 @@ namespace Umbraco.Core.Models [DataMember] public virtual IEnumerable AllowedContentTypes { - get { return _allowedContentTypes; } - set - { - SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector, - Ps.Value.ContentTypeSortComparer); - } + get => _allowedContentTypes; + set => SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector, + Ps.Value.ContentTypeSortComparer); + } + + /// + /// Gets or sets the content variation of the content type. + /// + public virtual ContentVariation Variations + { + get => _variations; + set => SetPropertyValueAndDetectChanges(value, ref _variations, Ps.Value.VaryBy); } /// @@ -293,7 +213,7 @@ namespace Umbraco.Core.Models [DataMember] public virtual PropertyGroupCollection PropertyGroups { - get { return _propertyGroups; } + get => _propertyGroups; set { _propertyGroups = value; @@ -320,10 +240,10 @@ namespace Umbraco.Core.Models /// public IEnumerable NoGroupPropertyTypes { - get { return _propertyTypes; } + get => _propertyTypes; set { - _propertyTypes = new PropertyTypeCollection(value); + _propertyTypes = new PropertyTypeCollection(IsPublishing, value); _propertyTypes.CollectionChanged += PropertyTypesChanged; PropertyTypesChanged(_propertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } @@ -333,13 +253,13 @@ namespace Umbraco.Core.Models /// A boolean flag indicating if a property type has been removed from this instance. /// /// - /// This is currently (specifically) used in order to know that we need to refresh the content cache which + /// This is currently (specifically) used in order to know that we need to refresh the content cache which /// needs to occur when a property has been removed from a content type /// [IgnoreDataMember] internal bool HasPropertyTypeBeenRemoved { - get { return _hasPropertyTypeBeenRemoved; } + get => _hasPropertyTypeBeenRemoved; private set { _hasPropertyTypeBeenRemoved = value; @@ -379,7 +299,7 @@ namespace Umbraco.Core.Models { if (PropertyTypeExists(propertyType.Alias) == false) { - _propertyTypes.Add(propertyType); + _propertyTypes.Add(propertyType); return true; } @@ -392,7 +312,7 @@ namespace Umbraco.Core.Models /// Alias of the PropertyType to move /// Name of the PropertyGroup to move the PropertyType to /// - /// If is null then the property is moved back to + /// If is null then the property is moved back to /// "generic properties" ie does not have a tab anymore. public bool MovePropertyType(string propertyTypeAlias, string propertyGroupName) { @@ -409,17 +329,15 @@ namespace Umbraco.Core.Models if (propertyGroupName != null && newPropertyGroup == null) return false; // get old group - var oldPropertyGroup = PropertyGroups.FirstOrDefault(x => + var oldPropertyGroup = PropertyGroups.FirstOrDefault(x => x.PropertyTypes.Any(y => y.Alias == propertyTypeAlias)); // set new group propertyType.PropertyGroupId = newPropertyGroup == null ? null : new Lazy(() => newPropertyGroup.Id, false); // remove from old group, if any - add to new group, if any - if (oldPropertyGroup != null) - oldPropertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias); - if (newPropertyGroup != null) - newPropertyGroup.PropertyTypes.Add(propertyType); + oldPropertyGroup?.PropertyTypes.RemoveItem(propertyTypeAlias); + newPropertyGroup?.PropertyTypes.Add(propertyType); return true; } @@ -445,7 +363,7 @@ namespace Umbraco.Core.Models if (_propertyTypes.Any(x => x.Alias == propertyTypeAlias)) { - _propertyTypes.RemoveItem(propertyTypeAlias); + _propertyTypes.RemoveItem(propertyTypeAlias); } } @@ -471,23 +389,11 @@ namespace Umbraco.Core.Models OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector); } - /// - /// Sets the ParentId from the lazy integer id - /// - /// Id of the Parent - public void SetLazyParentId(Lazy id) - { - _parentId = id; - } - /// /// PropertyTypes that are not part of a PropertyGroup /// [IgnoreDataMember] - internal PropertyTypeCollection PropertyTypeCollection - { - get { return _propertyTypes; } - } + internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes; /// /// Indicates whether a specific property on the current entity is dirty. @@ -555,7 +461,7 @@ namespace Umbraco.Core.Models //turn off change tracking clone.DisableChangeTracking(); //need to manually wire up the event handlers for the property type collections - we've ensured - // its ignored from the auto-clone process because its return values are unions, not raw and + // its ignored from the auto-clone process because its return values are unions, not raw and // we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842 clone._propertyTypes = (PropertyTypeCollection)_propertyTypes.DeepClone(); @@ -567,5 +473,26 @@ namespace Umbraco.Core.Models return clone; } + + public IContentType DeepCloneWithResetIdentities(string alias) + { + var clone = (ContentType)DeepClone(); + clone.Alias = alias; + clone.Key = Guid.Empty; + foreach (var propertyGroup in clone.PropertyGroups) + { + propertyGroup.ResetIdentity(); + propertyGroup.ResetDirtyProperties(false); + } + foreach (var propertyType in clone.PropertyTypes) + { + propertyType.ResetIdentity(); + propertyType.ResetDirtyProperties(false); + } + + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs new file mode 100644 index 0000000000..ef55f0d469 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs @@ -0,0 +1,20 @@ +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.Models +{ + /// + /// Provides extensions methods for . + /// + public static class ContentTypeBaseExtensions + { + public static PublishedItemType GetItemType(this IContentTypeBase contentType) + { + var type = contentType.GetType(); + var itemType = PublishedItemType.Unknown; + if (typeof(IContentType).IsAssignableFrom(type)) itemType = PublishedItemType.Content; + else if (typeof(IMediaType).IsAssignableFrom(type)) itemType = PublishedItemType.Media; + else if (typeof(IMemberType).IsAssignableFrom(type)) itemType = PublishedItemType.Member; + return itemType; + } + } +} diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index e53011cc51..8fdd7904e4 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -14,17 +14,17 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public abstract class ContentTypeCompositionBase : ContentTypeBase, IContentTypeComposition { + private static readonly Lazy Ps = new Lazy(); + private List _contentTypeComposition = new List(); internal List RemovedContentTypeKeyTracker = new List(); protected ContentTypeCompositionBase(int parentId) : base(parentId) - { - } + { } protected ContentTypeCompositionBase(IContentTypeComposition parent) : this(parent, null) - { - } + { } protected ContentTypeCompositionBase(IContentTypeComposition parent, string alias) : base(parent, alias) @@ -32,8 +32,7 @@ namespace Umbraco.Core.Models AddContentType(parent); } - private static readonly Lazy Ps = new Lazy(); - + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { public readonly PropertyInfo ContentTypeCompositionSelector = @@ -46,7 +45,7 @@ namespace Umbraco.Core.Models [DataMember] public IEnumerable ContentTypeComposition { - get { return _contentTypeComposition; } + get => _contentTypeComposition; set { _contentTypeComposition = value.ToList(); @@ -181,7 +180,7 @@ namespace Umbraco.Core.Models return null; // create the new group - var group = new PropertyGroup { Name = name, SortOrder = 0 }; + var group = new PropertyGroup(IsPublishing) { Name = name, SortOrder = 0 }; // check if it is inherited - there might be more than 1 but we want the 1st, to // reuse its sort order - if there are more than 1 and they have different sort @@ -233,7 +232,7 @@ namespace Umbraco.Core.Models } /// - /// Gets a list of ContentType aliases from the current composition + /// Gets a list of ContentType aliases from the current composition /// /// An enumerable list of string aliases /// Does not contain the alias of the Current ContentType @@ -245,7 +244,7 @@ namespace Umbraco.Core.Models } /// - /// Gets a list of ContentType Ids from the current composition + /// Gets a list of ContentType Ids from the current composition /// /// An enumerable list of integer ids /// Does not contain the Id of the Current ContentType @@ -272,4 +271,4 @@ namespace Umbraco.Core.Models return clone; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ContentTypeSort.cs b/src/Umbraco.Core/Models/ContentTypeSort.cs index 40d0522b52..d689a58e82 100644 --- a/src/Umbraco.Core/Models/ContentTypeSort.cs +++ b/src/Umbraco.Core/Models/ContentTypeSort.cs @@ -1,5 +1,5 @@ using System; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -55,7 +55,7 @@ namespace Umbraco.Core.Models protected bool Equals(ContentTypeSort other) { - return Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias); + return Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias); } public override bool Equals(object obj) @@ -77,4 +77,4 @@ namespace Umbraco.Core.Models } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ContentVariation.cs b/src/Umbraco.Core/Models/ContentVariation.cs new file mode 100644 index 0000000000..5b775b52b9 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentVariation.cs @@ -0,0 +1,36 @@ +using System; + +namespace Umbraco.Core.Models +{ + /// + /// Indicates the values accepted by a property. + /// + [Flags] + public enum ContentVariation : byte + { + /// + /// Unknown. + /// + Unknown = 0, + + /// + /// Accepts values for the invariant culture and the neutral segment. + /// + InvariantNeutral = 1, + + /// + /// Accepts values for a specified culture and the neutral segment. + /// + CultureNeutral = 2, + + /// + /// Accepts values for the invariant culture and a specified segment. + /// + InvariantSegment = 4, + + /// + /// Accepts values for a specified culture and a specified segment. + /// + CultureSegment = 8 + } +} diff --git a/src/Umbraco.Core/Models/ContentXmlEntity.cs b/src/Umbraco.Core/Models/ContentXmlEntity.cs deleted file mode 100644 index 93185834a3..0000000000 --- a/src/Umbraco.Core/Models/ContentXmlEntity.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Xml.Linq; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Models -{ - /// - /// Used in content/media/member repositories in order to add this type of entity to the persisted collection to be saved - /// in a single transaction during saving an entity - /// - internal class ContentXmlEntity : IAggregateRoot - where TContent : IContentBase - { - private readonly Func _xml; - - public ContentXmlEntity(TContent content, Func xml) - { - if (content == null) throw new ArgumentNullException("content"); - _xml = xml; - Content = content; - } - - public ContentXmlEntity(TContent content) - { - if (content == null) throw new ArgumentNullException("content"); - Content = content; - } - - public XElement Xml - { - get { return _xml(Content); } - } - - public TContent Content { get; private set; } - - public int Id - { - get { return Content.Id; } - set { throw new NotSupportedException(); } - } - - public Guid Key { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public DateTime? DeletedDate { get; set; } - - /// - /// Special case, always return false, this will cause the repositories managing - /// this object to always do an 'insert' but these are special repositories that - /// do an InsertOrUpdate on insert since the data for this needs to be managed this way - /// - public bool HasIdentity - { - get { return false; } - } - - public object DeepClone() - { - var clone = (ContentXmlEntity)MemberwiseClone(); - //Automatically deep clone ref properties that are IDeepCloneable - DeepCloneHelper.DeepCloneRefProperties(this, clone); - return clone; - } - - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs new file mode 100644 index 0000000000..9668864588 --- /dev/null +++ b/src/Umbraco.Core/Models/DataType.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Models +{ + /// + /// Implements . + /// + [Serializable] + [DataContract(IsReference = true)] + public class DataType : TreeEntityBase, IDataType + { + private static PropertySelectors _selectors; + + private IDataEditor _editor; + private ValueStorageType _databaseType; + private object _configuration; + private bool _hasConfiguration; + private string _configurationJson; + + /// + /// Initializes a new instance of the class. + /// + public DataType(IDataEditor editor, int parentId = -1) + { + _editor = editor ?? throw new ArgumentNullException(nameof(editor)); + ParentId = parentId; + } + + private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); + + private class PropertySelectors + { + public readonly PropertyInfo Editor = ExpressionHelper.GetPropertyInfo(x => x.Editor); + public readonly PropertyInfo DatabaseType = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); + public readonly PropertyInfo Configuration = ExpressionHelper.GetPropertyInfo(x => x.Configuration); + } + + /// + [IgnoreDataMember] + public IDataEditor Editor + { + get => _editor; + set + { + // ignore if no change + if (_editor.Alias == value.Alias) return; + OnPropertyChanged(Selectors.Editor); + + // try to map the existing configuration to the new configuration + // simulate saving to db and reloading (ie go via json) + var configuration = Configuration; + var json = JsonConvert.SerializeObject(configuration); + _editor = value; + Configuration = _editor.GetConfigurationEditor().FromDatabase(json); + } + } + + /// + [DataMember] + public string EditorAlias => _editor.Alias; + + /// + [DataMember] + public ValueStorageType DatabaseType + { + get => _databaseType; + set => SetPropertyValueAndDetectChanges(value, ref _databaseType, Selectors.DatabaseType); + } + + /// + [DataMember] + public object Configuration + { + get + { + // if we know we have a configuration (which may be null), return it + // if we don't have an editor, then we have no configuration, return null + // else, use the editor to get the configuration object + + if (_hasConfiguration) return _configuration; + + _configuration = _editor.GetConfigurationEditor().FromDatabase(_configurationJson); + _hasConfiguration = true; + _configurationJson = null; + + return _configuration; + } + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + // we don't support re-assigning the same object + // configurations are kinda non-mutable, mainly because detecting changes would be a pain + if (_configuration == value) // reference comparison + throw new ArgumentException("Configurations are kinda non-mutable. Do not reassign the same object.", nameof(value)); + + // validate configuration type + if (!_editor.GetConfigurationEditor().IsConfiguration(value)) + throw new ArgumentException($"Value of type {value.GetType().Name} cannot be a configuration for editor {_editor.Alias}, expecting.", nameof(value)); + + // extract database type from configuration object, if appropriate + if (value is IConfigureValueType valueTypeConfiguration) + DatabaseType = ValueTypes.ToStorageType(valueTypeConfiguration.ValueType); + + // extract database type from dictionary, if appropriate + if (value is IDictionary dictionaryConfiguration + && dictionaryConfiguration.TryGetValue(Constants.PropertyEditors.ConfigurationKeys.DataValueType, out var valueTypeObject) + && valueTypeObject is string valueTypeString + && ValueTypes.IsValue(valueTypeString)) + DatabaseType = ValueTypes.ToStorageType(valueTypeString); + + _configuration = value; + _hasConfiguration = true; + _configurationJson = null; + + // it's always a change + OnPropertyChanged(Selectors.Configuration); + } + } + + /// + /// Lazily set the configuration as a serialized json string. + /// + /// + /// Will be de-serialized on-demand. + /// This method is meant to be used when building entities from database, exclusively. + /// It does NOT register a property change to dirty. It ignores the fact that the configuration + /// may contain the database type, because the datatype DTO should also contain that database + /// type, and they should be the same. + /// Think before using! + /// + internal void SetLazyConfiguration(string configurationJson) + { + _hasConfiguration = false; + _configuration = null; + _configurationJson = configurationJson; + } + + /// + /// Gets a lazy configuration. + /// + /// + /// The configuration object will be lazily de-serialized. + /// This method is meant to be used when creating published datatypes, exclusively. + /// Think before using! + /// + internal Lazy GetLazyConfiguration() + { + // note: in both cases, make sure we capture what we need - we don't want + // to capture a reference to this full, potentially heavy, DataType instance. + + if (_hasConfiguration) + { + // if configuration has already been de-serialized, return + var capturedConfiguration = _configuration; + return new Lazy(() => capturedConfiguration); + } + else + { + // else, create a Lazy de-serializer + var capturedConfiguration = _configurationJson; + var capturedEditor = _editor; + return new Lazy(() => capturedEditor.GetConfigurationEditor().FromDatabase(capturedConfiguration)); + } + } + } +} diff --git a/src/Umbraco.Core/Models/DataTypeDatabaseType.cs b/src/Umbraco.Core/Models/DataTypeDatabaseType.cs deleted file mode 100644 index 2fccdc0645..0000000000 --- a/src/Umbraco.Core/Models/DataTypeDatabaseType.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models -{ - /// - /// Enum of the various DbTypes for which the Property values are stored - /// - [Serializable] - [DataContract] - public enum DataTypeDatabaseType - { - [EnumMember] - Ntext, - [EnumMember] - Nvarchar, - [EnumMember] - Integer, - [EnumMember] - Date, - [EnumMember] - Decimal - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs deleted file mode 100644 index 3ba5125a90..0000000000 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Models -{ - /// - /// Definition of a DataType/PropertyEditor - /// - /// - /// The definition exists as a database reference between an actual DataType/PropertyEditor - /// (identified by its control id), its prevalues (configuration) and the named DataType in the backoffice UI. - /// - [Serializable] - [DataContract(IsReference = true)] - public class DataTypeDefinition : Entity, IDataTypeDefinition - { - private int _parentId; - private string _name; - private int _sortOrder; - private int _level; - private string _path; - private int _creatorId; - private bool _trashed; - private string _propertyEditorAlias; - private DataTypeDatabaseType _databaseType; - - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the alternative contructor that specifies an alias")] - [EditorBrowsable(EditorBrowsableState.Never)] - public DataTypeDefinition(int parentId, Guid controlId) - { - _parentId = parentId; - - _propertyEditorAlias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(controlId, false); - if (_propertyEditorAlias == null) - { - //convert to Label! - LogHelper.Warn("Could not find a GUID -> Alias mapping for the legacy property editor with id " + controlId + ". The DataType has been converted to a Label."); - _propertyEditorAlias = Constants.PropertyEditors.NoEditAlias; - } - - _additionalData = new Dictionary(); - } - - public DataTypeDefinition(int parentId, string propertyEditorAlias) - { - _parentId = parentId; - _propertyEditorAlias = propertyEditorAlias; - - _additionalData = new Dictionary(); - } - - public DataTypeDefinition(string propertyEditorAlias) - { - _parentId = -1; - _propertyEditorAlias = propertyEditorAlias; - - _additionalData = new Dictionary(); - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - public readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); - public readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); - } - - /// - /// Gets or sets the Id of the Parent entity - /// - /// Might not be necessary if handled as a relation? - [DataMember] - public int ParentId - { - get { return _parentId; } - set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } - } - - /// - /// Gets or sets the name of the current entity - /// - [DataMember] - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// Gets or sets the sort order of the content entity - /// - [DataMember] - public int SortOrder - { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } - } - - /// - /// Gets or sets the level of the content entity - /// - [DataMember] - public int Level - { - get { return _level; } - set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } - } - - /// - /// Gets or sets the path - /// - [DataMember] - public string Path //Setting this value should be handled by the class not the user - { - get { return _path; } - set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } - } - - /// - /// Id of the user who created this entity - /// - [DataMember] - public int CreatorId - { - get { return _creatorId; } - set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.UserIdSelector); } - } - - //NOTE: SD: Why do we have this ?? - - /// - /// Boolean indicating whether this entity is Trashed or not. - /// - [DataMember] - public bool Trashed - { - get { return _trashed; } - internal set - { - SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["Trashed"] = value; - } - } - - [DataMember] - public string PropertyEditorAlias - { - get { return _propertyEditorAlias; } - set - { - SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["DatabaseType"] = value; - } - } - - /// - /// Id of the DataType control - /// - [DataMember] - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead. This method will return a generated GUID for any property editor alias not explicitly mapped to a legacy ID")] - public Guid ControlId - { - get - { - return LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias( - _propertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.GenerateId).Value; - } - set - { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(value, true); - PropertyEditorAlias = alias; - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["ControlId"] = value; - } - } - - /// - /// Gets or Sets the DatabaseType for which the DataType's value is saved as - /// - [DataMember] - public DataTypeDatabaseType DatabaseType - { - get { return _databaseType; } - set - { - SetPropertyValueAndDetectChanges(value, ref _databaseType, Ps.Value.DatabaseTypeSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["DatabaseType"] = value; - } - } - - private readonly IDictionary _additionalData; - /// - /// Some entities may expose additional data that other's might not, this custom data will be available in this collection - /// - [EditorBrowsable(EditorBrowsableState.Never)] - IDictionary IUmbracoEntity.AdditionalData - { - get { return _additionalData; } - } - } -} diff --git a/src/Umbraco.Core/Models/DataTypeExtensions.cs b/src/Umbraco.Core/Models/DataTypeExtensions.cs new file mode 100644 index 0000000000..df8a3caea8 --- /dev/null +++ b/src/Umbraco.Core/Models/DataTypeExtensions.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Models +{ + /// + /// Provides extensions methods for . + /// + public static class DataTypeExtensions + { + /// + /// Gets the configuration object. + /// + /// The expected type of the configuration object. + /// This datatype. + /// When the datatype configuration is not of the expected type. + public static T ConfigurationAs(this IDataType dataType) + where T : class + { + if (dataType == null) + throw new ArgumentNullException(nameof(dataType)); + + var configuration = dataType.Configuration; + + switch (configuration) + { + case null: + return null; + case T configurationAsT: + return configurationAsT; + } + + throw new InvalidCastException($"Cannot cast dataType configuration, of type {configuration.GetType().Name}, to {typeof(T).Name}."); + } + } +} diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index c1b45f63ce..44a714c2c1 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Umbraco.Core.Composing; namespace Umbraco.Core.Models { @@ -22,7 +23,7 @@ namespace Umbraco.Core.Models public PropertyInfo PropertyInfo { get; private set; } public bool IsDeepCloneable { get; set; } - public Type GenericListType { get; set; } + public Type GenericListType { get; set; } public bool IsList { get { return GenericListType != null; } @@ -39,7 +40,7 @@ namespace Umbraco.Core.Models /// /// /// - /// + /// public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable output) { var inputType = input.GetType(); @@ -67,7 +68,7 @@ namespace Umbraco.Core.Models { return null; } - + if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType)) { @@ -156,7 +157,7 @@ namespace Umbraco.Core.Models } else if (o is string || o.GetType().IsValueType) { - //check if the item is a value type or a string, then we can just use it + //check if the item is a value type or a string, then we can just use it newList.Add(o); } else @@ -196,4 +197,4 @@ namespace Umbraco.Core.Models } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index d5fcc89994..42f7e04fff 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -12,7 +12,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class DictionaryItem : Entity, IDictionaryItem + public class DictionaryItem : EntityBase, IDictionaryItem { public Func GetLanguage { get; set; } private Guid? _parentId; @@ -85,8 +85,8 @@ namespace Umbraco.Core.Models } SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector, - Ps.Value.DictionaryTranslationComparer); + Ps.Value.DictionaryTranslationComparer); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/DictionaryItemExtensions.cs b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs new file mode 100644 index 0000000000..edcf15dd88 --- /dev/null +++ b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs @@ -0,0 +1,30 @@ +using System.Linq; + +namespace Umbraco.Core.Models +{ + public static class DictionaryItemExtensions + { + /// + /// Returns the translation value for the language id, if no translation is found it returns an empty string + /// + /// + /// + /// + public static string GetTranslatedValue(this IDictionaryItem d, int languageId) + { + var trans = d.Translations.FirstOrDefault(x => x.LanguageId == languageId); + return trans == null ? string.Empty : trans.Value; + } + + /// + /// Returns the default translated value based on the default language + /// + /// + /// + public static string GetDefaultValue(this IDictionaryItem d) + { + var defaultTranslation = d.Translations.FirstOrDefault(x => x.Language.Id == 1); + return defaultTranslation == null ? string.Empty : defaultTranslation.Value; + } + } +} diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index 4e5b1c2437..ef41cedc91 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models @@ -11,7 +11,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class DictionaryTranslation : Entity, IDictionaryTranslation + public class DictionaryTranslation : EntityBase, IDictionaryTranslation { internal Func GetLanguage { get; set; } @@ -19,7 +19,7 @@ namespace Umbraco.Core.Models private string _value; //note: this will be memberwise cloned private int _languageId; - + public DictionaryTranslation(ILanguage language, string value) { if (language == null) throw new ArgumentNullException("language"); @@ -38,13 +38,13 @@ namespace Umbraco.Core.Models } internal DictionaryTranslation(int languageId, string value) - { + { _languageId = languageId; _value = value; } internal DictionaryTranslation(int languageId, string value, Guid uniqueId) - { + { _languageId = languageId; _value = value; Key = uniqueId; @@ -65,7 +65,7 @@ namespace Umbraco.Core.Models /// Marked as DoNotClone - TODO: this member shouldn't really exist here in the first place, the DictionaryItem /// class will have a deep hierarchy of objects which all get deep cloned which we don't want. This should have simply /// just referenced a language ID not the actual language object. In v8 we need to fix this. - /// We're going to have to do the same hacky stuff we had to do with the Template/File contents so that this is returned + /// We're going to have to do the same hacky stuff we had to do with the Template/File contents so that this is returned /// on a callback. /// [DataMember] @@ -85,7 +85,7 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); - _languageId = _language == null ? -1 : _language.Id; + _languageId = _language == null ? -1 : _language.Id; } } @@ -113,7 +113,7 @@ namespace Umbraco.Core.Models // turn off change tracking clone.DisableChangeTracking(); - + // this shouldn't really be needed since we're not tracking clone.ResetDirtyProperties(false); @@ -123,4 +123,4 @@ namespace Umbraco.Core.Models return clone; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/DoNotCloneAttribute.cs b/src/Umbraco.Core/Models/DoNotCloneAttribute.cs index a27df59b03..a827f7af14 100644 --- a/src/Umbraco.Core/Models/DoNotCloneAttribute.cs +++ b/src/Umbraco.Core/Models/DoNotCloneAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Core.Models { @@ -7,17 +7,17 @@ namespace Umbraco.Core.Models /// that should be ignored for cloning when using the DeepCloneHelper /// /// - /// + /// /// This attribute must be used: /// * when the property is backed by a field but the result of the property is the un-natural data stored in the field - /// + /// /// This attribute should not be used: /// * when the property is virtual /// * when the setter performs additional required logic other than just setting the underlying field - /// + /// /// internal class DoNotCloneAttribute : Attribute { - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs b/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs index 3798cb08e0..ee4955cda3 100644 --- a/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs +++ b/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs @@ -1,9 +1,4 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Umbraco.Core.Models.Editors { @@ -16,32 +11,35 @@ namespace Umbraco.Core.Models.Editors /// public class ContentPropertyData { - public ContentPropertyData(object value, PreValueCollection preValues) - : this(value, preValues, new Dictionary()) - { - } - - public ContentPropertyData(object value, PreValueCollection preValues, IDictionary additionalData) + public ContentPropertyData(object value, object dataTypeConfiguration) { Value = value; - PreValues = preValues; - AdditionalData = new ReadOnlyDictionary(additionalData); + DataTypeConfiguration = dataTypeConfiguration; } /// /// The value submitted for the property /// - public object Value { get; private set; } + public object Value { get; } /// - /// The pre-value collection for the content property + /// The data type configuration for the property. /// - public PreValueCollection PreValues { get; private set; } + public object DataTypeConfiguration { get; } /// - /// A dictionary containing any additional objects that are related to this property when saving + /// Gets or sets the unique identifier of the content owning the property. /// - public ReadOnlyDictionary AdditionalData { get; private set; } - + public Guid ContentKey { get; set; } + + /// + /// Gets or sets the unique identifier of the property type. + /// + public Guid PropertyTypeKey { get; set; } + + /// + /// Gets or sets the uploaded files. + /// + public ContentPropertyFile[] Files { get; set; } } } diff --git a/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs b/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs new file mode 100644 index 0000000000..53c2adf7c2 --- /dev/null +++ b/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs @@ -0,0 +1,23 @@ +namespace Umbraco.Core.Models.Editors +{ + /// + /// Represents an uploaded file for a property. + /// + public class ContentPropertyFile + { + /// + /// Gets or sets the property alias. + /// + public string PropertyAlias { get; set; } + + /// + /// Gets or sets the name of the file. + /// + public string FileName { get; set; } + + /// + /// Gets or sets the temporary path where the file has been uploaded. + /// + public string TempFilePath { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Entities/BeingDirty.cs b/src/Umbraco.Core/Models/Entities/BeingDirty.cs new file mode 100644 index 0000000000..ec447a62dc --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/BeingDirty.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace Umbraco.Core.Models.Entities +{ + /// + /// Provides a concrete implementation of . + /// + /// + /// This class is provided for classes that cannot inherit from + /// and therefore need to implement , by re-using some of + /// logic. + /// + public sealed class BeingDirty : BeingDirtyBase + { + /// + /// Sets a property value, detects changes and manages the dirty flag. + /// + /// The type of the value. + /// The new value. + /// A reference to the value to set. + /// The property selector. + /// A comparer to compare property values. + public new void SetPropertyValueAndDetectChanges(T value, ref T valueRef, PropertyInfo propertySelector, IEqualityComparer comparer = null) + { + base.SetPropertyValueAndDetectChanges(value, ref valueRef, propertySelector, comparer); + } + + /// + /// Registers that a property has changed. + /// + public new void OnPropertyChanged(PropertyInfo propertySelector) + { + base.OnPropertyChanged(propertySelector); + } + } +} diff --git a/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs b/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs new file mode 100644 index 0000000000..711b7c9b9f --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models.Entities +{ + /// + /// Provides a base implementation of and . + /// + [Serializable] + [DataContract(IsReference = true)] + public abstract class BeingDirtyBase : IRememberBeingDirty + { + private bool _withChanges = true; // should we track changes? + private Dictionary _currentChanges; // which properties have changed? + private Dictionary _savedChanges; // which properties had changed at last commit? + + #region ICanBeDirty + + /// + public virtual bool IsDirty() + { + return _currentChanges != null && _currentChanges.Any(); + } + + /// + public virtual bool IsPropertyDirty(string propertyName) + { + return _currentChanges != null && _currentChanges.Any(x => x.Key == propertyName); + } + + /// + public virtual IEnumerable GetDirtyProperties() + { + // ReSharper disable once MergeConditionalExpression + return _currentChanges == null + ? Enumerable.Empty() + : _currentChanges.Where(x => x.Value).Select(x => x.Key); + } + + /// + /// Saves dirty properties so they can be checked with WasDirty. + public virtual void ResetDirtyProperties() + { + ResetDirtyProperties(true); + } + + #endregion + + #region IRememberBeingDirty + + /// + public virtual bool WasDirty() + { + return _savedChanges != null && _savedChanges.Any(); + } + + /// + public virtual bool WasPropertyDirty(string propertyName) + { + return _savedChanges != null && _savedChanges.Any(x => x.Key == propertyName); + } + + /// + public void ResetWereDirtyProperties() + { + // note: cannot .Clear() because when memberwise-cloning this will be the SAME + // instance as the one on the clone, so we need to create a new instance. + _savedChanges = null; + } + + /// + public virtual void ResetDirtyProperties(bool rememberDirty) + { + // capture changes if remembering + // clone the dictionary in case it's shared by an entity clone + _savedChanges = rememberDirty && _currentChanges != null + ? _currentChanges.ToDictionary(v => v.Key, v => v.Value) + : null; + + // note: cannot .Clear() because when memberwise-clone this will be the SAME + // instance as the one on the clone, so we need to create a new instance. + _currentChanges = null; + } + + /// + public virtual IEnumerable GetWereDirtyProperties() + { + // ReSharper disable once MergeConditionalExpression + return _savedChanges == null + ? Enumerable.Empty() + : _savedChanges.Where(x => x.Value).Select(x => x.Key); + } + + #endregion + + #region Change Tracking + + /// + /// Occurs when a property changes. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Registers that a property has changed. + /// + protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) + { + if (_withChanges == false) + return; + + if (_currentChanges == null) + _currentChanges = new Dictionary(); + + _currentChanges[propertyInfo.Name] = true; + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name)); + } + + /// + /// Disables change tracking. + /// + public void DisableChangeTracking() + { + _withChanges = false; + } + + /// + /// Enables change tracking. + /// + public void EnableChangeTracking() + { + _withChanges = true; + } + + /// + /// Sets a property value, detects changes and manages the dirty flag. + /// + /// The type of the value. + /// The new value. + /// A reference to the value to set. + /// The property selector. + /// A comparer to compare property values. + protected void SetPropertyValueAndDetectChanges(T value, ref T valueRef, PropertyInfo propertySelector, IEqualityComparer comparer = null) + { + if (comparer == null) + { + // if no comparer is provided, use the default provider, as long as the value is not + // an IEnumerable - exclude strings, which are IEnumerable but have a default comparer + var typeofT = typeof(T); + if (!(typeofT == typeof(string)) && typeof(IEnumerable).IsAssignableFrom(typeofT)) + throw new ArgumentNullException(nameof(comparer), "A custom comparer must be supplied for IEnumerable values."); + comparer = EqualityComparer.Default; + } + + // compare values + var changed = _withChanges && comparer.Equals(valueRef, value) == false; + + // assign the new value + valueRef = value; + + // handle change + if (changed) + OnPropertyChanged(propertySelector); + } + + /// + /// Detects changes and manages the dirty flag. + /// + /// The type of the value. + /// The new value. + /// The original value. + /// The property selector. + /// A comparer to compare property values. + /// A value indicating whether we know values have changed and no comparison is required. + protected void DetectChanges(T value, T orig, PropertyInfo propertySelector, IEqualityComparer comparer, bool changed) + { + // compare values + changed = _withChanges && (changed || !comparer.Equals(orig, value)); + + // handle change + if (changed) + OnPropertyChanged(propertySelector); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs new file mode 100644 index 0000000000..6eeed53a89 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs @@ -0,0 +1,17 @@ +namespace Umbraco.Core.Models.Entities +{ + /// + /// Implements . + /// + public class ContentEntitySlim : EntitySlim, IContentEntitySlim + { + /// + public string ContentTypeAlias { get; set; } + + /// + public string ContentTypeIcon { get; set; } + + /// + public string ContentTypeThumbnail { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs new file mode 100644 index 0000000000..57cffa793c --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Models.Entities +{ + /// + /// Implements . + /// + public class DocumentEntitySlim : ContentEntitySlim, IDocumentEntitySlim + { + /// + public bool Published { get; set; } + + /// + public bool Edited { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Entities/EntityBase.cs b/src/Umbraco.Core/Models/Entities/EntityBase.cs new file mode 100644 index 0000000000..ab57d57ab6 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/EntityBase.cs @@ -0,0 +1,180 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models.Entities +{ + /// + /// Provides a base class for entities. + /// + [Serializable] + [DataContract(IsReference = true)] + [DebuggerDisplay("Id: {" + nameof(Id) + "}")] + public abstract class EntityBase : BeingDirtyBase, IEntity + { + private static readonly Lazy Ps = new Lazy(); + + private bool _hasIdentity; + private int _id; + private Guid _key; + private DateTime _createDate; + private DateTime _updateDate; + + // ReSharper disable once ClassNeverInstantiated.Local + private class PropertySelectors + { + public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); + public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); + public readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); + public readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); + } + + /// + [DataMember] + public int Id + { + get => _id; + set + { + SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); + _hasIdentity = value != 0; + } + } + + /// + [DataMember] + public Guid Key + { + get + { + // if an entity does NOT have a key yet, assign one now + if (_key == Guid.Empty) + _key = Guid.NewGuid(); + return _key; + } + set => SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); + } + + /// + [DataMember] + public DateTime CreateDate + { + get => _createDate; + set => SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector); + } + + /// + [DataMember] + public DateTime UpdateDate + { + get => _updateDate; + set => SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector); + } + + /// + [DataMember] + public DateTime? DeleteDate { get; set; } // no change tracking - not persisted + + /// + [DataMember] + public virtual bool HasIdentity => _hasIdentity; + + /// + /// Resets the entity identity. + /// + internal virtual void ResetIdentity() + { + _id = default; + _key = Guid.Empty; + _hasIdentity = false; + } + + /// + /// Updates the entity when it is being saved for the first time. + /// + internal virtual void AddingEntity() + { + var now = DateTime.Now; + + // set the create and update dates, if not already set + if (IsPropertyDirty("CreateDate") == false || _createDate == default) + CreateDate = now; + if (IsPropertyDirty("UpdateDate") == false || _updateDate == default) + UpdateDate = now; + } + + /// + /// Updates the entity when it is being saved. + /// + internal virtual void UpdatingEntity() + { + var now = DateTime.Now; + + // just in case + if (_createDate == default) + CreateDate = now; + + // set the update date if not already set + if (IsPropertyDirty("UpdateDate") == false || _updateDate == default) + UpdateDate = now; + } + + public virtual bool Equals(EntityBase other) + { + return other != null && (ReferenceEquals(this, other) || SameIdentityAs(other)); + } + + public override bool Equals(object obj) + { + return obj != null && (ReferenceEquals(this, obj) || SameIdentityAs(obj as EntityBase)); + } + + private bool SameIdentityAs(EntityBase other) + { + if (other == null) return false; + + // same identity if + // - same object (reference equals) + // - or same Clr type, both have identities, and they are identical + + if (ReferenceEquals(this, other)) + return true; + + return GetType() == other.GetType() && HasIdentity && other.HasIdentity && Id == other.Id; + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = HasIdentity.GetHashCode(); + hashCode = (hashCode * 397) ^ Id; + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } + + public virtual object DeepClone() + { + // memberwise-clone (ie shallow clone) the entity + var unused = Key; // ensure that 'this' has a key, before cloning + var clone = (EntityBase) MemberwiseClone(); + + // clear changes (ensures the clone has its own dictionaries) + // then disable change tracking + clone.ResetDirtyProperties(false); + clone.DisableChangeTracking(); + + // deep clone ref properties that are IDeepCloneable + DeepCloneHelper.DeepCloneRefProperties(this, clone); + + // clear changes again (just to be sure, because we were not tracking) + // then enable change tracking + clone.ResetDirtyProperties(false); + clone.EnableChangeTracking(); + + return clone; + } + } +} diff --git a/src/Umbraco.Core/Models/Entities/EntitySlim.cs b/src/Umbraco.Core/Models/Entities/EntitySlim.cs new file mode 100644 index 0000000000..163879bbe0 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/EntitySlim.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Models.Entities +{ + // fixme - changing the name of some properties that were in additionalData => must update corresponding javascript? + + /// + /// Implementation of for internal use. + /// + /// + /// Although it implements , this class does not + /// implement and everything this interface defines, throws. + /// Although it implements , this class does not + /// implement and deep-cloning throws. + /// + public class EntitySlim : IEntitySlim + { + private IDictionary _additionalData; + + /// + /// Gets an entity representing "root". + /// + public static readonly IEntitySlim Root = new EntitySlim { Path = "-1", Name = "root", HasChildren = true }; + + + // implement IEntity + + /// + [DataMember] + public int Id { get; set; } + + /// + [DataMember] + public Guid Key { get; set; } + + /// + [DataMember] + public DateTime CreateDate { get; set; } + + /// + [DataMember] + public DateTime UpdateDate { get; set; } + + /// + [DataMember] + public DateTime? DeleteDate { get; set; } + + /// + [DataMember] + public bool HasIdentity => Id != 0; + + + // implement ITreeEntity + + /// + [DataMember] + public string Name { get; set; } + + /// + [DataMember] + public int CreatorId { get; set; } + + /// + [DataMember] + public int ParentId { get; set; } + + /// + public void SetParent(ITreeEntity parent) => throw new WontImplementException(); + + /// + [DataMember] + public int Level { get; set; } + + /// + [DataMember] + public string Path { get; set; } + + /// + [DataMember] + public int SortOrder { get; set; } + + /// + [DataMember] + public bool Trashed { get; set; } + + + // implement IUmbracoEntity + + /// + [DataMember] + public IDictionary AdditionalData => _additionalData ?? (_additionalData = new Dictionary()); + + /// + [IgnoreDataMember] + public bool HasAdditionalData => _additionalData != null; + + + // implement IEntitySlim + + /// + [DataMember] + public Guid NodeObjectType { get; set; } + + /// + [DataMember] + public bool HasChildren { get; set; } + + /// + [DataMember] + public virtual bool IsContainer { get; set; } + + + /// + /// Represents a lightweight property. + /// + public class PropertySlim + { + /// + /// Initializes a new instance of the class. + /// + public PropertySlim(string editorAlias, object value) + { + PropertyEditorAlias = editorAlias; + Value = value; + } + + /// + /// Gets the property editor alias. + /// + public string PropertyEditorAlias { get; } + + /// + /// Gets the property value. + /// + public object Value { get; } + + protected bool Equals(PropertySlim other) + { + return PropertyEditorAlias.Equals(other.PropertyEditorAlias) && Equals(Value, other.Value); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((PropertySlim) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (PropertyEditorAlias.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0); + } + } + } + + #region IDeepCloneable + + /// + public object DeepClone() + { + throw new WontImplementException(); + } + + #endregion + + #region IRememberBeingDirty + + // IEntitySlim does *not* track changes, but since it indirectly implements IUmbracoEntity, + // and therefore IRememberBeingDirty, we have to have those methods - which all throw. + + public bool IsDirty() + { + throw new WontImplementException(); + } + + public bool IsPropertyDirty(string propName) + { + throw new WontImplementException(); + } + + public IEnumerable GetDirtyProperties() + { + throw new WontImplementException(); + } + + public void ResetDirtyProperties() + { + throw new WontImplementException(); + } + + public bool WasDirty() + { + throw new WontImplementException(); + } + + public bool WasPropertyDirty(string propertyName) + { + throw new WontImplementException(); + } + + public void ResetWereDirtyProperties() + { + throw new WontImplementException(); + } + + public void ResetDirtyProperties(bool rememberDirty) + { + throw new WontImplementException(); + } + + public IEnumerable GetWereDirtyProperties() + { + throw new WontImplementException(); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/Entities/ICanBeDirty.cs b/src/Umbraco.Core/Models/Entities/ICanBeDirty.cs new file mode 100644 index 0000000000..fc95161d7e --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/ICanBeDirty.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Entities +{ + /// + /// Defines an entity that tracks property changes and can be dirty. + /// + public interface ICanBeDirty + { + /// + /// Determines whether the current entity is dirty. + /// + bool IsDirty(); + + /// + /// Determines whether a specific property is dirty. + /// + bool IsPropertyDirty(string propName); + + /// + /// Gets properties that are dirty. + /// + IEnumerable GetDirtyProperties(); + + /// + /// Resets dirty properties. + /// + void ResetDirtyProperties(); + } +} diff --git a/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs new file mode 100644 index 0000000000..dc986a4cd9 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs @@ -0,0 +1,23 @@ +namespace Umbraco.Core.Models.Entities +{ + /// + /// Represents a lightweight content entity, managed by the entity service. + /// + public interface IContentEntitySlim : IEntitySlim + { + /// + /// Gets the content type alias. + /// + string ContentTypeAlias { get; } + + /// + /// Gets the content type icon. + /// + string ContentTypeIcon { get; } + + /// + /// Gets the content type thumbnail. + /// + string ContentTypeThumbnail { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs new file mode 100644 index 0000000000..471c0ba1e1 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Models.Entities +{ + /// + /// Represents a lightweight document entity, managed by the entity service. + /// + public interface IDocumentEntitySlim : IContentEntitySlim + { + /// + /// Gets a value indicating whether the document is published. + /// + bool Published { get; } + + /// + /// Gets a value indicating whether the document has edited properties. + /// + bool Edited { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Entities/IEntity.cs b/src/Umbraco.Core/Models/Entities/IEntity.cs new file mode 100644 index 0000000000..0b060e4dbd --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/IEntity.cs @@ -0,0 +1,45 @@ +using System; + +namespace Umbraco.Core.Models.Entities +{ + /// + /// Defines an entity. + /// + public interface IEntity : IDeepCloneable + { + /// + /// Gets or sets the integer identifier of the entity. + /// + int Id { get; set; } + + /// + /// Gets or sets the Guid unique identifier of the entity. + /// + Guid Key { get; set; } + + /// + /// Gets or sets the creation date. + /// + DateTime CreateDate { get; set; } + + /// + /// Gets or sets the last update date. + /// + DateTime UpdateDate { get; set; } + + /// + /// Gets or sets the delete date. + /// + /// + /// The delete date is null when the entity has not been deleted. + /// The delete date has a value when the entity instance has been deleted, but this value + /// is transient and not persisted in database (since the entity does not exist anymore). + /// + DateTime? DeleteDate { get; set; } + + /// + /// Gets a value indicating whether the entity has an identity. + /// + bool HasIdentity { get; } + } +} diff --git a/src/Umbraco.Core/Models/Entities/IEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IEntitySlim.cs new file mode 100644 index 0000000000..116d5c2b44 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/IEntitySlim.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Entities +{ + /// + /// Represents a lightweight entity, managed by the entity service. + /// + public interface IEntitySlim : IUmbracoEntity, IHaveAdditionalData + { + /// + /// Gets or sets the entity object type. + /// + Guid NodeObjectType { get; } + + /// + /// Gets or sets a value indicating whether the entity has children. + /// + bool HasChildren { get; } + + /// + /// Gets a value indicating whether the entity is a container. + /// + bool IsContainer { get; } + } +} diff --git a/src/Umbraco.Core/Models/Entities/IHaveAdditionalData.cs b/src/Umbraco.Core/Models/Entities/IHaveAdditionalData.cs new file mode 100644 index 0000000000..e2a74db060 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/IHaveAdditionalData.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Models.Entities +{ + /// + /// Provides support for additional data. + /// + /// + /// Additional data are transient, not deep-cloned. + /// + public interface IHaveAdditionalData + { + /// + /// Gets additional data for this entity. + /// + /// Can be empty, but never null. To avoid allocating, do not + /// test for emptyness, but use instead. + IDictionary AdditionalData { get; } + + /// + /// Determines whether this entity has additional data. + /// + /// Use this property to check for additional data without + /// getting , to avoid allocating. + bool HasAdditionalData { get; } + + // how to implement: + + /* + private IDictionary _additionalData; + + /// + [DataMember] + [DoNotClone] + PublicAccessEntry IDictionary AdditionalData => _additionalData ?? (_additionalData = new Dictionary()); + + /// + [IgnoreDataMember] + PublicAccessEntry bool HasAdditionalData => _additionalData != null; + */ + } +} diff --git a/src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs b/src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs new file mode 100644 index 0000000000..e679b98b93 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Entities +{ + /// + /// Defines an entity that tracks property changes and can be dirty, and remembers + /// which properties were dirty when the changes were committed. + /// + public interface IRememberBeingDirty : ICanBeDirty + { + /// + /// Determines whether the current entity is dirty. + /// + /// A property was dirty if it had been changed and the changes were committed. + bool WasDirty(); + + /// + /// Determines whether a specific property was dirty. + /// + /// A property was dirty if it had been changed and the changes were committed. + bool WasPropertyDirty(string propertyName); + + /// + /// Resets properties that were dirty. + /// + void ResetWereDirtyProperties(); + + /// + /// Resets dirty properties. + /// + /// A value indicating whether to remember dirty properties. + /// When is true, dirty properties are saved so they can be checked with WasDirty. + void ResetDirtyProperties(bool rememberDirty); + + /// + /// Gets properties that were dirty. + /// + IEnumerable GetWereDirtyProperties(); + } +} diff --git a/src/Umbraco.Core/Models/Entities/ITreeEntity.cs b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs new file mode 100644 index 0000000000..afa3399202 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs @@ -0,0 +1,56 @@ +namespace Umbraco.Core.Models.Entities +{ + /// + /// Defines an entity that belongs to a tree. + /// + public interface ITreeEntity : IEntity + { + /// + /// Gets or sets the name of the entity. + /// + string Name { get; set; } + + /// + /// Gets or sets the identifier of the user who created this entity. + /// + int CreatorId { get; set; } + + /// + /// Gets or sets the identifier of the parent entity. + /// + int ParentId { get; set; } + + /// + /// Sets the parent entity. + /// + /// Use this method to set the parent entity when the parent entity is known, but has not + /// been persistent and does not yet have an identity. The parent identifier will we retrieved + /// from the parent entity when needed. If the parent entity still does not have an entity by that + /// time, an exception will be thrown by getter. + void SetParent(ITreeEntity parent); + + /// + /// Gets or sets the level of the entity. + /// + int Level { get; set; } + + /// + /// Gets or sets the path to the entity. + /// + string Path { get; set; } + + /// + /// Gets or sets the sort order of the entity. + /// + int SortOrder { get; set; } + + /// + /// Gets a value indicating whether this entity is trashed. + /// + /// + /// Trashed entities are located in the recycle bin. + /// Always false for entities that do not support being trashed. + /// + bool Trashed { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs new file mode 100644 index 0000000000..e5f628b098 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Entities +{ + /// + /// Represents an entity that can be managed by the entity service. + /// + /// + /// An IUmbracoEntity can be related to another via the IRelationService. + /// IUmbracoEntities can be retrieved with the IEntityService. + /// An IUmbracoEntity can participate in notifications. + /// + public interface IUmbracoEntity : ITreeEntity, IRememberBeingDirty + { } +} diff --git a/src/Umbraco.Core/Models/EntityBase/IValueObject.cs b/src/Umbraco.Core/Models/Entities/IValueObject.cs similarity index 76% rename from src/Umbraco.Core/Models/EntityBase/IValueObject.cs rename to src/Umbraco.Core/Models/Entities/IValueObject.cs index 3fb76e598e..2d2e69e7ae 100644 --- a/src/Umbraco.Core/Models/EntityBase/IValueObject.cs +++ b/src/Umbraco.Core/Models/Entities/IValueObject.cs @@ -1,11 +1,11 @@ -namespace Umbraco.Core.Models.EntityBase -{ - /// - /// Marker interface for value object, eg. objects without - /// the same kind of identity as an Entity (with its Id). - /// - public interface IValueObject - { - - } -} \ No newline at end of file +namespace Umbraco.Core.Models.Entities +{ + /// + /// Marker interface for value object, eg. objects without + /// the same kind of identity as an Entity (with its Id). + /// + public interface IValueObject + { + + } +} diff --git a/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs b/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs new file mode 100644 index 0000000000..18e53a8a04 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs @@ -0,0 +1,124 @@ +using System; +using System.Reflection; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models.Entities +{ + /// + /// Provides a base class for tree entities. + /// + public abstract class TreeEntityBase : EntityBase, ITreeEntity + { + private static PropertySelectors _selectors; + private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); + + private string _name; + private int _creatorId; + private int _parentId; + private bool _hasParentId; + private ITreeEntity _parent; + private int _level; + private string _path; + private int _sortOrder; + private bool _trashed; + + private class PropertySelectors + { + public readonly PropertyInfo Name = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo CreatorId = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo ParentId = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo Level = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo Path = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo SortOrder = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo Trashed = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + } + + // fixme + // ParentId, Path, Level and Trashed all should be consistent, and all derive from parentId, really + + /// + [DataMember] + public string Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, Selectors.Name); + } + + /// + [DataMember] + public int CreatorId + { + get => _creatorId; + set => SetPropertyValueAndDetectChanges(value, ref _creatorId, Selectors.CreatorId); + } + + /// + [DataMember] + public int ParentId + { + get + { + if (_hasParentId) return _parentId; + + if (_parent == null) throw new InvalidOperationException("Content does not have a parent."); + if (!_parent.HasIdentity) throw new InvalidOperationException("Content's parent does not have an identity."); + + _parentId = _parent.Id; + if (_parentId == 0) + throw new Exception("Panic: parent has an identity but id is zero."); + + _hasParentId = true; + _parent = null; + return _parentId; + } + set + { + if (value == 0) + throw new ArgumentException("Value cannot be zero.", nameof(value)); + SetPropertyValueAndDetectChanges(value, ref _parentId, Selectors.ParentId); + _hasParentId = true; + _parent = null; + } + } + + /// + public void SetParent(ITreeEntity parent) + { + _hasParentId = false; + _parent = parent; + OnPropertyChanged(Selectors.ParentId); + } + + /// + [DataMember] + public int Level + { + get => _level; + set => SetPropertyValueAndDetectChanges(value, ref _level, Selectors.Level); + } + + /// + [DataMember] + public string Path + { + get => _path; + set => SetPropertyValueAndDetectChanges(value, ref _path, Selectors.Path); + } + + /// + [DataMember] + public int SortOrder + { + get => _sortOrder; + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Selectors.SortOrder); + } + + /// + [DataMember] + public bool Trashed + { + get => _trashed; + set => SetPropertyValueAndDetectChanges(value, ref _trashed, Selectors.Trashed); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs b/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs new file mode 100644 index 0000000000..54142a7527 --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Core.Models.Entities +{ + /// + /// Represents the path of a tree entity. + /// + public class TreeEntityPath + { + /// + /// Gets or sets the identifier of the entity. + /// + public int Id { get; set; } + + /// + /// Gets or sets the path of the entity. + /// + public string Path { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs deleted file mode 100644 index 296780c753..0000000000 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ /dev/null @@ -1,231 +0,0 @@ -using System; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models.EntityBase -{ - /// - /// Base Abstract Entity - /// - [Serializable] - [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}")] - public abstract class Entity : TracksChangesEntityBase, IEntity, IRememberBeingDirty, ICanBeDirty - { - private bool _hasIdentity; - private int _id; - private Guid _key; - private DateTime _createDate; - private DateTime _updateDate; - private bool _wasCancelled; - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); - public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); - public readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); - public readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); - public readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); - public readonly PropertyInfo WasCancelledSelector = ExpressionHelper.GetPropertyInfo(x => x.WasCancelled); - } - - /// - /// Integer Id - /// - [DataMember] - public int Id - { - get { return _id; } - set - { - SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); - HasIdentity = true; //set the has Identity - } - } - - /// - /// Guid based Id - /// - /// The key is currectly used to store the Unique Id from the - /// umbracoNode table, which many of the entities are based on. - [DataMember] - public Guid Key - { - get - { - // if an entity does NOT have a UniqueId yet, assign one now - if (_key == Guid.Empty) - _key = Guid.NewGuid(); - return _key; - } - set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); } - } - - /// - /// Gets or sets the Created Date - /// - [DataMember] - public DateTime CreateDate - { - get { return _createDate; } - set { SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector); } - } - - /// - /// Gets or sets the WasCancelled flag, which is used to track - /// whether some action against an entity was cancelled through some event. - /// This only exists so we have a way to check if an event was cancelled through - /// the new api, which also needs to take effect in the legacy api. - /// - [IgnoreDataMember] - [Obsolete("Anytime there's a cancellable method it needs to return an Attempt so we know the outcome instead of this hack, not all services have been updated to use this though yet.")] - internal bool WasCancelled - { - get { return _wasCancelled; } - set { SetPropertyValueAndDetectChanges(value, ref _wasCancelled, Ps.Value.WasCancelledSelector); } - } - - /// - /// Gets or sets the Modified Date - /// - [DataMember] - public DateTime UpdateDate - { - get { return _updateDate; } - set { SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector); } - } - - [IgnoreDataMember] - public DateTime? DeletedDate { get; set; } - - internal virtual void ResetIdentity() - { - _hasIdentity = false; - _id = default(int); - _key = Guid.Empty; - } - - /// - /// Method to call on entity saved when first added - /// - internal virtual void AddingEntity() - { - if (IsPropertyDirty("CreateDate") == false || _createDate == default(DateTime)) - CreateDate = DateTime.Now; - if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime)) - UpdateDate = DateTime.Now; - } - - /// - /// Method to call on entity saved/updated - /// - internal virtual void UpdatingEntity() - { - if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime)) - UpdateDate = DateTime.Now; - - //this is just in case - if (_createDate == default(DateTime)) - CreateDate = DateTime.Now; - } - - /// - /// Indicates whether the current entity has an identity, eg. Id. - /// - [DataMember] - public virtual bool HasIdentity - { - get - { - return _hasIdentity; - } - protected set { SetPropertyValueAndDetectChanges(value, ref _hasIdentity, Ps.Value.HasIdentitySelector); } - } - - //TODO: Make this NOT virtual or even exist really! - public virtual bool SameIdentityAs(IEntity other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - return SameIdentityAs(other as Entity); - } - - public virtual bool Equals(Entity other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - return SameIdentityAs(other); - } - - //TODO: Make this NOT virtual or even exist really! - public virtual Type GetRealType() - { - return GetType(); - } - - //TODO: Make this NOT virtual or even exist really! - public virtual bool SameIdentityAs(Entity other) - { - if (ReferenceEquals(null, other)) - return false; - - if (ReferenceEquals(this, other)) - return true; - - if (GetType() == other.GetRealType() && HasIdentity && other.HasIdentity) - return other.Id.Equals(Id); - - return false; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - if (ReferenceEquals(this, obj)) - return true; - - return SameIdentityAs(obj as IEntity); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = HasIdentity.GetHashCode(); - hashCode = (hashCode * 397) ^ Id; - hashCode = (hashCode * 397) ^ GetType().GetHashCode(); - return hashCode; - } - } - - public virtual object DeepClone() - { - //Memberwise clone on Entity will work since it doesn't have any deep elements - // for any sub class this will work for standard properties as well that aren't complex object's themselves. - var ignored = this.Key; // ensure that 'this' has a key, before cloning - var clone = (Entity)MemberwiseClone(); - //ensure the clone has it's own dictionaries - clone.ResetChangeTrackingCollections(); - //turn off change tracking - clone.DisableChangeTracking(); - //Automatically deep clone ref properties that are IDeepCloneable - DeepCloneHelper.DeepCloneRefProperties(this, clone); - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - - return clone; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/EntityPath.cs b/src/Umbraco.Core/Models/EntityBase/EntityPath.cs deleted file mode 100644 index 368d6bd87b..0000000000 --- a/src/Umbraco.Core/Models/EntityBase/EntityPath.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Models.EntityBase -{ - public class EntityPath - { - public int Id { get; set; } - public string Path { get; set; } - } -} diff --git a/src/Umbraco.Core/Models/EntityBase/IAggregateRoot.cs b/src/Umbraco.Core/Models/EntityBase/IAggregateRoot.cs deleted file mode 100644 index aacb5185e0..0000000000 --- a/src/Umbraco.Core/Models/EntityBase/IAggregateRoot.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Core.Models.EntityBase -{ - /// - /// Marker interface for aggregate roots - /// - public interface IAggregateRoot : IDeletableEntity - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/ICanBeDirty.cs b/src/Umbraco.Core/Models/EntityBase/ICanBeDirty.cs deleted file mode 100644 index 2f5a6725e1..0000000000 --- a/src/Umbraco.Core/Models/EntityBase/ICanBeDirty.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Umbraco.Core.Models.EntityBase -{ - /// - /// An interface that defines the object is tracking property changes and if it is Dirty - /// - public interface ICanBeDirty - { - bool IsDirty(); - bool IsPropertyDirty(string propName); - void ResetDirtyProperties(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/IDeletableEntity.cs b/src/Umbraco.Core/Models/EntityBase/IDeletableEntity.cs deleted file mode 100644 index 42f91b6a6c..0000000000 --- a/src/Umbraco.Core/Models/EntityBase/IDeletableEntity.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models.EntityBase -{ - public interface IDeletableEntity : IEntity - { - [DataMember] - DateTime? DeletedDate { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/IEntity.cs b/src/Umbraco.Core/Models/EntityBase/IEntity.cs deleted file mode 100644 index 059983bb38..0000000000 --- a/src/Umbraco.Core/Models/EntityBase/IEntity.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models.EntityBase -{ - /// - /// Defines an Entity. - /// Entities should always have an Id, Created and Modified date - /// - /// The current database schema doesn't provide a modified date - /// for all entities, so this will have to be changed at a later stage. - public interface IEntity : IDeepCloneable - { - /// - /// The Id of the entity - /// - [DataMember] - int Id { get; set; } - - /// - /// Guid based Id - /// - /// The key is currectly used to store the Unique Id from the - /// umbracoNode table, which many of the entities are based on. - [DataMember] - Guid Key { get; set; } - - /// - /// Gets or sets the Created Date - /// - [DataMember] - DateTime CreateDate { get; set; } - - /// - /// Gets or sets the Modified Date - /// - [DataMember] - DateTime UpdateDate { get; set; } - - /// - /// Indicates whether the current entity has an identity, eg. Id. - /// - [IgnoreDataMember] - bool HasIdentity { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs b/src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs deleted file mode 100644 index efebbd7c74..0000000000 --- a/src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Umbraco.Core.Models.EntityBase -{ - /// - /// An interface that defines if the object is tracking property changes and that is is also - /// remembering what property changes had been made after the changes were committed. - /// - public interface IRememberBeingDirty : ICanBeDirty - { - bool WasDirty(); - bool WasPropertyDirty(string propertyName); - void ForgetPreviouslyDirtyProperties(); - void ResetDirtyProperties(bool rememberPreviouslyChangedProperties); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/IUmbracoEntity.cs b/src/Umbraco.Core/Models/EntityBase/IUmbracoEntity.cs deleted file mode 100644 index 35f1955a61..0000000000 --- a/src/Umbraco.Core/Models/EntityBase/IUmbracoEntity.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Models.EntityBase -{ - public interface IUmbracoEntity : IAggregateRoot, IRememberBeingDirty, ICanBeDirty - { - /// - /// Profile of the user who created this Entity - /// - int CreatorId { get; set; } - - /// - /// Gets or sets the level of the Entity - /// - int Level { get; set; } - - /// - /// Gets or Sets the Name of the Entity - /// - string Name { get; set; } - - /// - /// Gets or sets the Id of the Parent Entity - /// - int ParentId { get; set; } - - /// - /// Gets or sets the path to the Entity - /// - string Path { get; set; } - - /// - /// Gets or sets the sort order of the Entity - /// - int SortOrder { get; set; } - - /// - /// Boolean indicating whether this Entity is Trashed or not. - /// If an Entity is Trashed it will be located in the Recyclebin. - /// - /// - /// When content is trashed it should be unpublished. - /// Not all entities support being trashed, they'll always return false. - /// - bool Trashed { get; } - - /// - /// Some entities may expose additional data that other's might not, this custom data will be available in this collection - /// - IDictionary AdditionalData { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs deleted file mode 100644 index 407237ad60..0000000000 --- a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs +++ /dev/null @@ -1,224 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models.EntityBase -{ - /// - /// A base class for use to implement IRememberBeingDirty/ICanBeDirty - /// - [Serializable] - [DataContract(IsReference = true)] - public abstract class TracksChangesEntityBase : IRememberBeingDirty - { - //TODO: This needs to go on to ICanBeDirty http://issues.umbraco.org/issue/U4-5662 - public virtual IEnumerable GetDirtyProperties() - { - return _propertyChangedInfo == null - ? Enumerable.Empty() - : _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key); - } - - private bool _changeTrackingEnabled = true; - - /// - /// Tracks the properties that have changed - /// - private IDictionary _propertyChangedInfo; - - /// - /// Tracks the properties that we're changed before the last commit (or last call to ResetDirtyProperties) - /// - private IDictionary _lastPropertyChangedInfo; - - /// - /// Property changed event - /// - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Method to call on a property setter. - /// - /// The property info. - protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) - { - //return if we're not tracking changes - if (_changeTrackingEnabled == false) return; - - if (_propertyChangedInfo == null) - _propertyChangedInfo = new Dictionary(); - - _propertyChangedInfo[propertyInfo.Name] = true; - - if (PropertyChanged != null) - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name)); - } - - /// - /// Indicates whether a specific property on the current entity is dirty. - /// - /// Name of the property to check - /// True if Property is dirty, otherwise False - public virtual bool IsPropertyDirty(string propertyName) - { - return _propertyChangedInfo != null && _propertyChangedInfo.Any(x => x.Key == propertyName); - } - - /// - /// Indicates whether the current entity is dirty. - /// - /// True if entity is dirty, otherwise False - public virtual bool IsDirty() - { - return _propertyChangedInfo != null && _propertyChangedInfo.Any(); - } - - /// - /// Indicates that the entity had been changed and the changes were committed - /// - /// - public virtual bool WasDirty() - { - return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any(); - } - - /// - /// Indicates whether a specific property on the current entity was changed and the changes were committed - /// - /// Name of the property to check - /// True if Property was changed, otherwise False. Returns false if the entity had not been previously changed. - public virtual bool WasPropertyDirty(string propertyName) - { - return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any(x => x.Key == propertyName); - } - - /// - /// Resets the remembered dirty properties from before the last commit - /// - public void ForgetPreviouslyDirtyProperties() - { - //NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME - // instance as the one on the clone, so we need to create a new instance. - _lastPropertyChangedInfo = null; - } - - /// - /// Resets dirty properties by clearing the dictionary used to track changes. - /// - /// - /// Please note that resetting the dirty properties could potentially - /// obstruct the saving of a new or updated entity. - /// - public virtual void ResetDirtyProperties() - { - ResetDirtyProperties(true); - } - - /// - /// Resets dirty properties by clearing the dictionary used to track changes. - /// - /// - /// true if we are to remember the last changes made after resetting - /// - /// - /// Please note that resetting the dirty properties could potentially - /// obstruct the saving of a new or updated entity. - /// - public virtual void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) - { - if (rememberPreviouslyChangedProperties) - { - //copy the changed properties to the last changed properties - if (_propertyChangedInfo != null) - { - _lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value); - } - } - - //NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME - // instance as the one on the clone, so we need to create a new instance. - _propertyChangedInfo = null; - } - - public void ResetChangeTrackingCollections() - { - _propertyChangedInfo = null; - _lastPropertyChangedInfo = null; - } - - public void DisableChangeTracking() - { - _changeTrackingEnabled = false; - } - - public void EnableChangeTracking() - { - _changeTrackingEnabled = true; - } - - /// - /// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did - /// it will ensure that the property has a dirty flag set. - /// - /// - /// - /// - /// returns true if the value changed - /// - /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we - /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set - /// to the same value, so it's really not dirty. - /// - internal void SetPropertyValueAndDetectChanges(T newVal, ref T origVal, PropertyInfo propertySelector) - { - if ((typeof(T) == typeof(string) == false) && TypeHelper.IsTypeAssignableFrom(typeof(T))) - { - throw new InvalidOperationException("This method does not support IEnumerable instances. For IEnumerable instances a manual custom equality check will be required"); - } - - SetPropertyValueAndDetectChanges(newVal, ref origVal, propertySelector, EqualityComparer.Default); - } - - /// - /// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did - /// it will ensure that the property has a dirty flag set. - /// - /// - /// - /// - /// The equality comparer to use - /// returns true if the value changed - /// - /// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we - /// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set - /// to the same value, so it's really not dirty. - /// - internal void SetPropertyValueAndDetectChanges(T newVal, ref T origVal, PropertyInfo propertySelector, IEqualityComparer comparer) - { - //don't track changes, just set the value - if (_changeTrackingEnabled == false) - { - //set the original value - origVal = newVal; - } - else - { - //check changed - var changed = comparer.Equals(origVal, newVal) == false; - - //set the original value - origVal = newVal; - - //raise the event if it was changed - if (changed) - { - OnPropertyChanged(propertySelector); - } - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs index 92965bf238..70f6cbd878 100644 --- a/src/Umbraco.Core/Models/EntityContainer.cs +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -1,21 +1,24 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { /// /// Represents a folder for organizing entities such as content types and data types. /// - public sealed class EntityContainer : UmbracoEntity + public sealed class EntityContainer : TreeEntityBase, IUmbracoEntity { private readonly Guid _containedObjectType; private static readonly Dictionary ObjectTypeMap = new Dictionary { - { Constants.ObjectTypes.DataTypeGuid, Constants.ObjectTypes.DataTypeContainerGuid }, - { Constants.ObjectTypes.DocumentTypeGuid, Constants.ObjectTypes.DocumentTypeContainerGuid }, - { Constants.ObjectTypes.MediaTypeGuid, Constants.ObjectTypes.MediaTypeContainerGuid } + { Constants.ObjectTypes.DataType, Constants.ObjectTypes.DataTypeContainer }, + { Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentTypeContainer }, + { Constants.ObjectTypes.MediaType, Constants.ObjectTypes.MediaTypeContainer } }; /// @@ -24,7 +27,7 @@ namespace Umbraco.Core.Models public EntityContainer(Guid containedObjectType) { if (ObjectTypeMap.ContainsKey(containedObjectType) == false) - throw new ArgumentException("Not a contained object type.", "containedObjectType"); + throw new ArgumentException("Not a contained object type.", nameof(containedObjectType)); _containedObjectType = containedObjectType; ParentId = -1; @@ -36,7 +39,7 @@ namespace Umbraco.Core.Models /// /// Initializes a new instance of an class. /// - internal EntityContainer(int id, Guid uniqueId, int parentId, string path, int level, int sortOrder, Guid containedObjectType, string name, int userId) + public EntityContainer(int id, Guid uniqueId, int parentId, string path, int level, int sortOrder, Guid containedObjectType, string name, int userId) : this(containedObjectType) { Id = id; @@ -52,18 +55,12 @@ namespace Umbraco.Core.Models /// /// Gets or sets the node object type of the contained objects. /// - public Guid ContainedObjectType - { - get { return _containedObjectType; } - } + public Guid ContainedObjectType => _containedObjectType; /// /// Gets the node object type of the container objects. /// - public Guid ContainerObjectType - { - get { return ObjectTypeMap[_containedObjectType]; } - } + public Guid ContainerObjectType => ObjectTypeMap[_containedObjectType]; /// /// Gets the container object type corresponding to a contained object type. @@ -73,7 +70,7 @@ namespace Umbraco.Core.Models public static Guid GetContainerObjectType(Guid containedObjectType) { if (ObjectTypeMap.ContainsKey(containedObjectType) == false) - throw new ArgumentException("Not a contained object type.", "containedObjectType"); + throw new ArgumentException("Not a contained object type.", nameof(containedObjectType)); return ObjectTypeMap[containedObjectType]; } @@ -86,8 +83,8 @@ namespace Umbraco.Core.Models { var contained = ObjectTypeMap.FirstOrDefault(x => x.Value == containerObjectType).Key; if (contained == null) - throw new ArgumentException("Not a container object type.", "containerObjectType"); + throw new ArgumentException("Not a container object type.", nameof(containerObjectType)); return contained; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/EntityExtensions.cs b/src/Umbraco.Core/Models/EntityExtensions.cs index 9fbd4ce592..fa7d9b6110 100644 --- a/src/Umbraco.Core/Models/EntityExtensions.cs +++ b/src/Umbraco.Core/Models/EntityExtensions.cs @@ -1,5 +1,4 @@ -using System.Linq; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -7,18 +6,21 @@ namespace Umbraco.Core.Models { /// - /// Returns true if this entity has just been created and persisted to the data store + /// Determines whether the entity was just created and persisted. /// - /// - /// - /// - /// This is useful when handling events to determine if an entity is a brand new entity or was - /// already existing. - /// - public static bool IsNewEntity(this IEntity entity) + public static bool IsNewEntity(this IRememberBeingDirty entity) { - var dirty = (IRememberBeingDirty)entity; - return dirty.WasPropertyDirty("Id"); + return entity.WasPropertyDirty("Id"); + } + + /// + /// Gets additional data. + /// + public static object GetAdditionalDataValueIgnoreCase(this IHaveAdditionalData entity, string key, object defaultValue) + { + if (!entity.HasAdditionalData) return defaultValue; + if (entity.AdditionalData.ContainsKeyIgnoreCase(key) == false) return defaultValue; + return entity.AdditionalData.GetValueIgnoreCase(key, defaultValue); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 41838a3841..cf202ea54f 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -4,7 +4,7 @@ using System.Reflection; using System.Runtime.Serialization; using System.Text; using Umbraco.Core.IO; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public abstract class File : Entity, IFile + public abstract class File : EntityBase, IFile { private string _path; private string _originalPath; @@ -73,7 +73,7 @@ namespace Umbraco.Core.Models get { if (_alias == null) - { + { var name = System.IO.Path.GetFileName(Path); if (name == null) return string.Empty; var lastIndexOf = name.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase); @@ -96,7 +96,7 @@ namespace Umbraco.Core.Models _alias = null; _name = null; - SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, Ps.Value.PathSelector); + SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, Ps.Value.PathSelector); } } @@ -138,7 +138,7 @@ namespace Umbraco.Core.Models { SetPropertyValueAndDetectChanges( value ?? string.Empty, // cannot set to null - ref _content, Ps.Value.ContentSelector); + ref _content, Ps.Value.ContentSelector); } } @@ -184,4 +184,4 @@ namespace Umbraco.Core.Models return clone; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Folder.cs b/src/Umbraco.Core/Models/Folder.cs index be8e9b6dc4..e23f944604 100644 --- a/src/Umbraco.Core/Models/Folder.cs +++ b/src/Umbraco.Core/Models/Folder.cs @@ -1,8 +1,8 @@ -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { - internal sealed class Folder : Entity + internal sealed class Folder : EntityBase { public Folder(string folderPath) { @@ -11,4 +11,4 @@ namespace Umbraco.Core.Models public string Path { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/GridValue.cs b/src/Umbraco.Core/Models/GridValue.cs index 87f6146af6..717fcd2f88 100644 --- a/src/Umbraco.Core/Models/GridValue.cs +++ b/src/Umbraco.Core/Models/GridValue.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -82,4 +82,4 @@ namespace Umbraco.Core.Models public string View { get; set; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IAuditEntry.cs b/src/Umbraco.Core/Models/IAuditEntry.cs new file mode 100644 index 0000000000..c097f84752 --- /dev/null +++ b/src/Umbraco.Core/Models/IAuditEntry.cs @@ -0,0 +1,60 @@ +using System; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents an audited event. + /// + /// + /// The free-form details properties can be used to capture relevant infos (for example, + /// a user email and identifier) at the time of the audited event, even though they may change + /// later on - but we want to keep a track of their value at that time. + /// Depending on audit loggers, these properties can be purely free-form text, or + /// contain json serialized objects. + /// + public interface IAuditEntry : IEntity, IRememberBeingDirty + { + /// + /// Gets or sets the identifier of the user triggering the audited event. + /// + int PerformingUserId { get; set; } + + /// + /// Gets or sets free-form details about the user triggering the audited event. + /// + string PerformingDetails { get; set; } + + /// + /// Gets or sets the IP address or the request triggering the audited event. + /// + string PerformingIp { get; set; } + + /// + /// Gets or sets the date and time of the audited event. + /// + DateTime EventDateUtc { get; set; } + + /// + /// Gets or sets the identifier of the user affected by the audited event. + /// + /// Not used when no single user is affected by the event. + int AffectedUserId { get; set; } + + /// + /// Gets or sets free-form details about the entity affected by the audited event. + /// + /// The entity affected by the event can be another user, a member... + string AffectedDetails { get; set; } + + /// + /// Gets or sets the type of the audited event. + /// + string EventType { get; set; } + + /// + /// Gets or sets free-form details about the audited event. + /// + string EventDetails { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/IAuditItem.cs b/src/Umbraco.Core/Models/IAuditItem.cs index 4a8b3554cd..9416e2a055 100644 --- a/src/Umbraco.Core/Models/IAuditItem.cs +++ b/src/Umbraco.Core/Models/IAuditItem.cs @@ -1,12 +1,12 @@ using System; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { - public interface IAuditItem : IAggregateRoot + public interface IAuditItem : IEntity { string Comment { get; } AuditType AuditType { get; } int UserId { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IConsent.cs b/src/Umbraco.Core/Models/IConsent.cs new file mode 100644 index 0000000000..7e0156fd6e --- /dev/null +++ b/src/Umbraco.Core/Models/IConsent.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a consent state. + /// + /// + /// A consent is fully identified by a source (whoever is consenting), a context (for + /// example, an application), and an action (whatever is consented). + /// A consent state registers the state of the consent (granted, revoked...). + /// + public interface IConsent : IEntity, IRememberBeingDirty + { + /// + /// Determines whether the consent entity represents the current state. + /// + bool Current { get; } + + /// + /// Gets the unique identifier of whoever is consenting. + /// + string Source { get; } + + /// + /// Gets the unique identifier of the context of the consent. + /// + /// + /// Represents the domain, application, scope... of the action. + /// When the action is a Udi, this should be the Udi type. + /// + string Context { get; } + + /// + /// Gets the unique identifier of the consented action. + /// + string Action { get; } + + /// + /// Gets the state of the consent. + /// + ConsentState State { get; } + + /// + /// Gets some additional free text. + /// + string Comment { get; } + + /// + /// Gets the previous states of this consent. + /// + IEnumerable History { get; } + } +} diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 3d385f1b36..ff270458bb 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -1,53 +1,84 @@ -using System; +using System; using System.ComponentModel; namespace Umbraco.Core.Models { /// /// Defines a Content object - /// + /// public interface IContent : IContentBase { /// - /// Gets or sets the template used by the Content. - /// This is used to override the default one from the ContentType. + /// Gets or sets the template used to render the content. /// ITemplate Template { get; set; } /// - /// Boolean indicating whether the Content is Published or not + /// Gets a value indicating whether the content is published. /// bool Published { get; } - [Obsolete("This will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - string Language { get; set; } + PublishedState PublishedState { get; } /// - /// Gets or Sets the date the Content should be released and thus be published + /// Gets a value indicating whether the content has been edited. + /// + bool Edited { get; } + + /// + /// Gets the published version identifier. + /// + int PublishedVersionId { get; } + + /// + /// Gets a value indicating whether the content item is a blueprint. + /// + bool Blueprint { get; } + + /// + /// Gets the template used to render the published version of the content. + /// + /// When editing the content, the template can change, but this will not until the content is published. + ITemplate PublishTemplate { get; } + + /// + /// Gets the name of the published version of the content. + /// + /// When editing the content, the name can change, but this will not until the content is published. + string PublishName { get; } + + /// + /// Gets the identifier of the user who published the content. + /// + int? PublisherId { get; } + + /// + /// Gets the date and time the content was published. + /// + DateTime? PublishDate { get; } + + /// + /// Gets or sets the date and time the content item should be published. /// DateTime? ReleaseDate { get; set; } /// - /// Gets or Sets the date the Content should expire and thus be unpublished + /// Gets or sets the date and time the content should be unpublished. /// DateTime? ExpireDate { get; set; } /// - /// Id of the user who wrote/updated the Content - /// - int WriterId { get; set; } - - /// - /// Gets the ContentType used by this content object + /// Gets the content type of this content. /// IContentType ContentType { get; } /// - /// Gets the current status of the Content + /// Gets the current status of the content. /// ContentStatus Status { get; } + // fixme - these two should move to some kind of service + /// /// Changes the for the current content object /// @@ -63,11 +94,6 @@ namespace Umbraco.Core.Models /// Boolean indicating whether to clear PropertyTypes upon change void ChangeContentType(IContentType contentType, bool clearProperties); - /// - /// Changes the Published state of the content object - /// - void ChangePublishedState(PublishedState state); - /// /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset /// @@ -75,18 +101,63 @@ namespace Umbraco.Core.Models IContent DeepCloneWithResetIdentities(); /// - /// Gets a value indicating whether the content has a published version. + /// Publishes all values. /// - bool HasPublishedVersion { get; } + /// A value indicating whether the values could be published. + /// + /// The document must then be published via the content service. + /// Values are not published if they are not valie. + /// + bool PublishAllValues(); /// - /// Gets the unique identifier of the published version, if any. + /// Publishes values. /// - Guid PublishedVersionGuid { get; } + /// A value indicating whether the values could be published. + /// + /// The document must then be published via the content service. + /// Values are not published if they are not valid. + /// + bool PublishValues(int? languageId = null, string segment = null); /// - /// Gets a value indicating whether the content item is a blueprint. + /// Publishes the culture/any values. /// - bool IsBlueprint { get; } + /// A value indicating whether the values could be published. + /// + /// The document must then be published via the content service. + /// Values are not published if they are not valie. + /// + bool PublishCultureValues(int? languageId = null); + + /// + /// Clears all published values. + /// + void ClearAllPublishedValues(); + + /// + /// Clears published values. + /// + void ClearPublishedValues(int? languageId = null, string segment = null); + + /// + /// Clears the culture/any published values. + /// + void ClearCulturePublishedValues(int? languageId = null); + + /// + /// Copies values from another document. + /// + void CopyAllValues(IContent other); + + /// + /// Copies values from another document. + /// + void CopyValues(IContent other, int? languageId = null, string segment = null); + + /// + /// Copies culture/any values from another document. + /// + void CopyCultureValues(IContent other, int? languageId = null); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 7d2075cb65..76df5b2f6f 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -1,6 +1,5 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.EntityBase; +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -16,9 +15,14 @@ namespace Umbraco.Core.Models int ContentTypeId { get; } /// - /// Gets the Guid Id of the Content's Version + /// Gets the identifier of the writer. /// - Guid Version { get; } + int WriterId { get; set; } + + /// + /// Gets the version identifier. + /// + int VersionId { get; } /// /// List of properties, which make up all the data available for this Content object @@ -39,45 +43,40 @@ namespace Umbraco.Core.Models IEnumerable PropertyTypes { get; } /// - /// Indicates whether the content object has a property with the supplied alias + /// Gets a value indicating whether the content entity has a property with the supplied alias. /// - /// Alias of the PropertyType - /// True if Property with given alias exists, otherwise False + /// Indicates that the content entity has a property with the supplied alias, but + /// not necessarily that the content has a value for that property. Could be missing. bool HasProperty(string propertyTypeAlias); /// /// Gets the value of a Property /// - /// Alias of the PropertyType - /// Value as an - object GetValue(string propertyTypeAlias); + object GetValue(string propertyTypeAlias, int? languageId = null, string segment = null, bool published = false); /// - /// Gets the value of a Property + /// Gets the typed value of a Property /// - /// Type of the value to return - /// Alias of the PropertyType - /// Value as a - TPassType GetValue(string propertyTypeAlias); + TValue GetValue(string propertyTypeAlias, int? languageId = null, string segment = null, bool published = false); /// - /// Sets the value of a Property + /// Sets the (edited) value of a Property /// - /// Alias of the PropertyType - /// Value to set for the Property - void SetValue(string propertyTypeAlias, object value); + void SetValue(string propertyTypeAlias, object value, int? languageId = null, string segment = null); /// - /// Boolean indicating whether the content and its properties are valid + /// Gets a value indicating whether the content and all its properties values are valid. /// - /// True if content is valid otherwise false - bool IsValid(); + Property[] ValidateAll(); /// - /// Changes the Trashed state of the content object + /// Gets a value indicating whether the content and its properties values are valid. /// - /// Boolean indicating whether content is trashed (true) or not trashed (false) - /// - void ChangeTrashedState(bool isTrashed, int parentId = -20); + Property[] Validate(int? languageId = null, string segment = null); + + /// + /// Gets a value indicating whether the content and its culture/any properties values are valid. + /// + Property[] ValidateCulture(int? languageId = null); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IContentType.cs b/src/Umbraco.Core/Models/IContentType.cs index 766a8eec81..66c4aed37a 100644 --- a/src/Umbraco.Core/Models/IContentType.cs +++ b/src/Umbraco.Core/Models/IContentType.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Models { @@ -37,4 +37,4 @@ namespace Umbraco.Core.Models /// IContentType DeepCloneWithResetIdentities(string newAlias); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index 80e62f50cf..72b8df8de9 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -43,6 +43,11 @@ namespace Umbraco.Core.Models /// bool IsContainer { get; set; } + /// + /// Gets or sets the content variation of the content type. + /// + ContentVariation Variations { get; set; } + /// /// Gets or Sets a list of integer Ids of the ContentTypes allowed under the ContentType /// @@ -75,12 +80,6 @@ namespace Umbraco.Core.Models /// Name of the to remove void RemovePropertyGroup(string propertyGroupName); - /// - /// Sets the ParentId from the lazy integer id - /// - /// Id of the Parent - void SetLazyParentId(Lazy id); - /// /// Checks whether a PropertyType with a given alias already exists /// @@ -119,4 +118,4 @@ namespace Umbraco.Core.Models /// bool MovePropertyType(string propertyTypeAlias, string propertyGroupName); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IContentTypeComposition.cs b/src/Umbraco.Core/Models/IContentTypeComposition.cs index ab7e068fdd..3d206e4660 100644 --- a/src/Umbraco.Core/Models/IContentTypeComposition.cs +++ b/src/Umbraco.Core/Models/IContentTypeComposition.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Models { @@ -44,15 +44,15 @@ namespace Umbraco.Core.Models bool ContentTypeCompositionExists(string alias); /// - /// Gets a list of ContentType aliases from the current composition + /// Gets a list of ContentType aliases from the current composition /// /// An enumerable list of string aliases IEnumerable CompositionAliases(); /// - /// Gets a list of ContentType Ids from the current composition + /// Gets a list of ContentType Ids from the current composition /// /// An enumerable list of integer ids IEnumerable CompositionIds(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IDataType.cs b/src/Umbraco.Core/Models/IDataType.cs new file mode 100644 index 0000000000..c7e682e4a2 --- /dev/null +++ b/src/Umbraco.Core/Models/IDataType.cs @@ -0,0 +1,38 @@ +using Umbraco.Core.Models.Entities; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a data type. + /// + public interface IDataType : IUmbracoEntity + { + /// + /// Gets or sets the property editor. + /// + IDataEditor Editor { get; set; } + + /// + /// Gets the property editor alias. + /// + string EditorAlias { get; } + + /// + /// Gets or sets the database type for the data type values. + /// + /// In most cases this is imposed by the property editor, but some editors + /// may support storing different types. + ValueStorageType DatabaseType { get; set; } + + /// + /// Gets or sets the configuration object. + /// + /// + /// The configuration object is serialized to Json and stored into the database. + /// The serialized Json is deserialized by the property editor, which by default should + /// return a Dictionary{string, object} but could return a typed object e.g. MyEditor.Configuration. + /// + object Configuration { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/IDataTypeDefinition.cs b/src/Umbraco.Core/Models/IDataTypeDefinition.cs deleted file mode 100644 index a33736e604..0000000000 --- a/src/Umbraco.Core/Models/IDataTypeDefinition.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Models -{ - public interface IDataTypeDefinition : IUmbracoEntity - { - /// - /// The Property editor alias assigned to the data type - /// - string PropertyEditorAlias { get; set; } - - /// - /// Id of the DataType control - /// - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead")] - Guid ControlId { get; set; } - - /// - /// Gets or Sets the DatabaseType for which the DataType's value is saved as - /// - DataTypeDatabaseType DatabaseType { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IDeepCloneable.cs b/src/Umbraco.Core/Models/IDeepCloneable.cs index 5f41f6d828..057326b3c7 100644 --- a/src/Umbraco.Core/Models/IDeepCloneable.cs +++ b/src/Umbraco.Core/Models/IDeepCloneable.cs @@ -1,9 +1,10 @@ -using System.Reflection; - -namespace Umbraco.Core.Models +namespace Umbraco.Core.Models { + /// + /// Provides a mean to deep-clone an object. + /// public interface IDeepCloneable { object DeepClone(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IDictionaryItem.cs b/src/Umbraco.Core/Models/IDictionaryItem.cs index ed88acfbec..6983a536d3 100644 --- a/src/Umbraco.Core/Models/IDictionaryItem.cs +++ b/src/Umbraco.Core/Models/IDictionaryItem.cs @@ -1,12 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { - public interface IDictionaryItem : IAggregateRoot, IRememberBeingDirty, ICanBeDirty + public interface IDictionaryItem : IEntity, IRememberBeingDirty { /// /// Gets or Sets the Parent Id of the Dictionary Item @@ -26,4 +25,4 @@ namespace Umbraco.Core.Models [DataMember] IEnumerable Translations { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IDictionaryTranslation.cs b/src/Umbraco.Core/Models/IDictionaryTranslation.cs index 25aa1e4395..c66322b75d 100644 --- a/src/Umbraco.Core/Models/IDictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/IDictionaryTranslation.cs @@ -1,5 +1,5 @@ -using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models @@ -20,4 +20,4 @@ namespace Umbraco.Core.Models [DataMember] string Value { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IDomain.cs b/src/Umbraco.Core/Models/IDomain.cs index b9f6bc65ec..55d5bc88c2 100644 --- a/src/Umbraco.Core/Models/IDomain.cs +++ b/src/Umbraco.Core/Models/IDomain.cs @@ -1,8 +1,8 @@ -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { - public interface IDomain : IAggregateRoot, IRememberBeingDirty, ICanBeDirty + public interface IDomain : IEntity, IRememberBeingDirty { int? LanguageId { get; set; } string DomainName { get; set; } @@ -14,4 +14,4 @@ namespace Umbraco.Core.Models /// string LanguageIsoCode { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IFile.cs b/src/Umbraco.Core/Models/IFile.cs index de900c50ec..9b974276c1 100644 --- a/src/Umbraco.Core/Models/IFile.cs +++ b/src/Umbraco.Core/Models/IFile.cs @@ -1,5 +1,5 @@ using System; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -7,7 +7,7 @@ namespace Umbraco.Core.Models /// Defines a File /// /// Used for Scripts, Stylesheets and Templates - public interface IFile : IAggregateRoot + public interface IFile : IEntity { /// /// Gets the Name of the File including extension @@ -47,4 +47,4 @@ namespace Umbraco.Core.Models [Obsolete("This is no longer used and will be removed from the codebase in future versions")] bool IsValid(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ILanguage.cs b/src/Umbraco.Core/Models/ILanguage.cs index acd1de10c3..23cef54180 100644 --- a/src/Umbraco.Core/Models/ILanguage.cs +++ b/src/Umbraco.Core/Models/ILanguage.cs @@ -1,11 +1,10 @@ -using System.Globalization; +using System.Globalization; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { - public interface ILanguage : IAggregateRoot, IRememberBeingDirty, ICanBeDirty + public interface ILanguage : IEntity, IRememberBeingDirty { /// /// Gets or sets the Iso Code for the Language @@ -24,5 +23,15 @@ namespace Umbraco.Core.Models /// [IgnoreDataMember] CultureInfo CultureInfo { get; } + + /// + /// Defines if this language is the default variant language when language variants are in use + /// + bool IsDefaultVariantLanguage { get; set; } + + /// + /// If true, a variant node cannot be published unless this language variant is created + /// + bool Mandatory { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IMacro.cs b/src/Umbraco.Core/Models/IMacro.cs index 4f366ad628..1100212190 100644 --- a/src/Umbraco.Core/Models/IMacro.cs +++ b/src/Umbraco.Core/Models/IMacro.cs @@ -1,13 +1,14 @@ +using System; using System.Collections.Generic; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { /// /// Defines a Macro /// - public interface IMacro : IAggregateRoot, IRememberBeingDirty, ICanBeDirty + public interface IMacro : IEntity, IRememberBeingDirty { /// /// Gets or sets the alias of the Macro @@ -62,6 +63,7 @@ namespace Umbraco.Core.Models /// /// Will usually only be filled if the ScriptFile is a Usercontrol [DataMember] + [Obsolete("This is no longer used, we should remove it in v8, CustomControl macros are gone")] string ControlAssembly { get; set; } /// @@ -90,4 +92,4 @@ namespace Umbraco.Core.Models ///// //MacroTypes MacroType(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IMacroProperty.cs b/src/Umbraco.Core/Models/IMacroProperty.cs index 8726f51317..b3c592fd5d 100644 --- a/src/Umbraco.Core/Models/IMacroProperty.cs +++ b/src/Umbraco.Core/Models/IMacroProperty.cs @@ -1,6 +1,6 @@ -using System; +using System; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -35,8 +35,8 @@ namespace Umbraco.Core.Models /// /// Gets or sets the parameter editor alias - /// + /// [DataMember] string EditorAlias { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IMacroPropertyType.cs b/src/Umbraco.Core/Models/IMacroPropertyType.cs deleted file mode 100644 index 7c4bc0057f..0000000000 --- a/src/Umbraco.Core/Models/IMacroPropertyType.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Umbraco.Core.Models -{ - /// - /// Defines a PropertyType (plugin) for a Macro - /// - internal interface IMacroPropertyType - { - /// - /// Gets the unique Alias of the Property Type - /// - string Alias { get; } - - /// - /// Gets the name of the Assembly used to render the Property Type - /// - string RenderingAssembly { get; } - - /// - /// Gets the name of the Type used to render the Property Type - /// - string RenderingType { get; } - - /// - /// Gets the Base Type for storing the PropertyType (Int32, String, Boolean) - /// - MacroPropertyTypeBaseTypes BaseType { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IMedia.cs b/src/Umbraco.Core/Models/IMedia.cs index 755d99eddb..02aa5db61c 100644 --- a/src/Umbraco.Core/Models/IMedia.cs +++ b/src/Umbraco.Core/Models/IMedia.cs @@ -24,4 +24,4 @@ namespace Umbraco.Core.Models /// Boolean indicating whether to clear PropertyTypes upon change void ChangeContentType(IMediaType contentType, bool clearProperties); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IMediaType.cs b/src/Umbraco.Core/Models/IMediaType.cs index e8f7c16190..28c5e89f19 100644 --- a/src/Umbraco.Core/Models/IMediaType.cs +++ b/src/Umbraco.Core/Models/IMediaType.cs @@ -15,4 +15,4 @@ namespace Umbraco.Core.Models /// IMediaType DeepCloneWithResetIdentities(string newAlias); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IMember.cs b/src/Umbraco.Core/Models/IMember.cs index d5838f30ff..83540b200b 100644 --- a/src/Umbraco.Core/Models/IMember.cs +++ b/src/Umbraco.Core/Models/IMember.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Models { - public interface IMember : IContentBase, IMembershipUser + public interface IMember : IContentBase, IMembershipUser, IHaveAdditionalData { /// /// String alias of the default ContentType @@ -15,4 +17,4 @@ namespace Umbraco.Core.Models /// IMemberType ContentType { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IMemberGroup.cs b/src/Umbraco.Core/Models/IMemberGroup.cs index 89f1c6c1cf..0b1e4a8324 100644 --- a/src/Umbraco.Core/Models/IMemberGroup.cs +++ b/src/Umbraco.Core/Models/IMemberGroup.cs @@ -1,12 +1,12 @@ using System.Collections.Generic; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { /// /// Represents a member type /// - public interface IMemberGroup : IAggregateRoot, IRememberBeingDirty, ICanBeDirty + public interface IMemberGroup : IEntity, IRememberBeingDirty, IHaveAdditionalData { /// /// The name of the member group @@ -17,10 +17,5 @@ namespace Umbraco.Core.Models /// Profile of the user who created this Entity /// int CreatorId { get; set; } - - /// - /// Some entities may expose additional data that other's might not, this custom data will be available in this collection - /// - IDictionary AdditionalData { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IMemberType.cs b/src/Umbraco.Core/Models/IMemberType.cs index 878cc24334..9596d88cca 100644 --- a/src/Umbraco.Core/Models/IMemberType.cs +++ b/src/Umbraco.Core/Models/IMemberType.cs @@ -19,6 +19,13 @@ /// bool MemberCanViewProperty(string propertyTypeAlias); + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + bool IsSensitiveProperty(string propertyTypeAlias); + /// /// Sets a boolean indicating whether a Property is editable by the Member. /// @@ -32,5 +39,12 @@ /// PropertyType Alias of the Property to set /// Boolean value, true or false void SetMemberCanViewProperty(string propertyTypeAlias, bool value); + + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetIsSensitiveProperty(string propertyTypeAlias, bool value); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IMigrationEntry.cs b/src/Umbraco.Core/Models/IMigrationEntry.cs index 65bb7bc1f3..5ab4853542 100644 --- a/src/Umbraco.Core/Models/IMigrationEntry.cs +++ b/src/Umbraco.Core/Models/IMigrationEntry.cs @@ -1,12 +1,12 @@ using System; using Semver; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { - public interface IMigrationEntry : IAggregateRoot, IRememberBeingDirty + public interface IMigrationEntry : IEntity, IRememberBeingDirty { string MigrationName { get; set; } SemVersion Version { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IPartialView.cs b/src/Umbraco.Core/Models/IPartialView.cs index 40a760427a..31099a1aae 100644 --- a/src/Umbraco.Core/Models/IPartialView.cs +++ b/src/Umbraco.Core/Models/IPartialView.cs @@ -4,4 +4,4 @@ { PartialViewType ViewType { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IPublishedContent.cs b/src/Umbraco.Core/Models/IPublishedContent.cs deleted file mode 100644 index 854ddfd47d..0000000000 --- a/src/Umbraco.Core/Models/IPublishedContent.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a cached content. - /// - /// - /// SD: A replacement for INode which needs to occur since INode doesn't contain the document type alias - /// and INode is poorly formatted with mutable properties (i.e. Lists instead of IEnumerable). - /// Stephan: initially, that was for cached published content only. Now, we're using it also for - /// cached preview (so, maybe unpublished) content. A better name would therefore be ICachedContent, as - /// has been suggested. However, can't change now. Maybe in v7? - /// - public interface IPublishedContent - { - #region ContentSet - - // Because of http://issues.umbraco.org/issue/U4-1797 and in order to implement - // Index() and methods that derive from it such as IsFirst(), IsLast(), etc... all - // content items must know about their containing content set. - - /// - /// Gets the content set to which the content belongs. - /// - /// The default set consists in the siblings of the content (including the content - /// itself) ordered by sortOrder. - IEnumerable ContentSet { get; } - - #endregion - - #region ContentType - - /// - /// Gets the content type. - /// - PublishedContentType ContentType { get; } - - #endregion - - #region Content - - int Id { get; } - int TemplateId { get; } - int SortOrder { get; } - string Name { get; } - string UrlName { get; } - string DocumentTypeAlias { get; } - int DocumentTypeId { get; } - string WriterName { get; } - string CreatorName { get; } - int WriterId { get; } - int CreatorId { get; } - string Path { get; } - DateTime CreateDate { get; } - DateTime UpdateDate { get; } - Guid Version { get; } - int Level { get; } - string Url { get; } - - /// - /// Gets a value indicating whether the content is a content (aka a document) or a media. - /// - PublishedItemType ItemType { get; } - - /// - /// Gets a value indicating whether the content is draft. - /// - /// A content is draft when it is the unpublished version of a content, which may - /// have a published version, or not. - bool IsDraft { get; } - - /// - /// Gets the index of the published content within its current owning content set. - /// - /// The index of the published content within its current owning content set. - int GetIndex(); - - #endregion - - #region Tree - - /// - /// Gets the parent of the content. - /// - /// The parent of root content is null. - IPublishedContent Parent { get; } - - /// - /// Gets the children of the content. - /// - /// Children are sorted by their sortOrder. - IEnumerable Children { get; } - - #endregion - - #region Properties - - /// - /// Gets the properties of the content. - /// - /// - /// Contains one IPublishedProperty for each property defined for the content type, including - /// inherited properties. Some properties may have no value. - /// The properties collection of an IPublishedContent instance should be read-only ie it is illegal - /// to add properties to the collection. - /// - ICollection Properties { get; } - - /// - /// Gets a property identified by its alias. - /// - /// The property alias. - /// The property identified by the alias. - /// - /// If the content type has no property with that alias, including inherited properties, returns null, - /// otherwise return a property -- that may have no value (ie HasValue is false). - /// The alias is case-insensitive. - /// - IPublishedProperty GetProperty(string alias); - - /// - /// Gets a property identified by its alias. - /// - /// The property alias. - /// A value indicating whether to navigate the tree upwards until a property with a value is found. - /// The property identified by the alias. - /// - /// Navigate the tree upwards and look for a property with that alias and with a value (ie HasValue is true). - /// If found, return the property. If no property with that alias is found, having a value or not, return null. Otherwise - /// return the first property that was found with the alias but had no value (ie HasValue is false). - /// The alias is case-insensitive. - /// - IPublishedProperty GetProperty(string alias, bool recurse); - - /// - /// Gets the value of a property identified by its alias. - /// - /// The property alias. - /// The value of the property identified by the alias. - /// - /// If GetProperty(alias) is null then returns null else return GetProperty(alias).Value. - /// So if the property has no value, returns the default value for that property type. - /// This one is defined here really because we cannot define index extension methods, but all it should do is: - /// var p = GetProperty(alias); return p == null ? null : p.Value; and nothing else. - /// The recursive syntax (eg "_title") is _not_ supported here. - /// The alias is case-insensitive. - /// - object this[string alias] { get; } // todo - should obsolete this[alias] (when?) - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IPublishedContentWithKey.cs b/src/Umbraco.Core/Models/IPublishedContentWithKey.cs deleted file mode 100644 index b0e71221b2..0000000000 --- a/src/Umbraco.Core/Models/IPublishedContentWithKey.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a cached content with a GUID key. - /// - /// This is temporary, because we cannot add the Key property to IPublishedContent without - /// breaking backward compatibility. With v8, it will be merged into IPublishedContent. - public interface IPublishedContentWithKey : IPublishedContent - { - Guid Key { get; } - } -} diff --git a/src/Umbraco.Core/Models/IPublishedProperty.cs b/src/Umbraco.Core/Models/IPublishedProperty.cs deleted file mode 100644 index a50163bc5c..0000000000 --- a/src/Umbraco.Core/Models/IPublishedProperty.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Umbraco.Core.Models -{ - /// - /// Represents a property of an IPublishedContent. - /// - public interface IPublishedProperty - { - /// - /// Gets the alias of the property. - /// - string PropertyTypeAlias { get; } - - /// - /// Gets a value indicating whether the property has a value. - /// - /// - /// This is somewhat implementation-dependent -- depending on whatever IPublishedCache considers - /// a missing value. - /// The XmlPublishedCache raw values are strings, and it will consider missing, null or empty (and - /// that includes whitespace-only) strings as "no value". - /// Other caches that get their raw value from the database would consider that a property has "no - /// value" if it is missing, null, or an empty string (including whitespace-only). - /// - bool HasValue { get; } - - /// - /// Gets the data value of the property. - /// - /// - /// The data value is whatever was passed to the property when it was instanciated, and it is - /// somewhat implementation-dependent -- depending on how the IPublishedCache is implemented. - /// The XmlPublishedCache raw values are strings exclusively since they come from the Xml cache. - /// For other caches that get their raw value from the database, it would be either a string, - /// an integer (Int32), or a date and time (DateTime). - /// If you're using that value, you're probably wrong, unless you're doing some internal - /// Umbraco stuff. - /// - object DataValue { get; } - - /// - /// Gets the object value of the property. - /// - /// - /// The value is what you want to use when rendering content in an MVC view ie in C#. - /// It can be null, or any type of CLR object. - /// It has been fully prepared and processed by the appropriate converter. - /// - object Value { get; } - - /// - /// Gets the XPath value of the property. - /// - /// - /// The XPath value is what you want to use when navigating content via XPath eg in the XSLT engine. - /// It must be either null, or a string, or an XPathNavigator. - /// It has been fully prepared and processed by the appropriate converter. - /// - object XPathValue { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IRedirectUrl.cs b/src/Umbraco.Core/Models/IRedirectUrl.cs index 419389f60d..f3c65fe89c 100644 --- a/src/Umbraco.Core/Models/IRedirectUrl.cs +++ b/src/Umbraco.Core/Models/IRedirectUrl.cs @@ -1,13 +1,13 @@ using System; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { /// /// Represents a redirect url. /// - public interface IRedirectUrl : IAggregateRoot, IRememberBeingDirty + public interface IRedirectUrl : IEntity, IRememberBeingDirty { /// /// Gets or sets the identifier of the content item. diff --git a/src/Umbraco.Core/Models/IRelation.cs b/src/Umbraco.Core/Models/IRelation.cs index ac37d4293a..bb8d218d9c 100644 --- a/src/Umbraco.Core/Models/IRelation.cs +++ b/src/Umbraco.Core/Models/IRelation.cs @@ -1,9 +1,9 @@ using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { - public interface IRelation : IAggregateRoot, IRememberBeingDirty, ICanBeDirty + public interface IRelation : IEntity, IRememberBeingDirty { /// /// Gets or sets the Parent Id of the Relation (Source) @@ -35,4 +35,4 @@ namespace Umbraco.Core.Models [IgnoreDataMember] int RelationTypeId { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IRelationType.cs b/src/Umbraco.Core/Models/IRelationType.cs index 265e7723e5..c52075efac 100644 --- a/src/Umbraco.Core/Models/IRelationType.cs +++ b/src/Umbraco.Core/Models/IRelationType.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { - public interface IRelationType : IAggregateRoot, IRememberBeingDirty, ICanBeDirty + public interface IRelationType : IEntity, IRememberBeingDirty { /// /// Gets or sets the Name of the RelationType @@ -38,4 +38,4 @@ namespace Umbraco.Core.Models [DataMember] Guid ChildObjectType { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IServerRegistration.cs b/src/Umbraco.Core/Models/IServerRegistration.cs index 8053ea0a6e..70d3964fc5 100644 --- a/src/Umbraco.Core/Models/IServerRegistration.cs +++ b/src/Umbraco.Core/Models/IServerRegistration.cs @@ -1,10 +1,10 @@ -using System; -using Umbraco.Core.Models.EntityBase; +using System; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Sync; namespace Umbraco.Core.Models { - public interface IServerRegistration : IServerAddress, IAggregateRoot, IRememberBeingDirty + public interface IServerRegistration : IServerAddress, IEntity, IRememberBeingDirty { /// /// Gets or sets the server unique identity. @@ -33,4 +33,4 @@ namespace Umbraco.Core.Models /// DateTime Accessed { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ITag.cs b/src/Umbraco.Core/Models/ITag.cs index d132e0fbd7..cdd71c6553 100644 --- a/src/Umbraco.Core/Models/ITag.cs +++ b/src/Umbraco.Core/Models/ITag.cs @@ -1,22 +1,29 @@ -using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { /// - /// Represents a Tag, which is composed of a Text, Group and NodeCount property. + /// Represents a tag entity. /// - public interface ITag : IAggregateRoot, IRememberBeingDirty, ICanBeDirty + public interface ITag : IEntity, IRememberBeingDirty { - [DataMember] - string Text { get; set; } - + /// + /// Gets or sets the tag group. + /// [DataMember] string Group { get; set; } - int NodeCount { get; } + /// + /// Gets or sets the tag text. + /// + [DataMember] + string Text { get; set; } - //TODO: enable this at some stage - //int ParentId { get; set; } + /// + /// Gets the number of nodes tagged with this tag. + /// + /// Only when returning from queries. + int NodeCount { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ITemplate.cs b/src/Umbraco.Core/Models/ITemplate.cs index 7a8d0c8bd0..8e3571a83c 100644 --- a/src/Umbraco.Core/Models/ITemplate.cs +++ b/src/Umbraco.Core/Models/ITemplate.cs @@ -1,6 +1,6 @@ using System; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -29,17 +29,10 @@ namespace Umbraco.Core.Models /// string MasterTemplateAlias { get; } - /// - /// Returns the that corresponds to the template file - /// - /// - [Obsolete("This is no longer used and will be removed from the codebase in future versions, use the IFileSystem DetermineRenderingEngine method instead")] - RenderingEngine GetTypeOfRenderingEngine(); - /// /// Set the mastertemplate /// /// void SetMasterTemplate(ITemplate masterTemplate); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/IUserControl.cs b/src/Umbraco.Core/Models/IUserControl.cs deleted file mode 100644 index 2660567258..0000000000 --- a/src/Umbraco.Core/Models/IUserControl.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Models -{ - public interface IUserControl : IFile - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IXsltFile.cs b/src/Umbraco.Core/Models/IXsltFile.cs index f6d4418f88..028b5581c9 100644 --- a/src/Umbraco.Core/Models/IXsltFile.cs +++ b/src/Umbraco.Core/Models/IXsltFile.cs @@ -2,6 +2,6 @@ { public interface IXsltFile : IFile { - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 1a8cdc446f..75ed8238d0 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -6,8 +6,8 @@ using System.Linq; using System.Reflection; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNet.Identity; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; @@ -15,6 +15,25 @@ namespace Umbraco.Core.Models.Identity { public class BackOfficeIdentityUser : IdentityUser, IdentityUserClaim>, IRememberBeingDirty { + private static readonly Lazy Ps = new Lazy(); + + private string _email; + private string _userName; + private int _id; + private bool _hasIdentity; + private DateTime? _lastLoginDateUtc; + private bool _emailConfirmed; + private string _name; + private int _accessFailedCount; + private string _passwordHash; + private string _culture; + private ObservableCollection _logins; + private Lazy> _getLogins; + private IReadOnlyUserGroup[] _groups; + private string[] _allowedSections; + private int[] _startMediaIds; + private int[] _startContentIds; + /// /// Used to construct a new instance without an identity /// @@ -24,8 +43,8 @@ namespace Umbraco.Core.Models.Identity /// public static BackOfficeIdentityUser CreateNew(string username, string email, string culture) { - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", "culture"); + 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(); user.DisableChangeTracking(); @@ -46,7 +65,7 @@ namespace Umbraco.Core.Models.Identity _startContentIds = new int[] { }; _groups = new IReadOnlyUserGroup[] { }; _allowedSections = new string[] { }; - _culture = Configuration.GlobalSettings.DefaultUILanguage; + _culture = UmbracoConfig.For.GlobalSettings().DefaultUILanguage; //fixme inject somehow? _groups = new IReadOnlyUserGroup[0]; _roles = new ObservableCollection>(); _roles.CollectionChanged += _roles_CollectionChanged; @@ -63,7 +82,7 @@ namespace Umbraco.Core.Models.Identity _startContentIds = new int[] { }; _groups = new IReadOnlyUserGroup[] { }; _allowedSections = new string[] { }; - _culture = Configuration.GlobalSettings.DefaultUILanguage; + _culture = UmbracoConfig.For.GlobalSettings().DefaultUILanguage; //fixme inject somehow? _groups = groups.ToArray(); _roles = new ObservableCollection>(_groups.Select(x => new IdentityUserRole { @@ -71,7 +90,7 @@ namespace Umbraco.Core.Models.Identity UserId = userId.ToString() })); _roles.CollectionChanged += _roles_CollectionChanged; - } + } public virtual async Task GenerateUserIdentityAsync(BackOfficeUserManager manager) { @@ -84,17 +103,14 @@ namespace Umbraco.Core.Models.Identity /// /// Returns true if an Id has been set on this object this will be false if the object is new and not peristed to the database /// - public bool HasIdentity - { - get { return _hasIdentity; } - } + public bool HasIdentity => _hasIdentity; public int[] CalculatedMediaStartNodeIds { get; internal set; } public int[] CalculatedContentStartNodeIds { get; internal set; } public override int Id { - get { return _id; } + get => _id; set { _id = value; @@ -107,8 +123,8 @@ namespace Umbraco.Core.Models.Identity /// public override string Email { - get { return _email; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } + get => _email; + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); } /// @@ -116,8 +132,8 @@ namespace Umbraco.Core.Models.Identity /// public override string UserName { - get { return _userName; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _userName, Ps.Value.UserNameSelector); } + get => _userName; + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _userName, Ps.Value.UserNameSelector); } /// @@ -125,8 +141,8 @@ namespace Umbraco.Core.Models.Identity /// public override DateTime? LastLoginDateUtc { - get { return _lastLoginDateUtc; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, Ps.Value.LastLoginDateUtcSelector); } + get => _lastLoginDateUtc; + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _lastLoginDateUtc, Ps.Value.LastLoginDateUtcSelector); } /// @@ -134,8 +150,8 @@ namespace Umbraco.Core.Models.Identity /// public override bool EmailConfirmed { - get { return _emailConfirmed; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, Ps.Value.EmailConfirmedSelector); } + get => _emailConfirmed; + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _emailConfirmed, Ps.Value.EmailConfirmedSelector); } /// @@ -143,8 +159,8 @@ namespace Umbraco.Core.Models.Identity /// public string Name { - get { return _name; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + get => _name; + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } /// @@ -152,8 +168,8 @@ namespace Umbraco.Core.Models.Identity /// public override int AccessFailedCount { - get { return _accessFailedCount; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, Ps.Value.AccessFailedCountSelector); } + get => _accessFailedCount; + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _accessFailedCount, Ps.Value.AccessFailedCountSelector); } /// @@ -161,8 +177,8 @@ namespace Umbraco.Core.Models.Identity /// public override string PasswordHash { - get { return _passwordHash; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _passwordHash, Ps.Value.PasswordHashSelector); } + get => _passwordHash; + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordHash, Ps.Value.PasswordHashSelector); } @@ -171,11 +187,11 @@ namespace Umbraco.Core.Models.Identity /// public int[] StartContentIds { - get { return _startContentIds; } + get => _startContentIds; set { if (value == null) value = new int[0]; - _tracker.SetPropertyValueAndDetectChanges(value, ref _startContentIds, Ps.Value.StartContentIdsSelector, Ps.Value.StartIdsComparer); + _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startContentIds, Ps.Value.StartContentIdsSelector, Ps.Value.StartIdsComparer); } } @@ -184,17 +200,17 @@ namespace Umbraco.Core.Models.Identity /// public int[] StartMediaIds { - get { return _startMediaIds; } + get => _startMediaIds; set { if (value == null) value = new int[0]; - _tracker.SetPropertyValueAndDetectChanges(value, ref _startMediaIds, Ps.Value.StartMediaIdsSelector, Ps.Value.StartIdsComparer); + _beingDirty.SetPropertyValueAndDetectChanges(value, ref _startMediaIds, Ps.Value.StartMediaIdsSelector, Ps.Value.StartIdsComparer); } } /// /// This is a readonly list of the user's allowed sections which are based on it's user groups - /// + /// public string[] AllowedSections { get { return _allowedSections ?? (_allowedSections = _groups.SelectMany(x => x.AllowedSections).Distinct().ToArray()); } @@ -202,13 +218,13 @@ namespace Umbraco.Core.Models.Identity public string Culture { - get { return _culture; } - set { _tracker.SetPropertyValueAndDetectChanges(value, ref _culture, Ps.Value.CultureSelector); } + get => _culture; + set => _beingDirty.SetPropertyValueAndDetectChanges(value, ref _culture, Ps.Value.CultureSelector); } public IReadOnlyUserGroup[] Groups { - get { return _groups; } + get => _groups; set { //so they recalculate @@ -227,7 +243,7 @@ namespace Umbraco.Core.Models.Identity } _roles.CollectionChanged += _roles_CollectionChanged; - _tracker.SetPropertyValueAndDetectChanges(value, ref _groups, Ps.Value.GroupsSelector, Ps.Value.GroupsComparer); + _beingDirty.SetPropertyValueAndDetectChanges(value, ref _groups, Ps.Value.GroupsSelector, Ps.Value.GroupsComparer); } } @@ -239,7 +255,7 @@ namespace Umbraco.Core.Models.Identity get { return true; } set { - //do nothing + //do nothing } } @@ -250,7 +266,7 @@ namespace Umbraco.Core.Models.Identity { get { - var isLocked = (LockoutEndDateUtc.HasValue && LockoutEndDateUtc.Value.ToLocalTime() >= DateTime.Now); + var isLocked = LockoutEndDateUtc.HasValue && LockoutEndDateUtc.Value.ToLocalTime() >= DateTime.Now; return isLocked; } } @@ -280,15 +296,15 @@ namespace Umbraco.Core.Models.Identity return _logins; } } - + void Logins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - _tracker.OnPropertyChanged(Ps.Value.LoginsSelector); + _beingDirty.OnPropertyChanged(Ps.Value.LoginsSelector); } private void _roles_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - _tracker.OnPropertyChanged(Ps.Value.RolesSelector); + _beingDirty.OnPropertyChanged(Ps.Value.RolesSelector); } private readonly ObservableCollection> _roles; @@ -304,7 +320,7 @@ namespace Umbraco.Core.Models.Identity { Roles.Add(new IdentityUserRole { - UserId = this.Id.ToString(), + UserId = Id.ToString(), RoleId = role }); } @@ -312,10 +328,7 @@ namespace Umbraco.Core.Models.Identity /// /// Override Roles because the value of these are the user's group aliases /// - public override ICollection> Roles - { - get { return _roles; } - } + public override ICollection> Roles => _roles; /// /// Used to set a lazy call back to populate the user's Login list @@ -323,70 +336,84 @@ namespace Umbraco.Core.Models.Identity /// public void SetLoginsCallback(Lazy> callback) { - if (callback == null) throw new ArgumentNullException("callback"); - _getLogins = callback; - } - - #region Change tracking - - public void DisableChangeTracking() - { - _tracker.DisableChangeTracking(); + _getLogins = callback ?? throw new ArgumentNullException(nameof(callback)); } - public void EnableChangeTracking() - { - _tracker.EnableChangeTracking(); - } + #region BeingDirty - /// - /// Since this class only has change tracking turned on for Email/Username this will return true if either of those have changed - /// - /// + private readonly BeingDirty _beingDirty = new BeingDirty(); + + /// public bool IsDirty() { - return _tracker.IsDirty(); + return _beingDirty.IsDirty(); } - /// - /// Returns true if the specified property is dirty - /// - /// - /// + /// public bool IsPropertyDirty(string propName) { - return _tracker.IsPropertyDirty(propName); + return _beingDirty.IsPropertyDirty(propName); + } + + /// + public IEnumerable GetDirtyProperties() + { + return _beingDirty.GetDirtyProperties(); + } + + /// + public void ResetDirtyProperties() + { + _beingDirty.ResetDirtyProperties(); + } + + /// + public bool WasDirty() + { + return _beingDirty.WasDirty(); + } + + /// + public bool WasPropertyDirty(string propertyName) + { + return _beingDirty.WasPropertyDirty(propertyName); + } + + /// + public void ResetWereDirtyProperties() + { + _beingDirty.ResetWereDirtyProperties(); + } + + /// + public void ResetDirtyProperties(bool rememberDirty) + { + _beingDirty.ResetDirtyProperties(rememberDirty); + } + + /// + public IEnumerable GetWereDirtyProperties() + => _beingDirty.GetWereDirtyProperties(); + + /// + /// Disables change tracking. + /// + public void DisableChangeTracking() + { + _beingDirty.DisableChangeTracking(); } /// - /// Resets dirty properties + /// Enables change tracking. /// - void ICanBeDirty.ResetDirtyProperties() + public void EnableChangeTracking() { - _tracker.ResetDirtyProperties(); + _beingDirty.EnableChangeTracking(); } - bool IRememberBeingDirty.WasDirty() - { - return _tracker.WasDirty(); - } + #endregion - bool IRememberBeingDirty.WasPropertyDirty(string propertyName) - { - return _tracker.WasPropertyDirty(propertyName); - } - - void IRememberBeingDirty.ForgetPreviouslyDirtyProperties() - { - _tracker.ForgetPreviouslyDirtyProperties(); - } - - public void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) - { - _tracker.ResetDirtyProperties(rememberPreviouslyChangedProperties); - } - - private static readonly Lazy Ps = new Lazy(); + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo(x => x.Email); @@ -412,41 +439,5 @@ namespace Umbraco.Core.Models.Identity groups => groups.GetHashCode()); } - - private readonly ChangeTracker _tracker = new ChangeTracker(); - private string _email; - private string _userName; - private int _id; - private bool _hasIdentity = false; - private DateTime? _lastLoginDateUtc; - private bool _emailConfirmed; - private string _name; - private int _accessFailedCount; - private string _passwordHash; - private string _culture; - private ObservableCollection _logins; - private Lazy> _getLogins; - private IReadOnlyUserGroup[] _groups; - private string[] _allowedSections; - private int[] _startMediaIds; - private int[] _startContentIds; - - /// - /// internal class used to track changes for properties that have it enabled - /// - private class ChangeTracker : TracksChangesEntityBase - { - /// - /// Make this public so that it's usable - /// - /// - public new void OnPropertyChanged(PropertyInfo propertyInfo) - { - base.OnPropertyChanged(propertyInfo); - } - } - #endregion - - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs index c95722f4e3..198f87cf21 100644 --- a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs @@ -1,25 +1,25 @@ -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Identity { - public interface IIdentityUserLogin : IAggregateRoot, IRememberBeingDirty, ICanBeDirty + public interface IIdentityUserLogin : IEntity, IRememberBeingDirty { /// /// The login provider for the login (i.e. facebook, google) - /// + /// /// string LoginProvider { get; set; } /// /// Key representing the login for the provider - /// + /// /// string ProviderKey { get; set; } /// /// User Id for the user who owns this login - /// + /// /// int UserId { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs deleted file mode 100644 index 954705d544..0000000000 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Linq; -using AutoMapper; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Security; - -namespace Umbraco.Core.Models.Identity -{ - public class IdentityModelMappings : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - config.CreateMap() - .BeforeMap((user, identityUser) => - { - identityUser.DisableChangeTracking(); - }) - .ConstructUsing(user => new BackOfficeIdentityUser(user.Id, user.Groups)) - .ForMember(user => user.LastLoginDateUtc, expression => expression.MapFrom(user => user.LastLoginDate.ToUniversalTime())) - .ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email)) - .ForMember(user => user.EmailConfirmed, expression => expression.MapFrom(user => user.EmailConfirmedDate.HasValue)) - .ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id)) - .ForMember(user => user.LockoutEndDateUtc, expression => expression.MapFrom(user => user.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null)) - .ForMember(user => user.IsApproved, expression => expression.MapFrom(user => user.IsApproved)) - .ForMember(user => user.UserName, expression => expression.MapFrom(user => user.Username)) - .ForMember(user => user.PasswordHash, expression => expression.MapFrom(user => GetPasswordHash(user.RawPasswordValue))) - .ForMember(user => user.Culture, expression => expression.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) - .ForMember(user => user.Name, expression => expression.MapFrom(user => user.Name)) - .ForMember(user => user.StartMediaIds, expression => expression.MapFrom(user => user.StartMediaIds)) - .ForMember(user => user.StartContentIds, expression => expression.MapFrom(user => user.StartContentIds)) - .ForMember(user => user.AccessFailedCount, expression => expression.MapFrom(user => user.FailedPasswordAttempts)) - .ForMember(user => user.CalculatedContentStartNodeIds, expression => expression.MapFrom(user => user.CalculateContentStartNodeIds(applicationContext.Services.EntityService))) - .ForMember(user => user.CalculatedMediaStartNodeIds, expression => expression.MapFrom(user => user.CalculateMediaStartNodeIds(applicationContext.Services.EntityService))) - .ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray())) - .AfterMap((user, identityUser) => - { - identityUser.ResetDirtyProperties(true); - identityUser.EnableChangeTracking(); - }); - - } - - private string GetPasswordHash(string storedPass) - { - return storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Identity/IdentityProfile.cs b/src/Umbraco.Core/Models/Identity/IdentityProfile.cs new file mode 100644 index 0000000000..f44003b62a --- /dev/null +++ b/src/Umbraco.Core/Models/Identity/IdentityProfile.cs @@ -0,0 +1,57 @@ +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 IdentityProfile : Profile + { + public IdentityProfile(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(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/Identity/IdentityUser.cs b/src/Umbraco.Core/Models/Identity/IdentityUser.cs index c867dcf622..69b9e26385 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUser.cs @@ -21,13 +21,13 @@ namespace Umbraco.Core.Models.Identity /// /// Constructor - /// + /// /// public IdentityUser() - { - this.Claims = new List(); - this.Roles = new List(); - this.Logins = new List(); + { + Claims = new List(); + Roles = new List(); + Logins = new List(); } /// @@ -37,94 +37,77 @@ namespace Umbraco.Core.Models.Identity /// /// Email - /// /// public virtual string Email { get; set; } /// /// True if the email is confirmed, default is false - /// /// public virtual bool EmailConfirmed { get; set; } /// /// The salted/hashed form of the user password - /// /// public virtual string PasswordHash { get; set; } /// /// A random value that should change whenever a users credentials have changed (password changed, login removed) - /// /// public virtual string SecurityStamp { get; set; } /// /// PhoneNumber for the user - /// /// public virtual string PhoneNumber { get; set; } /// /// True if the phone number is confirmed, default is false - /// /// public virtual bool PhoneNumberConfirmed { get; set; } /// /// Is two factor enabled for the user - /// /// public virtual bool TwoFactorEnabled { get; set; } /// /// DateTime in UTC when lockout ends, any time in the past is considered not locked out. - /// /// public virtual DateTime? LockoutEndDateUtc { get; set; } /// /// Is lockout enabled for this user - /// /// public virtual bool LockoutEnabled { get; set; } /// /// Used to record failures for the purposes of lockout - /// /// public virtual int AccessFailedCount { get; set; } /// /// Navigation property for user roles - /// /// - public virtual ICollection Roles { get; private set; } + public virtual ICollection Roles { get; } /// /// Navigation property for user claims - /// /// - public virtual ICollection Claims { get; private set; } + public virtual ICollection Claims { get; } /// /// Navigation property for user logins - /// /// - public virtual ICollection Logins { get; private set; } + public virtual ICollection Logins { get; } /// /// User ID (Primary Key) - /// /// public virtual TKey Id { get; set; } /// /// User name - /// /// public virtual string UserName { get; set; } - - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs b/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs index 832438f3c3..e117d2fd13 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs @@ -2,7 +2,7 @@ { /// /// EntityType that represents one specific user claim - /// + /// /// /// /// @@ -13,26 +13,26 @@ { /// /// Primary key - /// + /// /// public virtual int Id { get; set; } /// /// User Id for the user who owns this login - /// + /// /// public virtual TKey UserId { get; set; } /// /// Claim type - /// + /// /// public virtual string ClaimType { get; set; } /// /// Claim value - /// + /// /// public virtual string ClaimValue { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs index efed4ce37c..28a9fe2f15 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs @@ -1,13 +1,13 @@ -using System; -using Umbraco.Core.Models.EntityBase; +using System; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Identity { /// /// Entity type for a user's login (i.e. facebook, google) - /// + /// /// - public class IdentityUserLogin : Entity, IIdentityUserLogin + public class IdentityUserLogin : EntityBase, IIdentityUserLogin { public IdentityUserLogin(string loginProvider, string providerKey, int userId) { @@ -27,20 +27,20 @@ namespace Umbraco.Core.Models.Identity /// /// The login provider for the login (i.e. facebook, google) - /// + /// /// public string LoginProvider { get; set; } /// /// Key representing the login for the provider - /// + /// /// public string ProviderKey { get; set; } /// /// User Id for the user who owns this login - /// + /// /// public int UserId { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs b/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs index 5c68a97d86..ba9e87e46c 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUserRole.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Core.Models.Identity +namespace Umbraco.Core.Models.Identity { /// /// EntityType that represents a user belonging to a role - /// + /// /// /// /// @@ -13,14 +13,14 @@ namespace Umbraco.Core.Models.Identity { /// /// UserId for the user that is in the role - /// + /// /// public virtual TKey UserId { get; set; } /// /// RoleId for the role - /// + /// /// public virtual TKey RoleId { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index d915bee85b..ee50661639 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -2,31 +2,36 @@ using System.Globalization; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { /// - /// Represents a Language + /// Represents a Language. /// [Serializable] [DataContract(IsReference = true)] - public class Language : Entity, ILanguage + public class Language : EntityBase, ILanguage { + private static readonly Lazy Ps = new Lazy(); + private string _isoCode; private string _cultureName; + private bool _isDefaultVariantLanguage; + private bool _mandatory; public Language(string isoCode) { IsoCode = isoCode; } - private static readonly Lazy Ps = new Lazy(); - + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo(x => x.IsoCode); public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo(x => x.CultureName); + public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDefaultVariantLanguage); + public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); } /// @@ -35,8 +40,8 @@ namespace Umbraco.Core.Models [DataMember] public string IsoCode { - get { return _isoCode; } - set { SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector); } + get => _isoCode; + set => SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector); } /// @@ -45,17 +50,26 @@ namespace Umbraco.Core.Models [DataMember] public string CultureName { - get { return _cultureName; } - set { SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector); } + get => _cultureName ?? CultureInfo.GetCultureInfo(IsoCode).DisplayName; + set => SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector); } /// /// Returns a object for the current Language /// [IgnoreDataMember] - public CultureInfo CultureInfo + public CultureInfo CultureInfo => CultureInfo.GetCultureInfo(IsoCode); + + public bool IsDefaultVariantLanguage { - get { return CultureInfo.GetCultureInfo(IsoCode); } + get => _isDefaultVariantLanguage; + set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, Ps.Value.IsDefaultVariantLanguageSelector); + } + + public bool Mandatory + { + get => _mandatory; + set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index 58ee94bdc9..bd05abb1c1 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -5,7 +5,7 @@ using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Strings; namespace Umbraco.Core.Models @@ -15,7 +15,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class Macro : Entity, IMacro + public class Macro : EntityBase, IMacro { public Macro() { @@ -173,15 +173,15 @@ namespace Umbraco.Core.Models { OnPropertyChanged(Ps.Value.PropertiesSelector); } - - public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + + public override void ResetDirtyProperties(bool rememberDirty) { _addedProperties.Clear(); _removedProperties.Clear(); - base.ResetDirtyProperties(rememberPreviouslyChangedProperties); + base.ResetDirtyProperties(rememberDirty); foreach (var prop in Properties) { - ((TracksChangesEntityBase)prop).ResetDirtyProperties(rememberPreviouslyChangedProperties); + ((BeingDirtyBase)prop).ResetDirtyProperties(rememberDirty); } } @@ -320,7 +320,7 @@ namespace Umbraco.Core.Models [DataMember] public MacroPropertyCollection Properties { - get { return _properties; } + get { return _properties; } } public override object DeepClone() @@ -341,4 +341,4 @@ namespace Umbraco.Core.Models return clone; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MacroProperty.cs b/src/Umbraco.Core/Models/MacroProperty.cs index d3d82d10ad..2235efea95 100644 --- a/src/Umbraco.Core/Models/MacroProperty.cs +++ b/src/Umbraco.Core/Models/MacroProperty.cs @@ -1,7 +1,7 @@ -using System; +using System; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Models @@ -11,7 +11,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class MacroProperty : TracksChangesEntityBase, IMacroProperty, IRememberBeingDirty, IDeepCloneable + public class MacroProperty : BeingDirtyBase, IMacroProperty, IRememberBeingDirty, IDeepCloneable { public MacroProperty() { @@ -31,14 +31,6 @@ namespace Umbraco.Core.Models _name = name; _sortOrder = sortOrder; _key = Guid.NewGuid(); - - //try to get the new mapped parameter editor - var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(editorAlias, false); - if (mapped.IsNullOrWhiteSpace() == false) - { - editorAlias = mapped; - } - _editorAlias = editorAlias; } @@ -58,14 +50,6 @@ namespace Umbraco.Core.Models _name = name; _sortOrder = sortOrder; _key = key; - - //try to get the new mapped parameter editor - var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(editorAlias, false); - if (mapped.IsNullOrWhiteSpace() == false) - { - editorAlias = mapped; - } - _editorAlias = editorAlias; } @@ -84,7 +68,7 @@ namespace Umbraco.Core.Models public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); + public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); public readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.EditorAlias); } @@ -151,10 +135,7 @@ namespace Umbraco.Core.Models get { return _editorAlias; } set { - //try to get the new mapped parameter editor - var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(value, false); - var newVal = mapped.IsNullOrWhiteSpace() == false ? mapped : value; - SetPropertyValueAndDetectChanges(newVal, ref _editorAlias, Ps.Value.PropertyTypeSelector); + SetPropertyValueAndDetectChanges(value, ref _editorAlias, Ps.Value.PropertyTypeSelector); } } @@ -190,4 +171,4 @@ namespace Umbraco.Core.Models } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MacroPropertyCollection.cs b/src/Umbraco.Core/Models/MacroPropertyCollection.cs index de23d60e7c..bf5b29be9a 100644 --- a/src/Umbraco.Core/Models/MacroPropertyCollection.cs +++ b/src/Umbraco.Core/Models/MacroPropertyCollection.cs @@ -2,6 +2,7 @@ using System.Collections.ObjectModel; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Collections; namespace Umbraco.Core.Models { @@ -10,7 +11,7 @@ namespace Umbraco.Core.Models /// public class MacroPropertyCollection : ObservableDictionary, IDeepCloneable { - public MacroPropertyCollection() + public MacroPropertyCollection() : base(property => property.Alias) { } @@ -34,7 +35,7 @@ namespace Umbraco.Core.Models /// /// The existing property alias /// - /// + /// public void UpdateProperty(string currentAlias, string name = null, int? sortOrder = null, string editorAlias = null, string newAlias = null) { var prop = this[currentAlias]; @@ -45,7 +46,7 @@ namespace Umbraco.Core.Models if (name.IsNullOrWhiteSpace() == false) { - prop.Name = name; + prop.Name = name; } if (sortOrder.HasValue) { @@ -64,4 +65,4 @@ namespace Umbraco.Core.Models } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MacroPropertyTypeBaseTypes.cs b/src/Umbraco.Core/Models/MacroPropertyTypeBaseTypes.cs deleted file mode 100644 index f997e90b57..0000000000 --- a/src/Umbraco.Core/Models/MacroPropertyTypeBaseTypes.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models -{ - /// - /// Enum for the three allowed BaseTypes - /// - [Serializable] - [DataContract(IsReference = true)] - internal enum MacroPropertyTypeBaseTypes - { - [EnumMember] - Int32, - [EnumMember] - Boolean, - [EnumMember] - String - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/MacroTypes.cs b/src/Umbraco.Core/Models/MacroTypes.cs index caf9586362..90eacf5171 100644 --- a/src/Umbraco.Core/Models/MacroTypes.cs +++ b/src/Umbraco.Core/Models/MacroTypes.cs @@ -8,21 +8,17 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - internal enum MacroTypes + public enum MacroTypes { [EnumMember] Xslt = 1, [EnumMember] - CustomControl = 2, - [EnumMember] UserControl = 3, [EnumMember] Unknown = 4, [EnumMember] - Python = 5, - [EnumMember] Script = 6, [EnumMember] PartialView = 7 } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Mapping/IMapperConfiguration.cs b/src/Umbraco.Core/Models/Mapping/IMapperConfiguration.cs deleted file mode 100644 index f9c0c2cc1e..0000000000 --- a/src/Umbraco.Core/Models/Mapping/IMapperConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using AutoMapper; - -namespace Umbraco.Core.Models.Mapping -{ - /// - /// This is used to explicitly decorate any ApplicationEventHandler class if there are - /// AutoMapper configurations defined. - /// - /// - /// All automapper configurations are done during startup - /// inside an Automapper Initialize call which is better for performance - /// - public interface IMapperConfiguration : IApplicationEventHandler - { - void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext); - } -} diff --git a/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs b/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs deleted file mode 100644 index 046f4429b2..0000000000 --- a/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; - -namespace Umbraco.Core.Models.Mapping -{ - /// - /// Used to declare a mapper configuration - /// - /// - /// Use this class if your mapper configuration isn't also explicitly an ApplicationEventHandler. - /// - public abstract class MapperConfiguration : ApplicationEventHandler, IMapperConfiguration - { - public abstract void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext); - } - - -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Mapping/MappingExpressionExtensions.cs b/src/Umbraco.Core/Models/Mapping/MappingExpressionExtensions.cs deleted file mode 100644 index 570e51dbc3..0000000000 --- a/src/Umbraco.Core/Models/Mapping/MappingExpressionExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using AutoMapper; - -namespace Umbraco.Core.Models.Mapping -{ - internal static class MappingExpressionExtensions - { - /// - /// Ignores all unmapped members by default - Use with caution! - /// - /// - /// - /// - /// - public static IMappingExpression IgnoreAllUnmapped(this IMappingExpression expression) - { - expression.ForAllMembers(opt => opt.Ignore()); - return expression; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Media.cs b/src/Umbraco.Core/Models/Media.cs index a7e794a400..162e40a96e 100644 --- a/src/Umbraco.Core/Models/Media.cs +++ b/src/Umbraco.Core/Models/Media.cs @@ -20,9 +20,9 @@ namespace Umbraco.Core.Models /// Parent object /// MediaType for the current Media object public Media(string name, IMedia parent, IMediaType contentType) - : this(name, parent, contentType, new PropertyCollection()) - { - } + : this(name, parent, contentType, new PropertyCollection()) + { + } /// /// Constructor for creating a Media object @@ -32,11 +32,10 @@ namespace Umbraco.Core.Models /// MediaType for the current Media object /// Collection of properties public Media(string name, IMedia parent, IMediaType contentType, PropertyCollection properties) - : base(name, parent, contentType, properties) - { - Mandate.ParameterNotNull(contentType, "contentType"); - _contentType = contentType; - } + : base(name, parent, contentType, properties) + { + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + } /// /// Constructor for creating a Media object @@ -56,21 +55,17 @@ namespace Umbraco.Core.Models /// Id of the Parent IMedia /// MediaType for the current Media object /// Collection of properties - public Media(string name, int parentId, IMediaType contentType, PropertyCollection properties) + public Media(string name, int parentId, IMediaType contentType, PropertyCollection properties) : base(name, parentId, contentType, properties) { - Mandate.ParameterNotNull(contentType, "contentType"); - _contentType = contentType; + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); } /// /// Gets the ContentType used by this Media object /// [IgnoreDataMember] - public IMediaType ContentType - { - get { return _contentType; } - } + public IMediaType ContentType => _contentType; /// /// Changes the for the current Media object @@ -112,11 +107,11 @@ namespace Umbraco.Core.Models /// /// Boolean indicating whether content is trashed (true) or not trashed (false) /// - public override void ChangeTrashedState(bool isTrashed, int parentId = -20) + public void ChangeTrashedState(bool isTrashed, int parentId = -20) { Trashed = isTrashed; //The Media Recycle Bin Id is -21 so we correct that here ParentId = parentId == -20 ? -21 : parentId; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MediaExtensions.cs b/src/Umbraco.Core/Models/MediaExtensions.cs index a006548773..1f219d5e10 100644 --- a/src/Umbraco.Core/Models/MediaExtensions.cs +++ b/src/Umbraco.Core/Models/MediaExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -21,13 +21,14 @@ namespace Umbraco.Core.Models var val = media.Properties[propertyType]; if (val == null) return string.Empty; - var jsonString = val.Value as string; + //fixme doesn't take into account variants + var jsonString = val.GetValue() as string; if (jsonString == null) return string.Empty; - if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias) + if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField) return jsonString; - if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias) + if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper) { if (jsonString.DetectIsJson() == false) return jsonString; @@ -60,4 +61,4 @@ namespace Umbraco.Core.Models .ToArray(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MediaType.cs b/src/Umbraco.Core/Models/MediaType.cs index 8596cce910..bc8b7c4c89 100644 --- a/src/Umbraco.Core/Models/MediaType.cs +++ b/src/Umbraco.Core/Models/MediaType.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.Serialization; namespace Umbraco.Core.Models @@ -10,6 +10,8 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class MediaType : ContentTypeCompositionBase, IMediaType { + public const bool IsPublishingConst = false; + /// /// Constuctor for creating a MediaType with the parent's id. /// @@ -24,9 +26,9 @@ namespace Umbraco.Core.Models /// /// Use this to ensure inheritance from parent. /// - public MediaType(IMediaType parent) : this(parent, null) - { - } + public MediaType(IMediaType parent) : this(parent, null) + { + } /// /// Constuctor for creating a MediaType with the parent as an inherited type. @@ -39,6 +41,9 @@ namespace Umbraco.Core.Models { } + /// + public override bool IsPublishing => IsPublishingConst; + /// /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset /// @@ -64,4 +69,4 @@ namespace Umbraco.Core.Models return clone; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 1ed11af93e..a45b1e356b 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -14,6 +18,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Member : ContentBase, IMember { + private IDictionary _additionalData; private IMemberType _contentType; private readonly string _contentTypeAlias; private string _username; @@ -28,10 +33,8 @@ namespace Umbraco.Core.Models public Member(IMemberType contentType) : base("", -1, contentType, new PropertyCollection()) { - Mandate.ParameterNotNull(contentType, "contentType"); - + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); _contentTypeAlias = contentType.Alias; - _contentType = contentType; IsApproved = true; //this cannot be null but can be empty @@ -46,13 +49,12 @@ namespace Umbraco.Core.Models /// Name of the content /// ContentType for the current Content object public Member(string name, IMemberType contentType) - : this(contentType) + : base(name, -1, contentType, new PropertyCollection()) { - Mandate.ParameterNotNull(contentType, "contentType"); - Mandate.ParameterNotNullOrEmpty(name, "name"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); _contentTypeAlias = contentType.Alias; - _contentType = contentType; IsApproved = true; //this cannot be null but can be empty @@ -68,19 +70,18 @@ namespace Umbraco.Core.Models /// /// /// - public Member(string name, string email, string username, IMemberType contentType) + public Member(string name, string email, string username, IMemberType contentType, bool isApproved = true) : base(name, -1, contentType, new PropertyCollection()) { - Mandate.ParameterNotNull(contentType, "contentType"); - Mandate.ParameterNotNullOrEmpty(name, "name"); - Mandate.ParameterNotNullOrEmpty(email, "email"); - Mandate.ParameterNotNullOrEmpty(username, "username"); + if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullOrEmptyException(nameof(email)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullOrEmptyException(nameof(username)); + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); _contentTypeAlias = contentType.Alias; - _contentType = contentType; _email = email; _username = username; - IsApproved = true; + IsApproved = isApproved; //this cannot be null but can be empty _rawPasswordValue = ""; @@ -99,10 +100,9 @@ namespace Umbraco.Core.Models public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType) : base(name, -1, contentType, new PropertyCollection()) { - Mandate.ParameterNotNull(contentType, "contentType"); - + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); _contentTypeAlias = contentType.Alias; - _contentType = contentType; + _email = email; _username = username; _rawPasswordValue = rawPasswordValue; @@ -123,10 +123,8 @@ namespace Umbraco.Core.Models public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, bool isApproved) : base(name, -1, contentType, new PropertyCollection()) { - Mandate.ParameterNotNull(contentType, "contentType"); - + _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); _contentTypeAlias = contentType.Alias; - _contentType = contentType; _email = email; _username = username; _rawPasswordValue = rawPasswordValue; @@ -170,7 +168,19 @@ namespace Umbraco.Core.Models public string RawPasswordValue { get { return _rawPasswordValue; } - set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); } + set + { + if (value == null) + { + //special case, this is used to ensure that the password is not updated when persisting, in this case + //we don't want to track changes either + _rawPasswordValue = null; + } + else + { + SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); + } + } } /// @@ -198,9 +208,9 @@ namespace Umbraco.Core.Models var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.PasswordQuestion, "PasswordQuestion", default(string)); if (a.Success == false) return a.Result; - return Properties[Constants.Conventions.Member.PasswordQuestion].Value == null + return Properties[Constants.Conventions.Member.PasswordQuestion].GetValue() == null ? string.Empty - : Properties[Constants.Conventions.Member.PasswordQuestion].Value.ToString(); + : Properties[Constants.Conventions.Member.PasswordQuestion].GetValue().ToString(); } set { @@ -208,7 +218,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.PasswordQuestion, "PasswordQuestion") == false) return; - Properties[Constants.Conventions.Member.PasswordQuestion].Value = value; + Properties[Constants.Conventions.Member.PasswordQuestion].SetValue(value); } } @@ -218,7 +228,7 @@ namespace Umbraco.Core.Models /// /// For security reasons this value should be encrypted, the encryption process is handled by the memberhip provider /// Alias: umbracoMemberPasswordRetrievalAnswer - /// + /// /// Part of the standard properties collection. /// [IgnoreDataMember] @@ -229,9 +239,9 @@ namespace Umbraco.Core.Models var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.PasswordAnswer, "PasswordAnswer", default(string)); if (a.Success == false) return a.Result; - return Properties[Constants.Conventions.Member.PasswordAnswer].Value == null + return Properties[Constants.Conventions.Member.PasswordAnswer].GetValue() == null ? string.Empty - : Properties[Constants.Conventions.Member.PasswordAnswer].Value.ToString(); + : Properties[Constants.Conventions.Member.PasswordAnswer].GetValue().ToString(); } set { @@ -239,7 +249,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.PasswordAnswer, "PasswordAnswer") == false) return; - Properties[Constants.Conventions.Member.PasswordAnswer].Value = value; + Properties[Constants.Conventions.Member.PasswordAnswer].SetValue(value); } } @@ -258,9 +268,9 @@ namespace Umbraco.Core.Models var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, "Comments", default(string)); if (a.Success == false) return a.Result; - return Properties[Constants.Conventions.Member.Comments].Value == null + return Properties[Constants.Conventions.Member.Comments].GetValue() == null ? string.Empty - : Properties[Constants.Conventions.Member.Comments].Value.ToString(); + : Properties[Constants.Conventions.Member.Comments].GetValue().ToString(); } set { @@ -268,7 +278,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.Comments, "Comments") == false) return; - Properties[Constants.Conventions.Member.Comments].Value = value; + Properties[Constants.Conventions.Member.Comments].SetValue(value); } } @@ -284,12 +294,12 @@ namespace Umbraco.Core.Models { get { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsApproved, "IsApproved", + var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsApproved, "IsApproved", //This is the default value if the prop is not found true); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.IsApproved].Value == null) return true; - var tryConvert = Properties[Constants.Conventions.Member.IsApproved].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.IsApproved].GetValue() == null) return true; + var tryConvert = Properties[Constants.Conventions.Member.IsApproved].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -303,7 +313,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.IsApproved, "IsApproved") == false) return; - Properties[Constants.Conventions.Member.IsApproved].Value = value; + Properties[Constants.Conventions.Member.IsApproved].SetValue(value); } } @@ -321,8 +331,8 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsLockedOut, "IsLockedOut", false); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.IsLockedOut].Value == null) return false; - var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.IsLockedOut].GetValue() == null) return false; + var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -336,7 +346,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.IsLockedOut, "IsLockedOut") == false) return; - Properties[Constants.Conventions.Member.IsLockedOut].Value = value; + Properties[Constants.Conventions.Member.IsLockedOut].SetValue(value); } } @@ -354,8 +364,8 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLoginDate, "LastLoginDate", default(DateTime)); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastLoginDate].Value == null) return default(DateTime); - var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.LastLoginDate].GetValue() == null) return default(DateTime); + var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -369,7 +379,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.LastLoginDate, "LastLoginDate") == false) return; - Properties[Constants.Conventions.Member.LastLoginDate].Value = value; + Properties[Constants.Conventions.Member.LastLoginDate].SetValue(value); } } @@ -387,8 +397,8 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastPasswordChangeDate, "LastPasswordChangeDate", default(DateTime)); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value == null) return default(DateTime); - var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue() == null) return default(DateTime); + var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -402,7 +412,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.LastPasswordChangeDate, "LastPasswordChangeDate") == false) return; - Properties[Constants.Conventions.Member.LastPasswordChangeDate].Value = value; + Properties[Constants.Conventions.Member.LastPasswordChangeDate].SetValue(value); } } @@ -420,8 +430,8 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLockoutDate, "LastLockoutDate", default(DateTime)); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.LastLockoutDate].Value == null) return default(DateTime); - var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.LastLockoutDate].GetValue() == null) return default(DateTime); + var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -435,7 +445,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.LastLockoutDate, "LastLockoutDate") == false) return; - Properties[Constants.Conventions.Member.LastLockoutDate].Value = value; + Properties[Constants.Conventions.Member.LastLockoutDate].SetValue(value); } } @@ -454,8 +464,8 @@ namespace Umbraco.Core.Models { var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.FailedPasswordAttempts, "FailedPasswordAttempts", 0); if (a.Success == false) return a.Result; - if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value == null) return default(int); - var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value.TryConvertTo(); + if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue() == null) return default(int); + var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue().TryConvertTo(); if (tryConvert.Success) { return tryConvert.Result; @@ -469,7 +479,7 @@ namespace Umbraco.Core.Models Constants.Conventions.Member.FailedPasswordAttempts, "FailedPasswordAttempts") == false) return; - Properties[Constants.Conventions.Member.FailedPasswordAttempts].Value = value; + Properties[Constants.Conventions.Member.FailedPasswordAttempts].SetValue(value); } } @@ -486,7 +496,7 @@ namespace Umbraco.Core.Models /// User key from the Provider. /// /// - /// When using standard umbraco provider this key will + /// When using standard umbraco provider this key will /// correspond to the guid UniqueId/Key. /// Otherwise it will the one available from the asp.net /// membership provider. @@ -501,7 +511,7 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _providerUserKey, Ps.Value.ProviderUserKeySelector); } } - + /// /// Method to call when Entity is being saved /// @@ -523,12 +533,7 @@ namespace Umbraco.Core.Models get { return _contentType; } } - public override void ChangeTrashedState(bool isTrashed, int parentId = -20) - { - throw new NotSupportedException("Members can't be trashed as no Recycle Bin exists, so use of this method is invalid"); - } - - /* Internal experiment - only used for mapping queries. + /* Internal experiment - only used for mapping queries. * Adding these to have first level properties instead of the Properties collection. */ [IgnoreDataMember] @@ -546,66 +551,37 @@ namespace Umbraco.Core.Models private Attempt WarnIfPropertyTypeNotFoundOnGet(string propertyAlias, string propertyName, T defaultVal) { - Action doLog = () => LogHelper.Warn( - "Trying to access the '" - + propertyName - + "' property on " - + typeof(Member) - + " but the " - + propertyAlias - + " property does not exist on the member type so a default value is returned. Ensure that you have a property type with alias: " - + propertyAlias - + " configured on your member type in order to use the '" - + propertyName - + "' property on the model correctly."); + void DoLog(string logPropertyAlias, string logPropertyName) + => Current.Logger.Warn($"Trying to access the '{logPropertyName}' property on " + typeof(Member) + + $" but the {logPropertyAlias} property does not exist on the member type so a default value is returned. Ensure that you have a property type with alias: " + + logPropertyAlias + $" configured on your member type in order to use the '{logPropertyName}' property on the model correctly."); - //if the property doesn't exist, then do the logging and return a failure + // if the property doesn't exist, if (Properties.Contains(propertyAlias) == false) { - //we'll put a warn in the log if this entity has been persisted + // put a warn in the log if this entity has been persisted + // then return a failure if (HasIdentity) - { - doLog(); - } - return Attempt.Fail(defaultVal); - } - - //if the property doesn't have an identity but we do, then do logging and return failure - var prop = Properties.Single(x => x.Alias == propertyAlias); - if (prop.HasIdentity == false && HasIdentity) - { - doLog(); + DoLog(propertyAlias, propertyName); return Attempt.Fail(defaultVal); } return Attempt.Succeed(); } - private bool WarnIfPropertyTypeNotFoundOnSet(string propertyAlias, string propertyname) + private bool WarnIfPropertyTypeNotFoundOnSet(string propertyAlias, string propertyName) { - Action doLog = () => LogHelper.Warn("An attempt was made to set a value on the property '" - + propertyname - + "' on type " - + typeof(Member) - + " but the property type " - + propertyAlias - + " does not exist on the member type, ensure that this property type exists so that setting this property works correctly."); + void DoLog(string logPropertyAlias, string logPropertyName) + => Current.Logger.Warn($"An attempt was made to set a value on the property '{logPropertyName}' on type " + typeof(Member) + + $" but the property type {logPropertyAlias} does not exist on the member type, ensure that this property type exists so that setting this property works correctly."); - //if the property doesn't exist, then do the logging and return a failure + // if the property doesn't exist, if (Properties.Contains(propertyAlias) == false) { + // put a warn in the log if this entity has been persisted + // then return a failure if (HasIdentity) - { - doLog(); - } - return false; - } - - //if the property doesn't have an identity but we do, then do logging and return failure - var prop = Properties.Single(x => x.Alias == propertyAlias); - if (prop.HasIdentity == false && HasIdentity) - { - doLog(); + DoLog(propertyAlias, propertyName); return false; } @@ -627,5 +603,14 @@ namespace Umbraco.Core.Models return clone; } + + /// + [DataMember] + [DoNotClone] + public IDictionary AdditionalData => _additionalData ?? (_additionalData = new Dictionary()); + + /// + [IgnoreDataMember] + public bool HasAdditionalData => _additionalData != null; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MemberGroup.cs b/src/Umbraco.Core/Models/MemberGroup.cs index d6dc1be557..0653f75ef7 100644 --- a/src/Umbraco.Core/Models/MemberGroup.cs +++ b/src/Umbraco.Core/Models/MemberGroup.cs @@ -1,8 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -11,28 +11,34 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class MemberGroup : Entity, IMemberGroup + public class MemberGroup : EntityBase, IMemberGroup { - public MemberGroup() - { - AdditionalData = new Dictionary(); - } - + private static PropertySelectors _selectors; + private IDictionary _additionalData; private string _name; private int _creatorId; - private static readonly Lazy Ps = new Lazy(); + private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); private class PropertySelectors { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo Name = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo CreatorId = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); } + /// + [DataMember] + [DoNotClone] + public IDictionary AdditionalData => _additionalData ?? (_additionalData = new Dictionary()); + + /// + [IgnoreDataMember] + public bool HasAdditionalData => _additionalData != null; + [DataMember] public string Name { - get { return _name; } + get => _name; set { if (_name != value) @@ -43,16 +49,15 @@ namespace Umbraco.Core.Models AdditionalData["previousName"] = _name; } - SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); + SetPropertyValueAndDetectChanges(value, ref _name, Selectors.Name); } } + [DataMember] public int CreatorId { - get { return _creatorId; } - set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } + get => _creatorId; + set => SetPropertyValueAndDetectChanges(value, ref _creatorId, Selectors.CreatorId); } - - public IDictionary AdditionalData { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 4f79e1d231..76450e9115 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -12,6 +12,9 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class MemberType : ContentTypeCompositionBase, IMemberType { + private static readonly Lazy Ps = new Lazy(); + public const bool IsPublishingConst = false; + //Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId private string _alias; @@ -30,8 +33,20 @@ namespace Umbraco.Core.Models MemberTypePropertyTypes = new Dictionary(); } - private static readonly Lazy Ps = new Lazy(); + /// + public override bool IsPublishing => IsPublishingConst; + public override ContentVariation Variations + { + // note: although technically possible, variations on members don't make much sense + // and therefore are disabled - they are fully supported at service level, though, + // but not at published snapshot level. + + get => base.Variations; + set => throw new NotSupportedException("Variations are not supported on members."); + } + + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); @@ -43,7 +58,7 @@ namespace Umbraco.Core.Models [DataMember] public override string Alias { - get { return _alias; } + get => _alias; set { //NOTE: WE are overriding this because we don't want to do a ToSafeAlias when the alias is the special case of @@ -64,7 +79,7 @@ namespace Umbraco.Core.Models } /// - /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile) by the PropertyTypes' alias. + /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, IsSensitive) by the PropertyTypes' alias. /// [DataMember] internal IDictionary MemberTypePropertyTypes { get; private set; } @@ -76,12 +91,7 @@ namespace Umbraco.Core.Models /// public bool MemberCanEditProperty(string propertyTypeAlias) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) - { - return MemberTypePropertyTypes[propertyTypeAlias].IsEditable; - } - - return false; + return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsEditable; } /// @@ -91,12 +101,16 @@ namespace Umbraco.Core.Models /// public bool MemberCanViewProperty(string propertyTypeAlias) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) - { - return MemberTypePropertyTypes[propertyTypeAlias].IsVisible; - } - - return false; + return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsVisible; + } + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + public bool IsSensitiveProperty(string propertyTypeAlias) + { + return MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsSensitive; } /// @@ -106,13 +120,13 @@ namespace Umbraco.Core.Models /// Boolean value, true or false public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) { - MemberTypePropertyTypes[propertyTypeAlias].IsEditable = value; + propertyProfile.IsEditable = value; } else { - var tuple = new MemberTypePropertyProfileAccess(false, value); + var tuple = new MemberTypePropertyProfileAccess(false, value, false); MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); } } @@ -124,15 +138,33 @@ namespace Umbraco.Core.Models /// Boolean value, true or false public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) { - MemberTypePropertyTypes[propertyTypeAlias].IsVisible = value; + propertyProfile.IsVisible = value; } else { - var tuple = new MemberTypePropertyProfileAccess(value, false); + var tuple = new MemberTypePropertyProfileAccess(value, false, false); + MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); + } + } + + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetIsSensitiveProperty(string propertyTypeAlias, bool value) + { + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) + { + propertyProfile.IsSensitive = value; + } + else + { + var tuple = new MemberTypePropertyProfileAccess(false, false, true); MemberTypePropertyTypes.Add(propertyTypeAlias, tuple); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs index fa9e0b7307..386fdf560b 100644 --- a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs +++ b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs @@ -1,17 +1,19 @@ -namespace Umbraco.Core.Models +namespace Umbraco.Core.Models { /// /// Used to track the property types that are visible/editable on member profiles /// internal class MemberTypePropertyProfileAccess { - public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable) + public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable, bool isSenstive) { IsVisible = isVisible; IsEditable = isEditable; + IsSensitive = isSenstive; } public bool IsVisible { get; set; } public bool IsEditable { get; set; } + public bool IsSensitive { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs index ca0a910c05..f683db2830 100644 --- a/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs +++ b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Membership { @@ -10,7 +10,7 @@ namespace Umbraco.Core.Models.Membership /// /// This implements purely so it can be used with the repository layer which is why it's explicitly implemented. /// - public class ContentPermissionSet : EntityPermissionSet, IAggregateRoot + public class ContentPermissionSet : EntityPermissionSet, IEntity { private readonly IContent _content; @@ -43,7 +43,7 @@ namespace Umbraco.Core.Models.Membership DateTime IEntity.UpdateDate { get; set; } - DateTime? IDeletableEntity.DeletedDate { get; set; } + DateTime? IEntity.DeleteDate { get; set; } object IDeepCloneable.DeepClone() { @@ -51,4 +51,4 @@ namespace Umbraco.Core.Models.Membership } #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/EntityPermission.cs b/src/Umbraco.Core/Models/Membership/EntityPermission.cs index 8f609c0e13..11f86a1cf2 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermission.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermission.cs @@ -30,14 +30,14 @@ namespace Umbraco.Core.Models.Membership /// The assigned permissions for the user/entity combo /// public string[] AssignedPermissions { get; private set; } - + /// /// True if the permissions assigned to this object are the group's default permissions and not explicitly defined permissions /// /// /// This will be the case when looking up entity permissions and falling back to the default permissions /// - public bool IsDefaultPermissions { get; private set; } + public bool IsDefaultPermissions { get; private set; } public bool Equals(EntityPermission other) { @@ -63,4 +63,4 @@ namespace Umbraco.Core.Models.Membership } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs b/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs index b8e198bb83..12e874d5d7 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs @@ -54,4 +54,4 @@ namespace Umbraco.Core.Models.Membership private string[] _aggregatePermissions; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs index c33c4aa315..e85ab06f33 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Models.Membership { /// /// Represents an entity -> user group & permission key value pair collection - /// + /// public class EntityPermissionSet { private static readonly Lazy EmptyInstance = new Lazy(() => new EntityPermissionSet(-1, new EntityPermissionCollection())); @@ -52,4 +52,4 @@ namespace Umbraco.Core.Models.Membership } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs index 1aead03726..6cffa5bcd8 100644 --- a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs +++ b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs @@ -1,13 +1,13 @@ using System; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Membership { /// /// Defines the base contract for and /// - public interface IMembershipUser : IAggregateRoot - { + public interface IMembershipUser : IEntity + { object ProviderUserKey { get; set; } string Username { get; set; } string Email { get; set; } @@ -16,14 +16,14 @@ namespace Umbraco.Core.Models.Membership /// Gets or sets the raw password value /// string RawPasswordValue { get; set; } - + string PasswordQuestion { get; set; } - + /// /// Gets or sets the raw password answer value /// string RawPasswordAnswerValue { get; set; } - + string Comments { get; set; } bool IsApproved { get; set; } bool IsLockedOut { get; set; } @@ -44,4 +44,4 @@ namespace Umbraco.Core.Models.Membership //object ProfileId { get; set; } //IEnumerable Groups { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/IProfile.cs b/src/Umbraco.Core/Models/Membership/IProfile.cs index 0e6267a10b..e711c43cbe 100644 --- a/src/Umbraco.Core/Models/Membership/IProfile.cs +++ b/src/Umbraco.Core/Models/Membership/IProfile.cs @@ -2,10 +2,10 @@ { /// /// Defines the the User Profile interface - /// + /// public interface IProfile { int Id { get; } string Name { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs index deebc03401..571c13cf70 100644 --- a/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Models.Membership { @@ -28,4 +28,4 @@ namespace Umbraco.Core.Models.Membership IEnumerable AllowedSections { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index cb177539be..8219af17b9 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Membership { @@ -15,24 +15,13 @@ namespace Umbraco.Core.Models.Membership string Name { get; set; } int SessionTimeout { get; set; } - - [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - int StartContentId { get; set; } - int[] StartContentIds { get; set; } + int[] StartMediaIds { get; set; } + string Language { get; set; } - [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - int StartMediaId { get; set; } - - int[] StartMediaIds { get; set; } - - string Language { get; set; } - [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] [EditorBrowsable(EditorBrowsableState.Never)] - IUserType UserType { get; set; } + IUserType UserType { get; set; } DateTime? EmailConfirmedDate { get; set; } DateTime? InvitedDate { get; set; } @@ -40,12 +29,12 @@ namespace Umbraco.Core.Models.Membership /// /// Gets the groups that user is part of /// - IEnumerable Groups { get; } + IEnumerable Groups { get; } void RemoveGroup(string group); void ClearGroups(); void AddGroup(IReadOnlyUserGroup group); - + IEnumerable AllowedSections { get; } [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] @@ -54,7 +43,7 @@ namespace Umbraco.Core.Models.Membership [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] [EditorBrowsable(EditorBrowsableState.Never)] - void AddAllowedSection(string sectionAlias); + void AddAllowedSection(string sectionAlias); /// /// Exposes the basic profile data diff --git a/src/Umbraco.Core/Models/Membership/IUserGroup.cs b/src/Umbraco.Core/Models/Membership/IUserGroup.cs index 0282073a2e..508eb015ed 100644 --- a/src/Umbraco.Core/Models/Membership/IUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/IUserGroup.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; -using Umbraco.Core.Models.EntityBase; +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Membership { - public interface IUserGroup : IAggregateRoot + public interface IUserGroup : IEntity, IRememberBeingDirty { string Alias { get; set; } @@ -14,7 +14,7 @@ namespace Umbraco.Core.Models.Membership /// The icon /// string Icon { get; set; } - + /// /// The name /// @@ -41,4 +41,4 @@ namespace Umbraco.Core.Models.Membership /// int UserCount { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/IUserType.cs b/src/Umbraco.Core/Models/Membership/IUserType.cs index ba004cea4a..118d66074f 100644 --- a/src/Umbraco.Core/Models/Membership/IUserType.cs +++ b/src/Umbraco.Core/Models/Membership/IUserType.cs @@ -1,17 +1,17 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Models.Membership -{ - [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - public interface IUserType : IAggregateRoot - { - string Alias { get; set; } - string Name { get; set; } - IEnumerable Permissions { get; set; } - - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models.Membership +{ + [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IUserType : IEntity + { + string Alias { get; set; } + string Name { get; set; } + IEnumerable Permissions { get; set; } + + } +} diff --git a/src/Umbraco.Core/Models/Membership/MemberCountType.cs b/src/Umbraco.Core/Models/Membership/MemberCountType.cs index 89b428a827..dffa4766dc 100644 --- a/src/Umbraco.Core/Models/Membership/MemberCountType.cs +++ b/src/Umbraco.Core/Models/Membership/MemberCountType.cs @@ -10,4 +10,4 @@ LockedOut, Approved } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/MemberExportModel.cs b/src/Umbraco.Core/Models/Membership/MemberExportModel.cs new file mode 100644 index 0000000000..7153d380b4 --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/MemberExportModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Membership +{ + internal class MemberExportModel + { + public int Id { get; set; } + public Guid Key { get; set; } + public string Name { get; set; } + public string Username { get; set; } + public string Email { get; set; } + public List Groups { get; set; } + public string ContentTypeAlias { get; set; } + public DateTime CreateDate { get; set; } + public DateTime UpdateDate { get; set; } + public List Properties { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Membership/MemberExportProperty.cs b/src/Umbraco.Core/Models/Membership/MemberExportProperty.cs new file mode 100644 index 0000000000..546d9255ea --- /dev/null +++ b/src/Umbraco.Core/Models/Membership/MemberExportProperty.cs @@ -0,0 +1,14 @@ +using System; + +namespace Umbraco.Core.Models.Membership +{ + internal class MemberExportProperty + { + public int Id { get; set; } + public string Alias { get; set; } + public string Name { get; set; } + public object Value { get; set; } + public DateTime? CreateDate { get; set; } + public DateTime? UpdateDate { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Membership/MembershipScenario.cs b/src/Umbraco.Core/Models/Membership/MembershipScenario.cs index d198501321..ec092f83c1 100644 --- a/src/Umbraco.Core/Models/Membership/MembershipScenario.cs +++ b/src/Umbraco.Core/Models/Membership/MembershipScenario.cs @@ -30,4 +30,4 @@ /// StandaloneCustomProvider } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs b/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs index 2289983033..af3b2940d8 100644 --- a/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/MembershipUserExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Web.Security; +using Umbraco.Core.Composing; using Umbraco.Core.Security; using Umbraco.Core.Services; @@ -38,7 +39,7 @@ namespace Umbraco.Core.Models.Membership { return MembershipScenario.NativeUmbraco; } - var memberType = ApplicationContext.Current.Services.MemberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); + var memberType = Current.Services.MemberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); return memberType != null ? MembershipScenario.CustomProviderWithUmbracoLink : MembershipScenario.StandaloneCustomProvider; @@ -46,4 +47,4 @@ namespace Umbraco.Core.Models.Membership return _scenario.Value; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs index ceb2671067..2c93664ec6 100644 --- a/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -6,7 +6,7 @@ namespace Umbraco.Core.Models.Membership { public class ReadOnlyUserGroup : IReadOnlyUserGroup, IEquatable { - public ReadOnlyUserGroup(int id, string name, string icon, int? startContentId, int? startMediaId, string @alias, + public ReadOnlyUserGroup(int id, string name, string icon, int? startContentId, int? startMediaId, string @alias, IEnumerable allowedSections, IEnumerable permissions) { Name = name; @@ -67,4 +67,4 @@ namespace Umbraco.Core.Models.Membership return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs index 0f02f4f73b..0b189d70aa 100644 --- a/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs +++ b/src/Umbraco.Core/Models/Membership/UmbracoMembershipMember.cs @@ -49,7 +49,7 @@ namespace Umbraco.Core.Models.Membership _lastPasswordChangedDate = member.LastPasswordChangeDate.ToUniversalTime(); _lastLockoutDate = member.LastLockoutDate.ToUniversalTime(); } - + internal IMembershipUser Member { get { return _member; } @@ -125,4 +125,4 @@ namespace Umbraco.Core.Models.Membership get { return _providerName; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs b/src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs index 0ada5c3d0e..6ca2df2a38 100644 --- a/src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs +++ b/src/Umbraco.Core/Models/Membership/UmbracoMembershipUser.cs @@ -1,50 +1,50 @@ -using System; -using System.Web.Security; - -namespace Umbraco.Core.Models.Membership -{ - internal class UmbracoMembershipUser : MembershipUser where T : IMembershipUser - { - private T _user; - - #region Constructors - /// - /// Initializes a new instance of the class. - /// - public UmbracoMembershipUser(T user) - { - _user = user; - } - - /// - /// Initializes a new instance of the class. - /// - /// Name of the provider. - /// The name. - /// The provider user key. - /// The email. - /// The password question. - /// The comment. - /// if set to true [is approved]. - /// if set to true [is locked out]. - /// The creation date. - /// The last login date. - /// The last activity date. - /// The last password changed date. - /// The last lockout date. - /// The full name. - /// The language. - /// - public UmbracoMembershipUser(string providerName, string name, object providerUserKey, string email, - string passwordQuestion, string comment, bool isApproved, bool isLockedOut, - DateTime creationDate, DateTime lastLoginDate, DateTime lastActivityDate, DateTime lastPasswordChangedDate, - DateTime lastLockoutDate, string fullName, string language, T user) - : base( providerName, name, providerUserKey, email, passwordQuestion, comment, isApproved, isLockedOut, - creationDate, lastLoginDate, lastActivityDate, lastPasswordChangedDate, lastLockoutDate) - { - _user = user; - } - - #endregion - } -} \ No newline at end of file +using System; +using System.Web.Security; + +namespace Umbraco.Core.Models.Membership +{ + internal class UmbracoMembershipUser : MembershipUser where T : IMembershipUser + { + private T _user; + + #region Constructors + /// + /// Initializes a new instance of the class. + /// + public UmbracoMembershipUser(T user) + { + _user = user; + } + + /// + /// Initializes a new instance of the class. + /// + /// Name of the provider. + /// The name. + /// The provider user key. + /// The email. + /// The password question. + /// The comment. + /// if set to true [is approved]. + /// if set to true [is locked out]. + /// The creation date. + /// The last login date. + /// The last activity date. + /// The last password changed date. + /// The last lockout date. + /// The full name. + /// The language. + /// + public UmbracoMembershipUser(string providerName, string name, object providerUserKey, string email, + string passwordQuestion, string comment, bool isApproved, bool isLockedOut, + DateTime creationDate, DateTime lastLoginDate, DateTime lastActivityDate, DateTime lastPasswordChangedDate, + DateTime lastLockoutDate, string fullName, string language, T user) + : base(providerName, name, providerUserKey, email, passwordQuestion, comment, isApproved, isLockedOut, + creationDate, lastLoginDate, lastActivityDate, lastPasswordChangedDate, lastLockoutDate) + { + _user = user; + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 892de4a7e1..0e3ab70e0a 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -6,18 +6,19 @@ using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Membership { /// /// Represents a backoffice user - /// + /// [Serializable] [DataContract(IsReference = true)] - public class User : Entity, IUser, IProfile + public class User : EntityBase, IUser, IProfile { /// /// Constructor for creating a new/empty user @@ -26,7 +27,7 @@ namespace Umbraco.Core.Models.Membership { SessionTimeout = 60; _userGroups = new HashSet(); - _language = GlobalSettings.DefaultUILanguage; + _language = UmbracoConfig.For.GlobalSettings().DefaultUILanguage; //fixme inject somehow? _isApproved = true; _isLockedOut = false; _startContentIds = new int[] { }; @@ -59,7 +60,7 @@ namespace Umbraco.Core.Models.Membership _isLockedOut = false; _startContentIds = new int[] { }; _startMediaIds = new int[] { }; - + } /// @@ -82,7 +83,7 @@ namespace Umbraco.Core.Models.Membership if (startContentIds == null) throw new ArgumentNullException("startContentIds"); if (startMediaIds == null) throw new ArgumentNullException("startMediaIds"); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); + if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); Id = id; _name = name; @@ -158,7 +159,7 @@ namespace Umbraco.Core.Models.Membership (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), enum1 => enum1.GetHashCode()); } - + #region Implementation of IMembershipUser [IgnoreDataMember] @@ -167,7 +168,7 @@ namespace Umbraco.Core.Models.Membership get { return Id; } set { throw new NotSupportedException("Cannot set the provider user key for a user"); } } - + [DataMember] public DateTime? EmailConfirmedDate { @@ -249,10 +250,10 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] public string RawPasswordAnswerValue { get; set; } [IgnoreDataMember] - public string Comments { get; set; } - + public string Comments { get; set; } + #endregion - + #region Implementation of IUser public UserState UserState @@ -294,14 +295,14 @@ namespace Umbraco.Core.Models.Membership var groups = Groups.ToArray(); if (groups.Length == 0) { - //In backwards compatibility land, a user type cannot be null! so we need to return a fake one. + //In backwards compatibility land, a user type cannot be null! so we need to return a fake one. return new UserType { Alias = "temp", Id = int.MinValue, Key = Guid.Empty, CreateDate = default(DateTime), - DeletedDate = null, + DeleteDate = null, Name = "Temp", Permissions = new List(), UpdateDate = default(DateTime) @@ -313,7 +314,7 @@ namespace Umbraco.Core.Models.Membership if (foundBuiltIn != null) { //if the group isn't IUserGroup we'll need to look it up - realGroup = foundBuiltIn as IUserGroup ?? ApplicationContext.Current.Services.UserService.GetUserGroupById(foundBuiltIn.Id); + realGroup = foundBuiltIn as IUserGroup ?? Current.Services.UserService.GetUserGroupById(foundBuiltIn.Id); //return a mapped version of the group return new UserType @@ -322,7 +323,7 @@ namespace Umbraco.Core.Models.Membership Id = realGroup.Id, Key = realGroup.Key, CreateDate = realGroup.CreateDate, - DeletedDate = realGroup.DeletedDate, + DeleteDate = realGroup.DeleteDate, Name = realGroup.Name, Permissions = realGroup.Permissions, UpdateDate = realGroup.UpdateDate @@ -331,7 +332,7 @@ namespace Umbraco.Core.Models.Membership //otherwise return the first //if the group isn't IUserGroup we'll need to look it up - realGroup = groups[0] as IUserGroup ?? ApplicationContext.Current.Services.UserService.GetUserGroupById(groups[0].Id); + realGroup = groups[0] as IUserGroup ?? Current.Services.UserService.GetUserGroupById(groups[0].Id); //return a mapped version of the group return new UserType { @@ -339,7 +340,7 @@ namespace Umbraco.Core.Models.Membership Id = realGroup.Id, Key = realGroup.Key, CreateDate = realGroup.CreateDate, - DeletedDate = realGroup.DeletedDate, + DeleteDate = realGroup.DeleteDate, Name = realGroup.Name, Permissions = realGroup.Permissions, UpdateDate = realGroup.UpdateDate @@ -352,19 +353,19 @@ namespace Umbraco.Core.Models.Membership return; //the only other option we have here is to lookup the group (and we'll need to use singletons here :( ) - var found = ApplicationContext.Current.Services.UserService.GetUserGroupByAlias(value.Alias); + var found = Current.Services.UserService.GetUserGroupByAlias(value.Alias); if (found == null) throw new InvalidOperationException("No user group was found with the alias " + value.Alias + ", this API (IUser.UserType) is obsolete, use user groups instead"); //if it's found, all we can do is add it, we can't really replace them AddGroup(found.ToReadOnlyGroup()); } - } + } [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] [EditorBrowsable(EditorBrowsableState.Never)] void IUser.RemoveAllowedSection(string sectionAlias) - { + { //don't do anything if they aren't allowed it already if (AllowedSections.Contains(sectionAlias) == false) return; @@ -377,16 +378,16 @@ namespace Umbraco.Core.Models.Membership if (customUserGroup != null) { //if the group isn't IUserGroup we'll need to look it up - var realGroup = customUserGroup as IUserGroup ?? ApplicationContext.Current.Services.UserService.GetUserGroupById(customUserGroup.Id); + var realGroup = customUserGroup as IUserGroup ?? Current.Services.UserService.GetUserGroupById(customUserGroup.Id); realGroup.RemoveAllowedSection(sectionAlias); - //now we need to flag this for saving (hack!) + //now we need to flag this for saving (hack!) GroupsToSave.Add(realGroup); } else { throw new InvalidOperationException("Cannot remove the allowed section using this obsolete API. Modify the user's groups instead"); - } - + } + } [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] @@ -395,34 +396,34 @@ namespace Umbraco.Core.Models.Membership { //don't do anything if they are allowed it already if (AllowedSections.Contains(sectionAlias)) - return; + return; //This is here for backwards compat only. //First we'll check if the user is part of the 'admin' group. If so then we can ensure that the admin group has this section available to it. - //otherwise, the only thing we can do is create a custom user group for this user and add this section. - //We are checking for admin here because if the user is an admin and an allowed section is being added, then it's assumed it's to be added + //otherwise, the only thing we can do is create a custom user group for this user and add this section. + //We are checking for admin here because if the user is an admin and an allowed section is being added, then it's assumed it's to be added //for the whole admin group (i.e. Forms installer does this for admins) var groups = Groups.ToArray(); var admin = groups.FirstOrDefault(x => x.Alias == Constants.Security.AdminGroupAlias); if (admin != null) { //if the group isn't IUserGroup we'll need to look it up - var realGroup = admin as IUserGroup ?? ApplicationContext.Current.Services.UserService.GetUserGroupById(admin.Id); + var realGroup = admin as IUserGroup ?? Current.Services.UserService.GetUserGroupById(admin.Id); realGroup.AddAllowedSection(sectionAlias); - //now we need to flag this for saving (hack!) + //now we need to flag this for saving (hack!) GroupsToSave.Add(realGroup); } else - { + { //now we'll check if the user has a special 1:1 user group created for itself. This will occur if this method is used and also during an upgrade. //this comes in the alias form of userName + 'Group' var customUserGroup = groups.FirstOrDefault(x => x.Alias == (Username + "Group")); if (customUserGroup != null) { //if the group isn't IUserGroup we'll need to look it up - var realGroup = customUserGroup as IUserGroup ?? ApplicationContext.Current.Services.UserService.GetUserGroupById(customUserGroup.Id); + var realGroup = customUserGroup as IUserGroup ?? Current.Services.UserService.GetUserGroupById(customUserGroup.Id); realGroup.AddAllowedSection(sectionAlias); - //now we need to flag this for saving (hack!) + //now we need to flag this for saving (hack!) GroupsToSave.Add(realGroup); } @@ -432,19 +433,19 @@ namespace Umbraco.Core.Models.Membership Alias = Username + "Group", Name = "Group for " + Username }; - newUserGroup.AddAllowedSection(sectionAlias); - //add this user to this new group + newUserGroup.AddAllowedSection(sectionAlias); + //add this user to this new group AddGroup(newUserGroup); GroupsToSave.Add(newUserGroup); } - } - + } + /// /// This used purely for hacking backwards compatibility into this class for < 7.7 compat /// [DoNotClone] [IgnoreDataMember] - internal List GroupsToSave = new List(); + internal List GroupsToSave = new List(); public IProfile ProfileData { @@ -491,18 +492,6 @@ namespace Umbraco.Core.Models.Membership set { SetPropertyValueAndDetectChanges(value, ref _sessionTimeout, Ps.Value.SessionTimeoutSelector); } } - [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - int IUser.StartContentId - { - get - { - var calculatedStartNodes = this.CalculateContentStartNodeIds(ApplicationContext.Current.Services.EntityService); - return calculatedStartNodes.Length == 0 ? -1 : calculatedStartNodes[0]; - } - set { StartContentIds = new[] { value }; } - } - /// /// Gets or sets the start content id. /// @@ -517,18 +506,6 @@ namespace Umbraco.Core.Models.Membership set { SetPropertyValueAndDetectChanges(value, ref _startContentIds, Ps.Value.StartContentIdSelector, Ps.Value.IntegerEnumerableComparer); } } - [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - int IUser.StartMediaId - { - get - { - var calculatedStartNodes = this.CalculateMediaStartNodeIds(ApplicationContext.Current.Services.EntityService); - return calculatedStartNodes.Length == 0 ? -1 : calculatedStartNodes[0]; - } - set { StartMediaIds = new[] {value}; } - } - /// /// Gets or sets the start media id. /// @@ -565,7 +542,7 @@ namespace Umbraco.Core.Models.Membership { get { return _userGroups; } } - + public void RemoveGroup(string group) { foreach (var userGroup in _userGroups.ToArray()) @@ -588,7 +565,7 @@ namespace Umbraco.Core.Models.Membership //reset this flag so it's rebuilt with the assigned groups _allowedSections = null; OnPropertyChanged(Ps.Value.UserGroupsSelector); - } + } } public void AddGroup(IReadOnlyUserGroup group) @@ -598,9 +575,9 @@ namespace Umbraco.Core.Models.Membership //reset this flag so it's rebuilt with the assigned groups _allowedSections = null; OnPropertyChanged(Ps.Value.UserGroupsSelector); - } - } - + } + } + #endregion /// @@ -650,10 +627,10 @@ namespace Umbraco.Core.Models.Membership { var deepCloneable = kvp.Value as IDeepCloneable; cloneAdditionalData[kvp.Key] = deepCloneable == null ? kvp.Value : deepCloneable.DeepClone(); - } + } } } - + //need to create new collections otherwise they'll get copied by ref clone._userGroups = new HashSet(_userGroups); clone._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; @@ -706,5 +683,6 @@ namespace Umbraco.Core.Models.Membership return _user.GetHashCode(); } } + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index 7042c63ec1..db21c78438 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -1,155 +1,139 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Strings; - -namespace Umbraco.Core.Models.Membership -{ - /// - /// Represents a Group for a Backoffice User - /// - [Serializable] - [DataContract(IsReference = true)] - public class UserGroup : Entity, IUserGroup, IReadOnlyUserGroup - { - private int? _startContentId; - private int? _startMediaId; - private string _alias; - private string _icon; - private string _name; - private IEnumerable _permissions; - private readonly List _sectionCollection; - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); - public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); - public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); - public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); - - //Custom comparer for enumerable - public readonly DelegateEqualityComparer> StringEnumerableComparer = - new DelegateEqualityComparer>( - (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), - enum1 => enum1.GetHashCode()); - } +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Strings; - /// - /// Constructor to create a new user group - /// - public UserGroup() - { - _sectionCollection = new List(); - } - - /// - /// Constructor to create an existing user group - /// - /// - /// - /// - /// - /// - public UserGroup(int userCount, string alias, string name, IEnumerable permissions, string icon) - : this() - { - UserCount = userCount; - _alias = alias; - _name = name; - _permissions = permissions; - _icon = icon; - } - - [DataMember] - public int? StartMediaId - { - get { return _startMediaId; } - set { SetPropertyValueAndDetectChanges(value, ref _startMediaId, Ps.Value.StartMediaIdSelector); } - } - - [DataMember] - public int? StartContentId - { - get { return _startContentId; } - set { SetPropertyValueAndDetectChanges(value, ref _startContentId, Ps.Value.StartContentIdSelector); } +namespace Umbraco.Core.Models.Membership +{ + /// + /// Represents a Group for a Backoffice User + /// + [Serializable] + [DataContract(IsReference = true)] + public class UserGroup : EntityBase, IUserGroup, IReadOnlyUserGroup + { + private int? _startContentId; + private int? _startMediaId; + private string _alias; + private string _icon; + private string _name; + private IEnumerable _permissions; + private readonly List _sectionCollection; + + private static readonly Lazy Ps = new Lazy(); + + // ReSharper disable once ClassNeverInstantiated.Local // lazy-instanciated in Ps + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); + public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon); + public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); + public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); + + //Custom comparer for enumerable + public readonly DelegateEqualityComparer> StringEnumerableComparer = + new DelegateEqualityComparer>( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode()); } - - [DataMember] - public string Icon - { - get { return _icon; } - set { SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); } - } - - [DataMember] - public string Alias - { - get { return _alias; } - set - { - SetPropertyValueAndDetectChanges( - value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), - ref _alias, - Ps.Value.AliasSelector); - } - } - - [DataMember] - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// The set of default permissions for the user group - /// - /// - /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. - /// - [DataMember] - public IEnumerable Permissions - { - get { return _permissions; } - set - { - SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector, - Ps.Value.StringEnumerableComparer); - } - } - - public IEnumerable AllowedSections - { - get { return _sectionCollection; } - } - - public void RemoveAllowedSection(string sectionAlias) - { - if (_sectionCollection.Contains(sectionAlias)) - { - _sectionCollection.Remove(sectionAlias); - } - } - - public void AddAllowedSection(string sectionAlias) - { - if (_sectionCollection.Contains(sectionAlias) == false) - { - _sectionCollection.Add(sectionAlias); - } - } - - public void ClearAllowedSections() - { - _sectionCollection.Clear(); - } - - public int UserCount { get; private set; } - } -} \ No newline at end of file + + /// + /// Constructor to create a new user group + /// + public UserGroup() + { + _sectionCollection = new List(); + } + + /// + /// Constructor to create an existing user group + /// + /// + /// + /// + /// + /// + public UserGroup(int userCount, string alias, string name, IEnumerable permissions, string icon) + : this() + { + UserCount = userCount; + _alias = alias; + _name = name; + _permissions = permissions; + _icon = icon; + } + + [DataMember] + public int? StartMediaId + { + get => _startMediaId; + set => SetPropertyValueAndDetectChanges(value, ref _startMediaId, Ps.Value.StartMediaIdSelector); + } + + [DataMember] + public int? StartContentId + { + get => _startContentId; + set => SetPropertyValueAndDetectChanges(value, ref _startContentId, Ps.Value.StartContentIdSelector); + } + + [DataMember] + public string Icon + { + get => _icon; + set => SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); + } + + [DataMember] + public string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), ref _alias, Ps.Value.AliasSelector); + } + + [DataMember] + public string Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); + } + + /// + /// The set of default permissions for the user group + /// + /// + /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. + /// + [DataMember] + public IEnumerable Permissions + { + get => _permissions; + set => SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector, Ps.Value.StringEnumerableComparer); + } + + public IEnumerable AllowedSections => _sectionCollection; + + public void RemoveAllowedSection(string sectionAlias) + { + if (_sectionCollection.Contains(sectionAlias)) + _sectionCollection.Remove(sectionAlias); + } + + public void AddAllowedSection(string sectionAlias) + { + if (_sectionCollection.Contains(sectionAlias) == false) + _sectionCollection.Add(sectionAlias); + } + + public void ClearAllowedSections() + { + _sectionCollection.Clear(); + } + + public int UserCount { get; } + } +} diff --git a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs index ff2d302581..3903fe405b 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs @@ -1,5 +1,5 @@ -using Umbraco.Core.Models.Rdbms; -using System.Linq; +using System.Linq; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Models.Membership { @@ -17,10 +17,10 @@ namespace Umbraco.Core.Models.Membership public static IReadOnlyUserGroup ToReadOnlyGroup(this UserGroupDto group) { - return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, - group.StartContentId, group.StartMediaId, group.Alias, + return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, + group.StartContentId, group.StartMediaId, group.Alias, group.UserGroup2AppDtos.Select(x => x.AppAlias).ToArray(), group.DefaultPermissions == null ? Enumerable.Empty() : group.DefaultPermissions.ToCharArray().Select(x => x.ToString())); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/UserProfile.cs b/src/Umbraco.Core/Models/Membership/UserProfile.cs index d32854ab1b..8d7ae5437b 100644 --- a/src/Umbraco.Core/Models/Membership/UserProfile.cs +++ b/src/Umbraco.Core/Models/Membership/UserProfile.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Core.Models.Membership { @@ -43,4 +43,4 @@ namespace Umbraco.Core.Models.Membership return Equals(left, right) == false; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/UserState.cs b/src/Umbraco.Core/Models/Membership/UserState.cs index 5f6ee1615a..0f8d0ec030 100644 --- a/src/Umbraco.Core/Models/Membership/UserState.cs +++ b/src/Umbraco.Core/Models/Membership/UserState.cs @@ -11,4 +11,4 @@ LockedOut = 2, Invited = 3 } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Membership/UserType.cs b/src/Umbraco.Core/Models/Membership/UserType.cs index 13865efed9..d2e4a8f6cb 100644 --- a/src/Umbraco.Core/Models/Membership/UserType.cs +++ b/src/Umbraco.Core/Models/Membership/UserType.cs @@ -1,68 +1,78 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Strings; - -namespace Umbraco.Core.Models.Membership -{ - [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - [Serializable] - [DataContract(IsReference = true)] - internal class UserType : Entity, IUserType - { - private string _alias; - private string _name; - private IEnumerable _permissions; - private static readonly Lazy Ps = new Lazy(); - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); - } - - [DataMember] - public string Alias - { - get { return _alias; } - set - { - SetPropertyValueAndDetectChanges( - value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), - ref _alias, - Ps.Value.AliasSelector); - } - } - - [DataMember] - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// The set of default permissions for the user type - /// - /// - /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. - /// - [DataMember] - public IEnumerable Permissions - { - get { return _permissions; } - set - { - SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector, - //Custom comparer for enumerable - new DelegateEqualityComparer>( - (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), - enum1 => enum1.GetHashCode())); - } - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Strings; + +namespace Umbraco.Core.Models.Membership +{ + [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + [Serializable] + [DataContract(IsReference = true)] + internal class UserType : EntityBase, IUserType + { + private string _alias; + private string _name; + private IEnumerable _permissions; + private static readonly Lazy Ps = new Lazy(); + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.Permissions); + } + + public UserType(string name, string alias) + { + Name = name; + Alias = alias; + } + + public UserType() + { + } + + [DataMember] + public string Alias + { + get { return _alias; } + set + { + SetPropertyValueAndDetectChanges( + value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase), + ref _alias, + Ps.Value.AliasSelector); + } + } + + [DataMember] + public string Name + { + get { return _name; } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + } + + /// + /// The set of default permissions for the user type + /// + /// + /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. + /// + [DataMember] + public IEnumerable Permissions + { + get { return _permissions; } + set + { + SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector, + //Custom comparer for enumerable + new DelegateEqualityComparer>( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode())); + } + } + } +} diff --git a/src/Umbraco.Core/Models/MigrationEntry.cs b/src/Umbraco.Core/Models/MigrationEntry.cs index 575ee8efc3..9ac9ae58a4 100644 --- a/src/Umbraco.Core/Models/MigrationEntry.cs +++ b/src/Umbraco.Core/Models/MigrationEntry.cs @@ -1,11 +1,11 @@ using System; using System.Reflection; using Semver; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { - public class MigrationEntry : Entity, IMigrationEntry + public class MigrationEntry : EntityBase, IMigrationEntry { public MigrationEntry() { @@ -42,4 +42,4 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _version, Ps.Value.VersionSelector); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ObjectTypes.cs b/src/Umbraco.Core/Models/ObjectTypes.cs new file mode 100644 index 0000000000..2eb4d70a3a --- /dev/null +++ b/src/Umbraco.Core/Models/ObjectTypes.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Concurrent; +using System.Reflection; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Core.Models +{ + /// + /// Provides utilities and extension methods to handle object types. + /// + public static class ObjectTypes + { + // must be concurrent to avoid thread collisions! + private static readonly ConcurrentDictionary UmbracoGuids = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary UmbracoUdiTypes = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary UmbracoFriendlyNames = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary UmbracoTypes = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary GuidUdiTypes = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary GuidObjectTypes = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary GuidTypes = new ConcurrentDictionary(); + + private static FieldInfo GetEnumField(string name) + { + return typeof (UmbracoObjectTypes).GetField(name, BindingFlags.Public | BindingFlags.Static); + } + + private static FieldInfo GetEnumField(Guid guid) + { + var fields = typeof (UmbracoObjectTypes).GetFields(BindingFlags.Public | BindingFlags.Static); + foreach (var field in fields) + { + var attribute = field.GetCustomAttribute(false); + if (attribute != null && attribute.ObjectId == guid) return field; + } + + return null; + } + + /// + /// Gets the Umbraco object type corresponding to a name. + /// + public static UmbracoObjectTypes GetUmbracoObjectType(string name) + { + return (UmbracoObjectTypes) Enum.Parse(typeof (UmbracoObjectTypes), name, false); + } + + #region Guid object type utilities + + /// + /// Gets the Umbraco object type corresponding to an object type Guid. + /// + public static UmbracoObjectTypes GetUmbracoObjectType(Guid objectType) + { + return GuidObjectTypes.GetOrAdd(objectType, t => + { + var field = GetEnumField(objectType); + if (field == null) return UmbracoObjectTypes.Unknown; + + return (UmbracoObjectTypes) field.GetValue(null); + }); + } + + /// + /// Gets the Udi type corresponding to an object type Guid. + /// + public static string GetUdiType(Guid objectType) + { + return GuidUdiTypes.GetOrAdd(objectType, t => + { + var field = GetEnumField(objectType); + if (field == null) return Constants.UdiEntityType.Unknown; + + var attribute = field.GetCustomAttribute(false); + return attribute?.UdiType ?? Constants.UdiEntityType.Unknown; + }); + } + + /// + /// Gets the Clr type corresponding to an object type Guid. + /// + public static Type GetClrType(Guid objectType) + { + return GuidTypes.GetOrAdd(objectType, t => + { + var field = GetEnumField(objectType); + if (field == null) return null; + + var attribute = field.GetCustomAttribute(false); + return attribute?.ModelType; + }); + } + + #endregion + + #region UmbracoObjectTypes extension methods + + /// + /// Gets the object type Guid corresponding to this Umbraco object type. + /// + public static Guid GetGuid(this UmbracoObjectTypes objectType) + { + return UmbracoGuids.GetOrAdd(objectType, t => + { + var field = GetEnumField(t.ToString()); + var attribute = field.GetCustomAttribute(false); + + return attribute?.ObjectId ?? Guid.Empty; + }); + } + + /// + /// Gets the Udi type corresponding to this Umbraco object type. + /// + public static string GetUdiType(this UmbracoObjectTypes objectType) + { + return UmbracoUdiTypes.GetOrAdd(objectType, t => + { + var field = GetEnumField(t.ToString()); + var attribute = field.GetCustomAttribute(false); + + return attribute?.UdiType ?? Constants.UdiEntityType.Unknown; + }); + } + + /// + /// Gets the name corresponding to this Umbraco object type. + /// + public static string GetName(this UmbracoObjectTypes objectType) + { + return Enum.GetName(typeof (UmbracoObjectTypes), objectType); + } + + /// + /// Gets the friendly name corresponding to this Umbraco object type. + /// + public static string GetFriendlyName(this UmbracoObjectTypes objectType) + { + return UmbracoFriendlyNames.GetOrAdd(objectType, t => + { + var field = GetEnumField(t.ToString()); + var attribute = field.GetCustomAttribute(false); + + return attribute?.ToString() ?? string.Empty; + }); + } + + /// + /// Gets the Clr type corresponding to this Umbraco object type. + /// + public static Type GetClrType(this UmbracoObjectTypes objectType) + { + return UmbracoTypes.GetOrAdd(objectType, t => + { + var field = GetEnumField(t.ToString()); + var attribute = field.GetCustomAttribute(false); + + return attribute?.ModelType; + }); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs b/src/Umbraco.Core/Models/Packaging/InstallationSummary.cs similarity index 86% rename from src/Umbraco.Core/Packaging/Models/InstallationSummary.cs rename to src/Umbraco.Core/Models/Packaging/InstallationSummary.cs index 4e95df455c..3eb397d728 100644 --- a/src/Umbraco.Core/Packaging/Models/InstallationSummary.cs +++ b/src/Umbraco.Core/Models/Packaging/InstallationSummary.cs @@ -1,44 +1,43 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Packaging.Models -{ - [Serializable] - [DataContract(IsReference = true)] - public class InstallationSummary - { - public MetaData MetaData { get; set; } - public IEnumerable DataTypesInstalled { get; set; } - public IEnumerable LanguagesInstalled { get; set; } - public IEnumerable DictionaryItemsInstalled { get; set; } - public IEnumerable MacrosInstalled { get; set; } - public IEnumerable FilesInstalled { get; set; } - public IEnumerable TemplatesInstalled { get; set; } - public IEnumerable ContentTypesInstalled { get; set; } - public IEnumerable StylesheetsInstalled { get; set; } - public IEnumerable ContentInstalled { get; set; } - public IEnumerable Actions { get; set; } - public bool PackageInstalled { get; set; } - } - - internal static class InstallationSummaryExtentions - { - public static InstallationSummary InitEmpty(this InstallationSummary summary) - { - summary.Actions = new List(); - summary.ContentInstalled = new List(); - summary.ContentTypesInstalled = new List(); - summary.DataTypesInstalled = new List(); - summary.DictionaryItemsInstalled = new List(); - summary.FilesInstalled = new List(); - summary.LanguagesInstalled = new List(); - summary.MacrosInstalled = new List(); - summary.MetaData = new MetaData(); - summary.TemplatesInstalled = new List(); - summary.PackageInstalled = false; - return summary; - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models.Packaging +{ + [Serializable] + [DataContract(IsReference = true)] + public class InstallationSummary + { + public MetaData MetaData { get; set; } + public IEnumerable DataTypesInstalled { get; set; } + public IEnumerable LanguagesInstalled { get; set; } + public IEnumerable DictionaryItemsInstalled { get; set; } + public IEnumerable MacrosInstalled { get; set; } + public IEnumerable FilesInstalled { get; set; } + public IEnumerable TemplatesInstalled { get; set; } + public IEnumerable ContentTypesInstalled { get; set; } + public IEnumerable StylesheetsInstalled { get; set; } + public IEnumerable ContentInstalled { get; set; } + public IEnumerable Actions { get; set; } + public bool PackageInstalled { get; set; } + } + + internal static class InstallationSummaryExtentions + { + public static InstallationSummary InitEmpty(this InstallationSummary summary) + { + summary.Actions = new List(); + summary.ContentInstalled = new List(); + summary.ContentTypesInstalled = new List(); + summary.DataTypesInstalled = new List(); + summary.DictionaryItemsInstalled = new List(); + summary.FilesInstalled = new List(); + summary.LanguagesInstalled = new List(); + summary.MacrosInstalled = new List(); + summary.MetaData = new MetaData(); + summary.TemplatesInstalled = new List(); + summary.PackageInstalled = false; + return summary; + } + } +} diff --git a/src/Umbraco.Core/Packaging/Models/MetaData.cs b/src/Umbraco.Core/Models/Packaging/MetaData.cs similarity index 94% rename from src/Umbraco.Core/Packaging/Models/MetaData.cs rename to src/Umbraco.Core/Models/Packaging/MetaData.cs index f57fa9e329..a1cb450c5b 100644 --- a/src/Umbraco.Core/Packaging/Models/MetaData.cs +++ b/src/Umbraco.Core/Models/Packaging/MetaData.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.Serialization; -namespace Umbraco.Core.Packaging.Models +namespace Umbraco.Core.Models.Packaging { [Serializable] [DataContract(IsReference = true)] @@ -20,4 +20,4 @@ namespace Umbraco.Core.Packaging.Models public string Readme { get; set; } public string Control { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Packaging/PackageAction.cs b/src/Umbraco.Core/Models/Packaging/PackageAction.cs new file mode 100644 index 0000000000..dd9a4fd1a5 --- /dev/null +++ b/src/Umbraco.Core/Models/Packaging/PackageAction.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.Serialization; +using System.Xml.Linq; + +namespace Umbraco.Core.Models.Packaging +{ + public enum ActionRunAt + { + Undefined = 0, + Install, + Uninstall + } + + [Serializable] + [DataContract(IsReference = true)] + public class PackageAction + { + private ActionRunAt _runAt; + private bool? _undo; + public string Alias { get; set; } + + public string PackageName { get; set; } + + public ActionRunAt RunAt + { + get => _runAt == ActionRunAt.Undefined ? ActionRunAt.Install : _runAt; + set => _runAt = value; + } + + public bool Undo //NOTE: Should thid default to "False"? but the documentation says default "True" (http://our.umbraco.org/wiki/reference/packaging/package-actions) + { + get => _undo ?? true; + set => _undo = value; + } + + public XElement XmlData { get; set; } + } +} diff --git a/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs b/src/Umbraco.Core/Models/Packaging/PreInstallWarnings.cs similarity index 75% rename from src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs rename to src/Umbraco.Core/Models/Packaging/PreInstallWarnings.cs index 05330ce043..5850e2321c 100644 --- a/src/Umbraco.Core/Packaging/Models/PreInstallWarnings.cs +++ b/src/Umbraco.Core/Models/Packaging/PreInstallWarnings.cs @@ -1,19 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Packaging.Models -{ - [Serializable] - [DataContract(IsReference = true)] - internal class PreInstallWarnings - { - public KeyValuePair[] UnsecureFiles { get; set; } - public KeyValuePair[] FilesReplaced { get; set; } - public IEnumerable ConflictingMacroAliases { get; set; } - public IEnumerable ConflictingTemplateAliases { get; set; } - public IEnumerable ConflictingStylesheetNames { get; set; } - public IEnumerable AssembliesWithLegacyPropertyEditors { get; set; } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models.Packaging +{ + [Serializable] + [DataContract(IsReference = true)] + internal class PreInstallWarnings + { + public KeyValuePair[] UnsecureFiles { get; set; } + public KeyValuePair[] FilesReplaced { get; set; } + public IEnumerable ConflictingMacroAliases { get; set; } + public IEnumerable ConflictingTemplateAliases { get; set; } + public IEnumerable ConflictingStylesheetNames { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/PagedResult.cs b/src/Umbraco.Core/Models/PagedResult.cs index c7b91dd47a..88b2069ba4 100644 --- a/src/Umbraco.Core/Models/PagedResult.cs +++ b/src/Umbraco.Core/Models/PagedResult.cs @@ -57,4 +57,4 @@ namespace Umbraco.Core.Models return 0; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs index 3c7a3ee908..73ae8d83dd 100644 --- a/src/Umbraco.Core/Models/PartialView.cs +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -27,4 +27,4 @@ namespace Umbraco.Core.Models public PartialViewType ViewType { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PartialViewType.cs b/src/Umbraco.Core/Models/PartialViewType.cs index 6740d1fdd9..6204b6e165 100644 --- a/src/Umbraco.Core/Models/PartialViewType.cs +++ b/src/Umbraco.Core/Models/PartialViewType.cs @@ -4,7 +4,6 @@ { Unknown = 0, // default PartialView = 1, - PartialViewMacro = 2, - MacroScript = 3 + PartialViewMacro = 2 } } diff --git a/src/Umbraco.Core/Models/PathValidationExtensions.cs b/src/Umbraco.Core/Models/PathValidationExtensions.cs new file mode 100644 index 0000000000..74c2c9faa5 --- /dev/null +++ b/src/Umbraco.Core/Models/PathValidationExtensions.cs @@ -0,0 +1,115 @@ +using System; +using System.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Models +{ + /// + /// Provides extension methods for path validation. + /// + internal static class PathValidationExtensions + { + /// + /// Does a quick check on the entity's set path to ensure that it's valid and consistent + /// + /// + /// + public static void ValidatePathWithException(this NodeDto entity) + { + //don't validate if it's empty and it has no id + if (entity.NodeId == default(int) && entity.Path.IsNullOrWhiteSpace()) + return; + + if (entity.Path.IsNullOrWhiteSpace()) + throw new InvalidDataException($"The content item {entity.NodeId} has an empty path: {entity.Path} with parentID: {entity.ParentId}"); + + var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (pathParts.Length < 2) + { + //a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id + throw new InvalidDataException($"The content item {entity.NodeId} has an invalid path: {entity.Path} with parentID: {entity.ParentId}"); + } + + if (entity.ParentId != default(int) && pathParts[pathParts.Length - 2] != entity.ParentId.ToInvariantString()) + { + //the 2nd last id in the path must be it's parent id + throw new InvalidDataException($"The content item {entity.NodeId} has an invalid path: {entity.Path} with parentID: {entity.ParentId}"); + } + } + + /// + /// Does a quick check on the entity's set path to ensure that it's valid and consistent + /// + /// + /// + public static bool ValidatePath(this IUmbracoEntity entity) + { + //don't validate if it's empty and it has no id + if (entity.HasIdentity == false && entity.Path.IsNullOrWhiteSpace()) + return true; + + if (entity.Path.IsNullOrWhiteSpace()) + return false; + + var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (pathParts.Length < 2) + { + //a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id + return false; + } + + if (entity.ParentId != default(int) && pathParts[pathParts.Length - 2] != entity.ParentId.ToInvariantString()) + { + //the 2nd last id in the path must be it's parent id + return false; + } + + return true; + } + + /// + /// This will validate the entity's path and if it's invalid it will fix it, if fixing is required it will recursively + /// check and fix all ancestors if required. + /// + /// + /// + /// A callback specified to retrieve the parent entity of the entity + /// A callback specified to update a fixed entity + public static void EnsureValidPath(this T entity, + ILogger logger, + Func getParent, + Action update) + where T: IUmbracoEntity + { + if (entity.HasIdentity == false) + throw new InvalidOperationException("Could not ensure the entity path, the entity has not been assigned an identity"); + + if (entity.ValidatePath() == false) + { + logger.Warn(typeof(PathValidationExtensions), $"The content item {entity.Id} has an invalid path: {entity.Path} with parentID: {entity.ParentId}"); + if (entity.ParentId == -1) + { + entity.Path = string.Concat("-1,", entity.Id); + //path changed, update it + update(entity); + } + else + { + var parent = getParent(entity); + if (parent == null) + throw new NullReferenceException("Could not ensure path for entity " + entity.Id + " could not resolve it's parent " + entity.ParentId); + + //the parent must also be valid! + parent.EnsureValidPath(logger, getParent, update); + + entity.Path = string.Concat(parent.Path, ",", entity.Id); + //path changed, update it + update(entity); + } + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PreValue.cs b/src/Umbraco.Core/Models/PreValue.cs deleted file mode 100644 index 61d127f50a..0000000000 --- a/src/Umbraco.Core/Models/PreValue.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace Umbraco.Core.Models -{ - /// - /// Represents a stored pre-value field value - /// - public class PreValue : IDeepCloneable - { - public PreValue(int id, string value, int sortOrder) - { - Id = id; - Value = value; - SortOrder = sortOrder; - } - - public PreValue(int id, string value) - { - Id = id; - Value = value; - } - - public PreValue(string value) - { - Value = value; - } - - /// - /// The value stored for the pre-value field - /// - public string Value { get; set; } - - /// - /// The database id for the pre-value field value - /// - public int Id { get; private set; } - - /// - /// The sort order stored for the pre-value field value - /// - public int SortOrder { get; private set; } - - public virtual object DeepClone() - { - //Memberwise clone on PreValue will work since it doesn't have any deep elements - var clone = (PreValue)MemberwiseClone(); - return clone; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PreValueCollection.cs b/src/Umbraco.Core/Models/PreValueCollection.cs deleted file mode 100644 index 449eaaa3b7..0000000000 --- a/src/Umbraco.Core/Models/PreValueCollection.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Core.Models -{ - /// - /// Represents the pre-value data for a DataType - /// - /// - /// Due to the legacy nature of the data that can be stored for pre-values, we have this class which encapsulates the 2 different - /// ways that pre-values are stored: A string array or a Dictionary. - /// - /// Most legacy property editors won't support the dictionary format but new property editors should always use the dictionary format. - /// In order to get overrideable pre-values working we need a dictionary since we'll have to reference a pre-value by a key. - /// - public class PreValueCollection : IDeepCloneable - { - private IDictionary _preValuesAsDictionary; - private IEnumerable _preValuesAsArray; - public IEnumerable PreValuesAsArray - { - get - { - if (_preValuesAsArray == null) - { - throw new InvalidOperationException("The current pre-value collection is dictionary based, use the PreValuesAsDictionary property instead"); - } - return _preValuesAsArray; - } - set { _preValuesAsArray = value; } - } - - public IDictionary PreValuesAsDictionary - { - get - { - if (_preValuesAsDictionary == null) - { - throw new InvalidOperationException("The current pre-value collection is array based, use the PreValuesAsArray property instead"); - } - return _preValuesAsDictionary; - } - set { _preValuesAsDictionary = value; } - } - - /// - /// Check if it is a dictionary based collection - /// - public bool IsDictionaryBased - { - get { return _preValuesAsDictionary != null; } - } - - public PreValueCollection(IEnumerable preVals) - { - _preValuesAsArray = preVals; - } - - public PreValueCollection(IDictionary preVals) - { - _preValuesAsDictionary = preVals; - } - - /// - /// Regardless of how the pre-values are stored this will return as a dictionary, it will convert an array based to a dictionary - /// - /// - public IDictionary FormatAsDictionary() - { - if (IsDictionaryBased) - { - return PreValuesAsDictionary; - } - - //it's an array so need to format it, the alias will just be an iteration - var result = new Dictionary(); - var asArray = PreValuesAsArray.ToArray(); - for (var i = 0; i < asArray.Length; i++) - { - result.Add(i.ToInvariantString(), asArray[i]); - } - return result; - } - - public object DeepClone() - { - var clone = (PreValueCollection) MemberwiseClone(); - if (_preValuesAsArray != null) - { - clone._preValuesAsArray = _preValuesAsArray.Select(x => (PreValue)x.DeepClone()).ToArray(); - } - if (_preValuesAsDictionary != null) - { - clone._preValuesAsDictionary = _preValuesAsDictionary.ToDictionary(x => x.Key, x => (PreValue)x.Value.DeepClone()); - } - - - return clone; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 5df2eaae90..9e3f5fc2e0 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -1,104 +1,120 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Collections; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { /// - /// A Property contains a single piece of data + /// Represents a property. /// [Serializable] [DataContract(IsReference = true)] - public class Property : Entity + public class Property : EntityBase { - private PropertyType _propertyType; - private Guid _version; - private object _value; - private readonly PropertyTags _tagSupport = new PropertyTags(); - - protected Property() - { - - } - - public Property(PropertyType propertyType) - { - _propertyType = propertyType; - } - - public Property(PropertyType propertyType, object value) - { - _propertyType = propertyType; - Value = value; - } - - public Property(int id, Guid version, PropertyType propertyType, object value) - { - Id = id; - _propertyType = propertyType; - _version = version; - Value = value; - } + private List _values = new List(); + private PropertyValue _pvalue; + private Dictionary _vvalues; private static readonly Lazy Ps = new Lazy(); + protected Property() + { } + + public Property(PropertyType propertyType) + { + PropertyType = propertyType; + } + + public Property(int id, PropertyType propertyType) + { + Id = id; + PropertyType = propertyType; + } + + public class PropertyValue + { + private string _segment; + + public int? LanguageId { get; internal set; } + public string Segment + { + get => _segment; + internal set => _segment = value?.ToLowerInvariant(); + } + public object EditedValue { get; internal set; } + public object PublishedValue { get; internal set; } + + public PropertyValue Clone() + => new PropertyValue { LanguageId = LanguageId, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue }; + } + + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { - public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo(x => x.Value); - public readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo(x => x.Version); + public readonly PropertyInfo ValuesSelector = ExpressionHelper.GetPropertyInfo(x => x.Values); public readonly DelegateEqualityComparer PropertyValueComparer = new DelegateEqualityComparer( (o, o1) => { if (o == null && o1 == null) return true; - //custom comparer for strings. + // custom comparer for strings. + // if one is null and another is empty then they are the same if (o is string || o1 is string) - { - //if one is null and another is empty then they are the same - if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) - { - return true; - } - if (o == null || o1 == null) return false; - return o.Equals(o1); - } + return ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) || (o != null && o1 != null && o.Equals(o1)); if (o == null || o1 == null) return false; - //Custom comparer for enumerable if it is enumerable - var enum1 = o as IEnumerable; - var enum2 = o1 as IEnumerable; - if (enum1 != null && enum2 != null) - { - return enum1.Cast().UnsortedSequenceEqual(enum2.Cast()); - } + // custom comparer for enumerable + // ReSharper disable once MergeCastWithTypeCheck + if (o is IEnumerable && o1 is IEnumerable) + return ((IEnumerable) o).Cast().UnsortedSequenceEqual(((IEnumerable) o1).Cast()); + return o.Equals(o1); }, o => o.GetHashCode()); } - + /// - /// Returns the instance of the tag support, by default tags are not enabled + /// Returns the PropertyType, which this Property is based on /// - internal PropertyTags TagSupport + [IgnoreDataMember] + public PropertyType PropertyType { get; private set; } + + /// + /// Gets the list of values. + /// + [DataMember] + public IReadOnlyCollection Values { - get { return _tagSupport; } + get => _values; + set + { + // make sure we filter out invalid variations + // make sure we leave _vvalues null if possible + _values = value.Where(x => PropertyType.ValidateVariation(x.LanguageId, x.Segment, false)).ToList(); + _pvalue = _values.FirstOrDefault(x => !x.LanguageId.HasValue && x.Segment == null); + _vvalues = _values.Count > (_pvalue == null ? 0 : 1) + ? _values.Where(x => x != _pvalue).ToDictionary(x => new CompositeIntStringKey(x.LanguageId, x.Segment), x => x) + : null; + } } /// /// Returns the Alias of the PropertyType, which this Property is based on /// [DataMember] - public string Alias { get { return _propertyType.Alias; } } + public string Alias => PropertyType.Alias; /// /// Returns the Id of the PropertyType, which this Property is based on /// [IgnoreDataMember] - internal int PropertyTypeId { get { return _propertyType.Id; } } + internal int PropertyTypeId => PropertyType.Id; /// /// Returns the DatabaseType that the underlaying DataType is using to store its values @@ -107,114 +123,267 @@ namespace Umbraco.Core.Models /// Only used internally when saving the property value. /// [IgnoreDataMember] - internal DataTypeDatabaseType DataTypeDatabaseType - { - get { return _propertyType.DataTypeDatabaseType; } - } + internal ValueStorageType ValueStorageType => PropertyType.ValueStorageType; /// - /// Returns the PropertyType, which this Property is based on + /// Gets the value. /// - [IgnoreDataMember] - public PropertyType PropertyType { get { return _propertyType; } } - - /// - /// Gets or Sets the version id for the Property - /// - /// - /// The version will be the same for all Property objects in a collection on a Content - /// object, so not sure how much this makes sense but adding it to align with: - /// umbraco.interfaces.IProperty - /// - [DataMember] - public Guid Version + public object GetValue(int? languageId = null, string segment = null, bool published = false) { - get { return _version; } - set { SetPropertyValueAndDetectChanges(value, ref _version, Ps.Value.VersionSelector); } + if (!PropertyType.ValidateVariation(languageId, segment, false)) return null; + if (!languageId.HasValue && segment == null) return GetPropertyValue(_pvalue, published); + if (_vvalues == null) return null; + return _vvalues.TryGetValue(new CompositeIntStringKey(languageId, segment), out var pvalue) + ? GetPropertyValue(pvalue, published) + : null; } - private static void ThrowTypeException(object value, Type expected, string alias) + private object GetPropertyValue(PropertyValue pvalue, bool published) { - throw new InvalidOperationException(string.Format("Value \"{0}\" of type \"{1}\" could not be converted" - + " to type \"{2}\" which is expected by property type \"{3}\".", - value, value.GetType(), expected, alias)); + if (pvalue == null) return null; + + return PropertyType.IsPublishing + ? (published ? pvalue.PublishedValue : pvalue.EditedValue) + : pvalue.EditedValue; } - /// - /// Gets or Sets the value of the Property - /// - /// - /// Setting the value will trigger a type validation. - /// The type of the value has to be valid in order to be saved. - /// - [DataMember] - public object Value + // internal - must be invoked by the content item + // does *not* validate the value - content item must validate first + internal void PublishAllValues() { - get { return _value; } - set + // if invariant-neutral is supported, publish invariant-neutral + if (PropertyType.ValidateVariation(null, null, false)) + PublishPropertyValue(_pvalue); + + // publish everything not invariant-neutral that is supported + if (_vvalues != null) { - var isOfExpectedType = _propertyType.IsPropertyTypeValid(value); - - if (isOfExpectedType == false) // isOfExpectedType is true if value is null - so if false, value is *not* null - { - // "garbage-in", accept what we can & convert - // throw only if conversion is not possible - - var s = value.ToString(); - - switch (_propertyType.DataTypeDatabaseType) - { - case DataTypeDatabaseType.Nvarchar: - case DataTypeDatabaseType.Ntext: - value = s; - break; - case DataTypeDatabaseType.Integer: - if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null - else - { - var convInt = value.TryConvertTo(); - if (convInt == false) ThrowTypeException(value, typeof(int), _propertyType.Alias); - value = convInt.Result; - } - break; - case DataTypeDatabaseType.Decimal: - if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null - else - { - var convDecimal = value.TryConvertTo(); - if (convDecimal == false) ThrowTypeException(value, typeof (decimal), _propertyType.Alias); - // need to normalize the value (change the scaling factor and remove trailing zeroes) - // because the underlying database is going to mess with the scaling factor anyways. - value = convDecimal.Result.Normalize(); - } - break; - case DataTypeDatabaseType.Date: - if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null - else - { - var convDateTime = value.TryConvertTo(); - if (convDateTime == false) ThrowTypeException(value, typeof (DateTime), _propertyType.Alias); - value = convDateTime.Result; - } - break; - } - } - - SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector, Ps.Value.PropertyValueComparer); + var pvalues = _vvalues + .Where(x => PropertyType.ValidateVariation(x.Value.LanguageId, x.Value.Segment, false)) + .Select(x => x.Value); + foreach (var pvalue in pvalues) + PublishPropertyValue(pvalue); } } - /// - /// Boolean indicating whether the current value is valid - /// - /// - /// A valid value implies that it is ready for publishing. - /// Invalid property values can be saved, but not published. - /// - /// True is property value is valid, otherwise false - public bool IsValid() + // internal - must be invoked by the content item + // does *not* validate the value - content item must validate first + internal void PublishValue(int? languageId = null, string segment = null) { - return IsValid(Value); + PropertyType.ValidateVariation(languageId, segment, true); + + (var pvalue, _) = GetPValue(languageId, segment, false); + if (pvalue == null) return; + PublishPropertyValue(pvalue); + } + + // internal - must be invoked by the content item + // does *not* validate the value - content item must validate first + internal void PublishCultureValues(int? languageId = null) + { + // if invariant and invariant-neutral is supported, publish invariant-neutral + if (!languageId.HasValue && PropertyType.ValidateVariation(null, null, false)) + PublishPropertyValue(_pvalue); + + // publish everything not invariant-neutral that matches the culture and is supported + if (_vvalues != null) + { + var pvalues = _vvalues + .Where(x => x.Value.LanguageId == languageId) + .Where(x => PropertyType.ValidateVariation(languageId, x.Value.Segment, false)) + .Select(x => x.Value); + foreach (var pvalue in pvalues) + PublishPropertyValue(pvalue); + } + } + + // internal - must be invoked by the content item + internal void ClearPublishedAllValues() + { + if (PropertyType.ValidateVariation(null, null, false)) + ClearPublishedPropertyValue(_pvalue); + + if (_vvalues != null) + { + var pvalues = _vvalues + .Where(x => PropertyType.ValidateVariation(x.Value.LanguageId, x.Value.Segment, false)) + .Select(x => x.Value); + foreach (var pvalue in pvalues) + ClearPublishedPropertyValue(pvalue); + } + } + + // internal - must be invoked by the content item + internal void ClearPublishedValue(int? languageId = null, string segment = null) + { + PropertyType.ValidateVariation(languageId, segment, true); + (var pvalue, _) = GetPValue(languageId, segment, false); + if (pvalue == null) return; + ClearPublishedPropertyValue(pvalue); + } + + // internal - must be invoked by the content item + internal void ClearPublishedCultureValues(int? languageId = null) + { + if (!languageId.HasValue && PropertyType.ValidateVariation(null, null, false)) + ClearPublishedPropertyValue(_pvalue); + + if (_vvalues != null) + { + var pvalues = _vvalues + .Where(x => x.Value.LanguageId == languageId) + .Where(x => PropertyType.ValidateVariation(languageId, x.Value.Segment, false)) + .Select(x => x.Value); + foreach (var pvalue in pvalues) + ClearPublishedPropertyValue(pvalue); + } + } + + private void PublishPropertyValue(PropertyValue pvalue) + { + if (pvalue == null) return; + + if (!PropertyType.IsPublishing) + throw new NotSupportedException("Property type does not support publishing."); + var origValue = pvalue.PublishedValue; + pvalue.PublishedValue = PropertyType.ConvertAssignedValue(pvalue.EditedValue); + DetectChanges(pvalue.EditedValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, false); + } + + private void ClearPublishedPropertyValue(PropertyValue pvalue) + { + if (pvalue == null) return; + + if (!PropertyType.IsPublishing) + throw new NotSupportedException("Property type does not support publishing."); + var origValue = pvalue.PublishedValue; + pvalue.PublishedValue = PropertyType.ConvertAssignedValue(null); + DetectChanges(pvalue.EditedValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, false); + } + + /// + /// Sets a value. + /// + public void SetValue(object value, int? languageId = null, string segment = null) + { + PropertyType.ValidateVariation(languageId, segment, true); + (var pvalue, var change) = GetPValue(languageId, segment, true); + + var origValue = pvalue.EditedValue; + var setValue = PropertyType.ConvertAssignedValue(value); + + pvalue.EditedValue = setValue; + + DetectChanges(setValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, change); + } + + // bypasses all changes detection and is the *only* way to set the published value + internal void FactorySetValue(int? languageId, string segment, bool published, object value) + { + (var pvalue, _) = GetPValue(languageId, segment, true); + + if (published && PropertyType.IsPublishing) + pvalue.PublishedValue = value; + else + pvalue.EditedValue = value; + } + + private (PropertyValue, bool) GetPValue(bool create) + { + var change = false; + if (_pvalue == null) + { + if (!create) return (null, false); + _pvalue = new PropertyValue(); + _values.Add(_pvalue); + change = true; + } + return (_pvalue, change); + } + + private (PropertyValue, bool) GetPValue(int? languageId, string segment, bool create) + { + if (!languageId.HasValue && segment == null) + return GetPValue(create); + + var change = false; + if (_vvalues == null) + { + if (!create) return (null, false); + _vvalues = new Dictionary(); + change = true; + } + var k = new CompositeIntStringKey(languageId, segment); + if (!_vvalues.TryGetValue(k, out var pvalue)) + { + if (!create) return (null, false); + pvalue = _vvalues[k] = new PropertyValue(); + pvalue.LanguageId = languageId; + pvalue.Segment = segment; + _values.Add(pvalue); + change = true; + } + return (pvalue, change); + } + + /// + /// Gets a value indicating whether everything is valid. + /// + /// + public bool IsAllValid() + { + // invariant-neutral is supported, validate invariant-neutral + // includes mandatory validation + if (PropertyType.ValidateVariation(null, null, false) && !IsValidValue(_pvalue)) return false; + + // either invariant-neutral is not supported, or it is valid + // for anything else, validate the existing values (including mandatory), + // but we cannot validate mandatory globally (we don't know the possible cultures and segments) + + if (_vvalues == null) return true; + + var pvalues = _vvalues + .Where(x => PropertyType.ValidateVariation(x.Value.LanguageId, x.Value.Segment, false)) + .Select(x => x.Value) + .ToArray(); + + return pvalues.Length == 0 || pvalues.All(x => IsValidValue(x.EditedValue)); + } + + /// + /// Gets a value indicating whether the culture/any values are valid. + /// + /// An invalid value can be saved, but only valid values can be published. + public bool IsCultureValid(int? languageId) + { + // culture-neutral is supported, validate culture-neutral + // includes mandatory validation + if (PropertyType.ValidateVariation(languageId, null, false) && !IsValidValue(GetValue(languageId))) + return false; + + // either culture-neutral is not supported, or it is valid + // for anything non-neutral, validate the existing values (including mandatory), + // but we cannot validate mandatory globally (we don't know the possible segments) + + if (_vvalues == null) return true; + + var pvalues = _vvalues + .Where(x => x.Value.LanguageId == languageId) + .Where(x => PropertyType.ValidateVariation(languageId, x.Value.Segment, false)) + .Select(x => x.Value) + .ToArray(); + + return pvalues.Length == 0 || pvalues.All(x => IsValidValue(x.EditedValue)); + } + + /// + /// Gets a value indicating whether the value is valid. + /// + /// An invalid value can be saved, but only valid values can be published. + public bool IsValid(int? languageId = null, string segment = null) + { + // single value -> validates mandatory + return IsValidValue(GetValue(languageId, segment)); } /// @@ -222,24 +391,26 @@ namespace Umbraco.Core.Models /// /// /// True is property value is valid, otherwise false - public bool IsValid(object value) + private bool IsValidValue(object value) { - return _propertyType.IsPropertyValueValid(value); + return PropertyType.IsPropertyValueValid(value); } public override object DeepClone() { - var clone = (Property)base.DeepClone(); + var clone = (Property) base.DeepClone(); + //turn off change tracking clone.DisableChangeTracking(); + //need to manually assign since this is a readonly property - clone._propertyType = (PropertyType)PropertyType.DeepClone(); - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); + clone.PropertyType = (PropertyType) PropertyType.DeepClone(); + //re-enable tracking + clone.ResetDirtyProperties(false); // not needed really, since we're not tracking clone.EnableChangeTracking(); - + return clone; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index efa5111ecb..3d02c13e6c 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -4,13 +4,11 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; -using System.Threading; -using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { /// - /// Represents a Collection of objects + /// Represents a collection of property values. /// [Serializable] [DataContract(IsReference = true)] @@ -18,24 +16,28 @@ namespace Umbraco.Core.Models { private readonly object _addLocker = new object(); internal Action OnAdd; - internal Func ValidateAdd { get; set; } + internal Func AdditionValidator { get; set; } + /// + /// Initializes a new instance of the class. + /// internal PropertyCollection() : base(StringComparer.InvariantCultureIgnoreCase) + { } + + /// + /// Initializes a new instance of the class. + /// + /// A function validating added properties. + internal PropertyCollection(Func additionValidator) + : this() { + AdditionValidator = additionValidator; } /// - /// Initializes a new instance of the class with a delegate responsible for validating the addition of instances. + /// Initializes a new instance of the class. /// - /// The validation callback. - /// - internal PropertyCollection(Func validationCallback) - : this() - { - ValidateAdd = validationCallback; - } - public PropertyCollection(IEnumerable properties) : this() { @@ -43,24 +45,28 @@ namespace Umbraco.Core.Models } /// - /// Resets the collection to only contain the instances referenced in the parameter, whilst maintaining - /// any validation delegates such as + /// Replaces all properties, whilst maintaining validation delegates. /// - /// The properties. - /// internal void Reset(IEnumerable properties) { Clear(); - properties.ForEach(Add); + foreach (var property in properties) + Add(property); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - protected override void SetItem(int index, Property item) + /// + /// Replaces the property at the specified index with the specified property. + /// + protected override void SetItem(int index, Property property) { - base.SetItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + base.SetItem(index, property); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property, index)); } + /// + /// Removes the property at the specified index. + /// protected override void RemoveItem(int index) { var removed = this[index]; @@ -68,70 +74,68 @@ namespace Umbraco.Core.Models OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } - protected override void InsertItem(int index, Property item) + /// + /// Inserts the specified property at the specified index. + /// + protected override void InsertItem(int index, Property property) { - base.InsertItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + base.InsertItem(index, property); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); } + /// + /// Removes all properties. + /// protected override void ClearItems() { base.ClearItems(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - internal new void Add(Property item) + /// + /// Adds a property. + /// + internal new void Add(Property property) { - lock (_addLocker) + lock (_addLocker) // fixme - why are we locking here and not everywhere else?! { - var key = GetKeyForItem(item); + var key = GetKeyForItem(property); if (key != null) { - var exists = this.Contains(key); - if (exists) + if (Contains(key)) { - //NOTE: Consider checking type before value is set: item.PropertyType.DataTypeId == property.PropertyType.DataTypeId - //Transfer the existing value to the new property - var property = this[key]; - if (item.Id == 0 && property.Id != 0) - { - item.Id = property.Id; - } - if (item.Value == null && property.Value != null) - { - item.Value = property.Value; - } + // transfer id and values if ... + var existing = this[key]; - SetItem(IndexOfKey(key), item); + if (property.Id == 0 && existing.Id != 0) + property.Id = existing.Id; + + if (property.Values.Count == 0 && existing.Values.Count > 0) + property.Values = existing.Values.Select(x => x.Clone()).ToList(); + + // replace existing with property and return, + // SetItem invokes OnCollectionChanged (but not OnAdd) + SetItem(IndexOfKey(key), property); return; } } - base.Add(item); - OnAdd.IfNotNull(x => x.Invoke());//Could this not be replaced by a Mandate/Contract for ensuring item is not null - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + base.Add(property); + + OnAdd?.Invoke(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); } } /// - /// Determines whether this collection contains a whose alias matches the specified PropertyType. + /// Gets the index for a specified property alias. /// - /// Alias of the PropertyType. - /// true if the collection contains the specified alias; otherwise, false. - /// - public new bool Contains(string propertyTypeAlias) - { - return base.Contains(propertyTypeAlias); - } - public int IndexOfKey(string key) { - for (var i = 0; i < this.Count; i++) + for (var i = 0; i < Count; i++) { if (this[i].Alias.InvariantEquals(key)) - { return i; - } } return -1; } @@ -142,13 +146,8 @@ namespace Umbraco.Core.Models } /// - /// Gets the element with the specified PropertyType. + /// Gets the property with the specified PropertyType. /// - /// - /// - /// The element with the specified PropertyType. If an element with the specified PropertyType is not found, an exception is thrown. - /// - /// The PropertyType of the element to get. is null.An element with the specified key does not exist in the collection. internal Property this[PropertyType propertyType] { get @@ -157,66 +156,63 @@ namespace Umbraco.Core.Models } } + public bool TryGetValue(string propertyTypeAlias, out Property property) + { + property = this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + return property != null; + } + + /// + /// Occurs when the collection changes. + /// public event NotifyCollectionChangedEventHandler CollectionChanged; protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { - if (CollectionChanged != null) - { - CollectionChanged(this, args); - } + CollectionChanged?.Invoke(this, args); } /// - /// Ensures that the collection contains Properties for the passed in PropertyTypes + /// Ensures that the collection contains properties for the specified property types. /// - /// List of PropertyType protected internal void EnsurePropertyTypes(IEnumerable propertyTypes) { - if (/*!this.Any() &&*/ propertyTypes != null) - { - foreach (var propertyType in propertyTypes) - { - Add(new Property(propertyType)); - } - } + if (propertyTypes == null) + return; + + foreach (var propertyType in propertyTypes) + Add(new Property(propertyType)); } /// - /// Ensures that the collection is cleared from PropertyTypes not in the list of passed in PropertyTypes + /// Ensures that the collection does not contain properties not in the specified property types. /// - /// List of PropertyType protected internal void EnsureCleanPropertyTypes(IEnumerable propertyTypes) { - if (propertyTypes != null) - { - //Remove PropertyTypes that doesn't exist in the list of new PropertyTypes - var aliases = this.Select(p => p.Alias).Except(propertyTypes.Select(x => x.Alias)).ToList(); - foreach (var alias in aliases) - { - Remove(alias); - } + if (propertyTypes == null) + return; - //Add new PropertyTypes from the list of passed in PropertyTypes - foreach (var propertyType in propertyTypes) - { - Add(new Property(propertyType)); - } - } + var propertyTypesA = propertyTypes.ToArray(); + + var thisAliases = this.Select(x => x.Alias); + var typeAliases = propertyTypesA.Select(x => x.Alias); + var remove = thisAliases.Except(typeAliases).ToArray(); + foreach (var alias in remove) + Remove(alias); + + foreach (var propertyType in propertyTypesA) + Add(new Property(propertyType)); } /// - /// Create a deep clone of this property collection + /// Deep clones. /// - /// public object DeepClone() { - var newList = new PropertyCollection(); - foreach (var p in this) - { - newList.Add((Property)p.DeepClone()); - } - return newList; + var clone = new PropertyCollection(); + foreach (var property in this) + clone.Add((Property) property.DeepClone()); + return clone; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PropertyExtensions.cs b/src/Umbraco.Core/Models/PropertyExtensions.cs deleted file mode 100644 index 11907e12a6..0000000000 --- a/src/Umbraco.Core/Models/PropertyExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Xml; -using System.Xml.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using umbraco.interfaces; - -namespace Umbraco.Core.Models -{ - public static class PropertyExtensions - { - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// Xml of the property and its value - public static XElement ToXml(this Property property) - { - var xmlSerializer = new EntityXmlSerializer(); - return xmlSerializer.Serialize(ApplicationContext.Current.Services.DataTypeService, property); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 68f6599e2e..02007c34fb 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Specialized; using System.Diagnostics; -using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -14,23 +13,24 @@ namespace Umbraco.Core.Models [Serializable] [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}, Name: {Name}")] - public class PropertyGroup : Entity, IEquatable + public class PropertyGroup : EntityBase, IEquatable { + private static readonly Lazy Ps = new Lazy(); + private string _name; private int _sortOrder; private PropertyTypeCollection _propertyTypes; - public PropertyGroup() : this(new PropertyTypeCollection()) - { - } + public PropertyGroup(bool isPublishing) + : this(new PropertyTypeCollection(isPublishing)) + { } public PropertyGroup(PropertyTypeCollection propertyTypeCollection) { PropertyTypes = propertyTypeCollection; } - private static readonly Lazy Ps = new Lazy(); - + // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); @@ -38,7 +38,7 @@ namespace Umbraco.Core.Models public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyTypes); } - void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) + private void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e) { OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); } @@ -49,8 +49,8 @@ namespace Umbraco.Core.Models [DataMember] public string Name { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } /// @@ -59,8 +59,8 @@ namespace Umbraco.Core.Models [DataMember] public int SortOrder { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } + get => _sortOrder; + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } /// @@ -69,17 +69,16 @@ namespace Umbraco.Core.Models [DataMember] public PropertyTypeCollection PropertyTypes { - get { return _propertyTypes; } + get => _propertyTypes; set { _propertyTypes = value; - //since we're adding this collection to this group, we need to ensure that all the lazy values are set. + // since we're adding this collection to this group, + // we need to ensure that all the lazy values are set. foreach (var propertyType in _propertyTypes) - { - propertyType.PropertyGroupId = new Lazy(() => this.Id); - } - + propertyType.PropertyGroupId = new Lazy(() => Id); + _propertyTypes.CollectionChanged += PropertyTypesChanged; } } @@ -87,22 +86,14 @@ namespace Umbraco.Core.Models public bool Equals(PropertyGroup other) { if (base.Equals(other)) return true; - - //Check whether the PropertyGroup's properties are equal. - return Name.InvariantEquals(other.Name); + return other != null && Name.InvariantEquals(other.Name); } public override int GetHashCode() { - //Get hash code for the Name field if it is not null. - int baseHash = base.GetHashCode(); - - //Get hash code for the Alias field. - int nameHash = Name.ToLowerInvariant().GetHashCode(); - - //Calculate the hash code for the product. + var baseHash = base.GetHashCode(); + var nameHash = Name.ToLowerInvariant().GetHashCode(); return baseHash ^ nameHash; } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index 399fa22be6..abec6febcb 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -5,7 +5,6 @@ using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; using System.Threading; -using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { @@ -17,13 +16,11 @@ namespace Umbraco.Core.Models public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); - + internal Action OnAdd; internal PropertyGroupCollection() - { - - } + { } public PropertyGroupCollection(IEnumerable groups) { @@ -38,7 +35,8 @@ namespace Umbraco.Core.Models internal void Reset(IEnumerable groups) { Clear(); - groups.ForEach(Add); + foreach (var group in groups) + Add(group); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } @@ -74,12 +72,12 @@ namespace Umbraco.Core.Models //Note this is done to ensure existig groups can be renamed if (item.HasIdentity && item.Id > 0) { - var exists = this.Contains(item.Id); + var exists = Contains(item.Id); if (exists) { - var keyExists = this.Contains(item.Name); - if(keyExists) - throw new Exception(string.Format("Naming conflict: Changing the name of PropertyGroup '{0}' would result in duplicates", item.Name)); + var keyExists = Contains(item.Name); + if (keyExists) + throw new Exception($"Naming conflict: Changing the name of PropertyGroup '{item.Name}' would result in duplicates"); SetItem(IndexOfKey(item.Id), item); return; @@ -90,7 +88,7 @@ namespace Umbraco.Core.Models var key = GetKeyForItem(item); if (key != null) { - var exists = this.Contains(key); + var exists = Contains(key); if (exists) { SetItem(IndexOfKey(key), item); @@ -98,9 +96,9 @@ namespace Umbraco.Core.Models } } } - + base.Add(item); - OnAdd.IfNotNull(x => x.Invoke());//Could this not be replaced by a Mandate/Contract for ensuring item is not null + OnAdd?.Invoke(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } @@ -132,25 +130,17 @@ namespace Umbraco.Core.Models public int IndexOfKey(string key) { - for (var i = 0; i < this.Count; i++) - { + for (var i = 0; i < Count; i++) if (this[i].Name == key) - { return i; - } - } return -1; } public int IndexOfKey(int id) { - for (var i = 0; i < this.Count; i++) - { + for (var i = 0; i < Count; i++) if (this[i].Id == id) - { return i; - } - } return -1; } @@ -163,20 +153,17 @@ namespace Umbraco.Core.Models protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { - if (CollectionChanged != null) - { - CollectionChanged(this, args); - } + CollectionChanged?.Invoke(this, args); } public object DeepClone() { - var newGroup = new PropertyGroupCollection(); - foreach (var p in this) + var clone = new PropertyGroupCollection(); + foreach (var group in this) { - newGroup.Add((PropertyGroup)p.DeepClone()); - } - return newGroup; + clone.Add((PropertyGroup) group.DeepClone()); + } + return clone; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PropertyTagBehavior.cs b/src/Umbraco.Core/Models/PropertyTagBehavior.cs deleted file mode 100644 index 0a435ebf95..0000000000 --- a/src/Umbraco.Core/Models/PropertyTagBehavior.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Umbraco.Core.Models -{ - internal enum PropertyTagBehavior - { - Replace, - Remove, - Merge - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyTagChange.cs b/src/Umbraco.Core/Models/PropertyTagChange.cs new file mode 100644 index 0000000000..5c911f4c30 --- /dev/null +++ b/src/Umbraco.Core/Models/PropertyTagChange.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + /// + /// A set of tag changes. + /// + internal class PropertyTagChange + { + public ChangeType Type { get; set; } + + public IEnumerable<(string Type, string Tags)> Tags { get; set; } + + public enum ChangeType + { + Replace, + Remove, + Merge + } + } +} diff --git a/src/Umbraco.Core/Models/PropertyTags.cs b/src/Umbraco.Core/Models/PropertyTags.cs deleted file mode 100644 index 6075502131..0000000000 --- a/src/Umbraco.Core/Models/PropertyTags.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Core.Models -{ - /// - /// A property extension class that allows us to enable tags for any given property - /// - internal class PropertyTags - { - public PropertyTags() - { - Enable = false; - Behavior = PropertyTagBehavior.Merge; - } - - /// - /// The behavior of how to save the tags assigned - - /// Merge (keep existing and append new), - /// Remove (remove any of the tags in the Tags property that are currently assigned, - /// Replace (replace the currently assigned tags with the ones specified) - /// - public PropertyTagBehavior Behavior { get; set; } - - /// - /// Flags the property to have tagging enabled - /// - public bool Enable { get; set; } - - /// - /// The actual tags to associate - tag/group - /// - public IEnumerable> Tags { get; set; } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs new file mode 100644 index 0000000000..3121da85bd --- /dev/null +++ b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Models +{ + /// + /// Provides extension methods for the class to manage tags. + /// + public static class PropertyTagsExtensions + { + // fixme inject? + private static PropertyEditorCollection PropertyEditors => Current.PropertyEditors; + private static IDataTypeService DataTypeService => Current.Services.DataTypeService; + + // gets the tag configuration for a property + // from the datatype configuration, and the editor tag configuration attribute + internal static TagConfiguration GetTagConfiguration(this Property property) + { + if (property == null) throw new ArgumentNullException(nameof(property)); + + var editor = PropertyEditors[property.PropertyType.PropertyEditorAlias]; + var tagAttribute = editor.GetTagAttribute(); + if (tagAttribute == null) return null; + + var configurationObject = DataTypeService.GetDataType(property.PropertyType.DataTypeId).Configuration; + var configuration = ConfigurationEditor.ConfigurationAs(configurationObject); + + if (configuration.Delimiter == default) + configuration.Delimiter = tagAttribute.Delimiter; + + return configuration; + } + + /// + /// Assign default tags. + /// + /// The property. + /// The tags. + /// A value indicating whether to merge the tags with existing tags instead of replacing them. + /// Tags do not support variants. + public static void AssignTags(this Property property, IEnumerable tags, bool merge = false) + { + if (property == null) throw new ArgumentNullException(nameof(property)); + + var configuration = property.GetTagConfiguration(); + if (configuration == null) + throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); + + property.AssignTags(tags, merge, configuration.StorageType, configuration.Delimiter); + } + + // assumes that parameters are consistent with the datatype configuration + internal static void AssignTags(this Property property, IEnumerable tags, bool merge, TagsStorageType storageType, char delimiter) + { + // set the property value + var trimmedTags = tags.Select(x => x.Trim()).ToArray(); + + if (merge) + { + var currentTags = property.GetTagsValue(storageType, delimiter); + + switch (storageType) + { + case TagsStorageType.Csv: + property.SetValue(string.Join(delimiter.ToString(), currentTags.Union(trimmedTags))); // csv string + break; + + case TagsStorageType.Json: + property.SetValue(JsonConvert.SerializeObject(currentTags.Union(trimmedTags).ToArray())); // json array + break; + } + } + else + { + switch (storageType) + { + case TagsStorageType.Csv: + property.SetValue(string.Join(delimiter.ToString(), trimmedTags)); // csv string + break; + + case TagsStorageType.Json: + property.SetValue(JsonConvert.SerializeObject(trimmedTags)); // json array + break; + } + } + } + + /// + /// Removes default tags. + /// + /// The property. + /// The tags. + /// Tags do not support variants. + public static void RemoveTags(this Property property, IEnumerable tags) + { + if (property == null) throw new ArgumentNullException(nameof(property)); + + var configuration = property.GetTagConfiguration(); + if (configuration == null) + throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); + + property.RemoveTags(tags, configuration.StorageType, configuration.Delimiter); + } + + // assumes that parameters are consistent with the datatype configuration + private static void RemoveTags(this Property property, IEnumerable tags, TagsStorageType storageType, char delimiter) + { + // already empty = nothing to do + //fixme doesn't take into account variants + var value = property.GetValue()?.ToString(); + if (string.IsNullOrWhiteSpace(value)) return; + + // set the property value + var trimmedTags = tags.Select(x => x.Trim()).ToArray(); + var currentTags = property.GetTagsValue(storageType, delimiter); + switch (storageType) + { + case TagsStorageType.Csv: + property.SetValue(string.Join(delimiter.ToString(), currentTags.Except(trimmedTags))); // csv string + break; + + case TagsStorageType.Json: + property.SetValue(JsonConvert.SerializeObject(currentTags.Except(trimmedTags).ToArray())); // json array + break; + } + } + + internal static IEnumerable GetTagsValue(this Property property) + { + if (property == null) throw new ArgumentNullException(nameof(property)); + + var configuration = property.GetTagConfiguration(); + if (configuration == null) + throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); + + return property.GetTagsValue(configuration.StorageType, configuration.Delimiter); + } + + internal static IEnumerable GetTagsValue(this Property property, TagsStorageType storageType, char delimiter) + { + if (property == null) throw new ArgumentNullException(nameof(property)); + + //fixme doesn't take into account variants + var value = property.GetValue()?.ToString(); + if (string.IsNullOrWhiteSpace(value)) return Enumerable.Empty(); + + switch (storageType) + { + case TagsStorageType.Csv: + return value.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); + + case TagsStorageType.Json: + //fixme doesn't take into account variants + return JsonConvert.DeserializeObject(property.GetValue().ToString()).Select(x => x.ToString().Trim()); + + default: + throw new NotSupportedException($"Value \"{storageType}\" is not a valid TagsStorageType."); + } + } + + /// + /// Sets tags on a content property, based on the property editor tags configuration. + /// + /// The property. + /// The property value. + /// The datatype configuration. + /// + /// The value is either a string (delimited string) or an enumeration of strings (tag list). + /// This is used both by the content repositories to initialize a property with some tag values, and by the + /// content controllers to update a property with values received from the property editor. + /// + internal static void SetTagsValue(this Property property, object value, TagConfiguration tagConfiguration) + { + if (property == null) throw new ArgumentNullException(nameof(property)); + if (tagConfiguration == null) throw new ArgumentNullException(nameof(tagConfiguration)); + + var merge = false; // fixme always! + var storageType = tagConfiguration.StorageType; + var delimiter = tagConfiguration.Delimiter; + + SetTagsValue(property, value, merge, storageType, delimiter); + } + + // assumes that parameters are consistent with the datatype configuration + // value can be an enumeration of string, or a serialized value using storageType format + // fixme merge always false here?! + private static void SetTagsValue(Property property, object value, bool merge, TagsStorageType storageType, char delimiter) + { + if (value == null) value = Enumerable.Empty(); + + // if value is already an enumeration of strings, just use it + if (value is IEnumerable tags1) + { + property.AssignTags(tags1, merge, storageType, delimiter); + return; + } + + // otherwise, deserialize value based upon storage type + switch (storageType) + { + case TagsStorageType.Csv: + var tags2 = value.ToString().Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries); + property.AssignTags(tags2, merge, storageType, delimiter); + break; + + case TagsStorageType.Json: + try + { + var tags3 = JsonConvert.DeserializeObject>(value.ToString()); + property.AssignTags(tags3 ?? Enumerable.Empty(), merge, storageType, delimiter); + } + catch (Exception ex) + { + Current.Logger.Warn(typeof(PropertyTagsExtensions), ex, "Could not automatically convert stored json value to an enumerable string"); + } + break; + + default: + throw new ArgumentOutOfRangeException(nameof(storageType)); + } + } + } +} diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 0aebcb6544..0ab30eabf6 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -1,411 +1,434 @@ using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Text.RegularExpressions; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Strings; namespace Umbraco.Core.Models { - /// - /// Defines the type of a object + /// Represents a property type. /// [Serializable] [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] - public class PropertyType : Entity, IEquatable - { - private readonly bool _isExplicitDbType; + public class PropertyType : EntityBase, IEquatable + { + //fixme this should be invariant by default but for demo purposes and until the UI is updated to support changing a property type we'll make this neutral by default + private const ContentVariation DefaultVaryBy = ContentVariation.CultureNeutral; + + private static PropertySelectors _selectors; + + private readonly bool _forceValueStorageType; private string _name; private string _alias; private string _description; - private int _dataTypeDefinitionId; + private int _dataTypeId; private Lazy _propertyGroupId; private string _propertyEditorAlias; - private DataTypeDatabaseType _dataTypeDatabaseType; + private ValueStorageType _valueStorageType; private bool _mandatory; - private string _helpText; private int _sortOrder; private string _validationRegExp; + private ContentVariation _variations; - public PropertyType(IDataTypeDefinition dataTypeDefinition) + /// + /// Initializes a new instance of the class. + /// + public PropertyType(IDataType dataType) { - if (dataTypeDefinition == null) throw new ArgumentNullException("dataTypeDefinition"); + if (dataType == null) throw new ArgumentNullException(nameof(dataType)); - if(dataTypeDefinition.HasIdentity) - _dataTypeDefinitionId = dataTypeDefinition.Id; + if(dataType.HasIdentity) + _dataTypeId = dataType.Id; - _propertyEditorAlias = dataTypeDefinition.PropertyEditorAlias; - _dataTypeDatabaseType = dataTypeDefinition.DatabaseType; - } - - public PropertyType(IDataTypeDefinition dataTypeDefinition, string propertyTypeAlias) - : this(dataTypeDefinition) - { - _alias = GetAlias(propertyTypeAlias); - } - - public PropertyType(string propertyEditorAlias, DataTypeDatabaseType dataTypeDatabaseType) - : this(propertyEditorAlias, dataTypeDatabaseType, false) - { - } - - public PropertyType(string propertyEditorAlias, DataTypeDatabaseType dataTypeDatabaseType, string propertyTypeAlias) - : this(propertyEditorAlias, dataTypeDatabaseType, false, propertyTypeAlias) - { + _propertyEditorAlias = dataType.EditorAlias; + _valueStorageType = dataType.DatabaseType; + _variations = DefaultVaryBy; } /// - /// Used internally to assign an explicity database type for this property type regardless of what the underlying data type/property editor is. + /// Initializes a new instance of the class. /// - /// - /// - /// - internal PropertyType(string propertyEditorAlias, DataTypeDatabaseType dataTypeDatabaseType, bool isExplicitDbType) + public PropertyType(IDataType dataType, string propertyTypeAlias) + : this(dataType) { - _isExplicitDbType = isExplicitDbType; - _propertyEditorAlias = propertyEditorAlias; - _dataTypeDatabaseType = dataTypeDatabaseType; + _alias = SanitizeAlias(propertyTypeAlias); } /// - /// Used internally to assign an explicity database type for this property type regardless of what the underlying data type/property editor is. + /// Initializes a new instance of the class. /// - /// - /// - /// - /// - internal PropertyType(string propertyEditorAlias, DataTypeDatabaseType dataTypeDatabaseType, bool isExplicitDbType, string propertyTypeAlias) + public PropertyType(string propertyEditorAlias, ValueStorageType valueStorageType) + : this(propertyEditorAlias, valueStorageType, false) + { } + + /// + /// Initializes a new instance of the class. + /// + public PropertyType(string propertyEditorAlias, ValueStorageType valueStorageType, string propertyTypeAlias) + : this(propertyEditorAlias, valueStorageType, false, propertyTypeAlias) + { } + + /// + /// Initializes a new instance of the class. + /// + /// Set to true to force the value storage type. Values assigned to + /// the property, eg from the underlying datatype, will be ignored. + internal PropertyType(string propertyEditorAlias, ValueStorageType valueStorageType, bool forceValueStorageType, string propertyTypeAlias = null) { - _isExplicitDbType = isExplicitDbType; _propertyEditorAlias = propertyEditorAlias; - _dataTypeDatabaseType = dataTypeDatabaseType; - _alias = GetAlias(propertyTypeAlias); + _valueStorageType = valueStorageType; + _forceValueStorageType = forceValueStorageType; + _alias = propertyTypeAlias == null ? null : SanitizeAlias(propertyTypeAlias); + _variations = DefaultVaryBy; } - private static readonly Lazy Ps = new Lazy(); + private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); private class PropertySelectors { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo(x => x.Alias); - public readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo(x => x.Description); - public readonly PropertyInfo DataTypeDefinitionIdSelector = ExpressionHelper.GetPropertyInfo(x => x.DataTypeDefinitionId); - public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); - public readonly PropertyInfo DataTypeDatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DataTypeDatabaseType); - public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); - public readonly PropertyInfo HelpTextSelector = ExpressionHelper.GetPropertyInfo(x => x.HelpText); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo ValidationRegExpSelector = ExpressionHelper.GetPropertyInfo(x => x.ValidationRegExp); - public readonly PropertyInfo PropertyGroupIdSelector = ExpressionHelper.GetPropertyInfo>(x => x.PropertyGroupId); + public readonly PropertyInfo Name = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo Alias = ExpressionHelper.GetPropertyInfo(x => x.Alias); + public readonly PropertyInfo Description = ExpressionHelper.GetPropertyInfo(x => x.Description); + public readonly PropertyInfo DataTypeId = ExpressionHelper.GetPropertyInfo(x => x.DataTypeId); + public readonly PropertyInfo PropertyEditorAlias = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); + public readonly PropertyInfo ValueStorageType = ExpressionHelper.GetPropertyInfo(x => x.ValueStorageType); + public readonly PropertyInfo Mandatory = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); + public readonly PropertyInfo SortOrder = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo ValidationRegExp = ExpressionHelper.GetPropertyInfo(x => x.ValidationRegExp); + public readonly PropertyInfo PropertyGroupId = ExpressionHelper.GetPropertyInfo>(x => x.PropertyGroupId); + public readonly PropertyInfo VaryBy = ExpressionHelper.GetPropertyInfo(x => x.Variations); } /// - /// Gets of Sets the Name of the PropertyType + /// Gets a value indicating whether the content type, owning this property type, is publishing. + /// + public bool IsPublishing { get; internal set; } + + /// + /// Gets of sets the name of the property type. /// [DataMember] public string Name { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, Selectors.Name); } /// - /// Gets of Sets the Alias of the PropertyType + /// Gets of sets the alias of the property type. /// [DataMember] public string Alias { - get { return _alias; } - set { SetPropertyValueAndDetectChanges(GetAlias(value), ref _alias, Ps.Value.AliasSelector); } + get => _alias; + set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias, Selectors.Alias); } /// - /// Gets of Sets the Description for the PropertyType + /// Gets of sets the description of the property type. /// [DataMember] public string Description { - get { return _description; } - set { SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); } + get => _description; + set => SetPropertyValueAndDetectChanges(value, ref _description, Selectors.Description); } /// - /// Gets of Sets the Id of the DataType (Definition), which the PropertyType is "wrapping" + /// Gets or sets the identifier of the datatype for this property type. /// - /// This is actually the Id of the [DataMember] - public int DataTypeDefinitionId + public int DataTypeId { - get { return _dataTypeDefinitionId; } - set { SetPropertyValueAndDetectChanges(value, ref _dataTypeDefinitionId, Ps.Value.DataTypeDefinitionIdSelector); } + get => _dataTypeId; + set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, Selectors.DataTypeId); } + /// + /// Gets or sets the alias of the property editor for this property type. + /// [DataMember] public string PropertyEditorAlias { - get { return _propertyEditorAlias; } - set { SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); } + get => _propertyEditorAlias; + set => SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Selectors.PropertyEditorAlias); } /// - /// Gets of Sets the Id of the DataType control - /// - /// This is the Id of the actual DataType control - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead. This method will return a generated GUID for any property editor alias not explicitly mapped to a legacy ID")] - public Guid DataTypeId - { - get - { - return LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias( - _propertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.GenerateId).Value; - } - set - { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(value, true); - PropertyEditorAlias = alias; - } - } - - /// - /// Gets or Sets the DatabaseType for which the DataType's value is saved as + /// Gets or sets the database type for storing value for this property type. /// [DataMember] - internal DataTypeDatabaseType DataTypeDatabaseType + internal ValueStorageType ValueStorageType { - get { return _dataTypeDatabaseType; } + get => _valueStorageType; set { - //don't allow setting this if an explicit declaration has been made in the ctor - if (_isExplicitDbType) return; - - SetPropertyValueAndDetectChanges(value, ref _dataTypeDatabaseType, Ps.Value.DataTypeDatabaseTypeSelector); + if (_forceValueStorageType) return; // ignore changes + SetPropertyValueAndDetectChanges(value, ref _valueStorageType, Selectors.ValueStorageType); } } /// - /// Gets or sets the identifier of the PropertyGroup this PropertyType belongs to. + /// Gets or sets the identifier of the property group this property type belongs to. /// /// For generic properties, the value is null. [DataMember] internal Lazy PropertyGroupId { - get { return _propertyGroupId; } - set { SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, Ps.Value.PropertyGroupIdSelector); } + get => _propertyGroupId; + set => SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, Selectors.PropertyGroupId); } /// - /// Gets of Sets the Boolean indicating whether a value for this PropertyType is required + /// Gets of sets a value indicating whether a value for this property type is required. /// [DataMember] public bool Mandatory { - get { return _mandatory; } - set { SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector); } + get => _mandatory; + set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Selectors.Mandatory); } /// - /// Gets of Sets the Help text for the current PropertyType - /// - [DataMember] - [Obsolete("Not used anywhere, will be removed in future versions")] - public string HelpText - { - get { return _helpText; } - set { SetPropertyValueAndDetectChanges(value, ref _helpText, Ps.Value.HelpTextSelector); } - } - - /// - /// Gets of Sets the Sort order of the PropertyType, which is used for sorting within a group + /// Gets of sets the sort order of the property type. /// [DataMember] public int SortOrder { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } + get => _sortOrder; + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Selectors.SortOrder); } /// - /// Gets or Sets the RegEx for validation of legacy DataTypes + /// Gets or sets the regular expression validating the property values. /// [DataMember] public string ValidationRegExp { - get { return _validationRegExp; } - set { SetPropertyValueAndDetectChanges(value, ref _validationRegExp, Ps.Value.ValidationRegExpSelector); } - } - - private string GetAlias(string value) - { - //NOTE: WE are doing this because we don't want to do a ToSafeAlias when the alias is the special case of - // being prefixed with Constants.PropertyEditors.InternalGenericPropertiesPrefix - // which is used internally - - return value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) - ? value - : value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); + get => _validationRegExp; + set => SetPropertyValueAndDetectChanges(value, ref _validationRegExp, Selectors.ValidationRegExp); } /// - /// Create a new Property object from a "raw" database value. + /// Gets or sets the content variation of the property type. /// - /// Can be used for the "old" values where no serialization type exists - /// - /// - /// - /// - internal Property CreatePropertyFromRawValue(object value, Guid version, int id) + public ContentVariation Variations { - return new Property(id, version, this, value); + get => _variations; + set => SetPropertyValueAndDetectChanges(value, ref _variations, Selectors.VaryBy); } /// - /// Create a new Property object from a "raw" database value. - /// In some cases the value will need to be deserialized. + /// Validates that a variation is valid for the property type. /// - /// - /// - /// - internal Property CreatePropertyFromRawValue(object value, string serializationType) + public bool ValidateVariation(int? languageId, string segment, bool throwIfInvalid) { - //The value from the db needs to be deserialized and then added to the property - //if its not a simple type (Integer, Date, Nvarchar, Ntext) - /*if (DataTypeDatabaseType == DataTypeDatabaseType.Object) + ContentVariation variation; + if (languageId.HasValue) { - Type type = Type.GetType(serializationType); - var stream = new MemoryStream(Encoding.UTF8.GetBytes(value.ToString())); - var objValue = _service.FromStream(stream, type); - return new Property(this, objValue); - }*/ - - return new Property(this, value); + variation = segment != null + ? ContentVariation.CultureSegment + : ContentVariation.CultureNeutral; + } + else if (segment != null) + { + variation = ContentVariation.InvariantSegment; + } + else + { + variation = ContentVariation.InvariantNeutral; + } + if ((Variations & variation) == 0) + { + if (throwIfInvalid) + throw new NotSupportedException($"Variation {variation} is invalid for property type \"{Alias}\"."); + return false; + } + return true; } /// - /// Create a new Property object that conforms to the Type of the DataType - /// and can be validated according to DataType validation / Mandatory-check. + /// Creates a new property of this property type. /// - /// - /// - public Property CreatePropertyFromValue(object value) + public Property CreateProperty() { - //Note that validation will occur when setting the value on the Property - return new Property(this, value); + return new Property(this); } /// - /// Gets a value indicating whether the value is of the expected type - /// for the property, and can be assigned to the property "as is". + /// Determines whether a value is of the expected type for this property type. /// - /// The value. - /// True if the value is of the expected type for the property, - /// and can be assigned to the property "as is". Otherwise, false, to indicate - /// that some conversion is required. - public bool IsPropertyTypeValid(object value) + /// + /// If the value is of the expected type, it can be directly assigned to the property. + /// Otherwise, some conversion is required. + /// + public bool IsOfExpectedPropertyType(object value) { // null values are assumed to be ok if (value == null) return true; // check if the type of the value matches the type from the DataType/PropertyEditor + // then it can be directly assigned, anything else requires conversion var valueType = value.GetType(); - - //TODO Add PropertyEditor Type validation when its relevant to introduce - /*bool isEditorModel = value is IEditorModel; - if (isEditorModel && DataTypeControlId != Guid.Empty) + switch (ValueStorageType) { - //Find PropertyEditor by Id - var propertyEditor = PropertyEditorResolver.Current.GetById(DataTypeControlId); - - if (propertyEditor == null) - return false;//Throw exception instead? - - //Get the generic parameter of the PropertyEditor and check it against the type of the passed in (object) value - Type argument = propertyEditor.GetType().BaseType.GetGenericArguments()[0]; - return argument == type; - }*/ - - if (PropertyEditorAlias.IsNullOrWhiteSpace() == false) // fixme - always true? - { - // simple validation using the DatabaseType from the DataTypeDefinition - // and the Type of the passed in value - switch (DataTypeDatabaseType) - { - // fixme breaking! - case DataTypeDatabaseType.Integer: - return valueType == typeof(int); - case DataTypeDatabaseType.Decimal: - return valueType == typeof(decimal); - case DataTypeDatabaseType.Date: - return valueType == typeof(DateTime); - case DataTypeDatabaseType.Nvarchar: - return valueType == typeof(string); - case DataTypeDatabaseType.Ntext: - return valueType == typeof(string); - } + case ValueStorageType.Integer: + return valueType == typeof(int); + case ValueStorageType.Decimal: + return valueType == typeof(decimal); + case ValueStorageType.Date: + return valueType == typeof(DateTime); + case ValueStorageType.Nvarchar: + return valueType == typeof(string); + case ValueStorageType.Ntext: + return valueType == typeof(string); + default: + throw new NotSupportedException($"Not supported storage type \"{ValueStorageType}\"."); } - - // fixme - never reached + makes no sense? - // fallback for simple value types when no Control Id or Database Type is set - if (valueType.IsPrimitive || value is string) - return true; - - return false; } /// - /// Validates the Value from a Property according to the validation settings + /// Determines whether a value can be assigned to a property. /// - /// - /// True if valid, otherwise false - public bool IsPropertyValueValid(object value) + public bool IsValueAssignable(object value) => TryConvertAssignedValue(value, false, out _); + + /// + /// Converts a value assigned to a property. + /// + /// + /// The input value can be pretty much anything, and is converted to the actual Clr type + /// expected by the property (eg an integer if the property values are integers). + /// Throws if the value cannot be converted. + /// + public object ConvertAssignedValue(object value) => TryConvertAssignedValue(value, true, out var converted) ? converted : null; + + /// + /// Tries to convert a value assigned to a property. + /// + /// + /// + /// + public bool TryConvertAssignedValue(object value, out object converted) => TryConvertAssignedValue(value, false, out converted); + + private bool TryConvertAssignedValue(object value, bool throwOnError, out object converted) { - //If the Property is mandatory and value is null or empty, return false as the validation failed - if (Mandatory && (value == null || string.IsNullOrEmpty(value.ToString()))) - return false; - - //Check against Regular Expression for Legacy DataTypes - Validation exists and value is not null: - if(string.IsNullOrEmpty(ValidationRegExp) == false && (value != null && string.IsNullOrEmpty(value.ToString()) == false)) + var isOfExpectedType = IsOfExpectedPropertyType(value); + if (isOfExpectedType) { - try - { - var regexPattern = new Regex(ValidationRegExp); - return regexPattern.IsMatch(value.ToString()); - } - catch - { - throw new Exception(string .Format("Invalid validation expression on property {0}",this.Alias)); - } - + converted = value; + return true; } - //TODO: We must ensure that the property value can actually be saved based on the specified database type + // isOfExpectedType is true if value is null - so if false, value is *not* null + // "garbage-in", accept what we can & convert + // throw only if conversion is not possible - //TODO Add PropertyEditor validation when its relevant to introduce - /*if (value is IEditorModel && DataTypeControlId != Guid.Empty) + var s = value.ToString(); + converted = null; + + switch (ValueStorageType) { - //Find PropertyEditor by Id - var propertyEditor = PropertyEditorResolver.Current.GetById(DataTypeControlId); + case ValueStorageType.Nvarchar: + case ValueStorageType.Ntext: + { + converted = s; + return true; + } - //TODO Get the validation from the PropertyEditor if a validation attribute exists - //Will probably need to reflect the PropertyEditor in order to apply the validation - }*/ + case ValueStorageType.Integer: + if (s.IsNullOrWhiteSpace()) + return true; // assume empty means null + var convInt = value.TryConvertTo(); + if (convInt) + { + converted = convInt.Result; + return true; + } + if (throwOnError) + ThrowTypeException(value, typeof(int), Alias); + return false; - return true; + case ValueStorageType.Decimal: + if (s.IsNullOrWhiteSpace()) + return true; // assume empty means null + var convDecimal = value.TryConvertTo(); + if (convDecimal) + { + // need to normalize the value (change the scaling factor and remove trailing zeroes) + // because the underlying database is going to mess with the scaling factor anyways. + converted = convDecimal.Result.Normalize(); + return true; + } + if (throwOnError) + ThrowTypeException(value, typeof(decimal), Alias); + return false; + + case ValueStorageType.Date: + if (s.IsNullOrWhiteSpace()) + return true; // assume empty means null + var convDateTime = value.TryConvertTo(); + if (convDateTime) + { + converted = convDateTime.Result; + return true; + } + if (throwOnError) + ThrowTypeException(value, typeof(DateTime), Alias); + return false; + + default: + throw new NotSupportedException($"Not supported storage type \"{ValueStorageType}\"."); + } } + private static void ThrowTypeException(object value, Type expected, string alias) + { + throw new InvalidOperationException($"Cannot assign value \"{value}\" of type \"{value.GetType()}\" to property \"{alias}\" expecting type \"{expected}\"."); + } + + + //fixme - perhaps this and other validation methods should be a service level (not a model) thing? + /// + /// Determines whether a value is valid for this property type. + /// + public bool IsPropertyValueValid(object value) + { + var editor = Current.PropertyEditors[_propertyEditorAlias]; // fixme inject? + var configuration = Current.Services.DataTypeService.GetDataType(_dataTypeId).Configuration; // fixme inject? + var valueEditor = editor.GetValueEditor(configuration); + return !valueEditor.Validate(value, Mandatory, ValidationRegExp).Any(); + } + + /// + /// Sanitizes a property type alias. + /// + private static string SanitizeAlias(string value) + { + //NOTE: WE are doing this because we don't want to do a ToSafeAlias when the alias is the special case of + // being prefixed with Constants.PropertyEditors.InternalGenericPropertiesPrefix + // which is used internally + + return value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) + ? value + : value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); + } + + /// public bool Equals(PropertyType other) { - if (base.Equals(other)) return true; - - //Check whether the PropertyType's properties are equal. - return Alias.InvariantEquals(other.Alias); + return other != null && (base.Equals(other) || Alias.InvariantEquals(other.Alias)); } + /// public override int GetHashCode() { //Get hash code for the Name field if it is not null. @@ -418,6 +441,7 @@ namespace Umbraco.Core.Models return baseHash ^ hashAlias; } + /// public override object DeepClone() { var clone = (PropertyType)base.DeepClone(); @@ -436,4 +460,4 @@ namespace Umbraco.Core.Models return clone; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index a06e6d737d..4788221854 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -5,12 +5,11 @@ using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; using System.Threading; -using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { /// - /// Represents a collection of objects + /// Represents a collection of objects. /// [Serializable] [DataContract] @@ -22,16 +21,19 @@ namespace Umbraco.Core.Models [IgnoreDataMember] internal Action OnAdd; - internal PropertyTypeCollection() + internal PropertyTypeCollection(bool isPublishing) { - + IsPublishing = isPublishing; } - public PropertyTypeCollection(IEnumerable properties) + public PropertyTypeCollection(bool isPublishing, IEnumerable properties) + : this(isPublishing) { Reset(properties); } + public bool IsPublishing { get; } + /// /// Resets the collection to only contain the instances referenced in the parameter. /// @@ -40,12 +42,14 @@ namespace Umbraco.Core.Models internal void Reset(IEnumerable properties) { Clear(); - properties.ForEach(Add); + foreach (var property in properties) + Add(property); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } protected override void SetItem(int index, PropertyType item) { + item.IsPublishing = IsPublishing; base.SetItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } @@ -59,6 +63,7 @@ namespace Umbraco.Core.Models protected override void InsertItem(int index, PropertyType item) { + item.IsPublishing = IsPublishing; base.InsertItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } @@ -72,12 +77,15 @@ namespace Umbraco.Core.Models //TODO: Instead of 'new' this should explicitly implement one of the collection interfaces members internal new void Add(PropertyType item) { + item.IsPublishing = IsPublishing; + + // fixme redo this entirely!!! using (new WriteLock(_addLocker)) { var key = GetKeyForItem(item); if (key != null) { - var exists = this.Contains(key); + var exists = Contains(key); if (exists) { SetItem(IndexOfKey(key), item); @@ -85,7 +93,7 @@ namespace Umbraco.Core.Models } } - //check if the item's sort order is already in use + //check if the item's sort order is already in use if (this.Any(x => x.SortOrder == item.SortOrder)) { //make it the next iteration @@ -93,7 +101,7 @@ namespace Umbraco.Core.Models } base.Add(item); - OnAdd.IfNotNull(x => x.Invoke());//Could this not be replaced by a Mandate/Contract for ensuring item is not null + OnAdd?.Invoke(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } @@ -113,20 +121,14 @@ namespace Umbraco.Core.Models public void RemoveItem(string propertyTypeAlias) { var key = IndexOfKey(propertyTypeAlias); - //Only removes an item if the key was found - if(key != -1) - RemoveItem(key); + if (key != -1) RemoveItem(key); } public int IndexOfKey(string key) { - for (var i = 0; i < this.Count; i++) - { + for (var i = 0; i < Count; i++) if (this[i].Alias == key) - { return i; - } - } return -1; } @@ -139,20 +141,15 @@ namespace Umbraco.Core.Models protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { - if (CollectionChanged != null) - { - CollectionChanged(this, args); - } + CollectionChanged?.Invoke(this, args); } public object DeepClone() { - var newGroup = new PropertyTypeCollection(); - foreach (var p in this) - { - newGroup.Add((PropertyType)p.DeepClone()); - } - return newGroup; + var clone = new PropertyTypeCollection(IsPublishing); + foreach (var propertyType in this) + clone.Add((PropertyType) propertyType.DeepClone()); + return clone; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index 5989f885cb..a9f568e43a 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -1,17 +1,17 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { [Serializable] [DataContract(IsReference = true)] - public class PublicAccessEntry : Entity, IAggregateRoot + public class PublicAccessEntry : EntityBase { private readonly ObservableCollection _ruleCollection; private int _protectedNodeId; @@ -27,6 +27,9 @@ namespace Umbraco.Core.Models _ruleCollection = new ObservableCollection(ruleCollection); _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; + + foreach (var rule in _ruleCollection) + rule.AccessEntryId = Key; } public PublicAccessEntry(Guid id, int protectedNodeId, int loginNodeId, int noAccessNodeId, IEnumerable ruleCollection) @@ -40,6 +43,9 @@ namespace Umbraco.Core.Models _ruleCollection = new ObservableCollection(ruleCollection); _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; + + foreach (var rule in _ruleCollection) + rule.AccessEntryId = Key; } void _ruleCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -137,14 +143,14 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _protectedNodeId, Ps.Value.ProtectedNodeIdSelector); } } - public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + public override void ResetDirtyProperties(bool rememberDirty) { _removedRules.Clear(); - base.ResetDirtyProperties(rememberPreviouslyChangedProperties); + base.ResetDirtyProperties(rememberDirty); foreach (var publicAccessRule in _ruleCollection) { - publicAccessRule.ResetDirtyProperties(rememberPreviouslyChangedProperties); + publicAccessRule.ResetDirtyProperties(rememberDirty); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PublicAccessRule.cs b/src/Umbraco.Core/Models/PublicAccessRule.cs index ed2cbdbd48..67b9ece2f9 100644 --- a/src/Umbraco.Core/Models/PublicAccessRule.cs +++ b/src/Umbraco.Core/Models/PublicAccessRule.cs @@ -1,13 +1,13 @@ -using System; +using System; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { [Serializable] [DataContract(IsReference = true)] - public class PublicAccessRule : Entity + public class PublicAccessRule : EntityBase { private string _ruleValue; private string _ruleType; @@ -30,7 +30,7 @@ namespace Umbraco.Core.Models public readonly PropertyInfo RuleValueSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleValue); public readonly PropertyInfo RuleTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.RuleType); } - + public Guid AccessEntryId { get; internal set; } public string RuleValue @@ -45,6 +45,6 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _ruleType, Ps.Value.RuleTypeSelector); } } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs new file mode 100644 index 0000000000..f114d94116 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// + /// Represents a cached content. + /// + /// + /// SD: A replacement for INode which needs to occur since INode doesn't contain the document type alias + /// and INode is poorly formatted with mutable properties (i.e. Lists instead of IEnumerable). + /// Stephan: initially, that was for cached published content only. Now, we're using it also for + /// cached preview (so, maybe unpublished) content. A better name would therefore be ICachedContent, as + /// has been suggested. However, can't change now. Maybe in v7? + /// + public interface IPublishedContent : IPublishedElement + { + #region Content + + // fixme - all these are colliding with models => ? + // or could we force them to be 'new' in models? + + int Id { get; } + int TemplateId { get; } + int SortOrder { get; } + string Name { get; } + string UrlName { get; } // fixme rename + string DocumentTypeAlias { get; } // fixme obsolete + int DocumentTypeId { get; } // fixme obsolete + string WriterName { get; } + string CreatorName { get; } + int WriterId { get; } + int CreatorId { get; } + string Path { get; } + DateTime CreateDate { get; } + DateTime UpdateDate { get; } + int Level { get; } + string Url { get; } + + /// + /// Gets a value indicating whether the content is a content (aka a document) or a media. + /// + PublishedItemType ItemType { get; } + + /// + /// Gets a value indicating whether the content is draft. + /// + /// A content is draft when it is the unpublished version of a content, which may + /// have a published version, or not. + bool IsDraft { get; } + + #endregion + + #region Tree + + /// + /// Gets the parent of the content. + /// + /// The parent of root content is null. + IPublishedContent Parent { get; } + + /// + /// Gets the children of the content. + /// + /// Children are sorted by their sortOrder. + IEnumerable Children { get; } + + #endregion + + #region Properties + + /// + /// Gets a property identified by its alias. + /// + /// The property alias. + /// A value indicating whether to navigate the tree upwards until a property with a value is found. + /// The property identified by the alias. + /// + /// Navigate the tree upwards and look for a property with that alias and with a value (ie HasValue is true). + /// If found, return the property. If no property with that alias is found, having a value or not, return null. Otherwise + /// return the first property that was found with the alias but had no value (ie HasValue is false). + /// The alias is case-insensitive. + /// + IPublishedProperty GetProperty(string alias, bool recurse); + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentExtended.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentExtended.cs deleted file mode 100644 index 4446a21329..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentExtended.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Provides methods to handle extended content. - /// - internal interface IPublishedContentExtended : IPublishedContent - { - /// - /// Adds a property to the extended content. - /// - /// The property to add. - void AddProperty(IPublishedProperty property); - - /// - /// Gets a value indicating whether properties were added to the extended content. - /// - bool HasAddedProperties { get; } - - /// - /// Sets the content set of the extended content. - /// - /// - void SetContentSet(IEnumerable contentSet); - - /// - /// Resets the content set of the extended content. - /// - void ClearContentSet(); - - /// - /// Sets the index of the extended content. - /// - /// The index value. - void SetIndex(int value); - - /// - /// Resets the index of the extended content. - /// - void ClearIndex(); - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentModelFactory.cs deleted file mode 100644 index e2134c952e..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentModelFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Provides the model creation service. - /// - public interface IPublishedContentModelFactory - { - /// - /// Creates a strongly-typed model representing a published content. - /// - /// The original published content. - /// The strongly-typed model representing the published content, or the published content - /// itself it the factory has no model for that content type. - IPublishedContent CreateModel(IPublishedContent content); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs new file mode 100644 index 0000000000..e75e8a4eb9 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs @@ -0,0 +1,47 @@ +namespace Umbraco. Core.Models.PublishedContent +{ + /// + /// Creates published content types. + /// + public interface IPublishedContentTypeFactory + { + /// + /// Creates a published content type. + /// + /// An content type. + /// A published content type corresponding to the item type and content type. + PublishedContentType CreateContentType(IContentTypeComposition contentType); + + /// + /// Creates a published property type. + /// + /// The published content type owning the property. + /// A property type. + /// Is used by constructor to create property types. + PublishedPropertyType CreatePropertyType(PublishedContentType contentType, PropertyType propertyType); + + /// + /// Creates a published property type. + /// + /// The published content type owning the property. + /// The property type alias. + /// The datatype identifier. + /// The variations. + /// Is used by constructor to create special property types. + PublishedPropertyType CreatePropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations); + + /// + /// Gets a published datatype. + /// + PublishedDataType GetDataType(int id); + + /// + /// Notifies the factory of datatype changes. + /// + /// + /// This is so the factory can flush its caches. + /// Invoked by the IPublishedSnapshotService. + /// + void NotifyDataTypeChanges(int[] ids); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs new file mode 100644 index 0000000000..6cd4818587 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents a published element. + /// + public interface IPublishedElement + { + #region ContentType + + /// + /// Gets the content type. + /// + PublishedContentType ContentType { get; } + + #endregion + + #region PublishedElement + + /// + /// Gets the unique key of the published snapshot item. + /// + Guid Key { get; } + + #endregion + + #region Properties + + /// + /// Gets the properties of the element. + /// + /// Contains one IPublishedProperty for each property defined for the content type, including + /// inherited properties. Some properties may have no value. + IEnumerable Properties { get; } + + /// + /// Gets a property identified by its alias. + /// + /// The property alias. + /// The property identified by the alias. + /// + /// If the content type has no property with that alias, including inherited properties, returns null, + /// otherwise return a property -- that may have no value (ie HasValue is false). + /// The alias is case-insensitive. + /// + IPublishedProperty GetProperty(string alias); + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs new file mode 100644 index 0000000000..25d9643bed --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides the published model creation service. + /// + public interface IPublishedModelFactory + { + /// + /// Creates a strongly-typed model representing a published element. + /// + /// The original published element. + /// The strongly-typed model representing the published element, or the published element + /// itself it the factory has no model for the corresponding element type. + IPublishedElement CreateModel(IPublishedElement element); + + /// + /// Creates a List{T} of a strongly-typed model for a model type alias. + /// + /// The model type alias. + /// A List{T} of the strongly-typed model, exposed as an IList. + IList CreateModelList(string alias); + + /// + /// Maps a Clr type that may contain model types, to an actual Clr type. + /// + /// The Clr type. + /// The actual Clr type. + /// See for more details. + Type MapModelType(Type type); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs new file mode 100644 index 0000000000..24c654604c --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs @@ -0,0 +1,60 @@ +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents a property of an IPublishedElement. + /// + public interface IPublishedProperty + { + /// + /// Gets the alias of the property. + /// + string Alias { get; } + + /// + /// Gets a value indicating whether the property has a value. + /// + /// + /// This is somewhat implementation-dependent -- depending on whatever IPublishedCache considers + /// a missing value. + /// The XmlPublishedCache raw values are strings, and it will consider missing, null or empty (and + /// that includes whitespace-only) strings as "no value". + /// Other caches that get their raw value from the database would consider that a property has "no + /// value" if it is missing, null, or an empty string (including whitespace-only). + /// + bool HasValue(int? languageId = null, string segment = null); + + /// + /// Gets the source value of the property. + /// + /// + /// The source value is whatever was passed to the property when it was instanciated, and it is + /// somewhat implementation-dependent -- depending on how the IPublishedCache is implemented. + /// The XmlPublishedCache source values are strings exclusively since they come from the Xml cache. + /// For other caches that get their source value from the database, it would be either a string, + /// an integer (Int32), a date and time (DateTime) or a decimal (double). + /// If you're using that value, you're probably wrong, unless you're doing some internal + /// Umbraco stuff. + /// + object GetSourceValue(int? languageId = null, string segment = null); + + /// + /// Gets the object value of the property. + /// + /// + /// The value is what you want to use when rendering content in an MVC view ie in C#. + /// It can be null, or any type of CLR object. + /// It has been fully prepared and processed by the appropriate converter. + /// + object GetValue(int? languageId = null, string segment = null); + + /// + /// Gets the XPath value of the property. + /// + /// + /// The XPath value is what you want to use when navigating content via XPath eg in the XSLT engine. + /// It must be either null, or a string, or an XPathNavigator. + /// It has been fully prepared and processed by the appropriate converter. + /// + object GetXPathValue(int? languageId = null, string segment = null); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs b/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs new file mode 100644 index 0000000000..5dc42cc542 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs @@ -0,0 +1,169 @@ +using System.Web; + +namespace Umbraco.Core.Models.PublishedContent +{ + public class IndexedArrayItem + { + public IndexedArrayItem(TContent content, int index) + { + Content = content; + Index = index; + } + + public TContent Content { get; } + + public int Index { get; } + + public int TotalCount { get; internal set; } + + public bool IsFirst() + { + return Index == 0; + } + + public HtmlString IsFirst(string valueIfTrue) + { + return IsFirst(valueIfTrue, string.Empty); + } + + public HtmlString IsFirst(string valueIfTrue, string valueIfFalse) + { + return new HtmlString(IsFirst() ? valueIfTrue : valueIfFalse); + } + + public bool IsNotFirst() + { + return IsFirst() == false; + } + + public HtmlString IsNotFirst(string valueIfTrue) + { + return IsNotFirst(valueIfTrue, string.Empty); + } + + public HtmlString IsNotFirst(string valueIfTrue, string valueIfFalse) + { + return new HtmlString(IsNotFirst() ? valueIfTrue : valueIfFalse); + } + + public bool IsIndex(int index) + { + return Index == index; + } + + public HtmlString IsIndex(int index, string valueIfTrue) + { + return IsIndex(index, valueIfTrue, string.Empty); + } + + public HtmlString IsIndex(int index, string valueIfTrue, string valueIfFalse) + { + return new HtmlString(IsIndex(index) ? valueIfTrue : valueIfFalse); + } + + public bool IsModZero(int modulus) + { + return Index % modulus == 0; + } + + public HtmlString IsModZero(int modulus, string valueIfTrue) + { + return IsModZero(modulus, valueIfTrue, string.Empty); + } + + public HtmlString IsModZero(int modulus, string valueIfTrue, string valueIfFalse) + { + return new HtmlString(IsModZero(modulus) ? valueIfTrue : valueIfFalse); + } + + public bool IsNotModZero(int modulus) + { + return IsModZero(modulus) == false; + } + + public HtmlString IsNotModZero(int modulus, string valueIfTrue) + { + return IsNotModZero(modulus, valueIfTrue, string.Empty); + } + + public HtmlString IsNotModZero(int modulus, string valueIfTrue, string valueIfFalse) + { + return new HtmlString(IsNotModZero(modulus) ? valueIfTrue : valueIfFalse); + } + + public bool IsNotIndex(int index) + { + return IsIndex(index) == false; + } + + public HtmlString IsNotIndex(int index, string valueIfTrue) + { + return IsNotIndex(index, valueIfTrue, string.Empty); + } + + public HtmlString IsNotIndex(int index, string valueIfTrue, string valueIfFalse) + { + return new HtmlString(IsNotIndex(index) ? valueIfTrue : valueIfFalse); + } + + public bool IsLast() + { + return Index == TotalCount - 1; + } + + public HtmlString IsLast(string valueIfTrue) + { + return IsLast(valueIfTrue, string.Empty); + } + + public HtmlString IsLast(string valueIfTrue, string valueIfFalse) + { + return new HtmlString(IsLast() ? valueIfTrue : valueIfFalse); + } + + public bool IsNotLast() + { + return IsLast() == false; + } + + public HtmlString IsNotLast(string valueIfTrue) + { + return IsNotLast(valueIfTrue, string.Empty); + } + + public HtmlString IsNotLast(string valueIfTrue, string valueIfFalse) + { + return new HtmlString(IsNotLast() ? valueIfTrue : valueIfFalse); + } + + public bool IsEven() + { + return Index % 2 == 0; + } + + public HtmlString IsEven(string valueIfTrue) + { + return IsEven(valueIfTrue, string.Empty); + } + + public HtmlString IsEven(string valueIfTrue, string valueIfFalse) + { + return new HtmlString(IsEven() ? valueIfTrue : valueIfFalse); + } + + public bool IsOdd() + { + return Index % 2 == 1; + } + + public HtmlString IsOdd(string valueIfTrue) + { + return IsOdd(valueIfTrue, string.Empty); + } + + public HtmlString IsOdd(string valueIfTrue, string valueIfFalse) + { + return new HtmlString(IsOdd() ? valueIfTrue : valueIfFalse); + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs new file mode 100644 index 0000000000..962148e138 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs @@ -0,0 +1,397 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// + /// Represents the Clr type of a model. + /// + /// + /// ModelType.For("alias") + /// typeof (IEnumerable{}).MakeGenericType(ModelType.For("alias")) + /// Model.For("alias").MakeArrayType() + /// + public class ModelType : Type + { + private ModelType(string contentTypeAlias) + { + if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentNullOrEmptyException(nameof(contentTypeAlias)); + ContentTypeAlias = contentTypeAlias; + Name = "{" + ContentTypeAlias + "}"; + } + + /// + /// Gets the content type alias. + /// + public string ContentTypeAlias { get; } + + /// + public override string ToString() + => Name; + + /// + /// Gets the model type for a published element type. + /// + /// The published element type alias. + /// The model type for the published element type. + public static ModelType For(string alias) + => new ModelType(alias); + + /// + /// Gets the actual Clr type by replacing model types, if any. + /// + /// The type. + /// The model types map. + /// The actual Clr type. + public static Type Map(Type type, Dictionary modelTypes) + { + if (type is ModelType modelType) + { + if (modelTypes.TryGetValue(modelType.ContentTypeAlias, out Type actualType)) + return actualType; + throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\"."); + } + + if (type is ModelTypeArrayType arrayType) + { + if (modelTypes.TryGetValue(arrayType.ContentTypeAlias, out Type actualType)) + return actualType.MakeArrayType(); + throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\"."); + } + + if (type.IsGenericType == false) + return type; + var def = type.GetGenericTypeDefinition(); + if (def == null) + throw new InvalidOperationException("panic"); + + var args = type.GetGenericArguments().Select(x => Map(x, modelTypes)).ToArray(); + return def.MakeGenericType(args); + } + + /// + /// Gets the actual Clr type name by replacing model types, if any. + /// + /// The type. + /// The model types map. + /// The actual Clr type name. + public static string MapToName(Type type, Dictionary map) + { + if (type is ModelType modelType) + { + if (map.TryGetValue(modelType.ContentTypeAlias, out var actualTypeName)) + return actualTypeName; + throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\"."); + } + + if (type is ModelTypeArrayType arrayType) + { + if (map.TryGetValue(arrayType.ContentTypeAlias, out var actualTypeName)) + return actualTypeName + "[]"; + throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\"."); + } + + if (type.IsGenericType == false) + return type.FullName; + var def = type.GetGenericTypeDefinition(); + if (def == null) + throw new InvalidOperationException("panic"); + + var args = type.GetGenericArguments().Select(x => MapToName(x, map)).ToArray(); + var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`')); + return defFullName + "<" + string.Join(", ", args) + ">"; + } + + /// + /// Gets a value indicating whether two instances are equal. + /// + /// The first instance. + /// The second instance. + /// A value indicating whether the two instances are equal. + /// Knows how to compare instances. + public static bool Equals(Type t1, Type t2) + { + if (t1 == t2) + return true; + + if (t1 is ModelType m1 && t2 is ModelType m2) + return m1.ContentTypeAlias == m2.ContentTypeAlias; + + if (t1 is ModelTypeArrayType a1 && t2 is ModelTypeArrayType a2) + return a1.ContentTypeAlias == a2.ContentTypeAlias; + + if (t1.IsGenericType == false || t2.IsGenericType == false) + return false; + + var args1 = t1.GetGenericArguments(); + var args2 = t2.GetGenericArguments(); + if (args1.Length != args2.Length) return false; + + for (var i = 0; i < args1.Length; i++) + { + // ReSharper disable once CheckForReferenceEqualityInstead.2 + if (Equals(args1[i], args2[i]) == false) return false; + } + + return true; + } + + /// + protected override TypeAttributes GetAttributeFlagsImpl() + => TypeAttributes.Class; + + /// + public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) + => Array.Empty(); + + /// + protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) + => null; + + /// + public override Type[] GetInterfaces() + => Array.Empty(); + + /// + public override Type GetInterface(string name, bool ignoreCase) + => null; + + /// + public override EventInfo[] GetEvents(BindingFlags bindingAttr) + => Array.Empty(); + + /// + public override EventInfo GetEvent(string name, BindingFlags bindingAttr) + => null; + + /// + public override Type[] GetNestedTypes(BindingFlags bindingAttr) + => Array.Empty(); + + /// + public override Type GetNestedType(string name, BindingFlags bindingAttr) + => null; + + /// + public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) + => Array.Empty(); + + /// + protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) + => null; + + /// + public override MethodInfo[] GetMethods(BindingFlags bindingAttr) + => Array.Empty(); + + /// + protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) + => null; + + /// + public override FieldInfo[] GetFields(BindingFlags bindingAttr) + => Array.Empty(); + + /// + public override FieldInfo GetField(string name, BindingFlags bindingAttr) + => null; + + /// + public override MemberInfo[] GetMembers(BindingFlags bindingAttr) + => Array.Empty(); + + /// + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + => Array.Empty(); + + /// + public override object[] GetCustomAttributes(bool inherit) + => Array.Empty(); + + /// + public override bool IsDefined(Type attributeType, bool inherit) + => false; + + /// + public override Type GetElementType() + => null; + + /// + protected override bool HasElementTypeImpl() + => false; + + /// + protected override bool IsArrayImpl() + => false; + + /// + protected override bool IsByRefImpl() + => false; + + /// + protected override bool IsPointerImpl() + => false; + + /// + protected override bool IsPrimitiveImpl() + => false; + + /// + protected override bool IsCOMObjectImpl() + => false; + + /// + public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) + => throw new NotSupportedException(); + + /// + public override Type UnderlyingSystemType => this; + + /// + public override Type BaseType => null; + + /// + public override string Name { get; } + + /// + public override Guid GUID { get; } = Guid.NewGuid(); + + /// + public override Module Module => GetType().Module; // hackish but FullName requires something + + /// + public override Assembly Assembly => GetType().Assembly; // hackish but FullName requires something + + /// + public override string FullName => Name; + + /// + public override string Namespace => string.Empty; + + /// + public override string AssemblyQualifiedName => Name; + + /// + public override Type MakeArrayType() + => new ModelTypeArrayType(this); + } + + internal class ModelTypeArrayType : Type + { + private readonly Type _elementType; + + public ModelTypeArrayType(ModelType type) + { + _elementType = type; + ContentTypeAlias = type.ContentTypeAlias; + Name = "{" + type.ContentTypeAlias + "}[*]"; + } + + public string ContentTypeAlias { get; } + + public override string ToString() + => Name; + + protected override TypeAttributes GetAttributeFlagsImpl() + => TypeAttributes.Class; + + public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) + => Array.Empty(); + + protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) + => null; + + public override Type[] GetInterfaces() + => Array.Empty(); + + public override Type GetInterface(string name, bool ignoreCase) + => null; + + public override EventInfo[] GetEvents(BindingFlags bindingAttr) + => Array.Empty(); + + public override EventInfo GetEvent(string name, BindingFlags bindingAttr) + => null; + + public override Type[] GetNestedTypes(BindingFlags bindingAttr) + => Array.Empty(); + + public override Type GetNestedType(string name, BindingFlags bindingAttr) + => null; + + public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) + => Array.Empty(); + + protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) + => null; + + public override MethodInfo[] GetMethods(BindingFlags bindingAttr) + => Array.Empty(); + + protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) + => null; + + public override FieldInfo[] GetFields(BindingFlags bindingAttr) + => Array.Empty(); + + public override FieldInfo GetField(string name, BindingFlags bindingAttr) + => null; + + public override MemberInfo[] GetMembers(BindingFlags bindingAttr) + => Array.Empty(); + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + => Array.Empty(); + + public override object[] GetCustomAttributes(bool inherit) + => Array.Empty(); + + public override bool IsDefined(Type attributeType, bool inherit) + => false; + + public override Type GetElementType() + => _elementType; + + protected override bool HasElementTypeImpl() + => true; + + protected override bool IsArrayImpl() + => true; + + protected override bool IsByRefImpl() + => false; + + protected override bool IsPointerImpl() + => false; + + protected override bool IsPrimitiveImpl() + => false; + + protected override bool IsCOMObjectImpl() + => false; + + public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) + { + throw new NotSupportedException(); + } + + public override Type UnderlyingSystemType => this; + public override Type BaseType => null; + + public override string Name { get; } + public override Guid GUID { get; } = Guid.NewGuid(); + public override Module Module =>GetType().Module; // hackish but FullName requires something + public override Assembly Assembly => GetType().Assembly; // hackish but FullName requires something + public override string FullName => Name; + public override string Namespace => string.Empty; + public override string AssemblyQualifiedName => Name; + + public override int GetArrayRank() + => 1; + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs new file mode 100644 index 0000000000..77001f43ed --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents a no-operation factory. + public class NoopPublishedModelFactory : IPublishedModelFactory + { + /// + public IPublishedElement CreateModel(IPublishedElement element) => element; + + /// + public IList CreateModelList(string alias) => new List(); + + /// + public Type MapModelType(Type type) => typeof(IPublishedElement); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentEnumerable.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentEnumerable.cs index 7891253188..dc17265bb9 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentEnumerable.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentEnumerable.cs @@ -5,6 +5,8 @@ using System.Linq; namespace Umbraco.Core.Models.PublishedContent { + // fixme imported from 7.6 needs better explaination of what it is + /// /// The published content enumerable, this model is to allow ToString to be overriden for value converters to support legacy requests for string values /// @@ -23,8 +25,7 @@ namespace Umbraco.Core.Models.PublishedContent /// public PublishedContentEnumerable(IEnumerable publishedContent) { - if (publishedContent == null) throw new ArgumentNullException("publishedContent"); - _items = publishedContent; + _items = publishedContent ?? throw new ArgumentNullException(nameof(publishedContent)); } /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs deleted file mode 100644 index 56745ece66..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Linq; - -namespace Umbraco.Core.Models.PublishedContent -{ - [TypeConverter(typeof(PublishedContentTypeConverter))] - public class PublishedContentExtended : PublishedContentWrapped, IPublishedContentExtended - { - #region Constructor - - // protected for models, private for our static Extend method - protected PublishedContentExtended(IPublishedContent content) - : base(content) - { } - - #endregion - - #region Index - - private int? _index; - - public override int GetIndex() - { - // fast - if (_index.HasValue) return _index.Value; - - // slow -- and don't cache, not in a set - if (_contentSet == null) return Content.GetIndex(); - - // slow -- but cache for next time - var index = _contentSet.FindIndex(x => x.Id == Id); - if (index < 0) - throw new IndexOutOfRangeException("Could not find content in the content set."); - _index = index; - return index; - } - - #endregion - - #region Extend - - internal static IPublishedContentExtended Extend(IPublishedContent content, IEnumerable contentSet) - { - // first unwrap content down to the lowest possible level, ie either the deepest inner - // IPublishedContent or the first extended that has added properties. this is to avoid - // nesting extended objects as much as possible, so we try to re-extend that lowest - // object. - - var wrapped = content as PublishedContentExtended; - while (wrapped != null && ((IPublishedContentExtended)wrapped).HasAddedProperties == false) - wrapped = (content = wrapped.Unwrap()) as PublishedContentExtended; - - // if the factory returns something else than content it means it has created - // a model, and then that model has to inherit from PublishedContentExtended, - // => implements the internal IPublishedContentExtended. - - // here we assume that either the factory just created a model that implements - // IPublishedContentExtended and therefore does not need to be extended again, - // because it can carry the extra property - or that it did *not* create a - // model and therefore returned the original content unchanged. - - var model = content.CreateModel(); - IPublishedContent extended; - if (model == content) // == means the factory did not create a model - { - // so we have to extend - var contentWithKey = content as IPublishedContentWithKey; - extended = contentWithKey == null - ? new PublishedContentExtended(content) - : new PublishedContentWithKeyExtended(contentWithKey); - } - else - { - // else we can use what the factory returned - extended = model; - } - - // so extended should always implement IPublishedContentExtended, however if - // by mistake the factory returned a different object that does not implement - // IPublishedContentExtended (which would be an error), throw. - // - // see also PublishedContentExtensionsForModels.CreateModel - - // NOTE - // could we lift that constraint and accept that models just be IPublishedContent? - // would then mean that we cannot assume a model is IPublishedContentExtended, so - // either it is, or we need to wrap it. so instead of having - // (Model:IPublishedContentExtended (IPublishedContent)) - // we'd have - // (PublishedContentExtended (Model (IPublishedContent))) - // and it is that bad? any other consequences? - // - // would also allow the factory to cache the model (though that should really - // be done by the content cache, not by the factory). - - var extended2 = extended as IPublishedContentExtended; - if (extended2 == null) - throw new Exception("Extended does not implement IPublishedContentExtended."); - extended2.SetContentSet(contentSet); - return extended2; - } - - #endregion - - #region IPublishedContentExtended - - void IPublishedContentExtended.AddProperty(IPublishedProperty property) - { - if (_properties == null) - _properties = new Collection(); - _properties.Add(property); - } - - bool IPublishedContentExtended.HasAddedProperties - { - get { return _properties != null; } - } - - void IPublishedContentExtended.SetContentSet(IEnumerable contentSet) - { - _contentSet = contentSet; - } - - void IPublishedContentExtended.ClearContentSet() - { - _contentSet = null; - } - - void IPublishedContentExtended.SetIndex(int value) - { - _index = value; - } - - void IPublishedContentExtended.ClearIndex() - { - _index = null; - } - - #endregion - - #region Content set - - private IEnumerable _contentSet; - - public override IEnumerable ContentSet - { - get { return _contentSet ?? Content.ContentSet; } - } - - #endregion - - #region Properties - - private ICollection _properties; - - public override ICollection Properties - { - get - { - return _properties == null - ? Content.Properties - : Content.Properties.Union(_properties).ToList(); - } - } - - public override object this[string alias] - { - get - { - if (_properties != null) - { - var property = _properties.FirstOrDefault(prop => prop.PropertyTypeAlias.InvariantEquals(alias)); - if (property != null) return property.HasValue ? property.Value : null; - } - return Content[alias]; - } - } - - public override IPublishedProperty GetProperty(string alias) - { - return _properties == null - ? Content.GetProperty(alias) - : _properties.FirstOrDefault(prop => prop.PropertyTypeAlias.InvariantEquals(alias)) ?? Content.GetProperty(alias); - } - - #endregion - - public override string ToString() - { - return Id.ToString(); - } - } - -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs index f0b8ad92d6..7f42c022a5 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.Composing; namespace Umbraco.Core.Models.PublishedContent { @@ -17,29 +18,17 @@ namespace Umbraco.Core.Models.PublishedContent if (content == null) return null; - if (PublishedContentModelFactoryResolver.Current.HasValue == false) - return content; - // get model // if factory returns nothing, throw - // if factory just returns what it got, return - var model = PublishedContentModelFactoryResolver.Current.Factory.CreateModel(content); + var model = Current.PublishedModelFactory.CreateModel(content); if (model == null) - throw new Exception("IPublishedContentFactory returned null."); - if (ReferenceEquals(model, content)) - return content; + throw new Exception("Factory returned null."); - // at the moment, other parts of our code assume that all models will - // somehow implement IPublishedContentExtended and not just be IPublishedContent, - // so we'd better check this here to fail as soon as we can. - // - // see also PublishedContentExtended.Extend + // if factory returns a different type, throw + if (!(model is IPublishedContent publishedContent)) + throw new Exception($"Factory returned model of type {model.GetType().FullName} which does not implement IPublishedContent."); - var extended = model as IPublishedContentExtended; - if (extended == null) - throw new Exception("IPublishedContentFactory created an object that does not implement IPublishedContentModelExtended."); - - return model; + return publishedContent; } } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs index fa5587813e..f1d2a5f208 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs @@ -1,16 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Core.Models.PublishedContent +namespace Umbraco.Core.Models.PublishedContent { /// /// Represents a strongly-typed published content. /// /// Every strongly-typed published content class should inherit from PublishedContentModel /// (or inherit from a class that inherits from... etc.) so they are picked by the factory. - public abstract class PublishedContentModel : PublishedContentExtended + public abstract class PublishedContentModel : PublishedContentWrapped { /// /// Initializes a new instance of the class with diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelAttribute.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelAttribute.cs deleted file mode 100644 index a1712bf160..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Indicates that the class is a published content model for a specified content type. - /// - /// By default, the name of the class is assumed to be the content type alias. The - /// PublishedContentModelAttribute can be used to indicate a different alias. - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public sealed class PublishedContentModelAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a content type alias. - /// - /// The content type alias. - public PublishedContentModelAttribute(string contentTypeAlias) - { - if (string.IsNullOrWhiteSpace(contentTypeAlias)) - throw new ArgumentException("Argument cannot be null nor empty.", "contentTypeAlias"); - ContentTypeAlias = contentTypeAlias; - } - - /// - /// Gets or sets the content type alias. - /// - public string ContentTypeAlias { get; private set; } - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs deleted file mode 100644 index b3ad56d959..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Implements a strongly typed content model factory - /// - public class PublishedContentModelFactory : IPublishedContentModelFactory - { - //private readonly Dictionary _constructors - // = new Dictionary(); - - private readonly Dictionary> _constructors; - - /// - /// Initializes a new instance of the class with types. - /// - /// The model types. - /// - /// Types must implement IPublishedContent and have a unique constructor that - /// accepts one IPublishedContent as a parameter. - /// To activate, - /// - /// var types = PluginManager.Current.ResolveTypes{PublishedContentModel}(); - /// var factory = new PublishedContentModelFactoryImpl(types); - /// PublishedContentModelFactoryResolver.Current.SetFactory(factory); - /// - /// - public PublishedContentModelFactory(IEnumerable types) - { - var ctorArgTypes = new[] { typeof(IPublishedContent) }; - var constructors = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); - - foreach (var type in types) - { - var constructor = type.GetConstructor(ctorArgTypes); - if (constructor == null) - throw new InvalidOperationException(string.Format("Type {0} is missing a public constructor with one argument of type IPublishedContent.", type.FullName)); - var attribute = type.GetCustomAttribute(false); - var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; - - if (constructors.ContainsKey(typeName)) - throw new InvalidOperationException(string.Format("More that one type want to be a model for content type {0}.", typeName)); - - // should work everywhere, but slow - //_constructors[typeName] = constructor; - - // much faster with a dynamic method but potential MediumTrust issues - // here http://stackoverflow.com/questions/16363838/how-do-you-call-a-constructor-via-an-expression-tree-on-an-existing-object - - // fast enough and works in MediumTrust - // read http://boxbinary.com/2011/10/how-to-run-a-unit-test-in-medium-trust-with-nunitpart-three-umbraco-framework-testing/ - var exprArg = Expression.Parameter(typeof(IPublishedContent), "content"); - var exprNew = Expression.New(constructor, exprArg); - var expr = Expression.Lambda>(exprNew, exprArg); - var func = expr.Compile(); - constructors[typeName] = func; - } - - _constructors = constructors.Count > 0 ? constructors : null; - } - - public IPublishedContent CreateModel(IPublishedContent content) - { - // fail fast - if (_constructors == null) - return content; - - // be case-insensitive - var contentTypeAlias = content.DocumentTypeAlias; - - //ConstructorInfo constructor; - //return _constructors.TryGetValue(contentTypeAlias, out constructor) - // ? (IPublishedContent) constructor.Invoke(new object[] { content }) - // : content; - - Func constructor; - return _constructors.TryGetValue(contentTypeAlias, out constructor) - ? constructor(content) - : content; - } - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactoryResolver.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactoryResolver.cs deleted file mode 100644 index b2e882aa17..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactoryResolver.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Resolves the IPublishedContentModelFactory object. - /// - public class PublishedContentModelFactoryResolver : SingleObjectResolverBase - { - /// - /// Initializes a new instance of the . - /// - /// The resolver is created by the WebBootManager and thus the constructor remains internal. - internal PublishedContentModelFactoryResolver() - : base() - { } - - /// - /// Initializes a new instance of the with a factory. - /// - /// The factory. - /// The resolver is created by the WebBootManager and thus the constructor remains internal. - internal PublishedContentModelFactoryResolver(IPublishedContentModelFactory factory) - : base(factory) - { } - - /// - /// Sets the factory. - /// - /// The factory. - /// For developers, at application startup. - public void SetFactory(IPublishedContentModelFactory factory) - { - Value = factory; - } - - /// - /// Gets the factory. - /// - public IPublishedContentModelFactory Factory - { - get { return Value; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentOrderedSet.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentOrderedSet.cs deleted file mode 100644 index 25956aa3d4..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentOrderedSet.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Represents an ordered set of . - /// - /// The type of content. - public class PublishedContentOrderedSet : PublishedContentSet, IOrderedEnumerable - where T : class, IPublishedContent - { -// ReSharper disable ParameterTypeCanBeEnumerable.Local - internal PublishedContentOrderedSet(IOrderedEnumerable content) -// ReSharper restore ParameterTypeCanBeEnumerable.Local - : base(content) - { } - - // note: because we implement IOrderedEnumerable, we don't need to implement the ThenBy nor - // ThenByDescending methods here, only CreateOrderedEnumerable and that does it. - - #region IOrderedEnumerable - - public IOrderedEnumerable CreateOrderedEnumerable(Func keySelector, IComparer comparer, bool descending) - { - return new PublishedContentOrderedSet(((IOrderedEnumerable)Source).CreateOrderedEnumerable(keySelector, comparer, descending)); - } - - #endregion - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs deleted file mode 100644 index 369a554674..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs +++ /dev/null @@ -1,234 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Represents a set of . - /// - /// The type of content. - /// - /// A ContentSet{T} is created from an IEnumerable{T} using the ToContentSet - /// extension method. - /// The content set source is enumerated only once. Same as what you get - /// when you call ToList on an IEnumerable. Only, ToList enumerates its source when - /// created, whereas a content set enumerates its source only when the content set itself - /// is enumerated. - /// - public class PublishedContentSet : IEnumerable - where T : class, IPublishedContent - { - // used by ToContentSet extension method to initialize a new set from an IEnumerable. - internal PublishedContentSet(IEnumerable source) - { - if (source == null) - throw new ArgumentNullException("source"); - Source = source; - } - - #region Source - - protected readonly IEnumerable Source; - - #endregion - - #region Enumerated - - // cache the enumeration so we don't enumerate more than once. Same as what you get - // when you call ToList on an IEnumerable. Only, ToList enumerates its source when - // created, whereas a content set enumerates its source only when the content set itself - // is enumerated. - - // cache the wrapped items so if we reset the enumeration, we do not re-wrap everything (only new items). - - private T[] _enumerated; - private readonly Dictionary _xContent = new Dictionary(); - - // wrap an item, ie create the actual clone for this set - private T MapContentAsT(T t) - { - return MapContent(t) as T; - } - - internal IPublishedContentExtended MapContent(T t) - { - IPublishedContentExtended extend; - if (_xContent.TryGetValue(t, out extend)) return extend; - - extend = PublishedContentExtended.Extend(t, this); - var asT = extend as T; - if (asT == null) - throw new InvalidOperationException(string.Format("Failed extend a published content of type {0}." - + "Got {1} when expecting {2}.", t.GetType().FullName, extend.GetType().FullName, typeof(T).FullName)); - _xContent[t] = extend; - return extend; - } - - private T[] Enumerated - { - get - { - // enumerate the source and cache the result - // tell clones about their index within the set (for perfs purposes) - var index = 0; - return _enumerated ?? (_enumerated = Source.Select(t => - { - var extend = MapContent(t); - extend.SetIndex(index++); - return extend as T; - }).ToArray()); - } - } - - // indicates that the source has changed - // so the set can clear its inner caches - // should only be used by DynamicPublishedContentList - internal void SourceChanged() - { - // reset the cached enumeration so it's enumerated again - if (_enumerated == null) return; - _enumerated = null; - - foreach (var item in _xContent.Values) - item.ClearIndex(); - - var removed = _xContent.Keys.Except(Source); - foreach (var content in removed) - { - _xContent[content].ClearContentSet(); - _xContent.Remove(content); - } - } - - /// - /// Gets the number of items in the set. - /// - /// The number of items in the set. - /// Will cause the set to be enumerated if it hasn't been already. - public virtual int Count - { - get { return Enumerated.Length; } - } - #endregion - - #region IEnumerable - - public IEnumerator GetEnumerator() - { - return ((IEnumerable)Enumerated).GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - - #region Wrap methods returning T - - public T ElementAt(int index) - { - return MapContentAsT(Source.ElementAt(index)); - } - - public T ElementAtOrDefault(int index) - { - var element = Source.ElementAtOrDefault(index); - return element == null ? null : MapContentAsT(element); - } - - public T First() - { - return MapContentAsT(Source.First()); - } - - public T First(Func predicate) - { - return MapContentAsT(Source.First(predicate)); - } - - public T FirstOrDefault() - { - var first = Source.FirstOrDefault(); - return first == null ? null : MapContentAsT(first); - } - - public T FirstOrDefault(Func predicate) - { - var first = Source.FirstOrDefault(predicate); - return first == null ? null : MapContentAsT(first); - } - - public T Last() - { - return MapContentAsT(Source.Last()); - } - - public T Last(Func predicate) - { - return MapContentAsT(Source.Last(predicate)); - } - - public T LastOrDefault() - { - var last = Source.LastOrDefault(); - return last == null ? null : MapContentAsT(last); - } - - public T LastOrDefault(Func predicate) - { - var last = Source.LastOrDefault(predicate); - return last == null ? null : MapContentAsT(last); - } - - public T Single() - { - return MapContentAsT(Source.Single()); - } - - public T Single(Func predicate) - { - return MapContentAsT(Source.Single(predicate)); - } - - public T SingleOrDefault() - { - var single = Source.SingleOrDefault(); - return single == null ? null : MapContentAsT(single); - } - - public T SingleOrDefault(Func predicate) - { - var single = Source.SingleOrDefault(predicate); - return single == null ? null : MapContentAsT(single); - } - - #endregion - - #region Wrap methods returning IOrderedEnumerable - - public PublishedContentOrderedSet OrderBy(Func keySelector) - { - return new PublishedContentOrderedSet(Source.OrderBy(keySelector)); - } - - public PublishedContentOrderedSet OrderBy(Func keySelector, IComparer comparer) - { - return new PublishedContentOrderedSet(Source.OrderBy(keySelector, comparer)); - } - - public PublishedContentOrderedSet OrderByDescending(Func keySelector) - { - return new PublishedContentOrderedSet(Source.OrderByDescending(keySelector)); - } - - public PublishedContentOrderedSet OrderByDescending(Func keySelector, IComparer comparer) - { - return new PublishedContentOrderedSet(Source.OrderByDescending(keySelector, comparer)); - } - - #endregion - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 63164cf271..56d500167c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Cache; namespace Umbraco.Core.Models.PublishedContent { @@ -13,193 +12,160 @@ namespace Umbraco.Core.Models.PublishedContent public class PublishedContentType { private readonly PublishedPropertyType[] _propertyTypes; - private readonly HashSet _compositionAliases; // fast alias-to-index xref containing both the raw alias and its lowercase version private readonly Dictionary _indexes = new Dictionary(); - // internal so it can be used by PublishedNoCache which does _not_ want to cache anything and so will never - // use the static cache getter PublishedContentType.GetPublishedContentType(alias) below - anything else - // should use it. - internal PublishedContentType(IContentTypeComposition contentType) + /// + /// Initializes a new instance of the class with a content type. + /// + public PublishedContentType(IContentTypeComposition contentType, IPublishedContentTypeFactory factory) + : this(contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations) { - Id = contentType.Id; - Alias = contentType.Alias; - _compositionAliases = new HashSet(contentType.CompositionAliases(), StringComparer.InvariantCultureIgnoreCase); - _propertyTypes = contentType.CompositionPropertyTypes - .Select(x => new PublishedPropertyType(this, x)) - .ToArray(); + var propertyTypes = contentType.CompositionPropertyTypes + .Select(x => factory.CreatePropertyType(this, x)) + .ToList(); + + if (ItemType == PublishedItemType.Member) + EnsureMemberProperties(propertyTypes, factory); + + _propertyTypes = propertyTypes.ToArray(); + InitializeIndexes(); } - // internal so it can be used for unit tests - internal PublishedContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) + /// + /// Initializes a new instance of the with specific values. + /// + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// Values are assumed to be consisted and are not checked. + /// + public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations) + : this (id, alias, itemType, compositionAliases, variations) + { + var propertyTypesA = propertyTypes.ToArray(); + foreach (var propertyType in propertyTypesA) + propertyType.ContentType = this; + _propertyTypes = propertyTypesA; + + InitializeIndexes(); + } + + private PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations) { Id = id; Alias = alias; - _compositionAliases = new HashSet(compositionAliases, StringComparer.InvariantCultureIgnoreCase); - _propertyTypes = propertyTypes.ToArray(); - foreach (var propertyType in _propertyTypes) - propertyType.ContentType = this; - InitializeIndexes(); + ItemType = itemType; + CompositionAliases = new HashSet(compositionAliases, StringComparer.InvariantCultureIgnoreCase); + Variations = variations; } - // create detached content type - ie does not match anything in the DB - internal PublishedContentType(string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) - : this(0, alias, compositionAliases, propertyTypes) - { } - private void InitializeIndexes() { for (var i = 0; i < _propertyTypes.Length; i++) { var propertyType = _propertyTypes[i]; - _indexes[propertyType.PropertyTypeAlias] = i; - _indexes[propertyType.PropertyTypeAlias.ToLowerInvariant()] = i; + _indexes[propertyType.Alias] = i; + _indexes[propertyType.Alias.ToLowerInvariant()] = i; } } + // Members have properties such as IMember LastLoginDate which are plain C# properties and not content + // properties; they are exposed as pseudo content properties, as long as a content property with the + // same alias does not exist already. + private void EnsureMemberProperties(List propertyTypes, IPublishedContentTypeFactory factory) + { + var aliases = new HashSet(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase); + + foreach ((var alias, (var dataTypeId, var editorAlias)) in BuiltinMemberProperties) + { + if (aliases.Contains(alias)) continue; + propertyTypes.Add(factory.CreatePropertyType(this, alias, dataTypeId, ContentVariation.InvariantNeutral)); + } + } + + // fixme - this list somehow also exists in constants, see memberTypeRepository => remove duplicate! + private static readonly Dictionary BuiltinMemberProperties = new Dictionary + { + { "Email", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, + { "Username", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, + { "PasswordQuestion", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, + { "Comments", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, + { "IsApproved", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) }, + { "IsLockedOut", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) }, + { "LastLockoutDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, + { "CreateDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, + { "LastLoginDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, + { "LastPasswordChangeDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, + }; + #region Content type - public int Id { get; private set; } - public string Alias { get; private set; } - public HashSet CompositionAliases { get { return _compositionAliases; } } + /// + /// Gets the content type identifier. + /// + public int Id { get; } + + /// + /// Gets the content type alias. + /// + public string Alias { get; } + + /// + /// Gets the content item type. + /// + public PublishedItemType ItemType { get; } + + /// + /// Gets the aliases of the content types participating in the composition. + /// + public HashSet CompositionAliases { get; } + + /// + /// Gets the content variations of the content type. + /// + public ContentVariation Variations { get; } #endregion #region Properties - public IEnumerable PropertyTypes - { - get { return _propertyTypes; } - } + /// + /// Gets the content type properties. + /// + public IEnumerable PropertyTypes => _propertyTypes; - // alias is case-insensitive - // this is the ONLY place where we compare ALIASES! + /// + /// Gets a property type index. + /// + /// The alias is case-insensitive. This is the only place where alias strings are compared. public int GetPropertyIndex(string alias) { - int index; - if (_indexes.TryGetValue(alias, out index)) return index; // fastest + if (_indexes.TryGetValue(alias, out var index)) return index; // fastest if (_indexes.TryGetValue(alias.ToLowerInvariant(), out index)) return index; // slower return -1; } - // virtual for unit tests + // virtual for unit tests - fixme explain + /// + /// Gets a property type. + /// public virtual PublishedPropertyType GetPropertyType(string alias) { var index = GetPropertyIndex(alias); return GetPropertyType(index); } - // virtual for unit tests + // virtual for unit tests - fixme explain + /// + /// Gets a property type. + /// public virtual PublishedPropertyType GetPropertyType(int index) { return index >= 0 && index < _propertyTypes.Length ? _propertyTypes[index] : null; } #endregion - - #region Cache - - // these methods are called by ContentTypeCacheRefresher and DataTypeCacheRefresher - - internal static void ClearAll() - { - Logging.LogHelper.Debug("Clear all."); - // ok and faster to do it by types, assuming noone else caches PublishedContentType instances - //ApplicationContext.Current.ApplicationCache.ClearStaticCacheByKeySearch("PublishedContentType_"); - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes(); - } - - internal static void ClearContentType(int id) - { - Logging.LogHelper.Debug("Clear content type w/id {0}.", () => id); - - // we don't support "get all" at the moment - so, cheating - var all = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItemsByKeySearch("PublishedContentType_").ToArray(); - - // the one we want to clear - var clr = all.FirstOrDefault(x => x.Id == id); - if (clr == null) return; - - // those that have that one in their composition aliases - // note: CompositionAliases contains all recursive aliases - var oth = all.Where(x => x.CompositionAliases.InvariantContains(clr.Alias)).Select(x => x.Id); - - // merge ids - var ids = oth.Concat(new[] { clr.Id }).ToArray(); - - // clear them all at once - // we don't support "clear many at once" at the moment - so, cheating - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( - (key, value) => ids.Contains(value.Id)); - } - - internal static void ClearDataType(int id) - { - Logging.LogHelper.Debug("Clear data type w/id {0}.", () => id); - // there is no recursion to handle here because a PublishedContentType contains *all* its - // properties ie both its own properties and those that were inherited (it's based upon an - // IContentTypeComposition) and so every PublishedContentType having a property based upon - // the cleared data type, be it local or inherited, will be cleared. - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( - (key, value) => value.PropertyTypes.Any(x => x.DataTypeId == id)); - } - - public static PublishedContentType Get(PublishedItemType itemType, string alias) - { - var key = string.Format("PublishedContentType_{0}_{1}", - itemType.ToString().ToLowerInvariant(), alias.ToLowerInvariant()); - - var type = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItem(key, - () => CreatePublishedContentType(itemType, alias)); - - return type; - } - - private static PublishedContentType CreatePublishedContentType(PublishedItemType itemType, string alias) - { - if (GetPublishedContentTypeCallback != null) - return GetPublishedContentTypeCallback(alias); - - IContentTypeComposition contentType; - switch (itemType) - { - case PublishedItemType.Content: - contentType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias); - break; - case PublishedItemType.Media: - contentType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias); - break; - case PublishedItemType.Member: - contentType = ApplicationContext.Current.Services.MemberTypeService.Get(alias); - break; - default: - throw new ArgumentOutOfRangeException("itemType"); - } - - if (contentType == null) - throw new InvalidOperationException(string.Format("ContentTypeService failed to find a {0} type with alias \"{1}\".", - itemType.ToString().ToLower(), alias)); - - return new PublishedContentType(contentType); - } - - // for unit tests - changing the callback must reset the cache obviously - private static Func _getPublishedContentTypeCallBack; - internal static Func GetPublishedContentTypeCallback - { - get { return _getPublishedContentTypeCallBack; } - set - { - // see note above - //ClearAll(); - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheByKeySearch("PublishedContentType_"); - - _getPublishedContentTypeCallBack = value; - } - } - - #endregion } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs index 4141a19b35..094f481db9 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Globalization; using System.Linq; @@ -7,33 +7,22 @@ namespace Umbraco.Core.Models.PublishedContent { internal class PublishedContentTypeConverter : TypeConverter { - private static readonly Type[] ConvertableTypes = new[] - { - typeof(int) - }; + private static readonly Type[] ConvertingTypes = { typeof(int) }; public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { - return ConvertableTypes.Any(x => TypeHelper.IsTypeAssignableFrom(x, destinationType)) - || base.CanConvertFrom(context, destinationType); + return ConvertingTypes.Any(x => x.IsAssignableFrom(destinationType)) + || CanConvertFrom(context, destinationType); } - public override object ConvertTo( - ITypeDescriptorContext context, - CultureInfo culture, - object value, - Type destinationType) + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { - var publishedContent = value as IPublishedContent; - if (publishedContent == null) + if (!(value is IPublishedContent publishedContent)) return null; - if (TypeHelper.IsTypeAssignableFrom(destinationType)) - { - return publishedContent.Id; - } - - return base.ConvertTo(context, culture, value, destinationType); + return typeof(int).IsAssignableFrom(destinationType) + ? publishedContent.Id + : base.ConvertTo(context, culture, value, destinationType); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs new file mode 100644 index 0000000000..a98dca4f29 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides a default implementation for . + /// + internal class PublishedContentTypeFactory : IPublishedContentTypeFactory + { + private readonly IPublishedModelFactory _publishedModelFactory; + private readonly PropertyValueConverterCollection _propertyValueConverters; + private readonly IDataTypeService _dataTypeService; + private readonly object _publishedDataTypesLocker = new object(); + private Dictionary _publishedDataTypes; + + public PublishedContentTypeFactory(IPublishedModelFactory publishedModelFactory, PropertyValueConverterCollection propertyValueConverters, IDataTypeService dataTypeService) + { + _publishedModelFactory = publishedModelFactory; + _propertyValueConverters = propertyValueConverters; + _dataTypeService = dataTypeService; + } + + /// + public PublishedContentType CreateContentType(IContentTypeComposition contentType) + { + return new PublishedContentType(contentType, this); + } + + // for tests - fixme what's the point of the factory here? + internal PublishedContentType CreateContentType(int id, string alias, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.InvariantNeutral) + { + return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations); + } + + // for tests - fixme what's the point of the factory here? + internal PublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.InvariantNeutral) + { + return new PublishedContentType(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations); + } + + /// + public PublishedPropertyType CreatePropertyType(PublishedContentType contentType, PropertyType propertyType) + { + return new PublishedPropertyType(contentType, propertyType, _propertyValueConverters, _publishedModelFactory, this); + } + + /// + public PublishedPropertyType CreatePropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.InvariantNeutral) + { + return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this); + } + + // for tests + internal PublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, ContentVariation variations = ContentVariation.InvariantNeutral) + { + return new PublishedPropertyType(propertyTypeAlias, dataTypeId, umbraco, variations, _propertyValueConverters, _publishedModelFactory, this); + } + + /// + public PublishedDataType GetDataType(int id) + { + Dictionary publishedDataTypes; + lock (_publishedDataTypesLocker) + { + if (_publishedDataTypes == null) + { + var dataTypes = _dataTypeService.GetAll(); + _publishedDataTypes = dataTypes.ToDictionary( + x => x.Id, + x => new PublishedDataType(x.Id, x.EditorAlias, x is DataType d ? d.GetLazyConfiguration() : new Lazy(() => x.Configuration))); + } + + publishedDataTypes = _publishedDataTypes; + } + + if (!publishedDataTypes.TryGetValue(id, out var dataType)) + throw new ArgumentException("Not a valid datatype identifier.", nameof(id)); + + return dataType; + } + + /// + public void NotifyDataTypeChanges() + { + lock (_publishedDataTypesLocker) + { + _publishedDataTypes = null; + } + } + + /// + public void NotifyDataTypeChanges(int[] ids) + { + lock (_publishedDataTypesLocker) + { + foreach (var id in ids) + _publishedDataTypes.Remove(id); + var dataTypes = _dataTypeService.GetAll(ids); + foreach (var dataType in dataTypes) + _publishedDataTypes[dataType.Id] = new PublishedDataType(dataType.Id, dataType.EditorAlias, dataType is DataType d ? d.GetLazyConfiguration() : new Lazy(() => dataType.Configuration)); + } + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyExtended.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyExtended.cs deleted file mode 100644 index 492fd79796..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyExtended.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Umbraco.Core.Models.PublishedContent -{ - public class PublishedContentWithKeyExtended : PublishedContentExtended, IPublishedContentWithKey - { - // protected for models, internal for PublishedContentExtended static Extend method - protected internal PublishedContentWithKeyExtended(IPublishedContentWithKey content) - : base(content) - { } - - public Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } } - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyModel.cs deleted file mode 100644 index 4761a52617..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Umbraco.Core.Models.PublishedContent -{ - public abstract class PublishedContentWithKeyModel : PublishedContentModel, IPublishedContentWithKey - { - protected PublishedContentWithKeyModel(IPublishedContentWithKey content) - : base (content) - { } - - public Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } } - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyWrapped.cs deleted file mode 100644 index 35d7dd6f1f..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWithKeyWrapped.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// Provides an abstract base class for IPublishedContentWithKey implementations that - /// wrap and extend another IPublishedContentWithKey. - /// - public class PublishedContentWithKeyWrapped : PublishedContentWrapped, IPublishedContentWithKey - { - protected PublishedContentWithKeyWrapped(IPublishedContentWithKey content) - : base(content) - { } - - public virtual Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } } - } -} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 2767dc6b8b..a30726012e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -4,9 +4,7 @@ using System.Collections.Generic; namespace Umbraco.Core.Models.PublishedContent { // - // This class has two purposes. - // - // - First, we cannot implement strongly-typed content by inheriting from some sort + // we cannot implement strongly-typed content by inheriting from some sort // of "master content" because that master content depends on the actual content cache // that is being used. It can be an XmlPublishedContent with the XmlPublishedCache, // or just anything else. @@ -15,11 +13,6 @@ namespace Umbraco.Core.Models.PublishedContent // returned by the content cache, and providing extra properties (mostly) or // methods or whatever. This class provides the base for such encapsulation. // - // - Second, any time a content is used in a content set obtained from - // IEnumerable.ToContentSet(), it needs to be cloned and extended - // in order to know about its position in the set. This class provides the base - // for implementing such extension. - // /// /// Provides an abstract base class for IPublishedContent implementations that @@ -27,180 +20,92 @@ namespace Umbraco.Core.Models.PublishedContent /// public abstract class PublishedContentWrapped : IPublishedContent { - protected readonly IPublishedContent Content; + private readonly IPublishedContent _content; /// /// Initialize a new instance of the class - /// with an IPublishedContent instance to wrap and extend. + /// with an IPublishedContent instance to wrap. /// - /// The content to wrap and extend. + /// The content to wrap. protected PublishedContentWrapped(IPublishedContent content) { - Content = content; + _content = content; } /// /// Gets the wrapped content. /// /// The wrapped content, that was passed as an argument to the constructor. - public IPublishedContent Unwrap() - { - return Content; - } - - #region ContentSet - - public virtual IEnumerable ContentSet - { - get { return Content.ContentSet; } - } - - #endregion + public IPublishedContent Unwrap() => _content; #region ContentType - public virtual PublishedContentType ContentType { get { return Content.ContentType; } } + public virtual PublishedContentType ContentType => _content.ContentType; #endregion #region Content - public virtual int Id - { - get { return Content.Id; } - } + public virtual int Id => _content.Id; - public virtual int TemplateId - { - get { return Content.TemplateId; } - } + public Guid Key => _content.Key; - public virtual int SortOrder - { - get { return Content.SortOrder; } - } + public virtual int TemplateId => _content.TemplateId; - public virtual string Name - { - get { return Content.Name; } - } + public virtual int SortOrder => _content.SortOrder; - public virtual string UrlName - { - get { return Content.UrlName; } - } + public virtual string Name => _content.Name; - public virtual string DocumentTypeAlias - { - get { return Content.DocumentTypeAlias; } - } + public virtual string UrlName => _content.UrlName; - public virtual int DocumentTypeId - { - get { return Content.DocumentTypeId; } - } + public virtual string DocumentTypeAlias => _content.DocumentTypeAlias; - public virtual string WriterName - { - get { return Content.WriterName; } - } + public virtual int DocumentTypeId => _content.DocumentTypeId; - public virtual string CreatorName - { - get { return Content.CreatorName; } - } + public virtual string WriterName => _content.WriterName; - public virtual int WriterId - { - get { return Content.WriterId; } - } + public virtual string CreatorName => _content.CreatorName; - public virtual int CreatorId - { - get { return Content.CreatorId; } - } + public virtual int WriterId => _content.WriterId; - public virtual string Path - { - get { return Content.Path; } - } + public virtual int CreatorId => _content.CreatorId; - public virtual DateTime CreateDate - { - get { return Content.CreateDate; } - } + public virtual string Path => _content.Path; - public virtual DateTime UpdateDate - { - get { return Content.UpdateDate; } - } + public virtual DateTime CreateDate => _content.CreateDate; - public virtual Guid Version - { - get { return Content.Version; } - } + public virtual DateTime UpdateDate => _content.UpdateDate; - public virtual int Level - { - get { return Content.Level; } - } + public virtual int Level => _content.Level; - public virtual string Url - { - get { return Content.Url; } - } + public virtual string Url => _content.Url; - public virtual PublishedItemType ItemType - { - get { return Content.ItemType; } - } + public virtual PublishedItemType ItemType => _content.ItemType; - public virtual bool IsDraft - { - get { return Content.IsDraft; } - } - - public virtual int GetIndex() - { - return Content.GetIndex(); - } + public virtual bool IsDraft => _content.IsDraft; #endregion #region Tree - public virtual IPublishedContent Parent - { - get { return Content.Parent; } - } + public virtual IPublishedContent Parent => _content.Parent; - public virtual IEnumerable Children - { - get { return Content.Children; } - } + public virtual IEnumerable Children => _content.Children; #endregion #region Properties - public virtual ICollection Properties - { - get { return Content.Properties; } - } - - public virtual object this[string alias] - { - get { return Content[alias]; } - } + public virtual IEnumerable Properties => _content.Properties; public virtual IPublishedProperty GetProperty(string alias) { - return Content.GetProperty(alias); + return _content.GetProperty(alias); } public virtual IPublishedProperty GetProperty(string alias, bool recurse) { - return Content.GetProperty(alias, recurse); + return _content.GetProperty(alias, recurse); } #endregion diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedDataType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedDataType.cs new file mode 100644 index 0000000000..f3f10e63e2 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedDataType.cs @@ -0,0 +1,62 @@ +using System; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents a published data type. + /// + /// + /// Instances of the class are immutable, ie + /// if the data type changes, then a new class needs to be created. + /// These instances should be created by an . + /// + public class PublishedDataType + { + private readonly Lazy _lazyConfiguration; + + /// + /// Initializes a new instance of the class. + /// + internal PublishedDataType(int id, string editorAlias, Lazy lazyConfiguration) + { + _lazyConfiguration = lazyConfiguration; + + Id = id; + EditorAlias = editorAlias; + } + + /// + /// Gets the datatype identifier. + /// + public int Id { get; } + + /// + /// Gets the dat type editor alias. + /// + public string EditorAlias { get; } + + /// + /// Gets the data type configuration. + /// + public object Configuration => _lazyConfiguration.Value; + + /// + /// Gets the configuration object. + /// + /// The expected type of the configuration object. + /// When the datatype configuration is not of the expected type. + public T ConfigurationAs() + where T : class + { + switch (Configuration) + { + case null: + return null; + case T configurationAsT: + return configurationAsT; + } + + throw new InvalidCastException($"Cannot cast dataType configuration, of type {Configuration.GetType().Name}, to {typeof(T).Name}."); + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs new file mode 100644 index 0000000000..e0514b38d7 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs @@ -0,0 +1,21 @@ +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// + /// Represents a strongly-typed published element. + /// + /// Every strongly-typed property set class should inherit from PublishedElementModel + /// (or inherit from a class that inherits from... etc.) so they are picked by the factory. + public class PublishedElementModel : PublishedElementWrapped + { + /// + /// + /// Initializes a new instance of the class with + /// an original instance. + /// + /// The original content. + protected PublishedElementModel(IPublishedElement content) + : base(content) + { } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs new file mode 100644 index 0000000000..1989ac2caf --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides an abstract base class for IPublishedElement implementations that + /// wrap and extend another IPublishedElement. + /// + public abstract class PublishedElementWrapped : IPublishedElement + { + private readonly IPublishedElement _content; + + /// + /// Initializes a new instance of the class + /// with an IPublishedElement instance to wrap. + /// + /// The content to wrap. + protected PublishedElementWrapped(IPublishedElement content) + { + _content = content; + } + + /// + /// Gets the wrapped content. + /// + /// The wrapped content, that was passed as an argument to the constructor. + public IPublishedElement Unwrap() => _content; + + /// + public PublishedContentType ContentType => _content.ContentType; + + /// + public Guid Key => _content.Key; + + /// + public IEnumerable Properties => _content.Properties; + + /// + public IPublishedProperty GetProperty(string alias) => _content.GetProperty(alias); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs new file mode 100644 index 0000000000..e55fe66945 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs @@ -0,0 +1,29 @@ +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// The type of published element. + /// + /// Can be a simple element, or a document, a media, a member. + public enum PublishedItemType // fixme - need to rename to PublishedElementType but then conflicts? + { + /// + /// Unknown. + /// + Unknown = 0, + + /// + /// A document. + /// + Content, + + /// + /// A media. + /// + Media, + + /// + /// A member. + /// + Member + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs new file mode 100644 index 0000000000..16eb614ee7 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs @@ -0,0 +1,31 @@ +using System; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// + /// Indicates that the class is a published content model for a specified content type. + /// + /// By default, the name of the class is assumed to be the content type alias. The + /// PublishedContentModelAttribute can be used to indicate a different alias. + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class PublishedModelAttribute : Attribute + { + /// + /// + /// Initializes a new instance of the class with a content type alias. + /// + /// The content type alias. + public PublishedModelAttribute(string contentTypeAlias) + { + if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentNullOrEmptyException(nameof(contentTypeAlias)); + ContentTypeAlias = contentTypeAlias; + } + + /// + /// Gets or sets the content type alias. + /// + public string ContentTypeAlias { get; } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs new file mode 100644 index 0000000000..c72a89c1f2 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Implements a strongly typed content model factory + /// + public class PublishedModelFactory : IPublishedModelFactory + { + private readonly Dictionary _modelInfos; + private readonly Dictionary _modelTypeMap; + + private class ModelInfo + { + public Type ParameterType { get; set; } + public Func Ctor { get; set; } + public Type ModelType { get; set; } + public Func ListCtor { get; set; } + } + + /// + /// Initializes a new instance of the class with types. + /// + /// The model types. + /// + /// Types must implement IPublishedContent and have a unique constructor that + /// accepts one IPublishedContent as a parameter. + /// To activate, + /// + /// var types = TypeLoader.Current.GetTypes{PublishedContentModel}(); + /// var factory = new PublishedContentModelFactoryImpl(types); + /// PublishedContentModelFactoryResolver.Current.SetFactory(factory); + /// + /// + public PublishedModelFactory(IEnumerable types) + { + var modelInfos = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + var modelTypeMap = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var type in types) + { + // so... the model type has to implement a ctor with one parameter being, or inheriting from, + // IPublishedElement - but it can be IPublishedContent - so we cannot get one precise ctor, + // we have to iterate over all ctors and try to find the right one + + ConstructorInfo constructor = null; + Type parameterType = null; + + foreach (var ctor in type.GetConstructors()) + { + var parms = ctor.GetParameters(); + if (parms.Length == 1 && typeof(IPublishedElement).IsAssignableFrom(parms[0].ParameterType)) + { + if (constructor != null) + throw new InvalidOperationException($"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPublishedElement."); + constructor = ctor; + parameterType = parms[0].ParameterType; + } + } + + if (constructor == null) + throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPublishedElement."); + + var attribute = type.GetCustomAttribute(false); + var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; + + if (modelInfos.TryGetValue(typeName, out var modelInfo)) + throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); + + // have to use an unsafe ctor because we don't know the types, really + var modelCtor = ReflectionUtilities.EmitCtorUnsafe>(constructor); + modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type, Ctor = modelCtor }; + modelTypeMap[typeName] = type; + } + + _modelInfos = modelInfos.Count > 0 ? modelInfos : null; + _modelTypeMap = modelTypeMap; + } + + /// + public IPublishedElement CreateModel(IPublishedElement element) + { + // fail fast + if (_modelInfos == null) + return element; + + if (!_modelInfos.TryGetValue(element.ContentType.Alias, out var modelInfo)) + return element; + + // ReSharper disable once UseMethodIsInstanceOfType + if (modelInfo.ParameterType.IsAssignableFrom(element.GetType()) == false) + throw new InvalidOperationException($"Model {modelInfo.ModelType} expects argument of type {modelInfo.ParameterType.FullName}, but got {element.GetType().FullName}."); + + // can cast, because we checked when creating the ctor + return (IPublishedElement) modelInfo.Ctor(element); + } + + /// + public IList CreateModelList(string alias) + { + // fail fast + if (_modelInfos == null) + return new List(); + + if (!_modelInfos.TryGetValue(alias, out var modelInfo)) + return new List(); + + var ctor = modelInfo.ListCtor; + if (ctor != null) return ctor(); + + var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); + ctor = modelInfo.ListCtor = ReflectionUtilities.EmitCtor>(declaring: listType); + return ctor(); + } + + /// + public Type MapModelType(Type type) + => ModelType.Map(type, _modelTypeMap); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs index 4bf89b49fb..918bdb86e4 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Models.PublishedContent { @@ -8,24 +9,59 @@ namespace Umbraco.Core.Models.PublishedContent /// internal abstract class PublishedPropertyBase : IPublishedProperty { - public readonly PublishedPropertyType PropertyType; - - protected PublishedPropertyBase(PublishedPropertyType propertyType) + /// + /// Initializes a new instance of the class. + /// + protected PublishedPropertyBase(PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel) { - if (propertyType == null) - throw new ArgumentNullException("propertyType"); - PropertyType = propertyType; + PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType)); + ReferenceCacheLevel = referenceCacheLevel; + + ValidateCacheLevel(ReferenceCacheLevel, true); + ValidateCacheLevel(PropertyType.CacheLevel, false); } - public string PropertyTypeAlias + // validates the cache level + private static void ValidateCacheLevel(PropertyCacheLevel cacheLevel, bool validateUnknown) { - get { return PropertyType.PropertyTypeAlias; } + switch (cacheLevel) + { + case PropertyCacheLevel.Element: + case PropertyCacheLevel.Elements: + case PropertyCacheLevel.Snapshot: + case PropertyCacheLevel.None: + break; + case PropertyCacheLevel.Unknown: + if (!validateUnknown) goto default; + break; + default: + throw new Exception($"Invalid cache level \"{cacheLevel}\"."); + } } - // these have to be provided by the actual implementation - public abstract bool HasValue { get; } - public abstract object DataValue { get; } - public abstract object Value { get; } - public abstract object XPathValue { get; } + /// + /// Gets the property type. + /// + public PublishedPropertyType PropertyType { get; } + + /// + /// Gets the property reference cache level. + /// + public PropertyCacheLevel ReferenceCacheLevel { get; } + + /// + public string Alias => PropertyType.Alias; + + /// + public abstract bool HasValue(int? languageId = null, string segment = null); + + /// + public abstract object GetSourceValue(int? languageId = null, string segment = null); + + /// + public abstract object GetValue(int? languageId = null, string segment = null); + + /// + public abstract object GetXPathValue(int? languageId = null, string segment = null); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index d586bc155a..507e6f7843 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -1,130 +1,72 @@ using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; using System.Xml.Linq; using System.Xml.XPath; -using Umbraco.Core.Dynamics; using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Models.PublishedContent { /// - /// Represents an type. + /// Represents a published property type. /// /// Instances of the class are immutable, ie /// if the property type changes, then a new class needs to be created. public class PublishedPropertyType { + private readonly IPublishedModelFactory _publishedModelFactory; + private readonly PropertyValueConverterCollection _propertyValueConverters; + private readonly object _locker = new object(); + private volatile bool _initialized; + private IPropertyValueConverter _converter; + private PropertyCacheLevel _cacheLevel; + + private Type _modelClrType; + private Type _clrType; + #region Constructors - // clone - private PublishedPropertyType(PublishedPropertyType orig) + /// + /// Initialize a new instance of the class with a property type. + /// + /// + /// The new published property type belongs to the published content type. + /// + public PublishedPropertyType(PublishedContentType contentType, PropertyType propertyType, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + : this(propertyType.Alias, propertyType.DataTypeId, true, propertyType.Variations, propertyValueConverters, publishedModelFactory, factory) { - ContentType = orig.ContentType; - PropertyTypeAlias = orig.PropertyTypeAlias; - DataTypeId = orig.DataTypeId; - PropertyEditorAlias = orig.PropertyEditorAlias; - _converter = orig._converter; - _sourceCacheLevel = orig._sourceCacheLevel; - _objectCacheLevel = orig._objectCacheLevel; - _xpathCacheLevel = orig._xpathCacheLevel; - _clrType = orig._clrType; - - // do NOT copy the reduced cache levels - // as we should NOT clone a nested / detached type + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); } /// - /// Initialize a new instance of the class within a , - /// with a . + /// This constructor is for tests and is not intended to be used directly from application code. /// - /// The published content type. - /// The property type. - /// The new published property type belongs to the published content type and corresponds to the property type. - public PublishedPropertyType(PublishedContentType contentType, PropertyType propertyType) + /// + /// Values are assumed to be consisted and are not checked. + /// The new published property type belongs to the published content type. + /// + public PublishedPropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + : this(propertyTypeAlias, dataTypeId, isUserProperty, variations, propertyValueConverters, publishedModelFactory, factory) { - // PropertyEditor [1:n] DataTypeDefinition [1:n] PropertyType - - ContentType = contentType; - PropertyTypeAlias = propertyType.Alias; - - DataTypeId = propertyType.DataTypeDefinitionId; - PropertyEditorAlias = propertyType.PropertyEditorAlias; - - InitializeConverters(); + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); } /// - /// Initializes a new instance of the class with an existing - /// and a new property type alias. + /// This constructor is for tests and is not intended to be used directly from application code. /// - /// The new property type alias. - /// The existing published property type. - /// - /// The new published property type does not belong to a published content type. - /// It is a copy of the initial published property type, with a different alias. - /// - internal PublishedPropertyType(string propertyTypeAlias, PublishedPropertyType propertyType) - : this(propertyTypeAlias, propertyType.DataTypeId, propertyType.PropertyEditorAlias) - { } - - - /// - /// Initializes a new instance of the class with a property type alias and a property editor alias. - /// - /// The property type alias. - /// The property editor alias. - /// - /// The new published property type does not belong to a published content type. - /// It is based upon the property editor, but has no datatype definition. This will work as long - /// as the datatype definition is not required to process (eg to convert) the property values. For - /// example, this may not work if the related IPropertyValueConverter requires the datatype definition - /// to make decisions, fetch prevalues, etc. - /// The value of is assumed to be valid. - /// - internal PublishedPropertyType(string propertyTypeAlias, string propertyEditorAlias) - : this(propertyTypeAlias, 0, propertyEditorAlias) - { } - - /// - /// Initializes a new instance of the class with a property type alias and a datatype definition. - /// - /// The property type alias. - /// The datatype definition. /// + /// Values are assumed to be consistent and are not checked. /// The new published property type does not belong to a published content type. /// - internal PublishedPropertyType(string propertyTypeAlias, IDataTypeDefinition dataTypeDefinition) - : this(propertyTypeAlias, dataTypeDefinition.Id, dataTypeDefinition.PropertyEditorAlias) - { } - - /// - /// Initializes a new instance of the class with a property type alias, - /// a datatype definition identifier, and a property editor alias. - /// - /// The property type alias. - /// The datatype definition identifier. - /// The property editor alias. - /// Generally used only for testing, in production this will always be true - /// - /// The new published property type does not belong to a published content type. - /// The values of and are - /// assumed to be valid and consistent. - /// - internal PublishedPropertyType(string propertyTypeAlias, int dataTypeDefinitionId, string propertyEditorAlias, bool initConverters = true) + public PublishedPropertyType(string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) { - // ContentType - // - in unit tests, to be set by PublishedContentType when creating it - // - in detached types, remains null + _publishedModelFactory = publishedModelFactory ?? throw new ArgumentNullException(nameof(publishedModelFactory)); + _propertyValueConverters = propertyValueConverters ?? throw new ArgumentNullException(nameof(propertyValueConverters)); - PropertyTypeAlias = propertyTypeAlias; + Alias = propertyTypeAlias; - DataTypeId = dataTypeDefinitionId; - PropertyEditorAlias = propertyEditorAlias; + IsUserProperty = isUserProperty; + Variations = variations; - if (initConverters) - InitializeConverters(); + DataType = factory.GetDataType(dataTypeId); } #endregion @@ -132,396 +74,226 @@ namespace Umbraco.Core.Models.PublishedContent #region Property type /// - /// Gets or sets the published content type containing the property type. - /// - // internally set by PublishedContentType constructor - public PublishedContentType ContentType { get; internal set; } + /// Gets the published content type containing the property type. + /// + public PublishedContentType ContentType { get; internal set; } // internally set by PublishedContentType constructor /// - /// Gets or sets the alias uniquely identifying the property type. + /// Gets the data type. /// - public string PropertyTypeAlias { get; private set; } + public PublishedDataType DataType { get; } /// - /// Gets or sets the identifier uniquely identifying the data type supporting the property type. + /// Gets property type alias. /// - public int DataTypeId { get; private set; } + public string Alias { get; } /// - /// Gets or sets the alias uniquely identifying the property editor for the property type. + /// Gets the property editor alias. /// - public string PropertyEditorAlias { get; private set; } + public string EditorAlias => DataType.EditorAlias; + + /// + /// Gets a value indicating whether the property is a user content property. + /// + /// A non-user content property is a property that has been added to a + /// published content type by Umbraco but does not corresponds to a user-defined + /// published property. + public bool IsUserProperty { get; } + + /// + /// Gets the content variations of the property type. + /// + public ContentVariation Variations { get; } #endregion #region Converters - private IPropertyValueConverter _converter; - - private PropertyCacheLevel _sourceCacheLevel; - private PropertyCacheLevel _objectCacheLevel; - private PropertyCacheLevel _xpathCacheLevel; - - private Type _clrType = typeof (object); - - private void InitializeConverters() + private void Initialize() { - //TODO: Look at optimizing this method, it gets run for every property type for the document being rendered at startup, - // every precious second counts! - - var converters = PropertyValueConvertersResolver.Current.Converters.ToArray(); - var defaultConvertersWithAttributes = PropertyValueConvertersResolver.Current.DefaultConverters; + if (_initialized) return; + lock (_locker) + { + if (_initialized) return; + InitializeLocked(); + _initialized = true; + } + } + private void InitializeLocked() + { _converter = null; - - //get all converters for this property type - // todo: remove Union() once we drop IPropertyEditorValueConverter support. - var foundConverters = converters.Union(GetCompatConverters()).Where(x => x.IsConverter(this)).ToArray(); - if (foundConverters.Length == 1) - { - _converter = foundConverters[0]; - } - else if (foundConverters.Length > 1) - { - //more than one was found, we need to first figure out if one of these is an Umbraco default value type converter - //get the non-default and see if we have one - var nonDefault = foundConverters.Except(defaultConvertersWithAttributes.Select(x => x.Item1)).ToArray(); - if (nonDefault.Length == 1) - { - //there's only 1 custom converter registered that so use it - _converter = nonDefault[0]; - } - else if (nonDefault.Length > 1) - { - //this is not allowed, there cannot be more than 1 custom converter - throw new InvalidOperationException( - string.Format("Type '{2}' cannot be an IPropertyValueConverter" - + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" - + " for that property, and only one converter can exist for a property.", - ContentType.Alias, PropertyTypeAlias, - nonDefault[1].GetType().FullName, nonDefault[0].GetType().FullName)); - } - else - { - //we need to remove any converters that have been shadowed by another converter - var foundDefaultConvertersWithAttributes = defaultConvertersWithAttributes.Where(x => foundConverters.Contains(x.Item1)); - var shadowedTypes = foundDefaultConvertersWithAttributes.SelectMany(x => x.Item2.DefaultConvertersToShadow); - var shadowedDefaultConverters = foundConverters.Where(x => shadowedTypes.Contains(x.GetType())); - var nonShadowedDefaultConverters = foundConverters.Except(shadowedDefaultConverters).ToArray(); + var isdefault = false; - if (nonShadowedDefaultConverters.Length == 1) + foreach (var converter in _propertyValueConverters) + { + if (!converter.IsConverter(this)) + continue; + + if (_converter == null) + { + _converter = converter; + isdefault = _propertyValueConverters.IsDefault(converter); + continue; + } + + if (isdefault) + { + if (_propertyValueConverters.IsDefault(converter)) { - //assign to the single default converter - _converter = nonShadowedDefaultConverters[0]; + // previous was default, and got another default + if (_propertyValueConverters.Shadows(_converter, converter)) + { + // previous shadows, ignore + } + else if (_propertyValueConverters.Shadows(converter, _converter)) + { + // shadows previous, replace + _converter = converter; + } + else + { + // no shadow - bad + throw new InvalidOperationException(string.Format("Type '{2}' cannot be an IPropertyValueConverter" + + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" + + " for that property, and only one converter can exist for a property.", + ContentType.Alias, Alias, + converter.GetType().FullName, _converter.GetType().FullName)); + } } - else if (nonShadowedDefaultConverters.Length > 1) + else { - //this is not allowed, there cannot be more than 1 custom converter - throw new InvalidOperationException( - string.Format("Type '{2}' cannot be an IPropertyValueConverter" - + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" - + " for that property, and only one converter can exist for a property.", - ContentType.Alias, PropertyTypeAlias, - nonShadowedDefaultConverters[1].GetType().FullName, nonShadowedDefaultConverters[0].GetType().FullName)); + // previous was default, replaced by non-default + _converter = converter; + isdefault = false; } } - - } - - var converterMeta = _converter as IPropertyValueConverterMeta; - - // get the cache levels, quietely fixing the inconsistencies (no need to throw, really) - if (converterMeta != null) - { - _sourceCacheLevel = converterMeta.GetPropertyCacheLevel(this, PropertyCacheValue.Source); - _objectCacheLevel = converterMeta.GetPropertyCacheLevel(this, PropertyCacheValue.Object); - _xpathCacheLevel = converterMeta.GetPropertyCacheLevel(this, PropertyCacheValue.XPath); - } - else - { - _sourceCacheLevel = GetCacheLevel(_converter, PropertyCacheValue.Source); - _objectCacheLevel = GetCacheLevel(_converter, PropertyCacheValue.Object); - _xpathCacheLevel = GetCacheLevel(_converter, PropertyCacheValue.XPath); - } - if (_objectCacheLevel < _sourceCacheLevel) _objectCacheLevel = _sourceCacheLevel; - if (_xpathCacheLevel < _sourceCacheLevel) _xpathCacheLevel = _sourceCacheLevel; - - // get the CLR type of the converted value - if (_converter != null) - { - if (converterMeta != null) - { - _clrType = converterMeta.GetPropertyValueType(this); - } else { - var attr = _converter.GetType().GetCustomAttribute(false); - if (attr != null) - _clrType = attr.Type; + if (_propertyValueConverters.IsDefault(converter)) + { + // previous was non-default, ignore default + } + else + { + // previous was non-default, and got another non-default - bad + throw new InvalidOperationException(string.Format("Type '{2}' cannot be an IPropertyValueConverter" + + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" + + " for that property, and only one converter can exist for a property.", + ContentType.Alias, Alias, + converter.GetType().FullName, _converter.GetType().FullName)); + } } } + + _cacheLevel = _converter?.GetPropertyCacheLevel(this) ?? PropertyCacheLevel.Snapshot; + _modelClrType = _converter == null ? typeof (object) : _converter.GetPropertyValueType(this); + } + + /// + /// Gets the property cache level. + /// + public PropertyCacheLevel CacheLevel + { + get + { + if (!_initialized) Initialize(); + return _cacheLevel; + } } - static PropertyCacheLevel GetCacheLevel(IPropertyValueConverter converter, PropertyCacheValue value) + /// + /// Converts the source value into the intermediate value. + /// + /// The published element owning the property. + /// The source value. + /// A value indicating whether content should be considered draft. + /// The intermediate value. + public object ConvertSourceToInter(IPublishedElement owner, object source, bool preview) { - if (converter == null) - return PropertyCacheLevel.Request; + if (!_initialized) Initialize(); - var attr = converter.GetType().GetCustomAttributes(false) - .FirstOrDefault(x => x.Value == value || x.Value == PropertyCacheValue.All); - - return attr == null ? PropertyCacheLevel.Request : attr.Level; - } - - // converts the raw value into the source value - // uses converters, else falls back to dark (& performance-wise expensive) magic - // source: the property raw value - // preview: whether we are previewing or not - public object ConvertDataToSource(object source, bool preview) - { - // use the converter else use dark (& performance-wise expensive) magic - return _converter != null - ? _converter.ConvertDataToSource(this, source, preview) - : ConvertUsingDarkMagic(source); - } - - // gets the source cache level - public PropertyCacheLevel SourceCacheLevel { get { return _sourceCacheLevel; } } - - // converts the source value into the clr value - // uses converters, else returns the source value - // source: the property source value - // preview: whether we are previewing or not - public object ConvertSourceToObject(object source, bool preview) - { - // use the converter if any - // else just return the source value + // use the converter if any, else just return the source value return _converter != null - ? _converter.ConvertSourceToObject(this, source, preview) + ? _converter.ConvertSourceToIntermediate(owner, this, source, preview) : source; } - // gets the value cache level - public PropertyCacheLevel ObjectCacheLevel { get { return _objectCacheLevel; } } - - // converts the source value into the xpath value - // uses the converter else returns the source value as a string - // if successful, returns either a string or an XPathNavigator - // source: the property source value - // preview: whether we are previewing or not - public object ConvertSourceToXPath(object source, bool preview) + /// + /// Converts the intermediate value into the object value. + /// + /// The published element owning the property. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether content should be considered draft. + /// The object value. + public object ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { + if (!_initialized) Initialize(); + + // use the converter if any, else just return the inter value + return _converter != null + ? _converter.ConvertIntermediateToObject(owner, this, referenceCacheLevel, inter, preview) + : inter; + } + + /// + /// Converts the intermediate value into the XPath value. + /// + /// The published element owning the property. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether content should be considered draft. + /// The XPath value. + /// + /// The XPath value can be either a string or an XPathNavigator. + /// + public object ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + if (!_initialized) Initialize(); + // use the converter if any if (_converter != null) - return _converter.ConvertSourceToXPath(this, source, preview); + return _converter.ConvertIntermediateToXPath(owner, this, referenceCacheLevel, inter, preview); - // else just return the source value as a string or an XPathNavigator - if (source == null) return null; - var xElement = source as XElement; - if (xElement != null) + // else just return the inter value as a string or an XPathNavigator + if (inter == null) return null; + if (inter is XElement xElement) return xElement.CreateNavigator(); - return source.ToString().Trim(); - } - - // gets the xpath cache level - public PropertyCacheLevel XPathCacheLevel { get { return _xpathCacheLevel; } } - - internal static object ConvertUsingDarkMagic(object source) - { - // convert to string - var stringSource = source as string; - if (stringSource == null) return source; // not a string => return the object - stringSource = stringSource.Trim(); - if (stringSource.Length == 0) return null; // empty string => return null - - // try numbers and booleans - // make sure we use the invariant culture ie a dot decimal point, comma is for csv - // NOTE far from perfect: "01a" is returned as a string but "012" is returned as an integer... - int i; - if (int.TryParse(stringSource, NumberStyles.Integer, CultureInfo.InvariantCulture, out i)) - return i; - float f; - if (float.TryParse(stringSource, NumberStyles.Float, CultureInfo.InvariantCulture, out f)) - return f; - bool b; - if (bool.TryParse(stringSource, out b)) - return b; - - //TODO: We can change this just like we do for the JSON converter - but to maintain compatibility might mean this still has to remain here - - // try xml - that is expensive, performance-wise - XElement elt; - if (XmlHelper.TryCreateXElementFromPropertyValue(stringSource, out elt)) - return new DynamicXml(elt); // xml => return DynamicXml for compatiblity's sake - - return source; - } - - // gets the property CLR type - public Type ClrType { get { return _clrType; } } - - #endregion - - #region Compat - - // backward-compatibility: support IPropertyEditorValueConverter while we have to - // todo: remove once we drop IPropertyEditorValueConverter support. - - IEnumerable GetCompatConverters() - { - var propertyEditorGuid = LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias(PropertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.ReturnNull); - return PropertyEditorValueConvertersResolver.HasCurrent && propertyEditorGuid.HasValue - ? PropertyEditorValueConvertersResolver.Current.Converters - .Where(x => x.IsConverterFor(propertyEditorGuid.Value, ContentType.Alias, PropertyTypeAlias)) - .Select(x => new CompatConverter(x)) - : Enumerable.Empty(); - } - - private class CompatConverter : PropertyValueConverterBase - { - private readonly IPropertyEditorValueConverter _converter; - - public CompatConverter(IPropertyEditorValueConverter converter) - { - _converter = converter; - } - - public override bool IsConverter(PublishedPropertyType propertyType) - { - return true; - } - - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) - { - // NOTE: ignore preview, because IPropertyEditorValueConverter does not support it - return _converter.ConvertPropertyValue(source).Result; - } - } - - #endregion - - #region Detached - - private PropertyCacheLevel _sourceCacheLevelReduced = 0; - private PropertyCacheLevel _objectCacheLevelReduced = 0; - private PropertyCacheLevel _xpathCacheLevelReduced = 0; - - internal bool IsDetachedOrNested - { - // enough to test source - get { return _sourceCacheLevelReduced != 0; } + return inter.ToString().Trim(); } /// - /// Creates a detached clone of this published property type. + /// Gets the property model Clr type. /// - /// A detached clone of this published property type. /// - /// Only a published property type that has not already been detached or nested, can be detached. - /// Use detached published property type when creating detached properties outside of a published content. + /// The model Clr type may be a type, or may contain types. + /// For the actual Clr type, see . /// - internal PublishedPropertyType Detached() + public Type ModelClrType { - // verify - if (IsDetachedOrNested) - throw new Exception("PublishedPropertyType is already detached/nested."); - - var detached = new PublishedPropertyType(this); - detached._sourceCacheLevel - = detached._objectCacheLevel - = detached._xpathCacheLevel - = PropertyCacheLevel.Content; - // set to none to a) indicate it's detached / nested and b) make sure any nested - // types switch all their cache to .Content - detached._sourceCacheLevelReduced - = detached._objectCacheLevelReduced - = detached._xpathCacheLevelReduced - = PropertyCacheLevel.None; - - return detached; + get + { + if (!_initialized) Initialize(); + return _modelClrType; + } } /// - /// Creates a nested clone of this published property type within a specified container published property type. + /// Gets the property Clr type. /// - /// The container published property type. - /// A nested clone of this published property type /// - /// Only a published property type that has not already been detached or nested, can be nested. - /// Use nested published property type when creating detached properties within a published content. + /// Returns the actual Clr type which does not contain types. + /// Mapping from may throw if some instances + /// could not be mapped to actual Clr types. /// - internal PublishedPropertyType Nested(PublishedPropertyType containerType) + public Type ClrType { - // verify - if (IsDetachedOrNested) - throw new Exception("PublishedPropertyType is already detached/nested."); - - var nested = new PublishedPropertyType(this); - - // before we reduce, both xpath and object are >= source, and - // the way reduce works, the relative order of resulting xpath, object and source are preserved - - // Reduce() will set _xxxCacheLevelReduced thus indicating that the type is detached / nested - - Reduce(_sourceCacheLevel, _sourceCacheLevelReduced, ref nested._sourceCacheLevel, ref nested._sourceCacheLevelReduced); - Reduce(_objectCacheLevel, _objectCacheLevelReduced, ref nested._objectCacheLevel, ref nested._objectCacheLevelReduced); - Reduce(_xpathCacheLevel, _xpathCacheLevelReduced, ref nested._xpathCacheLevel, ref nested._xpathCacheLevelReduced); - - return nested; - } - - private static void Reduce( - PropertyCacheLevel containerCacheLevel, PropertyCacheLevel containerCacheLevelReduced, - ref PropertyCacheLevel nestedCacheLevel, ref PropertyCacheLevel nestedCacheLevelReduced) - { - // initialize if required - if (containerCacheLevelReduced == 0) - containerCacheLevelReduced = containerCacheLevel; - - switch (containerCacheLevelReduced) + get { - case PropertyCacheLevel.None: - // once .None, force .Content for everything - nestedCacheLevel = PropertyCacheLevel.Content; - nestedCacheLevelReduced = PropertyCacheLevel.None; // and propagate - break; - - case PropertyCacheLevel.Request: - // once .Request, force .Content for everything - nestedCacheLevel = PropertyCacheLevel.Content; - nestedCacheLevelReduced = PropertyCacheLevel.Request; // and propagate - break; - - case PropertyCacheLevel.Content: - // as long as .Content, accept anything - nestedCacheLevelReduced = nestedCacheLevel; // and it becomes the nested reduced - break; - - case PropertyCacheLevel.ContentCache: - // once .ContentCache, accept .Request and .Content but not .ContentCache - switch (nestedCacheLevel) - { - case PropertyCacheLevel.Request: - case PropertyCacheLevel.None: - // accept - nestedCacheLevelReduced = nestedCacheLevel; // and it becomes the nested reduced - break; - case PropertyCacheLevel.Content: - // accept - nestedCacheLevelReduced = PropertyCacheLevel.ContentCache; // and propagate - break; - case PropertyCacheLevel.ContentCache: - // force .Content - nestedCacheLevel = PropertyCacheLevel.Content; - nestedCacheLevelReduced = PropertyCacheLevel.ContentCache; // and propagate - break; - default: - throw new Exception("Unsupported PropertyCacheLevel value."); - } - break; - - default: - throw new Exception("Unsupported PropertyCacheLevel value."); + if (!_initialized) Initialize(); + return _clrType ?? (_clrType = _publishedModelFactory.MapModelType(_modelClrType)); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedSearchResult.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedSearchResult.cs new file mode 100644 index 0000000000..6d30334415 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedSearchResult.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Core.Models.PublishedContent +{ + public class PublishedSearchResult + { + public PublishedSearchResult(IPublishedContent content, float score) + { + Content = content; + Score = score; + } + + public IPublishedContent Content { get; } + public float Score { get; } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs new file mode 100644 index 0000000000..f938880060 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs @@ -0,0 +1,51 @@ +using System; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// + /// Represents a published property that has a unique invariant-neutral value + /// and caches conversion results locally. + /// + /// + /// Conversions results are stored within the property and will not + /// be refreshed, so this class is not suitable for cached properties. + /// Does not support variations: the ctor throws if the property type + /// supports variations. + /// + internal class RawValueProperty : PublishedPropertyBase + { + private readonly object _sourceValue; //the value in the db + private readonly Lazy _objectValue; + private readonly Lazy _xpathValue; + + public override object GetSourceValue(int? languageId = null, string segment = null) + => languageId == null & segment == null ? _sourceValue : null; + + public override bool HasValue(int? languageId = null, string segment = null) + { + var sourceValue = GetSourceValue(languageId, segment); + return sourceValue is string s ? !string.IsNullOrWhiteSpace(s) : sourceValue != null; + } + + public override object GetValue(int? languageId = null, string segment = null) + => languageId == null & segment == null ? _objectValue.Value : null; + + public override object GetXPathValue(int? languageId = null, string segment = null) + => languageId == null & segment == null ? _xpathValue.Value : null; + + public RawValueProperty(PublishedPropertyType propertyType, IPublishedElement content, object sourceValue, bool isPreviewing = false) + : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored + { + if (propertyType.Variations != ContentVariation.InvariantNeutral) + throw new ArgumentException("Property types with variations are not supported here.", nameof(propertyType)); + + _sourceValue = sourceValue; + + var interValue = new Lazy(() => PropertyType.ConvertSourceToInter(content, _sourceValue, isPreviewing)); + _objectValue = new Lazy(() => PropertyType.ConvertInterToObject(content, PropertyCacheLevel.Unknown, interValue.Value, isPreviewing)); + _xpathValue = new Lazy(() => PropertyType.ConvertInterToXPath(content, PropertyCacheLevel.Unknown, interValue.Value, isPreviewing)); + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedItemType.cs b/src/Umbraco.Core/Models/PublishedItemType.cs deleted file mode 100644 index 677205f61a..0000000000 --- a/src/Umbraco.Core/Models/PublishedItemType.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Umbraco.Core.Models -{ - /// - /// The type of published content, ie whether it is a content or a media. - /// - public enum PublishedItemType - { - /// - /// A content, ie what was formerly known as a document. - /// - Content, - - /// - /// A media. - /// - Media, - - /// - /// A member. - /// - Member - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublishedState.cs b/src/Umbraco.Core/Models/PublishedState.cs index 4469eba9cc..af9777710d 100644 --- a/src/Umbraco.Core/Models/PublishedState.cs +++ b/src/Umbraco.Core/Models/PublishedState.cs @@ -1,9 +1,61 @@ -namespace Umbraco.Core.Models +using System; + +namespace Umbraco.Core.Models { + /// + /// The states of a content item. + /// public enum PublishedState { + // versions management in repo: + // + // - published = the content is published + // repo: saving draft values + // update current version (draft) values + // + // - unpublished = the content is not published + // repo: saving draft values + // update current version (draft) values + // + // - publishing = the content is being published (transitory) + // if currently published: + // delete all draft values from current version, not current anymore + // create new version with published+draft values + // + // - unpublishing = the content is being unpublished (transitory) + // if currently published (just in case): + // delete all draft values from current version, not current anymore + // create new version with published+draft values (should be managed by service) + + // when a content item is loaded, its state is one of those two: + + /// + /// The content item is published. + /// Published, + // also: handled over to repo to save draft values for a published content + + /// + /// The content item is not published. + /// Unpublished, - Saved + // also: handled over to repo to save draft values for an unpublished content + + // when it is handled over to the repository, its state can also be one of those: + + /// + /// The version is being saved, in order to publish the content. + /// + /// The Publishing state is transitional. Once the version + /// is saved, its state changes to Published. + Publishing, + + /// + /// The version is being saved, in order to unpublish the content. + /// + /// The Unpublishing state is transitional. Once the version + /// is saved, its state changes to Unpublished. + Unpublishing + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Range.cs b/src/Umbraco.Core/Models/Range.cs index 3095d6342d..83afa11658 100644 --- a/src/Umbraco.Core/Models/Range.cs +++ b/src/Umbraco.Core/Models/Range.cs @@ -49,4 +49,4 @@ namespace Umbraco.Core.Models return this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Rdbms/AppDto.cs b/src/Umbraco.Core/Models/Rdbms/AppDto.cs deleted file mode 100644 index 797b10729a..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/AppDto.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoApp")] - [PrimaryKey("appAlias", autoIncrement = false)] - [ExplicitColumns] - internal class AppDto - { - [Column("appAlias")] - [PrimaryKeyColumn(AutoIncrement = false, Clustered = true)] - [Length(50)] - public string AppAlias { get; set; } - - [Column("appIcon")] - public string AppIcon { get; set; } - - [Column("appName")] - public string AppName { get; set; } - - [Column("appInitWithTreeAlias")] - [NullSetting(NullSetting = NullSettings.Null)] - public string AppInitWithTreeAlias { get; set; } - - [Column("sortOrder")] - [Constraint(Name = "DF_app_sortOrder", Default = "0")] - public byte SortOrder { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/AppTreeDto.cs b/src/Umbraco.Core/Models/Rdbms/AppTreeDto.cs deleted file mode 100644 index a1308ac4dd..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/AppTreeDto.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoAppTree")] - [PrimaryKey("appAlias", autoIncrement = false)] - [ExplicitColumns] - internal class AppTreeDto - { - [Column("treeSilent")] - [Constraint(Default = "0")] - public bool Silent { get; set; } - - [Column("treeInitialize")] - [Constraint(Default = "1")] - public bool Initialize { get; set; } - - [Column("treeSortOrder")] - public byte SortOrder { get; set; } - - [Column("appAlias")] - [PrimaryKeyColumn(AutoIncrement = false, Clustered = true, Name = "PK_umbracoAppTree", OnColumns = "appAlias, treeAlias")] - public string AppAlias { get; set; } - - [Column("treeAlias")] - public string Alias { get; set; } - - [Column("treeTitle")] - public string Title { get; set; } - - [Column("treeIconClosed")] - public string IconClosed { get; set; } - - [Column("treeIconOpen")] - public string IconOpen { get; set; } - - [Column("treeHandlerAssembly")] - public string HandlerAssembly { get; set; } - - [Column("treeHandlerType")] - public string HandlerType { get; set; } - - [Column("action")] - [NullSetting(NullSetting = NullSettings.Null)] - public string Action { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/ContentDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentDto.cs deleted file mode 100644 index 6a3868178a..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/ContentDto.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsContent")] - [PrimaryKey("pk")] - [ExplicitColumns] - internal class ContentDto - { - [Column("pk")] - [PrimaryKeyColumn] - public int PrimaryKey { get; set; } - - [Column("nodeId")] - [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsContent")] - public int NodeId { get; set; } - - [Column("contentType")] - [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] - public int ContentTypeId { get; set; } - - [ResultColumn] - public NodeDto NodeDto { get; set; } - } - -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs deleted file mode 100644 index 3bd85cccf6..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsContentVersion")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class ContentVersionDto - { - [Column("id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - [Column("ContentId")] - [ForeignKey(typeof(ContentDto), Column = "nodeId")] - [Index(IndexTypes.NonClustered, Name = "IX_cmsContentVersion_ContentId")] - public int NodeId { get; set; } - - [Column("VersionId")] - [Index(IndexTypes.UniqueNonClustered)] - public Guid VersionId { get; set; } - - [Column("VersionDate")] - [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime VersionDate { get; set; } - - [ResultColumn] - public ContentDto ContentDto { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/ContentXmlDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentXmlDto.cs deleted file mode 100644 index ca48a4074c..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/ContentXmlDto.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Data; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsContentXml")] - [PrimaryKey("nodeId", autoIncrement = false)] - [ExplicitColumns] - internal class ContentXmlDto - { - [Column("nodeId")] - [PrimaryKeyColumn(AutoIncrement = false)] - [ForeignKey(typeof(ContentDto), Column = "nodeId")] - public int NodeId { get; set; } - - [Column("xml")] - [SpecialDbType(SpecialDbTypes.NTEXT)] - public string Xml { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/DataTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/DataTypeDto.cs deleted file mode 100644 index 54146114a8..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/DataTypeDto.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsDataType")] - [PrimaryKey("pk")] - [ExplicitColumns] - internal class DataTypeDto - { - [Column("pk")] - [PrimaryKeyColumn(IdentitySeed = 40)] - public int PrimaryKey { get; set; } - - [Column("nodeId")] - [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.UniqueNonClustered)] - public int DataTypeId { get; set; } - - [Column("propertyEditorAlias")] - public string PropertyEditorAlias { get; set; } - - [Column("dbType")] - [Length(50)] - public string DbType { get; set; }//NOTE Is set to [varchar] (50) in Sql Server script - - [ResultColumn] - public NodeDto NodeDto { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/DataTypePreValueDto.cs b/src/Umbraco.Core/Models/Rdbms/DataTypePreValueDto.cs deleted file mode 100644 index 6e8fc29d95..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/DataTypePreValueDto.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsDataTypePreValues")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class DataTypePreValueDto - { - [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 10)] - public int Id { get; set; } - - [Column("datatypeNodeId")] - [ForeignKey(typeof(DataTypeDto), Column = "nodeId")] - public int DataTypeNodeId { get; set; } - - [Column("value")] - [NullSetting(NullSetting = NullSettings.Null)] - [SpecialDbType(SpecialDbTypes.NTEXT)] - public string Value { get; set; } - - [Column("sortorder")] - public int SortOrder { get; set; } - - [Column("alias")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(50)] - public string Alias { get; set; } - - protected bool Equals(DataTypePreValueDto other) - { - return Id == other.Id; - } - - 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((DataTypePreValueDto) obj); - } - - public override int GetHashCode() - { - return Id; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs b/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs deleted file mode 100644 index 88e04087c9..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsDocument")] - [PrimaryKey("versionId", autoIncrement = false)] - [ExplicitColumns] - internal class DocumentDto - { - [Column("nodeId")] - [ForeignKey(typeof(ContentDto), Column = "nodeId")] - [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsDocument", ForColumns = "nodeId, versionId")] - public int NodeId { get; set; } - - [Column("published")] - [Index(IndexTypes.NonClustered, Name = "IX_cmsDocument_published")] - public bool Published { get; set; } - - [Column("documentUser")] - public int WriterUserId { get; set; } - - [Column("versionId")] - [PrimaryKeyColumn(AutoIncrement = false)] - public Guid VersionId { get; set; } - - [Column("text")] - public string Text { get; set; } - - [Column("releaseDate")] - [NullSetting(NullSetting = NullSettings.Null)] - public DateTime? ReleaseDate { get; set; } - - [Column("expireDate")] - [NullSetting(NullSetting = NullSettings.Null)] - public DateTime? ExpiresDate { get; set; } - - [Column("updateDate")] - [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime UpdateDate { get; set; } - - [Column("templateId")] - [NullSetting(NullSetting = NullSettings.Null)] - [ForeignKey(typeof(TemplateDto), Column = "nodeId")] - public int? TemplateId { get; set; } - - [Column("newest")] - [Constraint(Default = "0")] - [Index(IndexTypes.NonClustered, Name = "IX_cmsDocument_newest")] - public bool Newest { get; set; } - - [ResultColumn] - public ContentVersionDto ContentVersionDto { get; set; } - - [ResultColumn] - public DocumentPublishedReadOnlyDto DocumentPublishedReadOnlyDto { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs deleted file mode 100644 index 7c6507f499..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using Umbraco.Core.Persistence; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsDocument")] - [PrimaryKey("versionId", autoIncrement = false)] - [ExplicitColumns] - internal class DocumentPublishedReadOnlyDto - { - [Column("nodeId")] - public int NodeId { get; set; } - - [Column("published")] - public bool Published { get; set; } - - [Column("versionId")] - public Guid VersionId { get; set; } - - [Column("newest")] - public bool Newest { get; set; } - - [Column("updateDate")] - public DateTime VersionDate { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/LanguageDto.cs b/src/Umbraco.Core/Models/Rdbms/LanguageDto.cs deleted file mode 100644 index a634dca0a4..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/LanguageDto.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoLanguage")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class LanguageDto - { - [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 2)] - public short Id { get; set; } - - [Column("languageISOCode")] - [Index(IndexTypes.UniqueNonClustered)] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(10)] - public string IsoCode { get; set; } - - [Column("languageCultureName")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(100)] - public string CultureName { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/LockDto.cs b/src/Umbraco.Core/Models/Rdbms/LockDto.cs deleted file mode 100644 index b543ce4241..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/LockDto.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoLock")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class LockDto - { - public LockDto() - { - Value = 1; - } - - [Column("id")] - [PrimaryKeyColumn(Name = "PK_umbracoLock")] - public int Id { get; set; } - - [Column("value")] - [NullSetting(NullSetting = NullSettings.NotNull)] - public int Value { get; set; } - - [Column("name")] - [NullSetting(NullSetting = NullSettings.NotNull)] - [Length(64)] - public string Name { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/MediaDto.cs b/src/Umbraco.Core/Models/Rdbms/MediaDto.cs deleted file mode 100644 index 799a9ab4b9..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/MediaDto.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsMedia")] - [PrimaryKey("versionId", autoIncrement = false)] - [ExplicitColumns] - internal class MediaDto - { - [Column("nodeId")] - [ForeignKey(typeof(ContentDto), Column = "nodeId")] - [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMedia", ForColumns = "nodeId, versionId, mediaPath")] - public int NodeId { get; set; } - - [Column("versionId")] - [PrimaryKeyColumn(AutoIncrement = false)] - public Guid VersionId { get; set; } - - [Column("mediaPath")] - [NullSetting(NullSetting = NullSettings.Null)] - public string MediaPath { get; set; } - - [ResultColumn] - public ContentVersionDto ContentVersionDto { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/Member2MemberGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/Member2MemberGroupDto.cs deleted file mode 100644 index 888f331364..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/Member2MemberGroupDto.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsMember2MemberGroup")] - [PrimaryKey("Member", autoIncrement = false)] - [ExplicitColumns] - internal class Member2MemberGroupDto - { - [Column("Member")] - [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsMember2MemberGroup", OnColumns = "Member, MemberGroup")] - [ForeignKey(typeof(MemberDto))] - public int Member { get; set; } - - [Column("MemberGroup")] - [ForeignKey(typeof(NodeDto))] - public int MemberGroup { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberDto.cs deleted file mode 100644 index cbe9f909f8..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsMember")] - [PrimaryKey("nodeId", autoIncrement = false)] - [ExplicitColumns] - internal class MemberDto - { - [Column("nodeId")] - [PrimaryKeyColumn(AutoIncrement = false)] - [ForeignKey(typeof(ContentDto), Column = "nodeId")] - [ForeignKey(typeof(NodeDto))] - public int NodeId { get; set; } - - [Column("Email")] - [Length(1000)] - [Constraint(Default = "''")] - public string Email { get; set; } - - [Column("LoginName")] - [Length(1000)] - [Constraint(Default = "''")] - [Index(IndexTypes.NonClustered, Name = "IX_cmsMember_LoginName")] - public string LoginName { get; set; } - - [Column("Password")] - [Length(1000)] - [Constraint(Default = "''")] - public string Password { get; set; } - - [ResultColumn] - public ContentVersionDto ContentVersionDto { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs b/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs deleted file mode 100644 index 1a76895e90..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoMigration")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class MigrationDto - { - [Column("id")] - [PrimaryKeyColumn(AutoIncrement = true, IdentitySeed = 100)] - public int Id { get; set; } - - [Column("name")] - [Length(255)] - [Index(IndexTypes.UniqueNonClustered, ForColumns = "name,version", Name = "IX_umbracoMigration")] - public string Name { get; set; } - - [Column("createDate")] - [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime CreateDate { get; set; } - - [Column("version")] - [Length(50)] - public string Version { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs deleted file mode 100644 index 9b575e8c95..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoNode")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class NodeDto - { - public const int NodeIdSeed = 1060; - - [Column("id")] - [PrimaryKeyColumn(Name = "PK_structure", IdentitySeed = NodeIdSeed)] - public int NodeId { get; set; } - - [Column("trashed")] - [Constraint(Default = "0")] - [Index(IndexTypes.NonClustered, Name = "IX_umbracoNodeTrashed")] - public bool Trashed { get; set; } - - [Column("parentID")] - [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.NonClustered, Name = "IX_umbracoNodeParentId")] - public int ParentId { get; set; } - - [Column("nodeUser")] - [NullSetting(NullSetting = NullSettings.Null)] - public int? UserId { get; set; } - - [Column("level")] - public short Level { get; set; } - - [Column("path")] - [Length(150)] - [Index(IndexTypes.NonClustered, Name = "IX_umbracoNodePath")] - public string Path { get; set; } - - [Column("sortOrder")] - public int SortOrder { get; set; } - - [Column("uniqueID")] - [NullSetting(NullSetting = NullSettings.NotNull)] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoNodeUniqueID")] - [Constraint(Default = SystemMethods.NewGuid)] - public Guid UniqueId { get; set; } - - [Column("text")] - [NullSetting(NullSetting = NullSettings.Null)] - public string Text { get; set; } - - [Column("nodeObjectType")] - [NullSetting(NullSetting = NullSettings.Null)] - [Index(IndexTypes.NonClustered, Name = "IX_umbracoNodeObjectType")] - public Guid? NodeObjectType { get; set; } - - [Column("createDate")] - [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime CreateDate { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PreviewXmlDto.cs b/src/Umbraco.Core/Models/Rdbms/PreviewXmlDto.cs deleted file mode 100644 index d981855f24..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/PreviewXmlDto.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsPreviewXml")] - [PrimaryKey("nodeId", autoIncrement = false)] - [ExplicitColumns] - internal class PreviewXmlDto - { - [Column("nodeId")] - [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsContentPreviewXml", OnColumns = "nodeId, versionId")] - [ForeignKey(typeof(ContentDto), Column = "nodeId")] - public int NodeId { get; set; } - - [Column("versionId")] - [ForeignKey(typeof(ContentVersionDto), Column = "VersionId")] - public Guid VersionId { get; set; } - - [Column("timestamp")] - public DateTime Timestamp { get; set; } - - [Column("xml")] - [SpecialDbType(SpecialDbTypes.NTEXT)] - public string Xml { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs deleted file mode 100644 index e9c1685bb3..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsPropertyData")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class PropertyDataDto - { - [Column("id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - [Column("contentNodeId")] - [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyData_1", ForColumns = "contentNodeId,versionId,propertytypeid")] - public int NodeId { get; set; } - - [Column("versionId")] - [NullSetting(NullSetting = NullSettings.Null)] - [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyData_2")] - public Guid? VersionId { get; set; } - - [Column("propertytypeid")] - [ForeignKey(typeof(PropertyTypeDto))] - [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyData_3")] - public int PropertyTypeId { get; set; } - - [Column("dataInt")] - [NullSetting(NullSetting = NullSettings.Null)] - public int? Integer { get; set; } - - private decimal? _decimalValue; - - [Column("dataDecimal")] - [NullSetting(NullSetting = NullSettings.Null)] - public decimal? Decimal - { - get - { - return _decimalValue; - } - set - { - // need to normalize the value (change the scaling factor and remove trailing zeroes) - // because the underlying database probably has messed with the scaling factor. - _decimalValue = value.HasValue ? (decimal?) value.Value.Normalize() : null; - } - } - - [Column("dataDate")] - [NullSetting(NullSetting = NullSettings.Null)] - public DateTime? Date { get; set; } - - [Column("dataNvarchar")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(500)] - public string VarChar { get; set; } - - [Column("dataNtext")] - [NullSetting(NullSetting = NullSettings.Null)] - [SpecialDbType(SpecialDbTypes.NTEXT)] - public string Text { get; set; } - - [ResultColumn] - public PropertyTypeDto PropertyTypeDto { get; set; } - - [Ignore] - public object GetValue - { - get - { - if (Integer.HasValue) - { - return Integer.Value; - } - - if (Decimal.HasValue) - { - return Decimal.Value; - } - - if (Date.HasValue) - { - return Date.Value; - } - - if (string.IsNullOrEmpty(VarChar) == false) - { - return VarChar; - } - - if (string.IsNullOrEmpty(Text) == false) - { - return Text; - } - - return null; - } - } - - protected bool Equals(PropertyDataDto other) - { - return Id == other.Id; - } - - 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((PropertyDataDto) obj); - } - - public override int GetHashCode() - { - return Id; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs deleted file mode 100644 index 57c973fb64..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using Umbraco.Core.Persistence; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsPropertyTypeGroup")] - [PrimaryKey("id", autoIncrement = true)] - [ExplicitColumns] - internal class PropertyTypeGroupReadOnlyDto - { - [Column("PropertyTypeGroupId")] - public int? Id { get; set; } - - [Column("PropertyGroupName")] - public string Text { get; set; } - - [Column("PropertyGroupSortOrder")] - public int SortOrder { get; set; } - - [Column("contenttypeNodeId")] - public int ContentTypeNodeId { get; set; } - - [Column("PropertyGroupUniqueID")] - public Guid UniqueId { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/TagDto.cs b/src/Umbraco.Core/Models/Rdbms/TagDto.cs deleted file mode 100644 index d612f656bb..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/TagDto.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsTags")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class TagDto - { - [Column("id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - [Column("tag")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(200)] - [Index(IndexTypes.NonClustered, ForColumns = "tag,group", Name = "IX_cmsTags")] - public string Tag { get; set; }//NOTE Is set to [varchar] (200) in Sql Server script - - [Column("ParentId")] - [NullSetting(NullSetting = NullSettings.Null)] - [ForeignKey(typeof(TagDto), Name = "FK_cmsTags_cmsTags")] - public int? ParentId { get; set; } - - [Column("group")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(100)] - public string Group { get; set; }//NOTE Is set to [varchar] (100) in Sql Server script - - [ResultColumn("NodeCount")] - public int NodeCount { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/TaskTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/TaskTypeDto.cs deleted file mode 100644 index 2ff61953ce..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/TaskTypeDto.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsTaskType")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class TaskTypeDto - { - [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 2)] - public byte Id { get; set; } - - [Column("alias")] - [Index(IndexTypes.NonClustered)] - public string Alias { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/User2UserGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/User2UserGroupDto.cs deleted file mode 100644 index afdbf661b5..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/User2UserGroupDto.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoUser2UserGroup")] - [ExplicitColumns] - internal class User2UserGroupDto - { - [Column("userId")] - [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_user2userGroup", OnColumns = "userId, userGroupId")] - [ForeignKey(typeof(UserDto))] - public int UserId { get; set; } - - [Column("userGroupId")] - [ForeignKey(typeof(UserGroupDto))] - public int UserGroupId { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UserTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/UserTypeDto.cs deleted file mode 100644 index 6a8656f7df..0000000000 --- a/src/Umbraco.Core/Models/Rdbms/UserTypeDto.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [Obsolete("Table no longer exists as of 7.7 - retained only to support migrations from previous versions")] - [TableName("umbracoUserType")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class UserTypeDto - { - [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 5)] - public short Id { get; set; } - - [Column("userTypeAlias")] - [Length(50)] - [NullSetting(NullSetting = NullSettings.Null)] - public string Alias { get; set; } - - [Column("userTypeName")] - public string Name { get; set; } - - [Column("userTypeDefaultPermissions")] - [Length(50)] - [NullSetting(NullSetting = NullSettings.Null)] - public string DefaultPermissions { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/RedirectUrl.cs b/src/Umbraco.Core/Models/RedirectUrl.cs index ff7746d0cf..187d9fdd6e 100644 --- a/src/Umbraco.Core/Models/RedirectUrl.cs +++ b/src/Umbraco.Core/Models/RedirectUrl.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -10,7 +10,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class RedirectUrl : Entity, IRedirectUrl + public class RedirectUrl : EntityBase, IRedirectUrl { /// /// Initializes a new instance of the class. @@ -64,4 +64,4 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _url, Ps.Value.UrlSelector); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index 6d383f9884..a06ca44441 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models @@ -11,7 +11,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class Relation : Entity, IAggregateRoot, IRelation + public class Relation : EntityBase, IRelation { //NOTE: The datetime column from umbracoRelation is set on CreateDate on the Entity private int _parentId; @@ -86,4 +86,4 @@ namespace Umbraco.Core.Models } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index 04ef897c1f..65d44d43d9 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Models @@ -11,7 +12,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class RelationType : Entity, IAggregateRoot, IRelationType + public class RelationType : EntityBase, IRelationType { private string _name; private string _alias; @@ -19,19 +20,19 @@ namespace Umbraco.Core.Models private Guid _parentObjectType; private Guid _childObjectType; - public RelationType(Guid childObjectType, Guid parentObjectType, string @alias) + public RelationType(Guid childObjectType, Guid parentObjectType, string alias) { - Mandate.ParameterNotNullOrEmpty(@alias, "alias"); + if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentNullOrEmptyException(nameof(alias)); _childObjectType = childObjectType; _parentObjectType = parentObjectType; _alias = alias; Name = _alias; } - public RelationType(Guid childObjectType, Guid parentObjectType, string @alias, string name) - :this(childObjectType, parentObjectType, @alias) + public RelationType(Guid childObjectType, Guid parentObjectType, string alias, string name) + : this(childObjectType, parentObjectType, alias) { - Mandate.ParameterNotNullOrEmpty(name, "name"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); Name = name; } @@ -99,4 +100,4 @@ namespace Umbraco.Core.Models } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Script.cs b/src/Umbraco.Core/Models/Script.cs index 325885d9ba..ee481b7fe5 100644 --- a/src/Umbraco.Core/Models/Script.cs +++ b/src/Umbraco.Core/Models/Script.cs @@ -20,11 +20,6 @@ namespace Umbraco.Core.Models : base(path, getFileContent) { } - [Obsolete("This is no longer used and will be removed from the codebase in future versions")] - public Script(string path, IContentSection contentConfig) - : base(path) - { } - /// /// Indicates whether the current entity has an identity, which in this case is a path/name. /// @@ -36,4 +31,4 @@ namespace Umbraco.Core.Models get { return string.IsNullOrEmpty(Path) == false; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Section.cs b/src/Umbraco.Core/Models/Section.cs index e3df5dbbbd..f1ca9c7a5d 100644 --- a/src/Umbraco.Core/Models/Section.cs +++ b/src/Umbraco.Core/Models/Section.cs @@ -15,7 +15,7 @@ public Section() { - + } public string Name { get; set; } @@ -23,4 +23,4 @@ public string Icon { get; set; } public int SortOrder { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs index f68f21d759..7e65111f7d 100644 --- a/src/Umbraco.Core/Models/ServerRegistration.cs +++ b/src/Umbraco.Core/Models/ServerRegistration.cs @@ -1,14 +1,14 @@ using System; using System.Globalization; using System.Reflection; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { /// /// Represents a registered server in a multiple-servers environment. /// - public class ServerRegistration : Entity, IServerRegistration + public class ServerRegistration : EntityBase, IServerRegistration { private string _serverAddress; private string _serverIdentity; @@ -123,4 +123,4 @@ namespace Umbraco.Core.Models return string.Format("{{\"{0}\", \"{1}\", {2}active, {3}master}}", ServerAddress, ServerIdentity, IsActive ? "" : "!", IsMaster ? "" : "!"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index 060246df54..0c697eb51f 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Models private void InitializeProperties() { - //if the value is already created, we need to be created and update the collection according to + //if the value is already created, we need to be created and update the collection according to //what is now in the content if (_properties != null && _properties.IsValueCreated) { @@ -63,7 +63,7 @@ namespace Umbraco.Core.Models var prop = new StylesheetProperty(stylesheetRule.Name, stylesheetRule.Selector, stylesheetRule.Styles); prop.PropertyChanged += Property_PropertyChanged; _properties.Value.Add(prop); - } + } } //we haven't read the properties yet so create the lazy delegate @@ -116,8 +116,8 @@ namespace Umbraco.Core.Models /// Returns a list of umbraco back office enabled stylesheet properties /// /// - /// An umbraco back office enabled stylesheet property has a special prefix, for example: - /// + /// An umbraco back office enabled stylesheet property has a special prefix, for example: + /// /// /** umb_name: MyPropertyName */ p { font-size: 1em; } /// [IgnoreDataMember] @@ -137,7 +137,7 @@ namespace Umbraco.Core.Models throw new DuplicateNameException("The property with the name " + property.Name + " already exists in the collection"); } - //now we need to serialize out the new property collection over-top of the string Content. + //now we need to serialize out the new property collection over-top of the string Content. Content = StylesheetHelper.AppendRule(Content, new StylesheetRule { Name = property.Name, @@ -160,7 +160,7 @@ namespace Umbraco.Core.Models Content = StylesheetHelper.ReplaceRule(Content, name, null); } } - + /// /// Indicates whether the current entity has an identity, which in this case is a path/name. /// @@ -172,4 +172,4 @@ namespace Umbraco.Core.Models get { return string.IsNullOrEmpty(Path) == false; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/StylesheetProperty.cs b/src/Umbraco.Core/Models/StylesheetProperty.cs index b0b2144044..2793cd0414 100644 --- a/src/Umbraco.Core/Models/StylesheetProperty.cs +++ b/src/Umbraco.Core/Models/StylesheetProperty.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class StylesheetProperty : TracksChangesEntityBase, IValueObject + public class StylesheetProperty : BeingDirtyBase, IValueObject { private string _alias; private string _value; @@ -57,4 +57,4 @@ namespace Umbraco.Core.Models } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Tag.cs b/src/Umbraco.Core/Models/Tag.cs index 92dfb9dee7..5309d1f2ea 100644 --- a/src/Umbraco.Core/Models/Tag.cs +++ b/src/Umbraco.Core/Models/Tag.cs @@ -1,58 +1,61 @@ using System; -using System.Collections; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { + /// + /// Represents a tag entity. + /// [Serializable] [DataContract(IsReference = true)] - public class Tag : Entity, ITag + public class Tag : EntityBase, ITag { - public Tag() - { - } + private static PropertySelectors _selectors; - public Tag(int id, string text, string @group) + private string _group; + private string _text; + + /// + /// Initializes a new instance of the class. + /// + public Tag() + { } + + /// + /// Initializes a new instance of the class. + /// + public Tag(int id, string group, string text) { Id = id; Text = text; - Group = @group; + Group = group; } - public Tag(int id, string text, string @group, int nodeCount) - : this(id, text, @group) - { - NodeCount = nodeCount; - } - - private static readonly Lazy Ps = new Lazy(); + private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); private class PropertySelectors { - public readonly PropertyInfo TextSelector = ExpressionHelper.GetPropertyInfo(x => x.Text); - public readonly PropertyInfo GroupSelector = ExpressionHelper.GetPropertyInfo(x => x.Group); - } - - private string _text; - private string _group; - - public string Text - { - get { return _text; } - set { SetPropertyValueAndDetectChanges(value, ref _text, Ps.Value.TextSelector); } + public readonly PropertyInfo Group = ExpressionHelper.GetPropertyInfo(x => x.Group); + public readonly PropertyInfo Text = ExpressionHelper.GetPropertyInfo(x => x.Text); } + /// public string Group { - get { return _group; } - set { SetPropertyValueAndDetectChanges(value, ref _group, Ps.Value.GroupSelector); } + get => _group; + set => SetPropertyValueAndDetectChanges(value, ref _group, Selectors.Group); } - public int NodeCount { get; internal set; } + /// + public string Text + { + get => _text; + set => SetPropertyValueAndDetectChanges(value, ref _text, Selectors.Text); + } - //TODO: enable this at some stage - //public int ParentId { get; set; } + /// + public int NodeCount { get; internal set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/TagCacheStorageType.cs b/src/Umbraco.Core/Models/TagCacheStorageType.cs deleted file mode 100644 index 5078a44d85..0000000000 --- a/src/Umbraco.Core/Models/TagCacheStorageType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Models -{ - public enum TagCacheStorageType - { - Csv, - Json - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/TaggableObjectTypes.cs b/src/Umbraco.Core/Models/TaggableObjectTypes.cs index ae54bc02bd..e669fc3af0 100644 --- a/src/Umbraco.Core/Models/TaggableObjectTypes.cs +++ b/src/Umbraco.Core/Models/TaggableObjectTypes.cs @@ -10,4 +10,4 @@ Media, Member } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/TaggedEntity.cs b/src/Umbraco.Core/Models/TaggedEntity.cs index 890412a6b6..ac194c15cd 100644 --- a/src/Umbraco.Core/Models/TaggedEntity.cs +++ b/src/Umbraco.Core/Models/TaggedEntity.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Models /// /// Represents a tagged entity. /// - /// Note that it is the properties of an entity (like Content, Media, Members, etc.) that is tagged, + /// Note that it is the properties of an entity (like Content, Media, Members, etc.) that is tagged, /// which is why this class is composed of a list of tagged properties and an Id reference to the actual entity. public class TaggedEntity { @@ -25,4 +25,4 @@ namespace Umbraco.Core.Models /// public IEnumerable TaggedProperties { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/TaggedProperty.cs b/src/Umbraco.Core/Models/TaggedProperty.cs index 9013c25887..2b9650b432 100644 --- a/src/Umbraco.Core/Models/TaggedProperty.cs +++ b/src/Umbraco.Core/Models/TaggedProperty.cs @@ -29,4 +29,4 @@ namespace Umbraco.Core.Models /// public IEnumerable Tags { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/TagsStorageType.cs b/src/Umbraco.Core/Models/TagsStorageType.cs new file mode 100644 index 0000000000..f594320e8b --- /dev/null +++ b/src/Umbraco.Core/Models/TagsStorageType.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Core.Models +{ + /// + /// Defines how tags are stored. + /// + /// Tags are always stored as a string, but the string can + /// either be a delimited string, or a serialized Json array. + public enum TagsStorageType + { + /// + /// Store tags as a delimited string. + /// + Csv, + + /// + /// Store tags as serialized Json. + /// + Json + } +} diff --git a/src/Umbraco.Core/Models/Task.cs b/src/Umbraco.Core/Models/Task.cs index fc6350a13b..1899463c49 100644 --- a/src/Umbraco.Core/Models/Task.cs +++ b/src/Umbraco.Core/Models/Task.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -10,7 +10,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class Task : Entity, IAggregateRoot + public class Task : EntityBase { private bool _closed; private TaskType _taskType; @@ -97,4 +97,4 @@ namespace Umbraco.Core.Models } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/TaskType.cs b/src/Umbraco.Core/Models/TaskType.cs index 5a1e21e661..3b1b3ecff2 100644 --- a/src/Umbraco.Core/Models/TaskType.cs +++ b/src/Umbraco.Core/Models/TaskType.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { @@ -10,7 +10,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class TaskType : Entity, IAggregateRoot + public class TaskType : EntityBase { private string _alias; @@ -36,4 +36,4 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index bef4384943..f7d74b2d5f 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -8,7 +8,6 @@ using System.Text; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Services; using Umbraco.Core.Strings; @@ -48,12 +47,6 @@ namespace Umbraco.Core.Models _masterTemplateId = new Lazy(() => -1); } - [Obsolete("This constructor should not be used, file path is determined by alias, setting the path here will have no affect")] - public Template(string path, string name, string alias) - : this(name, alias) - { - } - [DataMember] public Lazy MasterTemplateId { @@ -86,13 +79,6 @@ namespace Umbraco.Core.Models /// public bool IsMasterTemplate { get; internal set; } - [Obsolete("This is no longer used and will be removed from the codebase in future versions, use the IFileSystem DetermineRenderingEngine method instead")] - public RenderingEngine GetTypeOfRenderingEngine() - { - //Hack! TODO: Remove this method entirely - return ApplicationContext.Current.Services.FileService.DetermineTemplateRenderingEngine(this); - } - public void SetMasterTemplate(ITemplate masterTemplate) { if (masterTemplate == null) @@ -105,7 +91,7 @@ namespace Umbraco.Core.Models MasterTemplateId = new Lazy(() => masterTemplate.Id); MasterTemplateAlias = masterTemplate.Alias; } - + } protected override void DeepCloneNameAndAlias(File clone) diff --git a/src/Umbraco.Core/Models/TemplateNode.cs b/src/Umbraco.Core/Models/TemplateNode.cs index b792f6e8a7..13e5b22846 100644 --- a/src/Umbraco.Core/Models/TemplateNode.cs +++ b/src/Umbraco.Core/Models/TemplateNode.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Models /// The current template /// public ITemplate Template { get; set; } - + /// /// The children of the current template /// @@ -31,4 +31,4 @@ namespace Umbraco.Core.Models /// public TemplateNode Parent { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/UmbracoDomain.cs b/src/Umbraco.Core/Models/UmbracoDomain.cs index 8a3ef94d5b..d7266f77fe 100644 --- a/src/Umbraco.Core/Models/UmbracoDomain.cs +++ b/src/Umbraco.Core/Models/UmbracoDomain.cs @@ -1,13 +1,13 @@ -using System; +using System; using System.Reflection; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { [Serializable] [DataContract(IsReference = true)] - public class UmbracoDomain : Entity, IDomain + public class UmbracoDomain : EntityBase, IDomain { public UmbracoDomain(string domainName) { @@ -64,4 +64,4 @@ namespace Umbraco.Core.Models /// public string LanguageIsoCode { get; internal set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs deleted file mode 100644 index b789698704..0000000000 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ /dev/null @@ -1,266 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Models -{ - /// - /// Implementation of the for internal use. - /// - public class UmbracoEntity : Entity, IUmbracoEntity - { - private int _creatorId; - private int _level; - private string _name; - private int _parentId; - private string _path; - private int _sortOrder; - private bool _trashed; - private bool _hasChildren; - private bool _isPublished; - private bool _isDraft; - private bool _hasPendingChanges; - private string _contentTypeAlias; - private Guid _nodeObjectTypeId; - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - public readonly PropertyInfo HasChildrenSelector = ExpressionHelper.GetPropertyInfo(x => x.HasChildren); - public readonly PropertyInfo IsPublishedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsPublished); - public readonly PropertyInfo IsDraftSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDraft); - public readonly PropertyInfo HasPendingChangesSelector = ExpressionHelper.GetPropertyInfo(x => x.HasPendingChanges); - public readonly PropertyInfo ContentTypeAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeAlias); - public readonly PropertyInfo ContentTypeIconSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeIcon); - public readonly PropertyInfo ContentTypeThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeThumbnail); - public readonly PropertyInfo NodeObjectTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.NodeObjectTypeId); - } - - private string _contentTypeIcon; - private string _contentTypeThumbnail; - - public static readonly UmbracoEntity Root = new UmbracoEntity(false) {Path = "-1", Name = "root", HasChildren = true}; - - public UmbracoEntity() - { - AdditionalData = new Dictionary(); - } - - public UmbracoEntity(bool trashed) - { - AdditionalData = new Dictionary(); - Trashed = trashed; - } - - // for MySql - public UmbracoEntity(UInt64 trashed) - { - AdditionalData = new Dictionary(); - Trashed = trashed == 1; - } - - public int CreatorId - { - get { return _creatorId; } - set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); } - } - - public int Level - { - get { return _level; } - set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } - } - - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - public int ParentId - { - get { return _parentId; } - set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } - } - - public string Path - { - get { return _path; } - set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } - } - - public int SortOrder - { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } - } - - public bool Trashed - { - get { return _trashed; } - private set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); } - } - - public IDictionary AdditionalData { get; private set; } - - - public bool HasChildren - { - get { return _hasChildren; } - set - { - SetPropertyValueAndDetectChanges(value, ref _hasChildren, Ps.Value.HasChildrenSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - AdditionalData["HasChildren"] = value; - } - } - - public bool IsPublished - { - get { return _isPublished; } - set - { - SetPropertyValueAndDetectChanges(value, ref _isPublished, Ps.Value.IsPublishedSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - AdditionalData["IsPublished"] = value; - } - } - - public bool IsDraft - { - get { return _isDraft; } - set - { - SetPropertyValueAndDetectChanges(value, ref _isDraft, Ps.Value.IsDraftSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - AdditionalData["IsDraft"] = value; - } - } - - public bool HasPendingChanges - { - get { return _hasPendingChanges; } - set - { - SetPropertyValueAndDetectChanges(value, ref _hasPendingChanges, Ps.Value.HasPendingChangesSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - AdditionalData["HasPendingChanges"] = value; - } - } - - public string ContentTypeAlias - { - get { return _contentTypeAlias; } - set - { - SetPropertyValueAndDetectChanges(value, ref _contentTypeAlias, Ps.Value.ContentTypeAliasSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - AdditionalData["ContentTypeAlias"] = value; - } - } - - public string ContentTypeIcon - { - get { return _contentTypeIcon; } - set - { - SetPropertyValueAndDetectChanges(value, ref _contentTypeIcon, Ps.Value.ContentTypeIconSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - AdditionalData["ContentTypeIcon"] = value; - } - } - - public string ContentTypeThumbnail - { - get { return _contentTypeThumbnail; } - set - { - SetPropertyValueAndDetectChanges(value, ref _contentTypeThumbnail, Ps.Value.ContentTypeThumbnailSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - AdditionalData["ContentTypeThumbnail"] = value; - } - } - - public Guid NodeObjectTypeId - { - get { return _nodeObjectTypeId; } - set - { - SetPropertyValueAndDetectChanges(value, ref _nodeObjectTypeId, Ps.Value.NodeObjectTypeIdSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - AdditionalData["NodeObjectTypeId"] = value; - } - } - - public override object DeepClone() - { - var clone = (UmbracoEntity) base.DeepClone(); - //turn off change tracking - clone.DisableChangeTracking(); - //This ensures that any value in the dictionary that is deep cloneable is cloned too - foreach (var key in clone.AdditionalData.Keys.ToArray()) - { - var deepCloneable = clone.AdditionalData[key] as IDeepCloneable; - if (deepCloneable != null) - { - clone.AdditionalData[key] = deepCloneable.DeepClone(); - } - } - //this shouldn't really be needed since we're not tracking - clone.ResetDirtyProperties(false); - //re-enable tracking - clone.EnableChangeTracking(); - return clone; - } - - /// - /// A struction that can be contained in the additional data of an UmbracoEntity representing - /// a user defined property - /// - public class EntityProperty : IDeepCloneable - { - public string PropertyEditorAlias { get; set; } - public object Value { get; set; } - public object DeepClone() - { - //Memberwise clone on Entity will work since it doesn't have any deep elements - // for any sub class this will work for standard properties as well that aren't complex object's themselves. - var clone = MemberwiseClone(); - return clone; - } - - protected bool Equals(EntityProperty other) - { - return PropertyEditorAlias.Equals(other.PropertyEditorAlias) && string.Equals(Value, other.Value); - } - - 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((EntityProperty) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (PropertyEditorAlias.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0); - } - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs b/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs deleted file mode 100644 index a27657cbe0..0000000000 --- a/src/Umbraco.Core/Models/UmbracoEntityExtensions.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Web.UI.WebControls; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Models -{ - internal static class UmbracoEntityExtensions - { - /// - /// Does a quick check on the entity's set path to ensure that it's valid and consistent - /// - /// - /// - public static void ValidatePathWithException(this NodeDto entity) - { - //don't validate if it's empty and it has no id - if (entity.NodeId == default(int) && entity.Path.IsNullOrWhiteSpace()) - return; - - if (entity.Path.IsNullOrWhiteSpace()) - throw new InvalidDataException(string.Format("The content item {0} has an empty path: {1} with parentID: {2}", entity.NodeId, entity.Path, entity.ParentId)); - - var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - if (pathParts.Length < 2) - { - //a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id - throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", entity.NodeId, entity.Path, entity.ParentId)); - } - - if (entity.ParentId != default(int) && pathParts[pathParts.Length - 2] != entity.ParentId.ToInvariantString()) - { - //the 2nd last id in the path must be it's parent id - throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", entity.NodeId, entity.Path, entity.ParentId)); - } - } - - /// - /// Does a quick check on the entity's set path to ensure that it's valid and consistent - /// - /// - /// - public static bool ValidatePath(this IUmbracoEntity entity) - { - //don't validate if it's empty and it has no id - if (entity.HasIdentity == false && entity.Path.IsNullOrWhiteSpace()) - return true; - - if (entity.Path.IsNullOrWhiteSpace()) - return false; - - var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - if (pathParts.Length < 2) - { - //a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id - return false; - } - - if (entity.ParentId != default(int) && pathParts[pathParts.Length - 2] != entity.ParentId.ToInvariantString()) - { - //the 2nd last id in the path must be it's parent id - return false; - } - - return true; - } - - /// - /// This will validate the entity's path and if it's invalid it will fix it, if fixing is required it will recursively - /// check and fix all ancestors if required. - /// - /// - /// - /// A callback specified to retrieve the parent entity of the entity - /// A callback specified to update a fixed entity - public static void EnsureValidPath(this T entity, - ILogger logger, - Func getParent, - Action update) - where T: IUmbracoEntity - { - if (entity.HasIdentity == false) - throw new InvalidOperationException("Could not ensure the entity path, the entity has not been assigned an identity"); - - if (entity.ValidatePath() == false) - { - logger.Warn(typeof(UmbracoEntityExtensions), string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", entity.Id, entity.Path, entity.ParentId)); - if (entity.ParentId == -1) - { - entity.Path = string.Concat("-1,", entity.Id); - //path changed, update it - update(entity); - } - else - { - var parent = getParent(entity); - if (parent == null) - throw new NullReferenceException("Could not ensure path for entity " + entity.Id + " could not resolve it's parent " + entity.ParentId); - - //the parent must also be valid! - parent.EnsureValidPath(logger, getParent, update); - - entity.Path = string.Concat(parent.Path, ",", entity.Id); - //path changed, update it - update(entity); - } - } - } - - /// - /// When resolved from EntityService this checks if the entity has the HasChildren flag - /// - /// - /// - public static bool HasChildren(this IUmbracoEntity entity) - { - if (entity.AdditionalData.ContainsKey("HasChildren")) - { - var convert = entity.AdditionalData["HasChildren"].TryConvertTo(); - if (convert) - { - return convert.Result; - } - } - return false; - } - - - public static object GetAdditionalDataValueIgnoreCase(this IUmbracoEntity entity, string key, object defaultVal) - { - if (entity.AdditionalData.ContainsKeyIgnoreCase(key) == false) return defaultVal; - return entity.AdditionalData.GetValueIgnoreCase(key, defaultVal); - } - - /// - /// When resolved from EntityService this checks if the entity has the IsContainer flag - /// - /// - /// - public static bool IsContainer(this IUmbracoEntity entity) - { - if (entity.AdditionalData.ContainsKeyIgnoreCase("IsContainer") == false) return false; - var val = entity.AdditionalData.GetValueIgnoreCase("IsContainer", null); - if (val is bool && (bool) val) - { - return true; - } - return false; - } - - } -} diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index 163e4839b4..31b5f2e513 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Models /// /// Content Item Type /// - [UmbracoObjectType(Constants.ObjectTypes.ContentItemType)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.ContentItemType)] [FriendlyName("Content Item Type")] [Obsolete("This is not used and will be removed in future versions")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -26,14 +26,14 @@ namespace Umbraco.Core.Models /// /// Root /// - [UmbracoObjectType(Constants.ObjectTypes.SystemRoot)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.SystemRoot)] [FriendlyName("Root")] ROOT, /// /// Document /// - [UmbracoObjectType(Constants.ObjectTypes.Document, typeof(IContent))] + [UmbracoObjectType(Constants.ObjectTypes.Strings.Document, typeof(IContent))] [FriendlyName("Document")] [UmbracoUdiType(Constants.UdiEntityType.Document)] Document, @@ -41,7 +41,7 @@ namespace Umbraco.Core.Models /// /// Media /// - [UmbracoObjectType(Constants.ObjectTypes.Media, typeof(IMedia))] + [UmbracoObjectType(Constants.ObjectTypes.Strings.Media, typeof(IMedia))] [FriendlyName("Media")] [UmbracoUdiType(Constants.UdiEntityType.Media)] Media, @@ -49,7 +49,7 @@ namespace Umbraco.Core.Models /// /// Member Type /// - [UmbracoObjectType(Constants.ObjectTypes.MemberType, typeof(IMemberType))] + [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberType, typeof(IMemberType))] [FriendlyName("Member Type")] [UmbracoUdiType(Constants.UdiEntityType.MemberType)] MemberType, @@ -57,7 +57,7 @@ namespace Umbraco.Core.Models /// /// Template /// - [UmbracoObjectType(Constants.ObjectTypes.Template, typeof(ITemplate))] + [UmbracoObjectType(Constants.ObjectTypes.Strings.Template, typeof(ITemplate))] [FriendlyName("Template")] [UmbracoUdiType(Constants.UdiEntityType.Template)] Template, @@ -65,7 +65,7 @@ namespace Umbraco.Core.Models /// /// Member Group /// - [UmbracoObjectType(Constants.ObjectTypes.MemberGroup)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup)] [FriendlyName("Member Group")] [UmbracoUdiType(Constants.UdiEntityType.MemberGroup)] MemberGroup, @@ -74,7 +74,7 @@ namespace Umbraco.Core.Models /// /// Content Item /// - [UmbracoObjectType(Constants.ObjectTypes.ContentItem)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.ContentItem)] [FriendlyName("Content Item")] [Obsolete("This is not used and will be removed in future versions")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -83,7 +83,7 @@ namespace Umbraco.Core.Models /// /// "Media Type /// - [UmbracoObjectType(Constants.ObjectTypes.MediaType, typeof(IMediaType))] + [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaType, typeof(IMediaType))] [FriendlyName("Media Type")] [UmbracoUdiType(Constants.UdiEntityType.MediaType)] MediaType, @@ -91,7 +91,7 @@ namespace Umbraco.Core.Models /// /// Document Type /// - [UmbracoObjectType(Constants.ObjectTypes.DocumentType, typeof(IContentType))] + [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentType, typeof(IContentType))] [FriendlyName("Document Type")] [UmbracoUdiType(Constants.UdiEntityType.DocumentType)] DocumentType, @@ -99,14 +99,14 @@ namespace Umbraco.Core.Models /// /// Recycle Bin /// - [UmbracoObjectType(Constants.ObjectTypes.ContentRecycleBin)] - [FriendlyName("Recycle Bin")] + [UmbracoObjectType(Constants.ObjectTypes.Strings.ContentRecycleBin)] + [FriendlyName("Recycle Bin")] RecycleBin, /// /// Stylesheet /// - [UmbracoObjectType(Constants.ObjectTypes.Stylesheet)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.Stylesheet)] [FriendlyName("Stylesheet")] [UmbracoUdiType(Constants.UdiEntityType.Stylesheet)] Stylesheet, @@ -114,7 +114,7 @@ namespace Umbraco.Core.Models /// /// Member /// - [UmbracoObjectType(Constants.ObjectTypes.Member, typeof(IMember))] + [UmbracoObjectType(Constants.ObjectTypes.Strings.Member, typeof(IMember))] [FriendlyName("Member")] [UmbracoUdiType(Constants.UdiEntityType.Member)] Member, @@ -122,7 +122,7 @@ namespace Umbraco.Core.Models /// /// Data Type /// - [UmbracoObjectType(Constants.ObjectTypes.DataType, typeof(IDataTypeDefinition))] + [UmbracoObjectType(Constants.ObjectTypes.Strings.DataType, typeof(IDataType))] [FriendlyName("Data Type")] [UmbracoUdiType(Constants.UdiEntityType.DataType)] DataType, @@ -130,7 +130,7 @@ namespace Umbraco.Core.Models /// /// Document type container /// - [UmbracoObjectType(Constants.ObjectTypes.DocumentTypeContainer)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentTypeContainer)] [FriendlyName("Document Type Container")] [UmbracoUdiType(Constants.UdiEntityType.DocumentTypeContainer)] DocumentTypeContainer, @@ -138,7 +138,7 @@ namespace Umbraco.Core.Models /// /// Media type container /// - [UmbracoObjectType(Constants.ObjectTypes.MediaTypeContainer)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaTypeContainer)] [FriendlyName("Media Type Container")] [UmbracoUdiType(Constants.UdiEntityType.MediaTypeContainer)] MediaTypeContainer, @@ -146,7 +146,7 @@ namespace Umbraco.Core.Models /// /// Media type container /// - [UmbracoObjectType(Constants.ObjectTypes.DataTypeContainer)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.DataTypeContainer)] [FriendlyName("Data Type Container")] [UmbracoUdiType(Constants.UdiEntityType.DataTypeContainer)] DataTypeContainer, @@ -154,7 +154,7 @@ namespace Umbraco.Core.Models /// /// Relation type /// - [UmbracoObjectType(Constants.ObjectTypes.RelationType)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.RelationType)] [FriendlyName("Relation Type")] [UmbracoUdiType(Constants.UdiEntityType.RelationType)] RelationType, @@ -162,45 +162,45 @@ namespace Umbraco.Core.Models /// /// Forms Form /// - [UmbracoObjectType(Constants.ObjectTypes.FormsForm)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsForm)] [FriendlyName("Form")] FormsForm, /// /// Forms PreValue /// - [UmbracoObjectType(Constants.ObjectTypes.FormsPreValue)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsPreValue)] [FriendlyName("PreValue")] FormsPreValue, /// /// Forms DataSource /// - [UmbracoObjectType(Constants.ObjectTypes.FormsDataSource)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsDataSource)] [FriendlyName("DataSource")] FormsDataSource, /// /// Language /// - [UmbracoObjectType(Constants.ObjectTypes.Language)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.Language)] [FriendlyName("Language")] Language, /// /// Document Blueprint /// - [UmbracoObjectType(Constants.ObjectTypes.DocumentBlueprint, typeof(IContent))] + [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentBlueprint, typeof(IContent))] [FriendlyName("DocumentBlueprint")] [UmbracoUdiType(Constants.UdiEntityType.DocumentBlueprint)] DocumentBlueprint, - + /// /// Reserved Identifier /// - [UmbracoObjectType(Constants.ObjectTypes.IdReservation)] + [UmbracoObjectType(Constants.ObjectTypes.Strings.IdReservation)] [FriendlyName("Identifier Reservation")] IdReservation } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs b/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs deleted file mode 100644 index bacac30396..0000000000 --- a/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using Umbraco.Core.CodeAnnotations; - -namespace Umbraco.Core.Models -{ - /// - /// Extension methods for the UmbracoObjectTypes enum - /// - public static class UmbracoObjectTypesExtensions - { - //MUST be concurrent to avoid thread collisions! - private static readonly ConcurrentDictionary UmbracoObjectTypeCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary UmbracoObjectTypeUdiCache = new ConcurrentDictionary(); - - /// - /// Get an UmbracoObjectTypes value from it's name - /// - /// Enum value name - /// an UmbracoObjectTypes Enum value - public static UmbracoObjectTypes GetUmbracoObjectType(string name) - { - return (UmbracoObjectTypes)Enum.Parse(typeof(UmbracoObjectTypes), name, false); - } - - /// - /// Get an instance of an UmbracoObjectTypes enum value from it's GUID - /// - /// Enum value GUID - /// an UmbracoObjectTypes Enum value - public static UmbracoObjectTypes GetUmbracoObjectType(Guid guid) - { - var umbracoObjectType = UmbracoObjectTypes.Unknown; - - foreach (var name in Enum.GetNames(typeof(UmbracoObjectTypes))) - { - if (GetUmbracoObjectType(name).GetGuid() == guid) - { - umbracoObjectType = GetUmbracoObjectType(name); - } - } - - return umbracoObjectType; - } - - public static string GetUdiType(Guid guid) - { - var umbracoObjectType = Constants.UdiEntityType.Unknown; - - foreach (var name in Enum.GetNames(typeof(UmbracoObjectTypes))) - { - var objType = GetUmbracoObjectType(name); - if (objType.GetGuid() == guid) - { - umbracoObjectType = GetUdiType(objType); - } - } - return umbracoObjectType; - } - - /// - /// Extension method for the UmbracoObjectTypes enum to return the enum GUID - /// - /// UmbracoObjectTypes Enum value - /// a GUID value of the UmbracoObjectTypes - public static Guid GetGuid(this UmbracoObjectTypes umbracoObjectType) - { - return UmbracoObjectTypeCache.GetOrAdd(umbracoObjectType, types => - { - var type = typeof (UmbracoObjectTypes); - var memberInfo = type.GetMember(umbracoObjectType.ToString()); - var attributes = memberInfo[0].GetCustomAttributes(typeof (UmbracoObjectTypeAttribute), false); - - if (attributes.Length == 0) - return Guid.Empty; - - var attribute = (UmbracoObjectTypeAttribute) attributes[0]; - if (attribute == null) - return Guid.Empty; - - return attribute.ObjectId; - }); - } - - public static string GetUdiType(this UmbracoObjectTypes umbracoObjectType) - { - return UmbracoObjectTypeUdiCache.GetOrAdd(umbracoObjectType, types => - { - var type = typeof(UmbracoObjectTypes); - var memInfo = type.GetMember(umbracoObjectType.ToString()); - var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoUdiTypeAttribute), - false); - - if (attributes.Length == 0) - return Constants.UdiEntityType.Unknown; - - var attribute = ((UmbracoUdiTypeAttribute)attributes[0]); - if (attribute == null) - return Constants.UdiEntityType.Unknown; - - return attribute.UdiType; - }); - } - - /// - /// Extension method for the UmbracoObjectTypes enum to return the enum name - /// - /// UmbracoObjectTypes value - /// The enum name of the UmbracoObjectTypes value - public static string GetName(this UmbracoObjectTypes umbracoObjectType) - { - return Enum.GetName(typeof(UmbracoObjectTypes), umbracoObjectType); - } - - /// - /// Extension method for the UmbracoObejctTypes enum to return the enum friendly name - /// - /// UmbracoObjectTypes value - /// a string of the FriendlyName - public static string GetFriendlyName(this UmbracoObjectTypes umbracoObjectType) - { - var type = typeof(UmbracoObjectTypes); - var memInfo = type.GetMember(umbracoObjectType.ToString()); - var attributes = memInfo[0].GetCustomAttributes(typeof(FriendlyNameAttribute), - false); - - if (attributes.Length == 0) - return string.Empty; - - var attribute = ((FriendlyNameAttribute)attributes[0]); - if (attribute == null) - return string.Empty; - - return attribute.ToString(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UserControl.cs b/src/Umbraco.Core/Models/UserControl.cs deleted file mode 100644 index 6af4cd74cc..0000000000 --- a/src/Umbraco.Core/Models/UserControl.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Models -{ - /// - /// Represents a UserControl file - /// - [Serializable] - [DataContract(IsReference = true)] - public class UserControl : File, IUserControl - { - public UserControl(string path) - : this(path, (Func)null) - { } - - internal UserControl(string path, Func getFileContent) - : base(path, getFileContent) - { } - - /// - /// Indicates whether the current entity has an identity, which in this case is a path/name. - /// - /// - /// Overrides the default Entity identity check. - /// - public override bool HasIdentity - { - get { return string.IsNullOrEmpty(Path) == false; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index a65b307f24..f78de43f01 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -5,9 +5,8 @@ using System.Linq; using System.Net; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Identity; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -18,13 +17,32 @@ namespace Umbraco.Core.Models public static IEnumerable GetPermissions(this IUser user, string path, IUserService userService) { return userService.GetPermissionsForPath(user, path).GetAllPermissions(); - } - + } + public static bool HasSectionAccess(this IUser user, string app) { var apps = user.AllowedSections; return apps.Any(uApp => uApp.InvariantEquals(app)); - } + } + + /// + /// Determines whether this user is the 'super' user. + /// + public static bool IsSuper(this IUser user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return user.Id == Constants.Security.SuperId; + } + + /// + /// Determines whether this user belongs to the administrators group. + /// + /// The 'super' user does not automatically belongs to the administrators group. + public static bool IsAdmin(this IUser user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); + } /// /// Tries to lookup the user's gravatar to see if the endpoint can be reached, if so it returns the valid URL @@ -35,12 +53,12 @@ namespace Umbraco.Core.Models /// A list of 5 different sized avatar URLs /// internal static string[] GetUserAvatarUrls(this IUser user, ICacheProvider staticCache) - { + { //check if the user has explicitly removed all avatars including a gravatar, this will be possible and the value will be "none" if (user.Avatar == "none") { return new string[0]; - } + } if (user.Avatar.IsNullOrWhiteSpace()) { @@ -82,7 +100,7 @@ namespace Umbraco.Core.Models } //use the custom avatar - var avatarUrl = FileSystemProviderManager.Current.MediaFileSystem.GetUrl(user.Avatar); + var avatarUrl = Current.FileSystems.MediaFileSystem.GetUrl(user.Avatar); return new[] { avatarUrl + "?width=30&height=30&mode=crop", @@ -94,20 +112,30 @@ namespace Umbraco.Core.Models } + public static void ClearAllowedSections(this IUser user) + { + var allowed = user.AllowedSections.ToArray(); + foreach (var s in allowed) + { + user.RemoveAllowedSection(s); + } + } + /// /// Returns the culture info associated with this user, based on the language they're assigned to in the back office /// /// /// + /// /// - public static CultureInfo GetUserCulture(this IUser user, ILocalizedTextService textService) + public static CultureInfo GetUserCulture(this IUser user, ILocalizedTextService textService, IGlobalSettings globalSettings) { - if (user == null) throw new ArgumentNullException("user"); - if (textService == null) throw new ArgumentNullException("textService"); - return GetUserCulture(user.Language, textService); + if (user == null) throw new ArgumentNullException(nameof(user)); + if (textService == null) throw new ArgumentNullException(nameof(textService)); + return GetUserCulture(user.Language, textService, globalSettings); } - internal static CultureInfo GetUserCulture(string userLanguage, ILocalizedTextService textService) + internal static CultureInfo GetUserCulture(string userLanguage, ILocalizedTextService textService, IGlobalSettings globalSettings) { try { @@ -121,7 +149,7 @@ namespace Umbraco.Core.Models catch (CultureNotFoundException) { //return the default one - return CultureInfo.GetCultureInfo(GlobalSettings.DefaultUILanguage); + return CultureInfo.GetCultureInfo(globalSettings.DefaultUILanguage); } } @@ -158,19 +186,19 @@ namespace Umbraco.Core.Models internal static bool HasPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId) { switch (recycleBinId) - { + { case Constants.System.RecycleBinMedia: - return HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), recycleBinId); + return HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), recycleBinId); case Constants.System.RecycleBinContent: - return HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService), recycleBinId); - default: + return HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService), recycleBinId); + default: throw new NotSupportedException("Path access is only determined on content or media"); } - } - + } + internal static bool HasPathAccess(string path, int[] startNodeIds, int recycleBinId) { - if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", "path"); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); // check for no access if (startNodeIds.Length == 0) @@ -190,75 +218,72 @@ namespace Umbraco.Core.Models // check for a start node in the path return startNodeIds.Any(x => formattedPath.Contains(string.Concat(",", x, ","))); } - + internal static bool IsInBranchOfStartNode(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId, out bool hasPathAccess) { switch (recycleBinId) - { + { case Constants.System.RecycleBinMedia: - return IsInBranchOfStartNode(entity.Path, user.CalculateMediaStartNodeIds(entityService), user.GetMediaStartNodePaths(entityService), out hasPathAccess); + return IsInBranchOfStartNode(entity.Path, user.CalculateMediaStartNodeIds(entityService), user.GetMediaStartNodePaths(entityService), out hasPathAccess); case Constants.System.RecycleBinContent: - return IsInBranchOfStartNode(entity.Path, user.CalculateContentStartNodeIds(entityService), user.GetContentStartNodePaths(entityService), out hasPathAccess); - default: + return IsInBranchOfStartNode(entity.Path, user.CalculateContentStartNodeIds(entityService), user.GetContentStartNodePaths(entityService), out hasPathAccess); + default: throw new NotSupportedException("Path access is only determined on content or media"); } - } - + } + internal static bool IsInBranchOfStartNode(string path, int[] startNodeIds, string[] startNodePaths, out bool hasPathAccess) { - if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", "path"); - - hasPathAccess = false; + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); + + hasPathAccess = false; // check for no access if (startNodeIds.Length == 0) return false; // check for root access - if (startNodeIds.Contains(Constants.System.Root)) - { - hasPathAccess = true; - return true; - } - - //is it self? - var self = startNodePaths.Any(x => x == path); - if (self) - { - hasPathAccess = true; - return true; - } - - //is it ancestor? - var ancestor = startNodePaths.Any(x => x.StartsWith(path)); - if (ancestor) - { - hasPathAccess = false; - return true; - } - - //is it descendant? - var descendant = startNodePaths.Any(x => path.StartsWith(x)); - if (descendant) - { - hasPathAccess = true; - return true; - } - - return false; - } + if (startNodeIds.Contains(Constants.System.Root)) + { + hasPathAccess = true; + return true; + } + + //is it self? + var self = startNodePaths.Any(x => x == path); + if (self) + { + hasPathAccess = true; + return true; + } + + //is it ancestor? + var ancestor = startNodePaths.Any(x => x.StartsWith(path)); + if (ancestor) + { + //hasPathAccess = false; + return true; + } + + //is it descendant? + var descendant = startNodePaths.Any(x => path.StartsWith(x)); + if (descendant) + { + hasPathAccess = true; + return true; + } + + return false; + } /// - /// Determines whether this user is an admin. + /// Determines whether this user has access to view sensitive data /// /// - /// - /// true if this user is admin; otherwise, false. - /// - public static bool IsAdmin(this IUser user) + public static bool HasAccessToSensitiveData(this IUser user) { if (user == null) throw new ArgumentNullException("user"); - return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); + return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); } // calc. start nodes, combining groups' and user's, and excluding what's in the bin @@ -289,8 +314,8 @@ namespace Umbraco.Core.Models var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService); ToUserCache(user, cacheKey, vals); return vals; - } - + } + public static string[] GetMediaStartNodePaths(this IUser user, IEntityService entityService) { const string cacheKey = "MediaStartNodePaths"; @@ -298,12 +323,12 @@ namespace Umbraco.Core.Models var valuesInUserCache = FromUserCache(user, cacheKey); if (valuesInUserCache != null) return valuesInUserCache; - var startNodeIds = user.CalculateMediaStartNodeIds(entityService); - var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray(); - ToUserCache(user, cacheKey, vals); + var startNodeIds = user.CalculateMediaStartNodeIds(entityService); + var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray(); + ToUserCache(user, cacheKey, vals); return vals; - } - + } + public static string[] GetContentStartNodePaths(this IUser user, IEntityService entityService) { const string cacheKey = "ContentStartNodePaths"; @@ -311,32 +336,29 @@ namespace Umbraco.Core.Models var valuesInUserCache = FromUserCache(user, cacheKey); if (valuesInUserCache != null) return valuesInUserCache; - var startNodeIds = user.CalculateContentStartNodeIds(entityService); - var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray(); - ToUserCache(user, cacheKey, vals); + var startNodeIds = user.CalculateContentStartNodeIds(entityService); + var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray(); + ToUserCache(user, cacheKey, vals); return vals; } - private static T FromUserCache(IUser user, string cacheKey) + private static T FromUserCache(IUser user, string cacheKey) where T: class { - var entityUser = user as User; - if (entityUser == null) return null; + if (!(user is User entityUser)) return null; lock (entityUser.AdditionalDataLock) { - object allContentStartNodes; - return entityUser.AdditionalData.TryGetValue(cacheKey, out allContentStartNodes) + return entityUser.AdditionalData.TryGetValue(cacheKey, out var allContentStartNodes) ? allContentStartNodes as T : null; } } - private static void ToUserCache(IUser user, string cacheKey, T vals) + private static void ToUserCache(IUser user, string cacheKey, T vals) where T: class { - var entityUser = user as User; - if (entityUser == null) return; + if (!(user is User entityUser)) return; lock (entityUser.AdditionalDataLock) { @@ -361,7 +383,7 @@ namespace Umbraco.Core.Models binPath += Constants.System.RecycleBinMedia; break; default: - throw new ArgumentOutOfRangeException("objectType"); + throw new ArgumentOutOfRangeException(nameof(objectType)); } return binPath; } @@ -371,7 +393,9 @@ namespace Umbraco.Core.Models // assume groupSn and userSn each don't contain duplicates var asn = groupSn.Concat(userSn).Distinct().ToArray(); - var paths = entityService.GetAllPaths(objectType, asn).ToDictionary(x => x.Id, x => x.Path); + var paths = asn.Length > 0 + ? 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 @@ -380,8 +404,7 @@ namespace Umbraco.Core.Models var lsn = new List(); foreach (var sn in groupSn) { - string snp; - if (paths.TryGetValue(sn, out snp) == false) continue; // ignore rogue node (no path) + if (paths.TryGetValue(sn, out var snp) == false) continue; // ignore rogue node (no path) if (StartsWithPath(snp, binPath)) continue; // ignore bin @@ -392,9 +415,8 @@ namespace Umbraco.Core.Models var usn = new List(); foreach (var sn in userSn) - { - string snp; - if (paths.TryGetValue(sn, out snp) == false) continue; // ignore rogue node (no path) + { + if (paths.TryGetValue(sn, out var snp) == false) continue; // ignore rogue node (no path) if (StartsWithPath(snp, binPath)) continue; // ignore bin @@ -413,4 +435,4 @@ namespace Umbraco.Core.Models return lsn.ToArray(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs index b11ff67ead..c75b0496f3 100644 --- a/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs +++ b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs @@ -1,17 +1,31 @@ using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Models.Validation { /// - /// A custom validation attribute which adds additional metadata to the property to indicate that - /// the value is required to be persisted. + /// Specifies that a data field value is required in order to persist an object. /// /// - /// In Umbraco, we persist content even if it is invalid, however there are some properties that are absolutely required - /// in order to be persisted such as the Name of the content item. This attribute is re-usable to check for these types of - /// properties over any sort of model. + /// There are two levels of validation in Umbraco. (1) value validation is performed by + /// instances; it can prevent a content item from being published, but not from being saved. (2) required validation + /// of properties marked with ; it does prevent an object from being saved + /// and is used for properties that are absolutely mandatory, such as the name of a content item. /// public class RequiredForPersistenceAttribute : RequiredAttribute { + /// + /// Determines whether an object has all required values for persistence. + /// + internal static bool HasRequiredValuesForPersistence(object model) + { + return model.GetType().GetProperties().All(x => + { + var a = x.GetCustomAttribute(); + return a == null || a.IsValid(x.GetValue(model)); + }); + } } } diff --git a/src/Umbraco.Core/Models/ValueStorageType.cs b/src/Umbraco.Core/Models/ValueStorageType.cs new file mode 100644 index 0000000000..abbae60dc7 --- /dev/null +++ b/src/Umbraco.Core/Models/ValueStorageType.cs @@ -0,0 +1,47 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Represents the supported database types for storing a value. + /// + [Serializable] + [DataContract] + public enum ValueStorageType + { + // note: these values are written out in the database in some places, + // and then parsed back in a case-sensitive way - think about it before + // changing the casing of values. + + /// + /// Store property value as NText. + /// + [EnumMember] + Ntext, + + /// + /// Store property value as NVarChar. + /// + [EnumMember] + Nvarchar, + + /// + /// Store property value as Integer. + /// + [EnumMember] + Integer, + + /// + /// Store property value as Date. + /// + [EnumMember] + Date, + + /// + /// Store property value as Decimal. + /// + [EnumMember] + Decimal + } +} diff --git a/src/Umbraco.Core/Models/XsltFile.cs b/src/Umbraco.Core/Models/XsltFile.cs index 07f4d00084..a88cd2c127 100644 --- a/src/Umbraco.Core/Models/XsltFile.cs +++ b/src/Umbraco.Core/Models/XsltFile.cs @@ -29,4 +29,4 @@ namespace Umbraco.Core.Models get { return string.IsNullOrEmpty(Path) == false; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/NameValueCollectionExtensions.cs b/src/Umbraco.Core/NameValueCollectionExtensions.cs index 7b55d8131a..463d247f51 100644 --- a/src/Umbraco.Core/NameValueCollectionExtensions.cs +++ b/src/Umbraco.Core/NameValueCollectionExtensions.cs @@ -6,8 +6,8 @@ using System.Text; namespace Umbraco.Core { - internal static class NameValueCollectionExtensions - { + internal static class NameValueCollectionExtensions + { public static IEnumerable> AsEnumerable(this NameValueCollection nvc) { foreach (string key in nvc.AllKeys) @@ -16,10 +16,10 @@ namespace Umbraco.Core } } - public static bool ContainsKey(this NameValueCollection collection, string key) - { - return collection.Keys.Cast().Any(k => (string) k == key); - } + public static bool ContainsKey(this NameValueCollection collection, string key) + { + return collection.Keys.Cast().Any(k => (string) k == key); + } public static T GetValue(this NameValueCollection collection, string key, T defaultIfNotFound) { @@ -35,8 +35,8 @@ namespace Umbraco.Core } var result = val.TryConvertTo(); - + return result.Success ? result.Result : defaultIfNotFound; } - } + } } diff --git a/src/Umbraco.Core/NamedUdiRange.cs b/src/Umbraco.Core/NamedUdiRange.cs index c87e5db82e..f4c4066d61 100644 --- a/src/Umbraco.Core/NamedUdiRange.cs +++ b/src/Umbraco.Core/NamedUdiRange.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core +namespace Umbraco.Core { /// /// Represents a complemented with a name. @@ -31,4 +31,4 @@ namespace Umbraco.Core /// public string Name { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/NetworkHelper.cs b/src/Umbraco.Core/NetworkHelper.cs index 4719e03930..5f0bae70c4 100644 --- a/src/Umbraco.Core/NetworkHelper.cs +++ b/src/Umbraco.Core/NetworkHelper.cs @@ -47,4 +47,4 @@ namespace Umbraco.Core } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index ccd379f7d2..fb994f92f5 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; @@ -8,6 +8,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Xml; +using Newtonsoft.Json; using Umbraco.Core.Collections; namespace Umbraco.Core @@ -17,7 +18,7 @@ namespace Umbraco.Core /// public static class ObjectExtensions { - // Cache the various type lookups + private static readonly ConcurrentDictionary> ToObjectTypes = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary NullableGenericCache = new ConcurrentDictionary(); private static readonly ConcurrentDictionary InputTypeConverterCache = new ConcurrentDictionary(); private static readonly ConcurrentDictionary DestinationTypeConverterCache = new ConcurrentDictionary(); @@ -46,8 +47,8 @@ namespace Umbraco.Core /// public static void DisposeIfDisposable(this object input) { - var disposable = input as IDisposable; - if (disposable != null) disposable.Dispose(); + if (input is IDisposable disposable) + disposable.Dispose(); } /// @@ -59,9 +60,9 @@ namespace Umbraco.Core /// internal static T SafeCast(this object input) { - if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default(T); - if (input is T) return (T)input; - return default(T); + if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default; + if (input is T variable) return variable; + return default; } /// @@ -114,7 +115,7 @@ namespace Umbraco.Core } // Reference types are ok - return Attempt.SucceedIf(target.IsValueType == false, null); + return Attempt.If(target.IsValueType == false, null); } var inputType = input.GetType(); @@ -140,9 +141,9 @@ namespace Umbraco.Core if (underlying != null) { // Special case for empty strings for bools/dates which should return null if an empty string. - var inputString = input as string; - if (inputString != null) + if (input is string inputString) { + //TODO: Why the check against only bool/date when a string is null/empty? In what scenario can we convert to another type when the string is null or empty other than just being null? if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool))) { return Attempt.Succeed(null); @@ -167,8 +168,7 @@ namespace Umbraco.Core { // target is not a generic type - var inputString = input as string; - if (inputString != null) + if (input is string inputString) { // Try convert from string, returns an Attempt if the string could be // processed (either succeeded or failed), else null if we need to try @@ -208,9 +208,16 @@ namespace Umbraco.Core return Attempt.Succeed(outputConverter.ConvertFrom(input)); } + if (target.IsGenericType && GetCachedGenericNullableType(target) != null) + { + // cannot Convert.ChangeType as that does not work with nullable + // input has already been converted to the underlying type - just + // return input, there's an implicit conversion from T to T? anyways + return Attempt.Succeed(input); + } + // Re-check convertables since we altered the input through recursion - var convertible2 = input as IConvertible; - if (convertible2 != null) + if (input is IConvertible convertible2) { return Attempt.Succeed(Convert.ChangeType(convertible2, target)); } @@ -268,8 +275,7 @@ namespace Umbraco.Core { if (target == typeof(int)) { - int value; - if (int.TryParse(input, out value)) + if (int.TryParse(input, out var value)) { return Attempt.Succeed(value); } @@ -277,30 +283,26 @@ namespace Umbraco.Core // Because decimal 100.01m will happily convert to integer 100, it // makes sense that string "100.01" *also* converts to integer 100. var input2 = NormalizeNumberDecimalSeparator(input); - decimal value2; - return Attempt.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt32(value2)); + return Attempt.If(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2)); } if (target == typeof(long)) { - long value; - if (long.TryParse(input, out value)) + if (long.TryParse(input, out var value)) { return Attempt.Succeed(value); } // Same as int var input2 = NormalizeNumberDecimalSeparator(input); - decimal value2; - return Attempt.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt64(value2)); + return Attempt.If(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2)); } // TODO: Should we do the decimal trick for short, byte, unsigned? if (target == typeof(bool)) { - bool value; - if (bool.TryParse(input, out value)) + if (bool.TryParse(input, out var value)) { return Attempt.Succeed(value); } @@ -313,53 +315,42 @@ namespace Umbraco.Core switch (Type.GetTypeCode(target)) { case TypeCode.Int16: - short value; - return Attempt.SucceedIf(short.TryParse(input, out value), value); + return Attempt.If(short.TryParse(input, out var value), value); case TypeCode.Double: var input2 = NormalizeNumberDecimalSeparator(input); - double valueD; - return Attempt.SucceedIf(double.TryParse(input2, out valueD), valueD); + return Attempt.If(double.TryParse(input2, out var valueD), valueD); case TypeCode.Single: var input3 = NormalizeNumberDecimalSeparator(input); - float valueF; - return Attempt.SucceedIf(float.TryParse(input3, out valueF), valueF); + return Attempt.If(float.TryParse(input3, out var valueF), valueF); case TypeCode.Char: - char valueC; - return Attempt.SucceedIf(char.TryParse(input, out valueC), valueC); + return Attempt.If(char.TryParse(input, out var valueC), valueC); case TypeCode.Byte: - byte valueB; - return Attempt.SucceedIf(byte.TryParse(input, out valueB), valueB); + return Attempt.If(byte.TryParse(input, out var valueB), valueB); case TypeCode.SByte: - sbyte valueSb; - return Attempt.SucceedIf(sbyte.TryParse(input, out valueSb), valueSb); + return Attempt.If(sbyte.TryParse(input, out var valueSb), valueSb); case TypeCode.UInt32: - uint valueU; - return Attempt.SucceedIf(uint.TryParse(input, out valueU), valueU); + return Attempt.If(uint.TryParse(input, out var valueU), valueU); case TypeCode.UInt16: - ushort valueUs; - return Attempt.SucceedIf(ushort.TryParse(input, out valueUs), valueUs); + return Attempt.If(ushort.TryParse(input, out var valueUs), valueUs); case TypeCode.UInt64: - ulong valueUl; - return Attempt.SucceedIf(ulong.TryParse(input, out valueUl), valueUl); + return Attempt.If(ulong.TryParse(input, out var valueUl), valueUl); } } else if (target == typeof(Guid)) { - Guid value; - return Attempt.SucceedIf(Guid.TryParse(input, out value), value); + return Attempt.If(Guid.TryParse(input, out var value), value); } else if (target == typeof(DateTime)) { - DateTime value; - if (DateTime.TryParse(input, out value)) + if (DateTime.TryParse(input, out var value)) { switch (value.Kind) { @@ -379,28 +370,24 @@ namespace Umbraco.Core } else if (target == typeof(DateTimeOffset)) { - DateTimeOffset value; - return Attempt.SucceedIf(DateTimeOffset.TryParse(input, out value), value); + return Attempt.If(DateTimeOffset.TryParse(input, out var value), value); } else if (target == typeof(TimeSpan)) { - TimeSpan value; - return Attempt.SucceedIf(TimeSpan.TryParse(input, out value), value); + return Attempt.If(TimeSpan.TryParse(input, out var value), value); } else if (target == typeof(decimal)) { var input2 = NormalizeNumberDecimalSeparator(input); - decimal value; - return Attempt.SucceedIf(decimal.TryParse(input2, out value), value); + return Attempt.If(decimal.TryParse(input2, out var value), value); } else if (input != null && target == typeof(Version)) { - Version value; - return Attempt.SucceedIf(Version.TryParse(input, out value), value); + return Attempt.If(Version.TryParse(input, out var value), value); } // E_NOTIMPL IPAddress, BigInteger - return null; // We can't decide... + return null; // we can't decide... } internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) { @@ -483,8 +470,7 @@ namespace Umbraco.Core /// /// /// - public static IDictionary ToDictionary(this T o, - params Expression>[] ignoreProperties) + public static IDictionary ToDictionary(this T o, params Expression>[] ignoreProperties) { return o.ToDictionary(ignoreProperties.Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray()); } @@ -514,6 +500,39 @@ namespace Umbraco.Core return new Dictionary(); } + /// + /// Converts an object's properties into a dictionary. + /// + /// The object to convert. + /// A property namer function. + /// A dictionary containing each properties. + public static Dictionary ToObjectDictionary(T obj, Func namer = null) + { + if (obj == null) return new Dictionary(); + + string DefaultNamer(PropertyInfo property) + { + var jsonProperty = property.GetCustomAttribute(); + return jsonProperty?.PropertyName ?? property.Name; + } + + var t = obj.GetType(); + + if (namer == null) namer = DefaultNamer; + + if (!ToObjectTypes.TryGetValue(t, out var properties)) + { + properties = new Dictionary(); + + foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)) + properties[namer(p)] = ReflectionUtilities.EmitPropertyGetter(p); + + ToObjectTypes[t] = properties; + } + + return properties.ToDictionary(x => x.Key, x => ((Func) x.Value)(obj)); + } + internal static string ToDebugString(this object obj, int levels = 0) { if (obj == null) return "{null}"; @@ -538,7 +557,7 @@ namespace Umbraco.Core var items = (from object enumItem in enumerable let value = GetEnumPropertyDebugString(enumItem, levels) where value != null select value).Take(10).ToList(); return items.Any() - ? "{{ {0} }}".InvariantFormat(String.Join(", ", items)) + ? "{{ {0} }}".InvariantFormat(string.Join(", ", items)) : null; } @@ -634,9 +653,9 @@ namespace Umbraco.Core /// /// /// - internal static string ToXmlString(this object value) + internal static string ToXmlString(this object value) { - return value.ToXmlString(typeof(T)); + return value.ToXmlString(typeof (T)); } private static string GetEnumPropertyDebugString(object enumItem, int levels) @@ -665,7 +684,7 @@ namespace Umbraco.Core internal static Guid AsGuid(this object value) { - return value is Guid ? (Guid)value : Guid.Empty; + return value is Guid guid ? guid : Guid.Empty; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -681,13 +700,12 @@ namespace Umbraco.Core { var key = new CompositeTypeTypeKey(source, target); - TypeConverter typeConverter; - if (InputTypeConverterCache.TryGetValue(key, out typeConverter)) + if (InputTypeConverterCache.TryGetValue(key, out var typeConverter)) { return typeConverter; } - TypeConverter converter = TypeDescriptor.GetConverter(source); + var converter = TypeDescriptor.GetConverter(source); if (converter.CanConvertTo(target)) { return InputTypeConverterCache[key] = converter; @@ -702,8 +720,7 @@ namespace Umbraco.Core { var key = new CompositeTypeTypeKey(source, target); - TypeConverter typeConverter; - if (DestinationTypeConverterCache.TryGetValue(key, out typeConverter)) + if (DestinationTypeConverterCache.TryGetValue(key, out var typeConverter)) { return typeConverter; } @@ -721,8 +738,7 @@ namespace Umbraco.Core [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Type GetCachedGenericNullableType(Type type) { - Type underlyingType; - if (NullableGenericCache.TryGetValue(type, out underlyingType)) + if (NullableGenericCache.TryGetValue(type, out var underlyingType)) { return underlyingType; } @@ -741,13 +757,12 @@ namespace Umbraco.Core private static bool GetCachedCanAssign(object input, Type source, Type target) { var key = new CompositeTypeTypeKey(source, target); - bool canConvert; - if (AssignableTypeCache.TryGetValue(key, out canConvert)) + if (AssignableTypeCache.TryGetValue(key, out var canConvert)) { return canConvert; - } - - // "object is" is faster than "Type.IsAssignableFrom. + } + + // "object is" is faster than "Type.IsAssignableFrom. // We can use it to very quickly determine whether true/false if (input is IConvertible && target.IsAssignableFrom(source)) { @@ -761,8 +776,7 @@ namespace Umbraco.Core [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool GetCachedCanConvertToBoolean(Type type) { - bool result; - if (BoolConvertCache.TryGetValue(type, out result)) + if (BoolConvertCache.TryGetValue(type, out var result)) { return result; } diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs deleted file mode 100644 index eda1b94e37..0000000000 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using Umbraco.Core.Logging; -using umbraco.interfaces; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// A resolver to return all IApplicationEvents objects - /// - /// - /// This is disposable because after the app has started it should be disposed to release any memory being occupied by instances. - /// Ordering handlers: handlers are ordered by (ascending) weight. By default, handlers from the Umbraco.* or Concorde.* - /// assemblies have a -100 weight whereas any other handler has a weight of +100. A custom weight can be assigned to a handler - /// by marking the class with the WeightAttribute. For example, the CacheRefresherEventHandler is marked with [Weight(int.MinValue)] - /// because its events need to run before anything else. Positive weights are considered "user-space" while negative weights are - /// "core". Finally, users can register a filter to process the list (after it has been ordered) and re-order it, or remove handlers. - /// BEWARE! handlers order is an important thing, and removing handlers or reordering handlers can have unexpected consequences. - /// - public sealed class ApplicationEventsResolver : ManyObjectsResolverBase, IDisposable - { - private readonly LegacyStartupHandlerResolver _legacyResolver; - - /// - /// Constructor - /// - /// - /// - /// - internal ApplicationEventsResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable applicationEventHandlers) - : base(serviceProvider, logger, applicationEventHandlers) - { - //create the legacy resolver and only include the legacy types - _legacyResolver = new LegacyStartupHandlerResolver( - serviceProvider, logger, - applicationEventHandlers.Where(x => TypeHelper.IsTypeAssignableFrom(x) == false)); - } - - /// - /// Override in order to only return types of IApplicationEventHandler and above, - /// do not include the legacy types of IApplicationStartupHandler - /// - protected override IEnumerable InstanceTypes - { - get { return base.InstanceTypes.Where(TypeHelper.IsTypeAssignableFrom); } - } - - private List _orderedAndFiltered; - - /// - /// Gets the implementations. - /// - public IEnumerable ApplicationEventHandlers - { - get - { - if (_orderedAndFiltered == null) - { - _orderedAndFiltered = GetSortedValues().ToList(); - if (FilterCollection != null) - FilterCollection(_orderedAndFiltered); - } - return _orderedAndFiltered; - } - } - - /// - /// Gets or sets a delegate to filter the event handler list (EXPERT!). - /// - /// - /// This can be set on startup in the pre-boot process in either a custom boot manager or global.asax (UmbracoApplication). - /// Allows custom logic to execute in order to filter and/or re-order the event handlers prior to executing. - /// To be used by custom boot sequences where the boot loader needs to remove some handlers, or raise their priority. - /// Filtering the event handler collection can have ugly consequences. Use with care. - /// - public Action> FilterCollection - { - get { return _filterCollection; } - set - { - if (_orderedAndFiltered != null) - throw new InvalidOperationException("Cannot set the FilterCollection delegate once the ApplicationEventHandlers are resolved"); - if (_filterCollection != null) - throw new InvalidOperationException("Cannot set the FilterCollection delegate once it's already been specified"); - - _filterCollection = value; - } - } - - protected override int GetObjectWeight(object o) - { - var type = o.GetType(); - var attr = type.GetCustomAttribute(true); - if (attr != null) return attr.Weight; - var name = type.Assembly.FullName; - - // we should really attribute all our Core handlers, so this is temp - var core = name.InvariantStartsWith("Umbraco.") || name.InvariantStartsWith("Concorde."); - return core ? -DefaultPluginWeight : DefaultPluginWeight; - } - - /// - /// Create instances of all of the legacy startup handlers - /// - public void InstantiateLegacyStartupHandlers() - { - //this will instantiate them all - var handlers = _legacyResolver.LegacyStartupHandlers; - } - - protected override bool SupportsClear - { - get { return false; } - } - - protected override bool SupportsInsert - { - get { return false; } - } - - private class LegacyStartupHandlerResolver : ManyObjectsResolverBase, IDisposable - { - internal LegacyStartupHandlerResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable legacyStartupHandlers) - : base(serviceProvider, logger, legacyStartupHandlers) - { - - } - - public IEnumerable LegacyStartupHandlers - { - get { return Values; } - } - - public void Dispose() - { - ResetCollections(); - } - } - - private bool _disposed; - private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim(); - private Action> _filterCollection; - - /// - /// Gets a value indicating whether this instance is disposed. - /// - /// - /// true if this instance is disposed; otherwise, false. - /// - public bool IsDisposed - { - get { return _disposed; } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 - public void Dispose() - { - Dispose(true); - - // Use SupressFinalize in case a subclass of this type implements a finalizer. - GC.SuppressFinalize(this); - } - - ~ApplicationEventsResolver() - { - // Run dispose but let the class know it was due to the finalizer running. - Dispose(false); - } - - private void Dispose(bool disposing) - { - // Only operate if we haven't already disposed - if (IsDisposed || disposing == false) return; - - using (new WriteLock(_disposalLocker)) - { - // Check again now we're inside the lock - if (IsDisposed) return; - - // Call to actually release resources. This method is only - // kept separate so that the entire disposal logic can be used as a VS snippet - DisposeResources(); - - // Indicate that the instance has been disposed. - _disposed = true; - } - } - - /// - /// Clear out all of the instances, we don't want them hanging around and cluttering up memory - /// - private void DisposeResources() - { - _legacyResolver.Dispose(); - ResetCollections(); - _orderedAndFiltered.Clear(); - _orderedAndFiltered = null; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs b/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs deleted file mode 100644 index 821b93ba03..0000000000 --- a/src/Umbraco.Core/ObjectResolution/LazyManyObjectsResolverbase.cs +++ /dev/null @@ -1,302 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Web; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// The base class for all lazy many-objects resolvers. - /// - /// The type of the concrete resolver class. - /// The type of the resolved objects. - /// - /// This is a special case resolver for when types get lazily resolved in order to resolve the actual types. This is useful - /// for when there is some processing overhead (i.e. Type finding in assemblies) to return the Types used to instantiate the instances. - /// In some these cases we don't want to have to type-find during application startup, only when we need to resolve the instances. - /// Important notes about this resolver: it does not support Insert or Remove and therefore does not support any ordering unless - /// the types are marked with the WeightAttribute. - /// - public abstract class LazyManyObjectsResolverBase : ManyObjectsResolverBase - where TResolved : class - where TResolver : ResolverBase - { - #region Constructors - - /// - /// Initializes a new instance of the class with an empty list of objects, - /// and an optional lifetime scope. - /// - /// - /// - /// The lifetime scope of instantiated objects, default is per Application. - /// If is per HttpRequest then there must be a current HttpContext. - /// is per HttpRequest but the current HttpContext is null. - protected LazyManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - : base(serviceProvider, logger, scope) - { - Initialize(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use ctor specifying IServiceProvider instead")] - protected LazyManyObjectsResolverBase(ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - : base(scope) - { - Initialize(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use ctor specifying IServiceProvider instead")] - protected LazyManyObjectsResolverBase(HttpContextBase httpContext) - : base(httpContext) - { - Initialize(); - } - - protected LazyManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, IEnumerable> lazyTypeList, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - : this(serviceProvider, logger, scope) - { - AddTypes(lazyTypeList); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use ctor specifying IServiceProvider instead")] - protected LazyManyObjectsResolverBase(IEnumerable> lazyTypeList, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - : this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, lazyTypeList, scope) - { - } - - protected LazyManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, Func> typeListProducerList, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - : this(serviceProvider, logger, scope) - { - _typeListProducerList.Add(typeListProducerList); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use ctor specifying IServiceProvider instead")] - protected LazyManyObjectsResolverBase(Func> typeListProducerList, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - : this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, typeListProducerList, scope) - { - } - - protected LazyManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, HttpContextBase httpContext, IEnumerable> lazyTypeList) - : this(serviceProvider, logger, httpContext) - { - AddTypes(lazyTypeList); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use ctor specifying IServiceProvider instead")] - protected LazyManyObjectsResolverBase(HttpContextBase httpContext, IEnumerable> lazyTypeList) - : this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, httpContext, lazyTypeList) - { - } - - protected LazyManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, HttpContextBase httpContext, Func> typeListProducerList) - : this(serviceProvider, logger, httpContext) - { - _typeListProducerList.Add(typeListProducerList); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use ctor specifying IServiceProvider instead")] - protected LazyManyObjectsResolverBase(HttpContextBase httpContext, Func> typeListProducerList) - : this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, httpContext, typeListProducerList) - { - } - - #endregion - - private readonly List> _lazyTypeList = new List>(); - private readonly List>> _typeListProducerList = new List>>(); - private readonly List _excludedTypesList = new List(); - private Lazy> _resolvedTypes; - - /// - /// Initializes a new instance of the class with an empty list of objects, - /// with creation of objects based on an HttpRequest lifetime scope. - /// - /// - /// - /// The HttpContextBase corresponding to the HttpRequest. - /// is null. - protected LazyManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, HttpContextBase httpContext) - : base(serviceProvider, logger, httpContext) - { - } - - /// - /// Initializes a new instance of the class with an initial list of object types, - /// and an optional lifetime scope. - /// - /// - /// - /// The list of object types. - /// The lifetime scope of instantiated objects, default is per Application. - /// If is per HttpRequest then there must be a current HttpContext. - /// is per HttpRequest but the current HttpContext is null. - protected LazyManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, IEnumerable value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - : base(serviceProvider, logger, value, scope) - { - } - - private void Initialize() - { - _resolvedTypes = new Lazy>(() => - { - var resolvedTypes = new List(); - - // get the types by evaluating the lazy & producers - var types = new List(); - types.AddRange(_lazyTypeList.Select(x => x.Value)); - types.AddRange(_typeListProducerList.SelectMany(x => x())); - - // we need to validate each resolved type now since we could - // not do it before evaluating the lazy & producers - foreach (var type in types.Where(x => _excludedTypesList.Contains(x) == false)) - { - AddValidAndNoDuplicate(resolvedTypes, type); - } - - return resolvedTypes; - }); - } - - /// - /// Gets a value indicating whether the resolver has resolved types to create instances from. - /// - /// To be used in unit tests. - public bool HasResolvedTypes - { - get { return _resolvedTypes.IsValueCreated; } - } - - /// - /// Gets the list of types to create instances from. - /// - /// When called, will get the types from the lazy list. - protected override IEnumerable InstanceTypes - { - get { return _resolvedTypes.Value; } - } - - /// - /// Ensures that type is valid and not a duplicate - /// then appends the type to the end of the list - /// - /// - /// - private void AddValidAndNoDuplicate(List list, Type type) - { - EnsureCorrectType(type); - if (list.Contains(type)) - { - throw new InvalidOperationException(string.Format( - "Type {0} is already in the collection of types.", type.FullName)); - } - list.Add(type); - } - - #region Types collection manipulation - - /// - /// Removes types from the list of types, once it has been lazily evaluated, and before actual objects are instanciated. - /// - /// The type to remove. - public override void RemoveType(Type value) - { - EnsureSupportsRemove(); - - _excludedTypesList.Add(value); - } - - /// - /// Lazily adds types from lazy types. - /// - /// The lazy types, to add. - protected void AddTypes(IEnumerable> types) - { - EnsureSupportsAdd(); - - using (Resolution.Configuration) - using (GetWriteLock()) - { - foreach (var t in types) - { - _lazyTypeList.Add(t); - } - } - } - - /// - /// Lazily adds types from a function producing types. - /// - /// The functions producing types, to add. - public void AddTypeListDelegate(Func> typeListProducer) - { - EnsureSupportsAdd(); - - using (Resolution.Configuration) - using (GetWriteLock()) - { - _typeListProducerList.Add(typeListProducer); - } - } - - /// - /// Lazily adds a type from a lazy type. - /// - /// The lazy type, to add. - public void AddType(Lazy value) - { - EnsureSupportsAdd(); - - using (Resolution.Configuration) - using (GetWriteLock()) - { - _lazyTypeList.Add(value); - } - } - - /// - /// Lazily adds a type from an actual type. - /// - /// The actual type, to add. - /// The type is converted to a lazy type. - public override void AddType(Type value) - { - AddType(new Lazy(() => value)); - } - - /// - /// Clears all lazy types - /// - public override void Clear() - { - EnsureSupportsClear(); - - using (Resolution.Configuration) - using (GetWriteLock()) - { - _lazyTypeList.Clear(); - } - } - - #endregion - - #region Types collection manipulation support - - /// - /// Gets a false value indicating that the resolver does NOT support inserting types. - /// - protected override bool SupportsInsert - { - get { return false; } - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs b/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs deleted file mode 100644 index 62ce20cbbb..0000000000 --- a/src/Umbraco.Core/ObjectResolution/LegacyTransientObjectsResolver.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// The base class for old legacy factories such as the DataTypeFactory or CacheResolverFactory. - /// - /// The type of the concrete resolver class. - /// The type of the resolved objects. - /// - /// This class contains basic functionality to mimic the functionality in these old factories since they all return - /// transient objects (though this should be changed) and the method GetById needs to lookup a type to an ID and since - /// these old classes don't contain metadata, the objects need to be instantiated first to get their metadata, we then store this - /// for use in the GetById method. - /// - internal abstract class LegacyTransientObjectsResolver : LazyManyObjectsResolverBase - where TResolved : class - where TResolver : ResolverBase - { - private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - private ConcurrentDictionary _id2Type; - - #region Constructors - - /// - /// Initializes a new instance of the class with an initial list of object types. - /// - /// - /// - /// A function returning the list of object types. - /// - /// We are creating Transient instances (new instances each time) because this is how the legacy code worked and - /// I don't want to muck anything up by changing them to application based instances. - /// TODO: However, it would make much more sense to do this and would speed up the application plus this would make the GetById method much easier. - /// - protected LegacyTransientObjectsResolver(IServiceProvider serviceProvider, ILogger logger, Func> value) - : base(serviceProvider, logger, value, ObjectLifetimeScope.Transient) // new objects every time - { } - #endregion - - /// - /// Returns the unique identifier of the type of a specified object. - /// - /// The object. - /// The unique identifier of the type of . - protected abstract Guid GetUniqueIdentifier(TResolved value); - - /// - /// Returns a new instance for the type identified by its unique type identifier. - /// - /// The type identifier. - /// The value of the type uniquely identified by . - public TResolved GetById(Guid id) - { - EnsureIsInitialized(); - return _id2Type.ContainsKey(id) == false - ? null - : ServiceProvider.GetService(_id2Type[id]) as TResolved; - } - - /// - /// Populates the identifiers-to-types dictionnary. - /// - /// - /// This allow us to instantiate a type by ID since these legacy types doesn't contain any metadata. - /// We instanciate all types once to get their unique identifier, then build the dictionary so that - /// when GetById is called, we can instanciate a single object. - /// - protected void EnsureIsInitialized() - { - using (var l = new UpgradeableReadLock(_lock)) - { - if (_id2Type == null) - { - l.UpgradeToWriteLock(); - - _id2Type = new ConcurrentDictionary(); - foreach (var value in Values) - { - _id2Type.TryAdd(GetUniqueIdentifier(value), value.GetType()); - } - } - } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs deleted file mode 100644 index 6b64172633..0000000000 --- a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs +++ /dev/null @@ -1,658 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Threading; -using System.Web; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// The base class for all many-objects resolvers. - /// - /// The type of the concrete resolver class. - /// The type of the resolved objects. - public abstract class ManyObjectsResolverBase : ResolverBase - where TResolved : class - where TResolver : ResolverBase - { - private Lazy> _applicationInstances; - private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - private readonly string _httpContextKey; - private readonly List _instanceTypes = new List(); - private IEnumerable _sortedValues; - private readonly Func _httpContextGetter; - - private int _defaultPluginWeight = 100; - - #region Constructors - - /// - /// Initializes a new instance of the class with an empty list of objects, - /// and an optional lifetime scope. - /// - /// - /// - /// The lifetime scope of instantiated objects, default is per Application. - /// If is per HttpRequest then there must be a current HttpContext. - /// is per HttpRequest but the current HttpContext is null. - protected ManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - { - if (serviceProvider == null) throw new ArgumentNullException("serviceProvider"); - if (logger == null) throw new ArgumentNullException("logger"); - CanResolveBeforeFrozen = false; - if (scope == ObjectLifetimeScope.HttpRequest) - _httpContextGetter = () => new HttpContextWrapper(HttpContext.Current); - - ServiceProvider = serviceProvider; - Logger = logger; - LifetimeScope = scope; - if (scope == ObjectLifetimeScope.HttpRequest) - _httpContextKey = GetType().FullName; - _instanceTypes = new List(); - - InitializeAppInstances(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use ctor specifying IServiceProvider instead")] - protected ManyObjectsResolverBase(ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - : this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, scope) - { - - } - - /// - /// Initializes a new instance of the class with an empty list of objects, - /// with creation of objects based on an HttpRequest lifetime scope. - /// - /// - /// - /// The HttpContextBase corresponding to the HttpRequest. - /// is null. - protected ManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, HttpContextBase httpContext) - { - if (serviceProvider == null) throw new ArgumentNullException("serviceProvider"); - if (httpContext == null) throw new ArgumentNullException("httpContext"); - CanResolveBeforeFrozen = false; - Logger = logger; - LifetimeScope = ObjectLifetimeScope.HttpRequest; - _httpContextKey = GetType().FullName; - ServiceProvider = serviceProvider; - _httpContextGetter = () => httpContext; - _instanceTypes = new List(); - - InitializeAppInstances(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use ctor specifying IServiceProvider instead")] - protected ManyObjectsResolverBase(HttpContextBase httpContext) - : this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, httpContext) - { - - } - - /// - /// Initializes a new instance of the class with an initial list of object types, - /// and an optional lifetime scope. - /// - /// - /// - /// The list of object types. - /// The lifetime scope of instantiated objects, default is per Application. - /// If is per HttpRequest then there must be a current HttpContext. - /// is per HttpRequest but the current HttpContext is null. - protected ManyObjectsResolverBase(IServiceProvider serviceProvider, ILogger logger, IEnumerable value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - : this(serviceProvider, logger, scope) - { - _instanceTypes = value.ToList(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use ctor specifying IServiceProvider instead")] - protected ManyObjectsResolverBase(IEnumerable value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application) - : this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, value, scope) - { - - } - - /// - /// Initializes a new instance of the class with an initial list of objects, - /// with creation of objects based on an HttpRequest lifetime scope. - /// - /// The HttpContextBase corresponding to the HttpRequest. - /// The list of object types. - /// is null. - [Obsolete("Use ctor specifying IServiceProvider instead")] - protected ManyObjectsResolverBase(HttpContextBase httpContext, IEnumerable value) - : this(new ActivatorServiceProvider(), LoggerResolver.Current.Logger, httpContext) - { - _instanceTypes = value.ToList(); - } - #endregion - - private void InitializeAppInstances() - { - _applicationInstances = new Lazy>(() => CreateInstances().ToArray()); - } - - /// - /// Gets or sets a value indicating whether the resolver can resolve objects before resolution is frozen. - /// - /// This is false by default and is used for some special internal resolvers. - internal bool CanResolveBeforeFrozen { get; set; } - - /// - /// Gets the list of types to create instances from. - /// - protected virtual IEnumerable InstanceTypes - { - get { return _instanceTypes; } - } - - /// - /// Gets or sets the used to initialize this object, if any. - /// - /// If not null, then LifetimeScope will be ObjectLifetimeScope.HttpRequest. - protected HttpContextBase CurrentHttpContext - { - get - { - var context = _httpContextGetter == null ? null : _httpContextGetter(); - if (context == null) - throw new InvalidOperationException("Cannot use this resolver with lifetime 'HttpRequest' when there is no current HttpContext. Either use the ctor accepting an HttpContextBase, or use the resolver from within a request exclusively."); - return context; - } - } - - /// - /// Returns the service provider used to instantiate objects - /// - public IServiceProvider ServiceProvider { get; private set; } - - public ILogger Logger { get; private set; } - - /// - /// Gets or sets the lifetime scope of resolved objects. - /// - protected ObjectLifetimeScope LifetimeScope { get; private set; } - - /// - /// Gets the resolved object instances, sorted by weight. - /// - /// The sorted resolved object instances. - /// - /// The order is based upon the WeightAttribute and DefaultPluginWeight. - /// Weights are sorted ascendingly (lowest weights come first). - /// - protected IEnumerable GetSortedValues() - { - if (_sortedValues == null) - { - var values = Values.ToList(); - values.Sort((f1, f2) => GetObjectWeight(f1).CompareTo(GetObjectWeight(f2))); - _sortedValues = values; - } - return _sortedValues; - } - - /// - /// Gets or sets the default type weight. - /// - /// Determines the weight of types that do not have a WeightAttribute set on - /// them, when calling GetSortedValues. - protected virtual int DefaultPluginWeight - { - get { return _defaultPluginWeight; } - set { _defaultPluginWeight = value; } - } - - /// - /// Returns the weight of an object for user with GetSortedValues - /// - /// - /// - protected virtual int GetObjectWeight(object o) - { - var type = o.GetType(); - var attr = type.GetCustomAttribute(true); - return attr == null ? DefaultPluginWeight : attr.Weight; - } - - /// - /// Gets the resolved object instances. - /// - /// CanResolveBeforeFrozen is false, and resolution is not frozen. - protected IEnumerable Values - { - get - { - using (Resolution.Reader(CanResolveBeforeFrozen)) - { - // note: we apply .ToArray() to the output of CreateInstance() because that is an IEnumerable that - // comes from the PluginManager we want to be _sure_ that it's not a Linq of some sort, but the - // instances have actually been instanciated when we return. - - switch (LifetimeScope) - { - case ObjectLifetimeScope.HttpRequest: - - // create new instances per HttpContext - if (CurrentHttpContext.Items[_httpContextKey] == null) - { - var instances = CreateInstances().ToArray(); - var disposableInstances = instances.OfType(); - //Ensure anything resolved that is IDisposable is disposed when the request termintates - foreach (var disposable in disposableInstances) - { - CurrentHttpContext.DisposeOnPipelineCompleted(disposable); - } - CurrentHttpContext.Items[_httpContextKey] = instances; - } - return (TResolved[])CurrentHttpContext.Items[_httpContextKey]; - - case ObjectLifetimeScope.Application: - - return _applicationInstances.Value; - - case ObjectLifetimeScope.Transient: - default: - // create new instances each time - return CreateInstances().ToArray(); - } - } - } - } - - /// - /// Creates the object instances for the types contained in the types collection. - /// - /// A list of objects of type . - protected virtual IEnumerable CreateInstances() - { - return ServiceProvider.CreateInstances(InstanceTypes, Logger); - } - - #region Types collection manipulation - - /// - /// Removes a type. - /// - /// The type to remove. - /// the resolver does not support removing types, or - /// the type is not a valid type for the resolver. - public virtual void RemoveType(Type value) - { - EnsureSupportsRemove(); - - using (Resolution.Configuration) - using (var l = new UpgradeableReadLock(_lock)) - { - EnsureCorrectType(value); - - l.UpgradeToWriteLock(); - _instanceTypes.Remove(value); - } - } - - /// - /// Removes a type. - /// - /// The type to remove. - /// the resolver does not support removing types, or - /// the type is not a valid type for the resolver. - public void RemoveType() - where T : TResolved - { - RemoveType(typeof(T)); - } - - /// - /// Adds types. - /// - /// The types to add. - /// The types are appended at the end of the list. - /// the resolver does not support adding types, or - /// a type is not a valid type for the resolver, or a type is already in the collection of types. - protected void AddTypes(IEnumerable types) - { - EnsureSupportsAdd(); - - using (Resolution.Configuration) - using (new WriteLock(_lock)) - { - foreach (var t in types) - { - EnsureCorrectType(t); - if (_instanceTypes.Contains(t)) - { - throw new InvalidOperationException(string.Format( - "Type {0} is already in the collection of types.", t.FullName)); - } - _instanceTypes.Add(t); - } - } - } - - /// - /// Adds a type. - /// - /// The type to add. - /// The type is appended at the end of the list. - /// the resolver does not support adding types, or - /// the type is not a valid type for the resolver, or the type is already in the collection of types. - public virtual void AddType(Type value) - { - EnsureSupportsAdd(); - - using (Resolution.Configuration) - using (var l = new UpgradeableReadLock(_lock)) - { - EnsureCorrectType(value); - if (_instanceTypes.Contains(value)) - { - throw new InvalidOperationException(string.Format( - "Type {0} is already in the collection of types.", value.FullName)); - } - - l.UpgradeToWriteLock(); - _instanceTypes.Add(value); - } - } - - /// - /// Adds a type. - /// - /// The type to add. - /// The type is appended at the end of the list. - /// the resolver does not support adding types, or - /// the type is not a valid type for the resolver, or the type is already in the collection of types. - public void AddType() - where T : TResolved - { - AddType(typeof(T)); - } - - /// - /// Clears the list of types - /// - /// the resolver does not support clearing types. - public virtual void Clear() - { - EnsureSupportsClear(); - - using (Resolution.Configuration) - using (new WriteLock(_lock)) - { - _instanceTypes.Clear(); - } - } - - /// - /// WARNING! Do not use this unless you know what you are doing, clear all types registered and instances - /// created. Typically only used if a resolver is no longer used in an application and memory is to be GC'd - /// - internal virtual void ResetCollections() - { - using (new WriteLock(_lock)) - { - _instanceTypes.Clear(); - _sortedValues = null; - _applicationInstances = null; - } - } - - /// - /// Inserts a type at the specified index. - /// - /// The zero-based index at which the type should be inserted. - /// The type to insert. - /// the resolver does not support inserting types, or - /// the type is not a valid type for the resolver, or the type is already in the collection of types. - /// is out of range. - public virtual void InsertType(int index, Type value) - { - EnsureSupportsInsert(); - - using (Resolution.Configuration) - using (var l = new UpgradeableReadLock(_lock)) - { - EnsureCorrectType(value); - if (_instanceTypes.Contains(value)) - { - throw new InvalidOperationException(string.Format( - "Type {0} is already in the collection of types.", value.FullName)); - } - - l.UpgradeToWriteLock(); - _instanceTypes.Insert(index, value); - } - } - - /// - /// Inserts a type at the beginning of the list. - /// - /// The type to insert. - /// the resolver does not support inserting types, or - /// the type is not a valid type for the resolver, or the type is already in the collection of types. - public virtual void InsertType(Type value) - { - InsertType(0, value); - } - - /// - /// Inserts a type at the specified index. - /// - /// The type to insert. - /// The zero-based index at which the type should be inserted. - /// is out of range. - public void InsertType(int index) - where T : TResolved - { - InsertType(index, typeof(T)); - } - - /// - /// Inserts a type at the beginning of the list. - /// - /// The type to insert. - public void InsertType() - where T : TResolved - { - InsertType(0, typeof(T)); - } - - /// - /// Inserts a type before a specified, already existing type. - /// - /// The existing type before which to insert. - /// The type to insert. - /// the resolver does not support inserting types, or - /// one of the types is not a valid type for the resolver, or the existing type is not in the collection, - /// or the new type is already in the collection of types. - public virtual void InsertTypeBefore(Type existingType, Type value) - { - EnsureSupportsInsert(); - - using (Resolution.Configuration) - using (var l = new UpgradeableReadLock(_lock)) - { - EnsureCorrectType(existingType); - EnsureCorrectType(value); - if (_instanceTypes.Contains(existingType) == false) - { - throw new InvalidOperationException(string.Format( - "Type {0} is not in the collection of types.", existingType.FullName)); - } - if (_instanceTypes.Contains(value)) - { - throw new InvalidOperationException(string.Format( - "Type {0} is already in the collection of types.", value.FullName)); - } - int index = _instanceTypes.IndexOf(existingType); - - l.UpgradeToWriteLock(); - _instanceTypes.Insert(index, value); - } - } - - /// - /// Inserts a type before a specified, already existing type. - /// - /// The existing type before which to insert. - /// The type to insert. - /// the resolver does not support inserting types, or - /// one of the types is not a valid type for the resolver, or the existing type is not in the collection, - /// or the new type is already in the collection of types. - public void InsertTypeBefore() - where TExisting : TResolved - where T : TResolved - { - InsertTypeBefore(typeof(TExisting), typeof(T)); - } - - /// - /// Returns a value indicating whether the specified type is already in the collection of types. - /// - /// The type to look for. - /// A value indicating whether the type is already in the collection of types. - public virtual bool ContainsType(Type value) - { - using (new ReadLock(_lock)) - { - return _instanceTypes.Contains(value); - } - } - - /// - /// Gets the types in the collection of types. - /// - /// The types in the collection of types. - /// Returns an enumeration, the list cannot be modified. - public virtual IEnumerable GetTypes() - { - Type[] types; - using (new ReadLock(_lock)) - { - types = _instanceTypes.ToArray(); - } - return types; - } - - /// - /// Returns a value indicating whether the specified type is already in the collection of types. - /// - /// The type to look for. - /// A value indicating whether the type is already in the collection of types. - public bool ContainsType() - where T : TResolved - { - return ContainsType(typeof(T)); - } - - #endregion - - /// - /// Returns a WriteLock to use when modifying collections - /// - /// - protected WriteLock GetWriteLock() - { - return new WriteLock(_lock); - } - - #region Type utilities - - /// - /// Ensures that a type is a valid type for the resolver. - /// - /// The type to test. - /// the type is not a valid type for the resolver. - protected virtual void EnsureCorrectType(Type value) - { - if (TypeHelper.IsTypeAssignableFrom(value) == false) - throw new InvalidOperationException(string.Format( - "Type {0} is not an acceptable type for resolver {1}.", value.FullName, GetType().FullName)); - } - - #endregion - - #region Types collection manipulation support - - /// - /// Ensures that the resolver supports removing types. - /// - /// The resolver does not support removing types. - protected void EnsureSupportsRemove() - { - if (SupportsRemove == false) - throw new InvalidOperationException("This resolver does not support removing types"); - } - - /// - /// Ensures that the resolver supports clearing types. - /// - /// The resolver does not support clearing types. - protected void EnsureSupportsClear() - { - if (SupportsClear == false) - throw new InvalidOperationException("This resolver does not support clearing types"); - } - - /// - /// Ensures that the resolver supports adding types. - /// - /// The resolver does not support adding types. - protected void EnsureSupportsAdd() - { - if (SupportsAdd == false) - throw new InvalidOperationException("This resolver does not support adding new types"); - } - - /// - /// Ensures that the resolver supports inserting types. - /// - /// The resolver does not support inserting types. - protected void EnsureSupportsInsert() - { - if (SupportsInsert == false) - throw new InvalidOperationException("This resolver does not support inserting new types"); - } - - /// - /// Gets a value indicating whether the resolver supports adding types. - /// - protected virtual bool SupportsAdd - { - get { return true; } - } - - /// - /// Gets a value indicating whether the resolver supports inserting types. - /// - protected virtual bool SupportsInsert - { - get { return true; } - } - - /// - /// Gets a value indicating whether the resolver supports clearing types. - /// - protected virtual bool SupportsClear - { - get { return true; } - } - - /// - /// Gets a value indicating whether the resolver supports removing types. - /// - protected virtual bool SupportsRemove - { - get { return true; } - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/ObjectLifetimeScope.cs b/src/Umbraco.Core/ObjectResolution/ObjectLifetimeScope.cs deleted file mode 100644 index e7330ae124..0000000000 --- a/src/Umbraco.Core/ObjectResolution/ObjectLifetimeScope.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Umbraco.Core.ObjectResolution -{ - /// - /// Specifies the lifetime scope of resolved objects. - /// - public enum ObjectLifetimeScope - { - /// - /// A per-request object instance is created. - /// - HttpRequest, - - /// - /// A single application-wide object instance is created. - /// - Application, - - /// - /// A new object instance is created each time one is requested. - /// - Transient - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/Resolution.cs b/src/Umbraco.Core/ObjectResolution/Resolution.cs deleted file mode 100644 index 3cdaab365b..0000000000 --- a/src/Umbraco.Core/ObjectResolution/Resolution.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// Represents the status of objects resolution. - /// - /// - /// Before resolution is frozen it is possible to access its configuration, but not to get values. - /// Once resolution is frozen, it is not possible to access its configuration anymore, but it is possible to get values. - /// - internal static class Resolution - { - private static readonly ReaderWriterLockSlim ConfigurationLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - private volatile static bool _isFrozen; - - /// - /// Occurs when resolution is frozen. - /// - /// Occurs only once, since resolution can be frozen only once. - public static event EventHandler Frozen; - - /// - /// Gets or sets a value indicating whether resolution of objects is frozen. - /// - // internal for unit tests, use ReadFrozen if you want to be sure - internal static bool IsFrozen - { - get - { - using (new ReadLock(ConfigurationLock)) - { - return _isFrozen; - } - } - } - - public static IDisposable Reader(bool canReadUnfrozen = false) - { - IDisposable l = new ReadLock(ConfigurationLock); - if (canReadUnfrozen || _isFrozen) return l; - - l.Dispose(); - throw new InvalidOperationException("Resolution is not frozen, it is not yet possible to get values from it."); - } - - /// - /// Returns a disposable object that represents safe access to unfrozen resolution configuration. - /// - /// Should be used in a using(Resolution.Configuration) { ... } mode. - public static IDisposable Configuration - { - get - { - IDisposable l = new WriteLock(ConfigurationLock); - if (_isFrozen == false) return l; - - l.Dispose(); - throw new InvalidOperationException("Resolution is frozen, it is not possible to configure it anymore."); - } - } - - /// - /// Freezes resolution. - /// - /// resolution is already frozen. - public static void Freeze() - { - LogHelper.Debug(typeof (Resolution), "Freezing resolution"); - - using (new WriteLock(ConfigurationLock)) - { - if (_isFrozen) - throw new InvalidOperationException("Resolution is frozen. It is not possible to freeze it again."); - - _isFrozen = true; - } - - LogHelper.Debug(typeof(Resolution), "Resolution is frozen"); - - if (Frozen == null) return; - - try - { - Frozen(null, null); - } - catch (Exception e) - { - LogHelper.Error(typeof (Resolution), "Exception in Frozen event handler.", e); - throw; - } - } - - /// - /// Resets resolution, ie unfreezes it and clears Frozen event. - /// - /// To be used in unit tests. - internal static void Reset() - { - LogHelper.Debug(typeof(Resolution), "Resetting resolution"); - - /* - var trace = new System.Diagnostics.StackTrace(); - var testing = trace.GetFrames().Any(frame => - frame.GetMethod().DeclaringType.FullName.StartsWith("Umbraco.Tests")); - if (testing == false) - throw new InvalidOperationException("Only unit tests can reset configuration."); - */ - - using (new WriteLock(ConfigurationLock)) - { - _isFrozen = false; - } - Frozen = null; - } - } -} diff --git a/src/Umbraco.Core/ObjectResolution/ResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ResolverBase.cs deleted file mode 100644 index 4b3b21082c..0000000000 --- a/src/Umbraco.Core/ObjectResolution/ResolverBase.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Threading; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// Base non-generic class for resolvers - /// - public abstract class ResolverBase - { - protected ResolverBase(Action resetAction) - { - //add itself to the internal collection - ResolverCollection.Add(this, resetAction); - } - - } - - /// - /// The base class for all resolvers. - /// - /// The type of the concrete resolver class. - /// Provides singleton management to all resolvers. - public abstract class ResolverBase : ResolverBase - where TResolver : ResolverBase - { - - /// - /// The underlying singleton object instance - /// - static TResolver _resolver; - - /// - /// The lock for the singleton. - /// - /// - /// Though resharper says this is in error, it is actually correct. We want a different lock object for each generic type. - /// See this for details: http://confluence.jetbrains.net/display/ReSharper/Static+field+in+generic+type - /// - static readonly ReaderWriterLockSlim ResolversLock = new ReaderWriterLockSlim(); - - /// - /// Constructor set the reset action for the underlying object - /// - protected ResolverBase() - : base(() => Reset()) - { - - } - - /// - /// Gets or sets the resolver singleton instance. - /// - /// The value can be set only once, and cannot be read before it has been set. - /// value is read before it has been set, or value is set again once it has already been set. - /// value is null. - public static TResolver Current - { - get - { - using (new ReadLock(ResolversLock)) - { - if (_resolver == null) - throw new InvalidOperationException(string.Format( - "Current has not been initialized on {0}. You must initialize Current before trying to read it.", - typeof(TResolver).FullName)); - return _resolver; - } - } - - set - { - using (Resolution.Configuration) - using (new WriteLock(ResolversLock)) - { - if (value == null) - throw new ArgumentNullException("value"); - if (_resolver != null) - throw new InvalidOperationException(string.Format( - "Current has already been initialized on {0}. It is not possible to re-initialize Current once it has been initialized.", - typeof(TResolver).FullName)); - _resolver = value; - } - } - } - - /// - /// Gets a value indicating whether a the singleton instance has been set. - /// - public static bool HasCurrent - { - get - { - using (new ReadLock(ResolversLock)) - { - return _resolver != null; - } - } - } - - /// - /// Resets the resolver singleton instance to null. - /// - /// - /// To be used in unit tests. DO NOT USE THIS DURING PRODUCTION. - /// - /// - /// By default this is true because we always need to reset resolution before we reset a resolver, however in some insanely rare cases like unit testing you might not want to do this. - /// - protected internal static void Reset(bool resetResolution = true) - { - - //In order to reset a resolver, we always must reset the resolution - if (resetResolution) - { - Resolution.Reset(); - } - - //ensure its removed from the collection - ResolverCollection.Remove(_resolver); - - using (Resolution.Configuration) - using (new WriteLock(ResolversLock)) - { - _resolver = null; - } - - } - } -} diff --git a/src/Umbraco.Core/ObjectResolution/ResolverCollection.cs b/src/Umbraco.Core/ObjectResolution/ResolverCollection.cs deleted file mode 100644 index 34a7ff68bc..0000000000 --- a/src/Umbraco.Core/ObjectResolution/ResolverCollection.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// Simply used to track all ManyObjectsResolverBase instances so that we can - /// reset them all at once really easily. - /// - /// - /// Normally we'd use TypeFinding for this but because many of the resolvers are internal this won't work. - /// We'd rather not keep a static list of them so we'll dynamically add to this list based on the base - /// class of the ManyObjectsResolverBase. - /// - internal static class ResolverCollection - { - private static readonly ConcurrentDictionary Resolvers = new ConcurrentDictionary(); - - /// - /// Returns the number of resolvers created - /// - internal static int Count - { - get { return Resolvers.Count; } - } - - /// - /// Resets all resolvers - /// - internal static void ResetAll() - { - //take out each item from the bag and reset it - var keys = Resolvers.Keys.ToArray(); - foreach (var k in keys) - { - Action resetAction; - while (Resolvers.TryRemove(k, out resetAction)) - { - //call the reset action for the resolver - resetAction(); - } - } - } - - /// - /// This is called when the static Reset method or a ResolverBase{T} is called. - /// - internal static void Remove(ResolverBase resolver) - { - if (resolver == null) return; - Action action; - Resolvers.TryRemove(resolver, out action); - } - - /// - /// Adds a resolver to the collection - /// - /// - /// - /// - /// This is called when the creation of a ResolverBase occurs - /// - internal static void Add(ResolverBase resolver, Action resetAction) - { - Resolvers.TryAdd(resolver, resetAction); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/SingleObjectResolverBase.cs b/src/Umbraco.Core/ObjectResolution/SingleObjectResolverBase.cs deleted file mode 100644 index 27a82b17ef..0000000000 --- a/src/Umbraco.Core/ObjectResolution/SingleObjectResolverBase.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Threading; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// The base class for all single-object resolvers. - /// - /// The type of the concrete resolver class. - /// The type of the resolved object. - /// - /// Resolves "single" objects ie objects for which there is only one application-wide instance, such as the MVC Controller factory. - /// - public abstract class SingleObjectResolverBase : ResolverBase - where TResolved : class - where TResolver : ResolverBase - { - private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - private readonly bool _canBeNull; - private TResolved _value; - - #region Constructors - - /// - /// Initialize a new instance of the class. - /// - /// By default CanBeNull is false, so Value has to be initialized before being read, - /// otherwise an exception will be thrown when reading it. - protected SingleObjectResolverBase() - : this(false) - { } - - /// - /// Initialize a new instance of the class with an instance of the resolved object. - /// - /// An instance of the resolved object. - /// By default CanBeNull is false, so value has to be non-null, or Value has to be - /// initialized before being accessed, otherwise an exception will be thrown when reading it. - protected SingleObjectResolverBase(TResolved value) - : this(false) - { - _value = value; - } - - /// - /// Initialize a new instance of the class with a value indicating whether the resolved object instance can be null. - /// - /// A value indicating whether the resolved object instance can be null. - /// If CanBeNull is false, Value has to be initialized before being read, - /// otherwise an exception will be thrown when reading it. - protected SingleObjectResolverBase(bool canBeNull) - { - _canBeNull = canBeNull; - } - - /// - /// Initialize a new instance of the class with an instance of the resolved object, - /// and a value indicating whether that instance can be null. - /// - /// An instance of the resolved object. - /// A value indicating whether the resolved object instance can be null. - /// If CanBeNull is false, value has to be non-null, or Value has to be initialized before being read, - /// otherwise an exception will be thrown when reading it. - protected SingleObjectResolverBase(TResolved value, bool canBeNull) - { - _value = value; - _canBeNull = canBeNull; - } - - #endregion - - /// - /// Gets or sets a value indicating whether the resolver can resolve objects before resolution is frozen. - /// - /// This is false by default and is used for some special internal resolvers. - internal bool CanResolveBeforeFrozen { get; set; } - - /// - /// Gets a value indicating whether the resolved object instance can be null. - /// - public bool CanBeNull - { - get { return _canBeNull; } - } - - /// - /// Gets a value indicating whether the resolved object instance is null. - /// - public bool HasValue - { - get { return _value != null; } - } - - /// - /// Gets or sets the resolved object instance. - /// - /// - /// value is set to null, but cannot be null (CanBeNull is false). - /// value is read and is null, but cannot be null (CanBeNull is false), - /// or value is set (read) and resolution is (not) frozen. - protected TResolved Value - { - get - { - using (Resolution.Reader(CanResolveBeforeFrozen)) - using (new ReadLock(_lock)) - { - if (!_canBeNull && _value == null) - throw new InvalidOperationException(string.Format( - "Resolver {0} requires a value, and none was supplied.", this.GetType().FullName)); - - return _value; - } - } - - set - { - using (Resolution.Configuration) - using (var l = new UpgradeableReadLock(_lock)) - { - if (!_canBeNull && value == null) - throw new ArgumentNullException("value"); - - l.UpgradeToWriteLock(); - _value = value; - } - } - } - } -} diff --git a/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs b/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs deleted file mode 100644 index 16d18fb7b7..0000000000 --- a/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// Indicates the relative weight of a resolved object type. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class WeightAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a weight. - /// - /// The object type weight. - public WeightAttribute(int weight) - { - Weight = weight; - } - - /// - /// Gets or sets the weight of the object type. - /// - public int Weight { get; private set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/PackageActionsResolver.cs b/src/Umbraco.Core/PackageActionsResolver.cs deleted file mode 100644 index 717262d100..0000000000 --- a/src/Umbraco.Core/PackageActionsResolver.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using umbraco.interfaces; - -namespace Umbraco.Core -{ - /// - /// A resolver to return all IPackageAction objects - /// - internal sealed class PackageActionsResolver : LazyManyObjectsResolverBase - { - /// - /// Constructor - /// - /// - /// - /// - internal PackageActionsResolver(IServiceProvider serviceProvider, ILogger logger, Func> packageActions) - : base(serviceProvider, logger, packageActions) - { - - } - - /// - /// Gets the implementations. - /// - public IEnumerable PackageActions - { - get - { - return Values; - } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs index 9c80b53008..b0424067bf 100644 --- a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs +++ b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Packaging if (macroService != null) _macroService = macroService; else throw new ArgumentNullException("macroService"); } - + public IEnumerable FindConflictingStylesheets(XElement stylesheetNotes) { if (string.Equals(Constants.Packaging.StylesheetsNodeName, stylesheetNotes.Name.LocalName) == false) @@ -73,7 +73,7 @@ namespace Umbraco.Core.Packaging throw new ArgumentException("Node must be a \"" + Constants.Packaging.MacrosNodeName + "\" node", "macroNodes"); } - + return macroNodes.Elements(Constants.Packaging.MacroNodeName) .Select(n => { @@ -89,4 +89,4 @@ namespace Umbraco.Core.Packaging .Where(v => v != null); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Packaging/DefaultPackageContext.cs b/src/Umbraco.Core/Packaging/DefaultPackageContext.cs index 3598f0f292..c08418ec86 100644 --- a/src/Umbraco.Core/Packaging/DefaultPackageContext.cs +++ b/src/Umbraco.Core/Packaging/DefaultPackageContext.cs @@ -42,4 +42,4 @@ namespace Umbraco.Core.Packaging get { return _publicPackageManager.Value; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Packaging/IConflictingPackageData.cs b/src/Umbraco.Core/Packaging/IConflictingPackageData.cs index bd28b5e94f..12f3e8f8a4 100644 --- a/src/Umbraco.Core/Packaging/IConflictingPackageData.cs +++ b/src/Umbraco.Core/Packaging/IConflictingPackageData.cs @@ -10,4 +10,4 @@ namespace Umbraco.Core.Packaging IEnumerable FindConflictingTemplates(XElement templateNotes); IEnumerable FindConflictingMacros(XElement macroNodes); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Packaging/IPackageContext.cs b/src/Umbraco.Core/Packaging/IPackageContext.cs index f1420d1654..41cf372ce6 100644 --- a/src/Umbraco.Core/Packaging/IPackageContext.cs +++ b/src/Umbraco.Core/Packaging/IPackageContext.cs @@ -6,6 +6,6 @@ namespace Umbraco.Core.Packaging { IPackageManager LocalPackageManager { get; } IPackageManager PublicPackageManager { get; } - IPackagePathResolver LocalPathResolver { get; } + IPackagePathResolver LocalPathResolver { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Packaging/IPackageExtraction.cs b/src/Umbraco.Core/Packaging/IPackageExtraction.cs index 1ee88925d3..c19ea733df 100644 --- a/src/Umbraco.Core/Packaging/IPackageExtraction.cs +++ b/src/Umbraco.Core/Packaging/IPackageExtraction.cs @@ -59,4 +59,4 @@ namespace Umbraco.Core.Packaging /// IEnumerable ReadFilesFromArchive(string packageFilePath, IEnumerable filesToGet); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Packaging/IPackageInstallation.cs b/src/Umbraco.Core/Packaging/IPackageInstallation.cs index 2a0b764a37..dac05c47f1 100644 --- a/src/Umbraco.Core/Packaging/IPackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/IPackageInstallation.cs @@ -1,5 +1,5 @@ using System.Xml.Linq; -using Umbraco.Core.Packaging.Models; +using Umbraco.Core.Models.Packaging; namespace Umbraco.Core.Packaging { @@ -10,4 +10,4 @@ namespace Umbraco.Core.Packaging PreInstallWarnings GetPreInstallWarnings(string packageFilePath); XElement GetConfigXmlElement(string packageFilePath); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Packaging/Models/PackageAction.cs b/src/Umbraco.Core/Packaging/Models/PackageAction.cs deleted file mode 100644 index 58f7de8078..0000000000 --- a/src/Umbraco.Core/Packaging/Models/PackageAction.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Runtime.Serialization; -using System.Xml.Linq; - -namespace Umbraco.Core.Packaging.Models -{ - public enum ActionRunAt - { - Undefined = 0, - Install, - Uninstall - } - - [Serializable] - [DataContract(IsReference = true)] - public class PackageAction - { - private ActionRunAt _runAt; - private bool? _undo; - public string Alias { get; set; } - - public string PackageName { get; set; } - - public ActionRunAt RunAt - { - get { return _runAt == ActionRunAt.Undefined ? ActionRunAt.Install : _runAt; } - set { _runAt = value; } - } - - public bool Undo //NOTE: Should thid default to "False"? but the documentation says default "True" (http://our.umbraco.org/wiki/reference/packaging/package-actions) - { - get { return _undo ?? true; } - set { _undo = value; } - } - - public XElement XmlData { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs b/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs index 95f7c7d791..1c31283ee8 100644 --- a/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Models; +using Umbraco.Core.Models.Packaging; namespace Umbraco.Core.Packaging.Models { @@ -10,7 +11,7 @@ namespace Umbraco.Core.Packaging.Models public class UninstallationSummary { public MetaData MetaData { get; set; } - public IEnumerable DataTypesUninstalled { get; set; } + public IEnumerable DataTypesUninstalled { get; set; } public IEnumerable LanguagesUninstalled { get; set; } public IEnumerable DictionaryItemsUninstalled { get; set; } public IEnumerable MacrosUninstalled { get; set; } @@ -21,14 +22,14 @@ namespace Umbraco.Core.Packaging.Models public IEnumerable ContentUninstalled { get; set; } public bool PackageUninstalled { get; set; } } - + internal static class UninstallationSummaryExtentions { public static UninstallationSummary InitEmpty(this UninstallationSummary summary) { summary.ContentUninstalled = new List(); summary.ContentTypesUninstalled = new List(); - summary.DataTypesUninstalled = new List(); + summary.DataTypesUninstalled = new List(); summary.DictionaryItemsUninstalled = new List(); summary.FilesUninstalled = new List(); summary.LanguagesUninstalled = new List(); @@ -39,4 +40,4 @@ namespace Umbraco.Core.Packaging.Models return summary; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs index 57664b8a83..484a67dd9b 100644 --- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs +++ b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using System.Security; using System.Security.Permissions; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; namespace Umbraco.Core.Packaging @@ -29,16 +30,18 @@ namespace Umbraco.Core.Packaging /// public static IEnumerable ScanAssembliesForTypeReference(IEnumerable assemblys, out string[] errorReport) { - // beware! when toying with domains, use a safe call context! + // need to wrap in a safe call context in order to prevent whatever Umbraco stores + // in the logical call context from flowing to the created app domain (eg, the + // UmbracoDatabase is *not* serializable and cannot and should not flow). using (new SafeCallContext()) { var appDomain = GetTempAppDomain(); var type = typeof(PackageBinaryInspector); try { - var value = (PackageBinaryInspector) appDomain.CreateInstanceAndUnwrap( - type.Assembly.FullName, - type.FullName); + var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap( + type.Assembly.FullName, + type.FullName); // do NOT turn PerformScan into static (even if ReSharper says so)! var result = value.PerformScan(assemblys.ToArray(), out errorReport); return result; @@ -189,7 +192,7 @@ namespace Umbraco.Core.Packaging assemblyName.Name, "' see error log for full details.")); assembliesWithErrors.Add(a); - LogHelper.Error("An error occurred scanning package assemblies", ex); + Current.Logger.Error("An error occurred scanning package assemblies", ex); } } } @@ -233,7 +236,7 @@ namespace Umbraco.Core.Packaging a.GetName().Name, "' see error log for full details.")); assembliesWithErrors.Add(a); - LogHelper.Error("An error occurred scanning package assemblies", ex); + Current.Logger.Error("An error occurred scanning package assemblies", ex); } } diff --git a/src/Umbraco.Core/Packaging/PackageBuilding.cs b/src/Umbraco.Core/Packaging/PackageBuilding.cs deleted file mode 100644 index e8c09d12b8..0000000000 --- a/src/Umbraco.Core/Packaging/PackageBuilding.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Umbraco.Core.Services; - -namespace Umbraco.Core.Packaging -{ - internal interface IPackageBuilding - { - } - - internal class PackageBuilding : IPackageBuilding - { - private readonly IPackagingService _packagingService; - - public PackageBuilding(IPackagingService packagingService) - { - _packagingService = packagingService; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Packaging/PackageExtraction.cs b/src/Umbraco.Core/Packaging/PackageExtraction.cs index bc79ddd9b4..752cc80697 100644 --- a/src/Umbraco.Core/Packaging/PackageExtraction.cs +++ b/src/Umbraco.Core/Packaging/PackageExtraction.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.IO.Compression; +using System.IO.Compression; namespace Umbraco.Core.Packaging { @@ -66,7 +66,7 @@ namespace Umbraco.Core.Packaging string.Format("Error - file isn't a package. only extentions: \"{0}\" is allowed", string.Join(", ", alowedExtension))); } } - + public void CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath) { CopyFilesFromArchive(packageFilePath, new[]{new KeyValuePair(fileInPackageName, destinationfilePath) } ); @@ -151,7 +151,7 @@ namespace Umbraco.Core.Packaging var files = new HashSet(filesToGet.Select(f => f.ToLowerInvariant())); - using (var fs = File.OpenRead(packageFilePath)) + using (var fs = File.OpenRead(packageFilePath)) using (var zipArchive = new ZipArchive(fs)) { foreach (var zipEntry in zipArchive.Entries) @@ -188,4 +188,4 @@ namespace Umbraco.Core.Packaging } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index 522048d2d7..2e33bfbf2b 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -8,9 +8,8 @@ using System.Xml.XPath; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Packaging.Models; +using Umbraco.Core.Models.Packaging; using Umbraco.Core.Services; -using umbraco.interfaces; using File = System.IO.File; namespace Umbraco.Core.Packaging @@ -26,18 +25,18 @@ namespace Umbraco.Core.Packaging public PackageInstallation(IPackagingService packagingService, IMacroService macroService, IFileService fileService, IPackageExtraction packageExtraction) - : this(packagingService, macroService, fileService, packageExtraction, GlobalSettings.FullpathToRoot) + : this(packagingService, macroService, fileService, packageExtraction, GlobalSettings.FullPathToRoot) {} public PackageInstallation(IPackagingService packagingService, IMacroService macroService, IFileService fileService, IPackageExtraction packageExtraction, string fullPathToRoot) { - if (packageExtraction != null) _packageExtraction = packageExtraction; + if (packageExtraction != null) _packageExtraction = packageExtraction; else throw new ArgumentNullException("packageExtraction"); - + if (macroService != null) _macroService = macroService; else throw new ArgumentNullException("macroService"); - + if (fileService != null) _fileService = fileService; else throw new ArgumentNullException("fileService"); @@ -46,7 +45,7 @@ namespace Umbraco.Core.Packaging _fullPathToRoot = fullPathToRoot; } - + public IConflictingPackageData ConflictingPackageData { private get @@ -78,7 +77,7 @@ namespace Umbraco.Core.Packaging _fullPathToRoot = value; } } - + public MetaData GetMetaData(string packageFilePath) { try @@ -97,7 +96,7 @@ namespace Umbraco.Core.Packaging try { XElement rootElement = GetConfigXmlElement(packageFilePath); - return GetPreInstallWarnings(packageFilePath, rootElement); + return GetPreInstallWarnings(rootElement); } catch (Exception e) { @@ -120,7 +119,7 @@ namespace Umbraco.Core.Packaging XElement actions; MetaData metaData; InstallationSummary installationSummary; - + try { XElement rootElement = GetConfigXmlElement(packageFile); @@ -148,7 +147,7 @@ namespace Umbraco.Core.Packaging try { - var dataTypeDefinitions = EmptyEnumerableIfNull(dataTypes) ?? InstallDataTypes(dataTypes, userId); + var dataTypeDefinitions = EmptyEnumerableIfNull(dataTypes) ?? InstallDataTypes(dataTypes, userId); installationSummary.DataTypesInstalled = dataTypeDefinitions; var languagesInstalled = EmptyEnumerableIfNull(languages) ?? InstallLanguages(languages, userId); @@ -169,11 +168,11 @@ namespace Umbraco.Core.Packaging var documentTypesInstalled = EmptyEnumerableIfNull(documentTypes) ?? InstallDocumentTypes(documentTypes, userId); installationSummary.ContentTypesInstalled =documentTypesInstalled; - var stylesheetsInstalled = EmptyEnumerableIfNull(styleSheets) ?? InstallStylesheets(styleSheets, userId); + var stylesheetsInstalled = EmptyEnumerableIfNull(styleSheets) ?? InstallStylesheets(styleSheets); installationSummary.StylesheetsInstalled = stylesheetsInstalled; - var documentsInstalled = documents != null ? InstallDocuments(documents, userId) - : EmptyEnumerableIfNull(documentSet) + var documentsInstalled = documents != null ? InstallDocuments(documents, userId) + : EmptyEnumerableIfNull(documentSet) ?? InstallDocuments(documentSet, userId); installationSummary.ContentInstalled = documentsInstalled; @@ -289,7 +288,7 @@ namespace Umbraco.Core.Packaging var packageAction = new PackageAction { - XmlData = elemet, + XmlData = elemet, Alias = aliasAttr.Value, PackageName = packageName, }; @@ -328,7 +327,7 @@ namespace Umbraco.Core.Packaging .ToArray(); } - private IEnumerable InstallStylesheets(XElement styleSheetsElement, int userId = 0) + private IEnumerable InstallStylesheets(XElement styleSheetsElement) { if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName) == false) { @@ -372,7 +371,7 @@ namespace Umbraco.Core.Packaging sourceDestination = AppendRootToDestination(FullPathToRoot, sourceDestination); _packageExtraction.CopyFilesFromArchive(packageFilePath, sourceDestination); - + return sourceDestination.Select(sd => sd.Value).ToArray(); } @@ -413,7 +412,7 @@ namespace Umbraco.Core.Packaging return _packagingService.ImportLanguages(languageElement, userId); } - private IEnumerable InstallDataTypes(XElement dataTypeElements, int userId = 0) + private IEnumerable InstallDataTypes(XElement dataTypeElements, int userId = 0) { if (string.Equals(Constants.Packaging.DataTypesNodeName, dataTypeElements.Name.LocalName) == false) { @@ -425,13 +424,13 @@ namespace Umbraco.Core.Packaging return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId); } - private PreInstallWarnings GetPreInstallWarnings(string packagePath, XElement rootElement) + private PreInstallWarnings GetPreInstallWarnings(XElement rootElement) { XElement files = rootElement.Element(Constants.Packaging.FilesNodeName); XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName); XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName); XElement alias = rootElement.Element(Constants.Packaging.MacrosNodeName); - + var sourceDestination = EmptyArrayIfNull>(files) ?? ExtractSourceDestinationFileInformation(files); var installWarnings = new PreInstallWarnings(); @@ -444,11 +443,10 @@ namespace Umbraco.Core.Packaging var stylesheetNames = EmptyEnumerableIfNull(styleSheets) ?? ConflictingPackageData.FindConflictingStylesheets(styleSheets); installWarnings.ConflictingStylesheetNames = stylesheetNames; - + installWarnings.UnsecureFiles = FindUnsecureFiles(sourceDestination); installWarnings.FilesReplaced = FindFilesToBeReplaced(sourceDestination); - installWarnings.AssembliesWithLegacyPropertyEditors = FindLegacyPropertyEditors(packagePath, sourceDestination); - + return installWarnings; } @@ -457,19 +455,6 @@ namespace Umbraco.Core.Packaging return sourceDestination.Where(sd => File.Exists(Path.Combine(FullPathToRoot, sd.Value))).ToArray(); } - private IEnumerable FindLegacyPropertyEditors(string packagePath, IEnumerable> sourceDestinationPair) - { - var dlls = sourceDestinationPair.Where( - sd => (Path.GetExtension(sd.Value) ?? string.Empty).Equals(".dll", StringComparison.InvariantCultureIgnoreCase)).Select(sd => sd.Key).ToArray(); - - if (dlls.Any() == false) { return new List(); } - - // Now we want to see if the DLLs contain any legacy data types since we want to warn people about that - string[] assemblyErrors; - IEnumerable assemblyesToScan =_packageExtraction.ReadFilesFromArchive(packagePath, dlls); - return PackageBinaryInspector.ScanAssembliesForTypeReference(assemblyesToScan, out assemblyErrors).ToArray(); - } - private KeyValuePair[] FindUnsecureFiles(IEnumerable> sourceDestinationPair) { return sourceDestinationPair.Where(sd => IsFileDestinationUnsecure(sd.Value)).ToArray(); @@ -484,7 +469,7 @@ namespace Umbraco.Core.Packaging string extension = Path.GetExtension(destination); return extension != null && extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase); } - + private KeyValuePair[] ExtractSourceDestinationFileInformation(XElement filesElement) { if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName) == false) @@ -520,7 +505,7 @@ namespace Umbraco.Core.Packaging var relativeDir = UpdatePathPlaceholders(PrepareAsFilePathElement(orgPathElement.Value)); var relativePath = Path.Combine(relativeDir, fileName); - + return new KeyValuePair(guidElement.Value, relativePath); }).ToArray(); @@ -588,7 +573,7 @@ namespace Umbraco.Core.Packaging int val; return xElement == null ? defaultValue : int.TryParse(xElement.Value, out val) ? val : defaultValue; } - + private static string UpdatePathPlaceholders(string path) { if (path.Contains("[$")) @@ -602,4 +587,4 @@ namespace Umbraco.Core.Packaging return path; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/BulkDataReader.cs b/src/Umbraco.Core/Persistence/BulkDataReader.cs index 8df4dd536e..6ede6a1859 100644 --- a/src/Umbraco.Core/Persistence/BulkDataReader.cs +++ b/src/Umbraco.Core/Persistence/BulkDataReader.cs @@ -17,23 +17,23 @@ namespace Umbraco.Core.Persistence /// A base implementation of that is suitable for . /// /// - /// + /// /// Borrowed from Microsoft: /// See: https://blogs.msdn.microsoft.com/anthonybloesch/2013/01/23/bulk-loading-data-with-idatareader-and-sqlbulkcopy/ - /// + /// /// This implementation is designed to be very memory efficient requiring few memory resources and to support /// rapid transfer of data to SQL Server. /// /// Subclasses should implement , , /// , , . /// If they contain disposable resources they should override . - /// + /// /// SD: Alternatively, we could have used a LinqEntityDataReader which is nicer to use but it uses quite a lot of reflection and /// I thought this would just be quicker. /// Simple example of that: https://github.com/gridsum/DataflowEx/blob/master/Gridsum.DataflowEx/Databases/BulkDataReader.cs /// Full example of that: https://github.com/matthewschrager/Repository/blob/master/Repository.EntityFramework/EntityDataReader.cs /// So we know where to find that if we ever need it, these would convert any Linq data source to an IDataReader - /// + /// /// internal abstract class BulkDataReader : IDataReader { diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs new file mode 100644 index 0000000000..a245f33dd8 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -0,0 +1,82 @@ +// ReSharper disable once CheckNamespace +namespace Umbraco.Core +{ + public static partial class Constants + { + public static class DatabaseSchema + { + public const string TableNamePrefix = "umbraco"; + + public static class Tables + { + public const string Lock = /*TableNamePrefix*/ "umbraco" + "Lock"; + public const string Log = /*TableNamePrefix*/ "umbraco" + "Log"; + + public const string Node = /*TableNamePrefix*/ "umbraco" + "Node"; + public const string NodeData = /*TableNamePrefix*/ "cms" + "ContentNu"; + public const string NodeXml = /*TableNamePrefix*/ "cms" + "ContentXml"; + public const string NodePreviewXml = /*TableNamePrefix*/ "cms" + "PreviewXml"; // fixme dbfix kill merge with ContentXml + + public const string ContentType = /*TableNamePrefix*/ "cms" + "ContentType"; // fixme dbfixrename and split uElementType, uDocumentType + public const string ContentChildType = /*TableNamePrefix*/ "cms" + "ContentTypeAllowedContentType"; + public const string DocumentType = /*TableNamePrefix*/ "cms" + "DocumentType"; // fixme dbfixmust rename corresponding DTO + public const string ElementTypeTree = /*TableNamePrefix*/ "cms" + "ContentType2ContentType"; // fixme dbfixwhy can't we just use uNode for this? + public const string DataType = TableNamePrefix + "DataType"; + public const string Template = /*TableNamePrefix*/ "cms" + "Template"; + + public const string Content = TableNamePrefix + "Content"; + public const string ContentVersion = TableNamePrefix + "ContentVersion"; + public const string Document = TableNamePrefix + "Document"; + public const string DocumentVersion = TableNamePrefix + "DocumentVersion"; + public const string MediaVersion = TableNamePrefix + "MediaVersion"; + + public const string PropertyType = /*TableNamePrefix*/ "cms" + "PropertyType"; + public const string PropertyTypeGroup = /*TableNamePrefix*/ "cms" + "PropertyTypeGroup"; + public const string PropertyData = TableNamePrefix + "PropertyData"; + + public const string RelationType = /*TableNamePrefix*/ "umbraco" + "RelationType"; + public const string Relation = /*TableNamePrefix*/ "umbraco" + "Relation"; + + public const string Domain = /*TableNamePrefix*/ "umbraco" + "Domains"; + public const string Language = /*TableNamePrefix*/ "umbraco" + "Language"; + public const string DictionaryEntry = /*TableNamePrefix*/ "cms" + "Dictionary"; + public const string DictionaryValue = /*TableNamePrefix*/ "cms" + "LanguageText"; + + public const string User = /*TableNamePrefix*/ "umbraco" + "User"; + public const string UserGroup = /*TableNamePrefix*/ "umbraco" + "UserGroup"; + public const string UserStartNode = /*TableNamePrefix*/ "umbraco" + "UserStartNode"; + public const string User2UserGroup = /*TableNamePrefix*/ "umbraco" + "User2UserGroup"; + public const string User2NodeNotify = /*TableNamePrefix*/ "umbraco" + "User2NodeNotify"; + public const string UserGroup2App = /*TableNamePrefix*/ "umbraco" + "UserGroup2App"; + public const string UserGroup2NodePermission = /*TableNamePrefix*/ "umbraco" + "UserGroup2NodePermission"; + public const string ExternalLogin = /*TableNamePrefix*/ "umbraco" + "ExternalLogin"; + + public const string Macro = /*TableNamePrefix*/ "cms" + "Macro"; + public const string MacroProperty = /*TableNamePrefix*/ "cms" + "MacroProperty"; + + public const string Member = /*TableNamePrefix*/ "cms" + "Member"; + public const string MemberType = /*TableNamePrefix*/ "cms" + "MemberType"; + public const string Member2MemberGroup = /*TableNamePrefix*/ "cms" + "Member2MemberGroup"; + + public const string Access = /*TableNamePrefix*/ "umbraco" + "Access"; + public const string AccessRule = /*TableNamePrefix*/ "umbraco" + "AccessRule"; + public const string RedirectUrl = /*TableNamePrefix*/ "umbraco" + "RedirectUrl"; + + public const string CacheInstruction = /*TableNamePrefix*/ "umbraco" + "CacheInstruction"; + public const string Server = /*TableNamePrefix*/ "umbraco" + "Server"; + + public const string Tag = /*TableNamePrefix*/ "cms" + "Tags"; + public const string TagRelationship = /*TableNamePrefix*/ "cms" + "TagRelationship"; + + public const string Task = /*TableNamePrefix*/ "cms" + "Task"; + public const string TaskType = /*TableNamePrefix*/ "cms" + "TaskType"; + + public const string KeyValue = TableNamePrefix + "KeyValue"; + + public const string AuditEntry = /*TableNamePrefix*/ "umbraco" + "Audit"; + public const string Consent = /*TableNamePrefix*/ "umbraco" + "Consent"; + public const string UserLogin = /*TableNamePrefix*/ "umbraco" + "UserLogin"; + } + } + } +} diff --git a/src/Umbraco.Core/Persistence/Constants-DbProviderNames.cs b/src/Umbraco.Core/Persistence/Constants-DbProviderNames.cs new file mode 100644 index 0000000000..b79269d80e --- /dev/null +++ b/src/Umbraco.Core/Persistence/Constants-DbProviderNames.cs @@ -0,0 +1,13 @@ + // ReSharper disable once CheckNamespace +namespace Umbraco.Core +{ + static partial class Constants + { + public static class DbProviderNames + { + public const string SqlServer = "System.Data.SqlClient"; + public const string MySql = "MySql.Data.MySqlClient"; + public const string SqlCe = "System.Data.SqlServerCe.4.0"; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs index 49c6f933fb..6f5d4bb0dc 100644 --- a/src/Umbraco.Core/Persistence/Constants-Locks.cs +++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs @@ -6,6 +6,14 @@ namespace Umbraco.Core public static class Locks { public const int Servers = -331; + public const int ContentTypes = -332; + public const int ContentTree = -333; + public const int MediaTree = -334; + public const int MemberTree = -335; + public const int MediaTypes = -336; + public const int MemberTypes = -337; + public const int Domains = -338; + public const int KeyValues = -339; } } } diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs index 33c7616687..529fb54ec7 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ConstraintAttribute.cs @@ -22,4 +22,4 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations /// public object Default { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs index a4797a9088..dc80cd5925 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs @@ -30,4 +30,4 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations /// PrimaryKey column is used by default public string Column { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs index 9152384ffa..fd4a4026f2 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs @@ -12,12 +12,12 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations { IndexType = indexType; } - + /// /// Gets or sets the name of the Index /// /// - /// Overrides default naming of indexes: + /// Overrides default naming of indexes: /// IX_tableName /// public string Name { get; set; }//Overrides default naming of indexes: IX_tableName @@ -32,4 +32,4 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations /// public string ForColumns { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexTypes.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexTypes.cs index 9e6b3af8c6..b99e18f96a 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexTypes.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexTypes.cs @@ -9,4 +9,4 @@ NonClustered, UniqueNonClustered } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/LengthAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/LengthAttribute.cs index dd977aeb43..a82f66ea64 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/LengthAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/LengthAttribute.cs @@ -19,4 +19,4 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations /// public int Length { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettingAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettingAttribute.cs index af6df1613a..b75b8650c7 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettingAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettingAttribute.cs @@ -17,4 +17,4 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations /// public NullSettings NullSetting { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettings.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettings.cs index 3901cc9101..d07b0ebf85 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettings.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/NullSettings.cs @@ -8,4 +8,4 @@ Null, NotNull } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/PrimaryKeyColumnAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/PrimaryKeyColumnAttribute.cs index 62db23e3cc..b8cbf27c77 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/PrimaryKeyColumnAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/PrimaryKeyColumnAttribute.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations /// Gets or sets the name of the PrimaryKey. /// /// - /// Overrides the default naming of a PrimaryKey constraint: + /// Overrides the default naming of a PrimaryKey constraint: /// PK_tableName /// public string Name { get; set; } @@ -56,4 +56,4 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations /// public int IdentitySeed { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ReferencesAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ReferencesAttribute.cs index d6c2611990..cd700cc8ba 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ReferencesAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ReferencesAttribute.cs @@ -18,4 +18,4 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations /// public Type Type { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs index 44333da0bc..a54925dfc3 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypeAttribute.cs @@ -21,4 +21,4 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations /// public SpecialDbTypes DatabaseType { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs index af3a1d8d76..efb9fc6ea1 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs @@ -9,4 +9,4 @@ NTEXT, NCHAR } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseDebugHelper.cs b/src/Umbraco.Core/Persistence/DatabaseDebugHelper.cs index 4cb8327f5b..0bbd6f5bd5 100644 --- a/src/Umbraco.Core/Persistence/DatabaseDebugHelper.cs +++ b/src/Umbraco.Core/Persistence/DatabaseDebugHelper.cs @@ -16,8 +16,8 @@ namespace Umbraco.Core.Persistence public static void SetCommand(IDbCommand command, string context) { - var prof = command as StackExchange.Profiling.Data.ProfiledDbCommand; - if (prof != null) command = prof.InternalCommand; + command = command.UnwrapUmbraco(); + lock (Commands) { Commands.Enqueue(Tuple.Create(context, new WeakReference(command))); @@ -40,10 +40,11 @@ namespace Umbraco.Core.Persistence public static string GetReferencedObjects(IDbConnection con) { - var prof = con as StackExchange.Profiling.Data.ProfiledDbConnection; - if (prof != null) con = prof.InnerConnection; + con = con.UnwrapUmbraco(); + var ceCon = con as System.Data.SqlServerCe.SqlCeConnection; if (ceCon != null) return null; // "NotSupported: SqlCE"; + var dbCon = con as DbConnection; return dbCon == null ? "NotSupported: " + con.GetType() @@ -164,7 +165,7 @@ namespace Umbraco.Core.Persistence } } - return result == null ? null : result.ToString(); + return result?.ToString(); } } } diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs index 05db31640d..de29c25314 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ColumnDefinition.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using Umbraco.Core.Persistence.DatabaseAnnotations; @@ -32,4 +32,4 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions public virtual string TableName { get; set; } public virtual ModificationType ModificationType { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintDefinition.cs index a3b7d99802..8cda7e1a28 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintDefinition.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { @@ -19,4 +19,4 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions public string TableName { get; set; } public ICollection Columns = new HashSet(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintType.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintType.cs index 63d979ecb4..b47500f265 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintType.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ConstraintType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { public enum ConstraintType { @@ -6,4 +6,4 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions Unique, NonUnique } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DbIndexDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DbIndexDefinition.cs index e495d61550..33d9e20311 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DbIndexDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DbIndexDefinition.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { /// /// Represents a database index definition retreived by querying the database @@ -10,4 +10,4 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions public virtual string ColumnName { get; set; } public virtual bool IsUnique { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs index e3c35e01b4..b1ae542659 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs @@ -2,6 +2,7 @@ using System.Data; using System.Linq; using System.Reflection; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.SqlSyntax; @@ -9,9 +10,9 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { internal static class DefinitionFactory { - public static TableDefinition GetTableDefinition(ISqlSyntaxProvider syntaxProvider, Type modelType) + public static TableDefinition GetTableDefinition(Type modelType, ISqlSyntaxProvider sqlSyntax) { - //Looks for PetaPoco's TableNameAtribute for the name of the table + //Looks for NPoco's TableNameAtribute for the name of the table //If no attribute is set we use the name of the Type as the default convention var tableNameAttribute = modelType.FirstAttribute(); string tableName = tableNameAttribute == null ? modelType.Name : tableNameAttribute.Value; @@ -32,7 +33,7 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions //Otherwise use the name of the property itself as the default convention var columnAttribute = propertyInfo.FirstAttribute(); string columnName = columnAttribute != null ? columnAttribute.Name : propertyInfo.Name; - var columnDefinition = GetColumnDefinition(syntaxProvider, modelType, propertyInfo, columnName, tableName); + var columnDefinition = GetColumnDefinition(modelType, propertyInfo, columnName, tableName, sqlSyntax); tableDefinition.Columns.Add(columnDefinition); //Creates a foreignkey definition and adds it to the collection on the table definition @@ -58,7 +59,7 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions return tableDefinition; } - public static ColumnDefinition GetColumnDefinition(ISqlSyntaxProvider syntaxProvider, Type modelType, PropertyInfo propertyInfo, string columnName, string tableName) + public static ColumnDefinition GetColumnDefinition(Type modelType, PropertyInfo propertyInfo, string columnName, string tableName, ISqlSyntaxProvider sqlSyntax) { var definition = new ColumnDefinition{ Name = columnName, TableName = tableName, ModificationType = ModificationType.Create }; @@ -108,9 +109,9 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions var constraintAttribute = propertyInfo.FirstAttribute(); if (constraintAttribute != null) { - //Special case for MySQL as it can't have multiple default DateTime values, which + //Special case for MySQL as it can't have multiple default DateTime values, which //is what the umbracoServer table definition is trying to create - if (syntaxProvider is MySqlSyntaxProvider && definition.TableName == "umbracoServer" && + if (sqlSyntax is MySqlSyntaxProvider && definition.TableName == "umbracoServer" && definition.TableName.ToLowerInvariant() == "lastNotifiedDate".ToLowerInvariant()) return definition; @@ -174,4 +175,4 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions return definition; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DeletionDataDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DeletionDataDefinition.cs index f4610e7841..5afd18d6ff 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DeletionDataDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DeletionDataDefinition.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { public class DeletionDataDefinition : List> { - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/Direction.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/Direction.cs index 2e98185ce9..1425cf1dc9 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/Direction.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/Direction.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { public enum Direction { Ascending = 0, Descending = 1 } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ForeignKeyDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ForeignKeyDefinition.cs index 3012c57b0e..b6233b5517 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ForeignKeyDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ForeignKeyDefinition.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Data; namespace Umbraco.Core.Persistence.DatabaseModelDefinitions @@ -24,4 +24,4 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions public virtual ICollection ForeignColumns { get; set; } public virtual ICollection PrimaryColumns { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexColumnDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexColumnDefinition.cs index b2bb090ac1..039c1cc312 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexColumnDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexColumnDefinition.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { public class IndexColumnDefinition { public virtual string Name { get; set; } public virtual Direction Direction { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs index ec46d35780..8ae67c3b75 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Persistence.DatabaseModelDefinitions @@ -19,4 +19,4 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions public virtual ICollection Columns { get; set; } public IndexTypes IndexType { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/InsertionDataDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/InsertionDataDefinition.cs index ae49ab224d..3405853cce 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/InsertionDataDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/InsertionDataDefinition.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { public class InsertionDataDefinition : List> { - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ModificationType.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ModificationType.cs index 1ab4e17dcf..c6e2b52306 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ModificationType.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/ModificationType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { public enum ModificationType { @@ -10,4 +10,4 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions Update, Delete } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs index 24c2294b59..3415ad71dd 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/SystemMethods.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Persistence.DatabaseModelDefinitions +namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { public enum SystemMethods { @@ -7,4 +7,4 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions //NewSequentialId, //CurrentUTCDateTime } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/TableDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/TableDefinition.cs index 18180049e9..afb7ca3d8e 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/TableDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/TableDefinition.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { @@ -17,4 +17,4 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions public virtual ICollection ForeignKeys { get; set; } public virtual ICollection Indexes { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DatabaseProviders.cs b/src/Umbraco.Core/Persistence/DatabaseProviders.cs deleted file mode 100644 index ec0064b8cf..0000000000 --- a/src/Umbraco.Core/Persistence/DatabaseProviders.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Core.Persistence -{ - public enum DatabaseProviders - { - SqlServer, - SqlAzure, - SqlServerCE, - MySql, - PostgreSQL, - Oracle, - SQLite - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs deleted file mode 100644 index 944d5a8a74..0000000000 --- a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Initial; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Persistence -{ - - public class DatabaseSchemaHelper - { - private readonly Database _db; - private readonly ILogger _logger; - private readonly ISqlSyntaxProvider _syntaxProvider; - private readonly BaseDataCreation _baseDataCreation; - - public DatabaseSchemaHelper(Database db, ILogger logger, ISqlSyntaxProvider syntaxProvider) - { - _db = db; - _logger = logger; - _syntaxProvider = syntaxProvider; - _baseDataCreation = new BaseDataCreation(db, logger); - } - - public bool TableExist(string tableName) - { - return _syntaxProvider.DoesTableExist(_db, tableName); - } - - internal void UninstallDatabaseSchema() - { - var creation = new DatabaseSchemaCreation(_db, _logger, _syntaxProvider); - creation.UninstallDatabaseSchema(); - } - - /// - /// Creates the Umbraco db schema in the Database of the current Database. - /// Safe method that is only able to create the schema in non-configured - /// umbraco instances. - /// - public void CreateDatabaseSchema(ApplicationContext applicationContext) - { - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); - CreateDatabaseSchema(true, applicationContext); - } - - /// - /// Creates the Umbraco db schema in the Database of the current Database - /// with the option to guard the db from having the schema created - /// multiple times. - /// - /// - /// - public void CreateDatabaseSchema(bool guardConfiguration, ApplicationContext applicationContext) - { - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); - - if (guardConfiguration && applicationContext.IsConfigured) - throw new Exception("Umbraco is already configured!"); - - CreateDatabaseSchemaDo(applicationContext.Services.MigrationEntryService); - } - - internal void CreateDatabaseSchemaDo(bool guardConfiguration, ApplicationContext applicationContext) - { - if (guardConfiguration && applicationContext.IsConfigured) - throw new Exception("Umbraco is already configured!"); - - CreateDatabaseSchemaDo(applicationContext.Services.MigrationEntryService); - } - - internal void CreateDatabaseSchemaDo(IMigrationEntryService migrationEntryService) - { - _logger.Info("Initializing database schema creation"); - - var creation = new DatabaseSchemaCreation(_db, _logger, _syntaxProvider); - creation.InitializeDatabaseSchema(); - - _logger.Info("Finalized database schema creation"); - } - - public void CreateTable(bool overwrite) - where T : new() - { - var tableType = typeof(T); - CreateTable(overwrite, tableType); - } - - public void CreateTable() - where T : new() - { - var tableType = typeof(T); - CreateTable(false, tableType); - } - - public void CreateTable(bool overwrite, Type modelType) - { - var tableDefinition = DefinitionFactory.GetTableDefinition(_syntaxProvider, modelType); - var tableName = tableDefinition.Name; - - string createSql = _syntaxProvider.Format(tableDefinition); - string createPrimaryKeySql = _syntaxProvider.FormatPrimaryKey(tableDefinition); - var foreignSql = _syntaxProvider.Format(tableDefinition.ForeignKeys); - var indexSql = _syntaxProvider.Format(tableDefinition.Indexes); - - var tableExist = TableExist(tableName); - if (overwrite && tableExist) - { - DropTable(tableName); - tableExist = false; - } - - if (tableExist == false) - { - using (var transaction = _db.GetTransaction()) - { - //Execute the Create Table sql - int created = _db.Execute(new Sql(createSql)); - _logger.Info(string.Format("Create Table sql {0}:\n {1}", created, createSql)); - - //If any statements exists for the primary key execute them here - if (!string.IsNullOrEmpty(createPrimaryKeySql)) - { - int createdPk = _db.Execute(new Sql(createPrimaryKeySql)); - _logger.Info(string.Format("Primary Key sql {0}:\n {1}", createdPk, createPrimaryKeySql)); - } - - //Turn on identity insert if db provider is not mysql - if (_syntaxProvider.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) - _db.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", _syntaxProvider.GetQuotedTableName(tableName)))); - - //Call the NewTable-event to trigger the insert of base/default data - //OnNewTable(tableName, _db, e, _logger); - - _baseDataCreation.InitializeBaseData(tableName); - - //Turn off identity insert if db provider is not mysql - if (_syntaxProvider.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) - _db.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF;", _syntaxProvider.GetQuotedTableName(tableName)))); - - //Special case for MySql - if (_syntaxProvider is MySqlSyntaxProvider && tableName.Equals("umbracoUser")) - { - _db.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); - } - - //Loop through index statements and execute sql - foreach (var sql in indexSql) - { - int createdIndex = _db.Execute(new Sql(sql)); - _logger.Info(string.Format("Create Index sql {0}:\n {1}", createdIndex, sql)); - } - - //Loop through foreignkey statements and execute sql - foreach (var sql in foreignSql) - { - int createdFk = _db.Execute(new Sql(sql)); - _logger.Info(string.Format("Create Foreign Key sql {0}:\n {1}", createdFk, sql)); - } - - - - transaction.Complete(); - } - } - - _logger.Info(string.Format("New table '{0}' was created", tableName)); - } - - public void DropTable() - where T : new() - { - Type type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - if (tableNameAttribute == null) - throw new Exception( - string.Format( - "The Type '{0}' does not contain a TableNameAttribute, which is used to find the name of the table to drop. The operation could not be completed.", - type.Name)); - - string tableName = tableNameAttribute.Value; - DropTable(tableName); - } - - public void DropTable(string tableName) - { - var sql = new Sql(string.Format( - _syntaxProvider.DropTable, - _syntaxProvider.GetQuotedTableName(tableName))); - _db.Execute(sql); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs b/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs index 5ef29aa951..48edee3c94 100644 --- a/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs +++ b/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs @@ -8,11 +8,11 @@ namespace Umbraco.Core.Persistence internal static class DatabaseNodeLockExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ValidateDatabase(UmbracoDatabase database) + private static void ValidateDatabase(IUmbracoDatabase database) { if (database == null) throw new ArgumentNullException("database"); - if (database.CurrentTransactionIsolationLevel < IsolationLevel.RepeatableRead) + if (database.GetCurrentTransactionIsolationLevel() < IsolationLevel.RepeatableRead) throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); } @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence // that record which will be kept until the transaction is ended, effectively locking // out all other accesses to that record - thus obtaining an exclusive lock over the // protected resources. - public static void AcquireLockNodeWriteLock(this UmbracoDatabase database, int nodeId) + public static void AcquireLockNodeWriteLock(this IUmbracoDatabase database, int nodeId) { ValidateDatabase(database); @@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence // that record which will be kept until the transaction is ended, effectively preventing // other write accesses to that record - thus obtaining a shared lock over the protected // resources. - public static void AcquireLockNodeReadLock(this UmbracoDatabase database, int nodeId) + public static void AcquireLockNodeReadLock(this IUmbracoDatabase database, int nodeId) { ValidateDatabase(database); @@ -40,4 +40,4 @@ namespace Umbraco.Core.Persistence new { @id = nodeId }); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/DbCommandExtensions.cs b/src/Umbraco.Core/Persistence/DbCommandExtensions.cs new file mode 100644 index 0000000000..a13eed3dcf --- /dev/null +++ b/src/Umbraco.Core/Persistence/DbCommandExtensions.cs @@ -0,0 +1,29 @@ +using System.Data; + +namespace Umbraco.Core.Persistence +{ + internal static class DbCommandExtensions + { + /// + /// Unwraps a database command. + /// + /// UmbracoDatabase wraps the original database connection in various layers (see + /// OnConnectionOpened); this unwraps and returns the original database command. + public static IDbCommand UnwrapUmbraco(this IDbCommand command) + { + IDbCommand unwrapped; + + var c = command; + do + { + unwrapped = c; + + var profiled = unwrapped as StackExchange.Profiling.Data.ProfiledDbCommand; + if (profiled != null) unwrapped = profiled.InternalCommand; + + } while (c != unwrapped); + + return unwrapped; + } + } +} diff --git a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs index 86072c45d5..4eccfe800f 100644 --- a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs +++ b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs @@ -1,108 +1,59 @@ using System; -using System.Collections.Generic; -using System.Configuration; using System.Data; using System.Data.Common; using System.Data.SqlClient; using System.Data.SqlServerCe; using System.Linq; -using System.Text; -using System.Threading.Tasks; using MySql.Data.MySqlClient; -using Umbraco.Core.Logging; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.FaultHandling; namespace Umbraco.Core.Persistence { internal static class DbConnectionExtensions { - - public static DatabaseProviders DetectProviderFromConnectionString(string connString) + public static string DetectProviderNameFromConnectionString(string connectionString) { - var builder = new DbConnectionStringBuilder {ConnectionString = connString}; + var builder = new DbConnectionStringBuilder { ConnectionString = connectionString }; var allKeys = builder.Keys.Cast(); - var mySql = new[] {"Server", "Database", "Uid", "Pwd"}; + var mySql = new[] { "Server", "Database", "Uid", "Pwd" }; if (mySql.All(x => allKeys.InvariantContains(x))) { - return DatabaseProviders.MySql; + return Constants.DbProviderNames.MySql; } - if (allKeys.InvariantContains("Data Source") + if (allKeys.InvariantContains("Data Source") //this dictionary is case insensitive && builder["Data source"].ToString().InvariantContains(".sdf")) { - return DatabaseProviders.SqlServerCE; + return Constants.DbProviderNames.SqlCe; } - return DatabaseProviders.SqlServer; + return Constants.DbProviderNames.SqlServer; } - public static bool IsConnectionAvailable(string connString, DatabaseProviders provider) + public static bool IsConnectionAvailable(string connectionString, string providerName) { - DbProviderFactory factory; - switch (provider) - { - case DatabaseProviders.SqlServer: - case DatabaseProviders.SqlAzure: - factory = DbProviderFactories.GetFactory(Constants.DatabaseProviders.SqlServer); - break; - case DatabaseProviders.SqlServerCE: - factory = DbProviderFactories.GetFactory("System.Data.SqlServerCe.4.0"); - break; - case DatabaseProviders.MySql: - factory = DbProviderFactories.GetFactory(Constants.DatabaseProviders.MySql); - break; - case DatabaseProviders.PostgreSQL: - case DatabaseProviders.Oracle: - case DatabaseProviders.SQLite: - default: - throw new NotSupportedException("The provider " + provider + " is not supported"); - } + if (providerName != Constants.DbProviderNames.SqlCe + && providerName != Constants.DbProviderNames.MySql + && providerName != Constants.DbProviderNames.SqlServer) + throw new NotSupportedException($"Provider \"{providerName}\" is not supported."); - var conn = factory.CreateConnection(); - if (conn == null) - { - throw new InvalidOperationException("Could not create a connection for provider " + provider); - } - conn.ConnectionString = connString; - using (var connection = conn) + var factory = DbProviderFactories.GetFactory(providerName); + var connection = factory.CreateConnection(); + + if (connection == null) + throw new InvalidOperationException($"Could not create a connection for provider \"{providerName}\"."); + + connection.ConnectionString = connectionString; + using (connection) { return connection.IsAvailable(); } } - public static string GetConnStringExSecurityInfo(this IDbConnection connection) - { - try - { - if (connection is SqlConnection) - { - var builder = new SqlConnectionStringBuilder(connection.ConnectionString); - return string.Format("DataSource: {0}, InitialCatalog: {1}", builder.DataSource, builder.InitialCatalog); - } - - if (connection is SqlCeConnection) - { - var builder = new SqlCeConnectionStringBuilder(connection.ConnectionString); - return string.Format("DataSource: {0}", builder.DataSource); - } - - if (connection is MySqlConnection) - { - var builder = new MySqlConnectionStringBuilder(connection.ConnectionString); - return string.Format("Server: {0}, Database: {1}", builder.Server, builder.Database); - } - } - catch (Exception ex) - { - LogHelper.WarnWithException(typeof(DbConnectionExtensions), - "Could not resolve connection string parameters", ex); - return "(Could not resolve)"; - } - - throw new ArgumentException(string.Format("The connection type {0} is not supported", connection.GetType())); - } - public static bool IsAvailable(this IDbConnection connection) { try @@ -110,17 +61,70 @@ namespace Umbraco.Core.Persistence connection.Open(); connection.Close(); } - catch (DbException exc) + catch (DbException e) { // Don't swallow this error, the exception is super handy for knowing "why" its not available - LogHelper.WarnWithException(typeof(DbConnectionExtensions), - "Configured database is reporting as not being available! {0}", exc, connection.GetConnStringExSecurityInfo); + Current.Logger.Warn(e, "Configured database is reporting as not being available."); return false; } return true; } - + /// + /// Unwraps a database connection. + /// + /// UmbracoDatabase wraps the original database connection in various layers (see + /// OnConnectionOpened); this unwraps and returns the original database connection. + internal static IDbConnection UnwrapUmbraco(this IDbConnection connection) + { + IDbConnection unwrapped; + + var c = connection; + do + { + unwrapped = c; + + var profiled = unwrapped as StackExchange.Profiling.Data.ProfiledDbConnection; + if (profiled != null) unwrapped = profiled.InnerConnection; + + var retrying = unwrapped as RetryDbConnection; + if (retrying != null) unwrapped = retrying.Inner; + + } while (c != unwrapped); + + return unwrapped; + } + + public static string GetConnStringExSecurityInfo(this IDbConnection connection) + { + try + { + if (connection is SqlConnection) + { + var builder = new SqlConnectionStringBuilder(connection.ConnectionString); + return $"DataSource: {builder.DataSource}, InitialCatalog: {builder.InitialCatalog}"; + } + + if (connection is SqlCeConnection) + { + var builder = new SqlCeConnectionStringBuilder(connection.ConnectionString); + return $"DataSource: {builder.DataSource}"; + } + + if (connection is MySqlConnection) + { + var builder = new MySqlConnectionStringBuilder(connection.ConnectionString); + return $"Server: {builder.Server}, Database: {builder.Database}"; + } + } + catch (Exception ex) + { + Current.Logger.Warn(typeof(DbConnectionExtensions), ex, "Could not resolve connection string parameters"); + return "(Could not resolve)"; + } + + throw new ArgumentException($"The connection type {connection.GetType()} is not supported"); + } } } diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs deleted file mode 100644 index 46c7dae1d9..0000000000 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using Umbraco.Core.Logging; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.Persistence -{ - /// - /// The default implementation for the IDatabaseFactory - /// - /// - /// If we are running in an http context - /// it will create one per context, otherwise it will be a global singleton object which is NOT thread safe - /// since we need (at least) a new instance of the database object per thread. - /// - internal class DefaultDatabaseFactory : DisposableObjectSlim, IDatabaseFactory2 - { - private readonly string _connectionStringName; - private readonly ILogger _logger; - public string ConnectionString { get; private set; } - public string ProviderName { get; private set; } - - //private static readonly object Locker = new object(); - - // bwc imposes a weird x-dependency between database factory and scope provider... - public IScopeProviderInternal ScopeProvider { get; set; } - - /// - /// Constructor accepting custom connection string - /// - /// Name of the connection string in web.config - /// - public DefaultDatabaseFactory(string connectionStringName, ILogger logger) - { - if (logger == null) throw new ArgumentNullException("logger"); - Mandate.ParameterNotNullOrEmpty(connectionStringName, "connectionStringName"); - - //if (NonContextValue != null) throw new Exception("NonContextValue is not null."); - - _connectionStringName = connectionStringName; - _logger = logger; - } - - /// - /// Constructor accepting custom connectino string and provider name - /// - /// Connection String to use with Database - /// Database Provider for the Connection String - /// - public DefaultDatabaseFactory(string connectionString, string providerName, ILogger logger) - { - if (logger == null) throw new ArgumentNullException("logger"); - Mandate.ParameterNotNullOrEmpty(connectionString, "connectionString"); - Mandate.ParameterNotNullOrEmpty(providerName, "providerName"); - - //if (NonContextValue != null) throw new Exception("NonContextValue is not null."); - - ConnectionString = connectionString; - ProviderName = providerName; - _logger = logger; - } - - public UmbracoDatabase CreateDatabase() - { - return ScopeProvider.GetAmbientOrNoScope().Database; - } - - public UmbracoDatabase CreateNewDatabase() - { - return CreateDatabaseInstance(); - - } - - internal UmbracoDatabase CreateDatabaseInstance() - { - var database = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false - ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) - : new UmbracoDatabase(_connectionStringName, _logger); - database.DatabaseFactory = this; - return database; - } - - protected override void DisposeResources() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/AccessDto.cs b/src/Umbraco.Core/Persistence/Dtos/AccessDto.cs similarity index 84% rename from src/Umbraco.Core/Models/Rdbms/AccessDto.cs rename to src/Umbraco.Core/Persistence/Dtos/AccessDto.cs index 37b1dbddd8..98ca37fbf8 100644 --- a/src/Umbraco.Core/Models/Rdbms/AccessDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/AccessDto.cs @@ -1,15 +1,13 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Security.AccessControl; -using Umbraco.Core.Persistence; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -namespace Umbraco.Core.Models.Rdbms +namespace Umbraco.Core.Persistence.Dtos { - [TableName("umbracoAccess")] - [PrimaryKey("id", autoIncrement = false)] + [TableName(Constants.DatabaseSchema.Tables.Access)] + [PrimaryKey("id", AutoIncrement = false)] [ExplicitColumns] internal class AccessDto { @@ -39,6 +37,7 @@ namespace Umbraco.Core.Models.Rdbms public DateTime UpdateDate { get; set; } [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "AccessId")] public List Rules { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs b/src/Umbraco.Core/Persistence/Dtos/AccessRuleDto.cs similarity index 85% rename from src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs rename to src/Umbraco.Core/Persistence/Dtos/AccessRuleDto.cs index 78e3444e56..6ec6104581 100644 --- a/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/AccessRuleDto.cs @@ -1,12 +1,12 @@ -using System; -using Umbraco.Core.Persistence; +using System; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -namespace Umbraco.Core.Models.Rdbms +namespace Umbraco.Core.Persistence.Dtos { - [TableName("umbracoAccessRule")] - [PrimaryKey("id", autoIncrement = false)] + [TableName(Constants.DatabaseSchema.Tables.AccessRule)] + [PrimaryKey("id", AutoIncrement = false)] [ExplicitColumns] internal class AccessRuleDto { @@ -33,4 +33,4 @@ namespace Umbraco.Core.Models.Rdbms [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime UpdateDate { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Dtos/AuditEntryDto.cs b/src/Umbraco.Core/Persistence/Dtos/AuditEntryDto.cs new file mode 100644 index 0000000000..27eeef8e56 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/AuditEntryDto.cs @@ -0,0 +1,59 @@ +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.AuditEntry)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class AuditEntryDto + { + public const int IpLength = 64; + public const int EventTypeLength = 256; + public const int DetailsLength = 1024; + + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + // there is NO foreign key to the users table here, neither for performing user nor for + // affected user, so we can delete users and NOT delete the associated audit trails, and + // users can still be identified via the details free-form text fields. + + [Column("performingUserId")] + public int PerformingUserId { get; set; } + + [Column("performingDetails")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(DetailsLength)] + public string PerformingDetails { get; set; } + + [Column("performingIp")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(IpLength)] + public string PerformingIp { get; set; } + + [Column("eventDateUtc")] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime EventDateUtc { get; set; } + + [Column("affectedUserId")] + public int AffectedUserId { get; set; } + + [Column("affectedDetails")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(DetailsLength)] + public string AffectedDetails { get; set; } + + [Column("eventType")] + [Length(EventTypeLength)] + public string EventType { get; set; } + + [Column("eventDetails")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(DetailsLength)] + public string EventDetails { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/CacheInstructionDto.cs b/src/Umbraco.Core/Persistence/Dtos/CacheInstructionDto.cs similarity index 89% rename from src/Umbraco.Core/Models/Rdbms/CacheInstructionDto.cs rename to src/Umbraco.Core/Persistence/Dtos/CacheInstructionDto.cs index 8646190423..1fc5eb90a8 100644 --- a/src/Umbraco.Core/Models/Rdbms/CacheInstructionDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/CacheInstructionDto.cs @@ -1,10 +1,10 @@ using System; -using Umbraco.Core.Persistence; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; -namespace Umbraco.Core.Models.Rdbms +namespace Umbraco.Core.Persistence.Dtos { - [TableName("umbracoCacheInstruction")] + [TableName(Constants.DatabaseSchema.Tables.CacheInstruction)] [PrimaryKey("id")] [ExplicitColumns] internal class CacheInstructionDto @@ -33,4 +33,4 @@ namespace Umbraco.Core.Models.Rdbms [Constraint(Default = 1)] public int InstructionCount { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Dtos/ConsentDto.cs b/src/Umbraco.Core/Persistence/Dtos/ConsentDto.cs new file mode 100644 index 0000000000..763df352de --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/ConsentDto.cs @@ -0,0 +1,42 @@ +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Consent)] + [PrimaryKey("id")] + [ExplicitColumns] + public class ConsentDto + { + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("current")] + public bool Current { get; set; } + + [Column("source")] + [Length(512)] + public string Source { get; set; } + + [Column("context")] + [Length(128)] + public string Context { get; set; } + + [Column("action")] + [Length(512)] + public string Action { get; set; } + + [Column("createDate")] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime CreateDate { get; set; } + + [Column("state")] + public int State { get; set; } + + [Column("comment")] + public string Comment { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentDto.cs new file mode 100644 index 0000000000..32e95fcc78 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/ContentDto.cs @@ -0,0 +1,33 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(TableName)] + [PrimaryKey("nodeId", AutoIncrement = false)] + [ExplicitColumns] + internal class ContentDto + { + private const string TableName = Constants.DatabaseSchema.Tables.Content; + + [Column("nodeId")] + [PrimaryKeyColumn(AutoIncrement = false)] + [ForeignKey(typeof(NodeDto))] + public int NodeId { get; set; } + + [Column("contentTypeId")] + [ForeignKey(typeof(ContentTypeDto), Column = "NodeId")] + public int ContentTypeId { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "NodeId")] + public NodeDto NodeDto { get; set; } + + // although a content has many content versions, + // they can only be loaded one by one (as several content), + // so this here is a OneToOne reference + [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] + public ContentVersionDto ContentVersionDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs new file mode 100644 index 0000000000..f427c5aa4a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs @@ -0,0 +1,26 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.NodeData)] + [PrimaryKey("nodeId", AutoIncrement = false)] + [ExplicitColumns] + internal class ContentNuDto + { + [Column("nodeId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsContentNu", OnColumns = "nodeId, published")] + [ForeignKey(typeof(ContentDto), Column = "nodeId")] + public int NodeId { get; set; } + + [Column("published")] + public bool Published { get; set; } + + [Column("data")] + [SpecialDbType(SpecialDbTypes.NTEXT)] + public string Data { get; set; } + + [Column("rv")] + public long Rv { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/ContentType2ContentTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentType2ContentTypeDto.cs similarity index 82% rename from src/Umbraco.Core/Models/Rdbms/ContentType2ContentTypeDto.cs rename to src/Umbraco.Core/Persistence/Dtos/ContentType2ContentTypeDto.cs index 89773ef1ee..1ce9040f68 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentType2ContentTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentType2ContentTypeDto.cs @@ -1,19 +1,19 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsContentType2ContentType")] - [ExplicitColumns] - internal class ContentType2ContentTypeDto - { - [Column("parentContentTypeId")] - [PrimaryKeyColumn(AutoIncrement = false, Clustered = true, Name = "PK_cmsContentType2ContentType", OnColumns = "parentContentTypeId, childContentTypeId")] - [ForeignKey(typeof(NodeDto), Name = "FK_cmsContentType2ContentType_umbracoNode_parent")] - public int ParentId { get; set; } - - [Column("childContentTypeId")] - [ForeignKey(typeof(NodeDto), Name = "FK_cmsContentType2ContentType_umbracoNode_child")] - public int ChildId { get; set; } - } -} \ No newline at end of file +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.ElementTypeTree)] + [ExplicitColumns] + internal class ContentType2ContentTypeDto + { + [Column("parentContentTypeId")] + [PrimaryKeyColumn(AutoIncrement = false, Clustered = true, Name = "PK_cmsContentType2ContentType", OnColumns = "parentContentTypeId, childContentTypeId")] + [ForeignKey(typeof(NodeDto), Name = "FK_cmsContentType2ContentType_umbracoNode_parent")] + public int ParentId { get; set; } + + [Column("childContentTypeId")] + [ForeignKey(typeof(NodeDto), Name = "FK_cmsContentType2ContentType_umbracoNode_child")] + public int ChildId { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/ContentTypeAllowedContentTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentTypeAllowedContentTypeDto.cs similarity index 81% rename from src/Umbraco.Core/Models/Rdbms/ContentTypeAllowedContentTypeDto.cs rename to src/Umbraco.Core/Persistence/Dtos/ContentTypeAllowedContentTypeDto.cs index 91904437f0..b17b98bf6c 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentTypeAllowedContentTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentTypeAllowedContentTypeDto.cs @@ -1,27 +1,28 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsContentTypeAllowedContentType")] - [PrimaryKey("Id", autoIncrement = false)] - [ExplicitColumns] - internal class ContentTypeAllowedContentTypeDto - { - [Column("Id")] - [ForeignKey(typeof(ContentTypeDto), Name = "FK_cmsContentTypeAllowedContentType_cmsContentType", Column = "nodeId")] - [PrimaryKeyColumn(AutoIncrement = false, Clustered = true, Name = "PK_cmsContentTypeAllowedContentType", OnColumns = "Id, AllowedId")] - public int Id { get; set; } - - [Column("AllowedId")] - [ForeignKey(typeof(ContentTypeDto), Name = "FK_cmsContentTypeAllowedContentType_cmsContentType1", Column = "nodeId")] - public int AllowedId { get; set; } - - [Column("SortOrder")] - [Constraint(Name = "df_cmsContentTypeAllowedContentType_sortOrder", Default = "0")] - public int SortOrder { get; set; } - - [ResultColumn] - public ContentTypeDto ContentTypeDto { get; set; } - } -} \ No newline at end of file +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.ContentChildType)] + [PrimaryKey("Id", AutoIncrement = false)] + [ExplicitColumns] + internal class ContentTypeAllowedContentTypeDto + { + [Column("Id")] + [ForeignKey(typeof(ContentTypeDto), Name = "FK_cmsContentTypeAllowedContentType_cmsContentType", Column = "nodeId")] + [PrimaryKeyColumn(AutoIncrement = false, Clustered = true, Name = "PK_cmsContentTypeAllowedContentType", OnColumns = "Id, AllowedId")] + public int Id { get; set; } + + [Column("AllowedId")] + [ForeignKey(typeof(ContentTypeDto), Name = "FK_cmsContentTypeAllowedContentType_cmsContentType1", Column = "nodeId")] + public int AllowedId { get; set; } + + [Column("SortOrder")] + [Constraint(Name = "df_cmsContentTypeAllowedContentType_sortOrder", Default = "0")] + public int SortOrder { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne)] + public ContentTypeDto ContentTypeDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/ContentTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs similarity index 82% rename from src/Umbraco.Core/Models/Rdbms/ContentTypeDto.cs rename to src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs index 8d163abf44..0cfc53eead 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs @@ -1,49 +1,53 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsContentType")] - [PrimaryKey("pk")] - [ExplicitColumns] - internal class ContentTypeDto - { - [Column("pk")] - [PrimaryKeyColumn(IdentitySeed = 535)] - public int PrimaryKey { get; set; } - - [Column("nodeId")] - [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsContentType")] - public int NodeId { get; set; } - - [Column("alias")] - [NullSetting(NullSetting = NullSettings.Null)] - public string Alias { get; set; } - - [Column("icon")] - [Index(IndexTypes.NonClustered)] - [NullSetting(NullSetting = NullSettings.Null)] - public string Icon { get; set; } - - [Column("thumbnail")] - [Constraint(Default = "folder.png")] - public string Thumbnail { get; set; } - - [Column("description")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(1500)] - public string Description { get; set; } - - [Column("isContainer")] - [Constraint(Default = "0")] - public bool IsContainer { get; set; } - - [Column("allowAtRoot")] - [Constraint(Default = "0")] - public bool AllowAtRoot { get; set; } - - [ResultColumn] - public NodeDto NodeDto { get; set; } - } -} \ No newline at end of file +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.ContentType)] + [PrimaryKey("pk")] + [ExplicitColumns] + internal class ContentTypeDto + { + [Column("pk")] + [PrimaryKeyColumn(IdentitySeed = 535)] + public int PrimaryKey { get; set; } + + [Column("nodeId")] + [ForeignKey(typeof(NodeDto))] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsContentType")] + public int NodeId { get; set; } + + [Column("alias")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Alias { get; set; } + + [Column("icon")] + [Index(IndexTypes.NonClustered)] + [NullSetting(NullSetting = NullSettings.Null)] + public string Icon { get; set; } + + [Column("thumbnail")] + [Constraint(Default = "folder.png")] + public string Thumbnail { get; set; } + + [Column("description")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(1500)] + public string Description { get; set; } + + [Column("isContainer")] + [Constraint(Default = "0")] + public bool IsContainer { get; set; } + + [Column("allowAtRoot")] + [Constraint(Default = "0")] + public bool AllowAtRoot { get; set; } + + [Column("variations")] + [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] + public byte Variations { get; set; } + + [ResultColumn] + public NodeDto NodeDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/ContentTypeTemplateDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentTypeTemplateDto.cs similarity index 77% rename from src/Umbraco.Core/Models/Rdbms/ContentTypeTemplateDto.cs rename to src/Umbraco.Core/Persistence/Dtos/ContentTypeTemplateDto.cs index 88ef02ea90..8b2a267a99 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentTypeTemplateDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentTypeTemplateDto.cs @@ -1,10 +1,10 @@ -using Umbraco.Core.Persistence; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; -namespace Umbraco.Core.Models.Rdbms +namespace Umbraco.Core.Persistence.Dtos { - [TableName("cmsDocumentType")] - [PrimaryKey("contentTypeNodeId", autoIncrement = false)] + [TableName(Constants.DatabaseSchema.Tables.DocumentType)] + [PrimaryKey("contentTypeNodeId", AutoIncrement = false)] [ExplicitColumns] internal class ContentTypeTemplateDto { @@ -23,6 +23,7 @@ namespace Umbraco.Core.Models.Rdbms public bool IsDefault { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne)] public ContentTypeDto ContentTypeDto { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs new file mode 100644 index 0000000000..13b7c30d45 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs @@ -0,0 +1,48 @@ +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(TableName)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class ContentVersionDto + { + private const string TableName = Constants.DatabaseSchema.Tables.ContentVersion; + + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("nodeId")] + [ForeignKey(typeof(ContentDto))] + public int NodeId { get; set; } + + [Column("versionDate")] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime VersionDate { get; set; } + + [Column("userId")] + public int UserId { get; set; } + + [Column("current")] + public bool Current { get; set; } + + // about current: + // there is nothing in the DB that guarantees that there will be one, and exactly one, current version per content item. + // that would require circular FKs that are impossible (well, it is possible to create them, but not to insert). + // we could use a content.currentVersionId FK that would need to be nullable, or (better?) an additional table + // linking a content itemt to its current version (nodeId, versionId) - that would guarantee uniqueness BUT it would + // not guarantee existence - so, really... we are trusting our code to manage 'current' correctly. + + [Column("text")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Text { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "NodeId", ReferenceMemberName = "NodeId")] + public ContentDto ContentDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentXmlDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentXmlDto.cs new file mode 100644 index 0000000000..5929f5cb81 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/ContentXmlDto.cs @@ -0,0 +1,23 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.NodeXml)] + [PrimaryKey("nodeId", AutoIncrement = false)] + [ExplicitColumns] + internal class ContentXmlDto + { + [Column("nodeId")] + [PrimaryKeyColumn(AutoIncrement = false)] + [ForeignKey(typeof(ContentDto), Column = "nodeId")] + public int NodeId { get; set; } + + [Column("xml")] + [SpecialDbType(SpecialDbTypes.NTEXT)] + public string Xml { get; set; } + + [Column("rv")] + public long Rv { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/DataTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/DataTypeDto.cs new file mode 100644 index 0000000000..cc51ab19ee --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/DataTypeDto.cs @@ -0,0 +1,32 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.DataType)] + [PrimaryKey("nodeId", AutoIncrement = false)] + [ExplicitColumns] + internal class DataTypeDto + { + [Column("nodeId")] + [PrimaryKeyColumn(AutoIncrement = false)] + [ForeignKey(typeof(NodeDto))] + public int NodeId { get; set; } + + [Column("propertyEditorAlias")] + public string EditorAlias { get; set; } // fixme - length?! + + [Column("dbType")] + [Length(50)] + public string DbType { get; set; } + + [Column("config")] + [SpecialDbType(SpecialDbTypes.NTEXT)] + [NullSetting(NullSetting = NullSettings.Null)] + public string Configuration { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "DataTypeId")] + public NodeDto NodeDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs b/src/Umbraco.Core/Persistence/Dtos/DictionaryDto.cs similarity index 79% rename from src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs rename to src/Umbraco.Core/Persistence/Dtos/DictionaryDto.cs index 1e1940ad8d..655474b217 100644 --- a/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/DictionaryDto.cs @@ -1,34 +1,35 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsDictionary")] - [PrimaryKey("pk")] - [ExplicitColumns] - internal class DictionaryDto - { - [Column("pk")] - [PrimaryKeyColumn] - public int PrimaryKey { get; set; } - - [Column("id")] - [Index(IndexTypes.UniqueNonClustered)] - public Guid UniqueId { get; set; } - - [Column("parent")] - [NullSetting(NullSetting = NullSettings.Null)] - [ForeignKey(typeof(DictionaryDto), Column = "id")] - public Guid? Parent { get; set; } - - [Column("key")] - [Length(450)] - [Index(IndexTypes.NonClustered, Name = "IX_cmsDictionary_key")] - public string Key { get; set; } - - [ResultColumn] - public List LanguageTextDtos { get; set; } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.DictionaryEntry)] + [PrimaryKey("pk")] + [ExplicitColumns] + internal class DictionaryDto + { + [Column("pk")] + [PrimaryKeyColumn] + public int PrimaryKey { get; set; } + + [Column("id")] + [Index(IndexTypes.UniqueNonClustered)] + public Guid UniqueId { get; set; } + + [Column("parent")] + [NullSetting(NullSetting = NullSettings.Null)] + [ForeignKey(typeof(DictionaryDto), Column = "id")] + public Guid? Parent { get; set; } + + [Column("key")] + [Length(450)] + [Index(IndexTypes.NonClustered, Name = "IX_cmsDictionary_key")] + public string Key { get; set; } + + [ResultColumn] + [Reference(ReferenceType.Many, ColumnName = "UniqueId", ReferenceMemberName = "UniqueId")] + public List LanguageTextDtos { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/DocumentDto.cs b/src/Umbraco.Core/Persistence/Dtos/DocumentDto.cs new file mode 100644 index 0000000000..fd3df69b8a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/DocumentDto.cs @@ -0,0 +1,66 @@ +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(TableName)] + [PrimaryKey("nodeId", AutoIncrement = false)] + [ExplicitColumns] + internal class DocumentDto + { + private const string TableName = Constants.DatabaseSchema.Tables.Document; + + [Column("nodeId")] + [PrimaryKeyColumn(AutoIncrement = false)] + [ForeignKey(typeof(ContentDto))] + public int NodeId { get; set; } + + [Column("published")] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Published")] + public bool Published { get; set; } + + [Column("edited")] + public bool Edited { get; set; } + + [Column("releaseDate")] + [NullSetting(NullSetting = NullSettings.Null)] + public DateTime? ReleaseDate { get; set; } + + [Column("expireDate")] + [NullSetting(NullSetting = NullSettings.Null)] + public DateTime? ExpiresDate { get; set; } + + //[Column("publishDate")] + //[NullSetting(NullSetting = NullSettings.Null)] // is contentVersionDto.VersionDate for the published version + //public DateTime? PublishDate { get; set; } + + //[Column("publishUserId")] + //[NullSetting(NullSetting = NullSettings.Null)] // is contentVersionDto.UserId for the published version + //public int? PublishUserId { get; set; } + + //[Column("publishName")] + //[NullSetting(NullSetting = NullSettings.Null)] // is contentVersionDto.Text for the published version + //public string PublishName { get; set; } + + //[Column("publishTemplateId")] + //[NullSetting(NullSetting = NullSettings.Null)] // is documentVersionDto.TemplateId for the published version + //public int? PublishTemplateId { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] + public ContentDto ContentDto { get; set; } + + // although a content has many content versions, + // they can only be loaded one by one (as several content), + // so this here is a OneToOne reference + [ResultColumn] + [Reference(ReferenceType.OneToOne)] + public DocumentVersionDto DocumentVersionDto { get; set; } + + // same + [ResultColumn] + [Reference(ReferenceType.OneToOne)] + public DocumentVersionDto PublishedVersionDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/DocumentPublishedReadOnlyDto.cs b/src/Umbraco.Core/Persistence/Dtos/DocumentPublishedReadOnlyDto.cs new file mode 100644 index 0000000000..27bb0989cc --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/DocumentPublishedReadOnlyDto.cs @@ -0,0 +1,26 @@ +using System; +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Document)] + [PrimaryKey("versionId", AutoIncrement = false)] + [ExplicitColumns] + internal class DocumentPublishedReadOnlyDto + { + [Column("nodeId")] + public int NodeId { get; set; } + + [Column("published")] + public bool Published { get; set; } + + [Column("versionId")] + public Guid VersionId { get; set; } + + [Column("newest")] + public bool Newest { get; set; } + + [Column("updateDate")] + public DateTime VersionDate { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/DocumentVersionDto.cs b/src/Umbraco.Core/Persistence/Dtos/DocumentVersionDto.cs new file mode 100644 index 0000000000..5697d188e9 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/DocumentVersionDto.cs @@ -0,0 +1,30 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(TableName)] + [PrimaryKey("id", AutoIncrement = false)] + [ExplicitColumns] + internal class DocumentVersionDto + { + private const string TableName = Constants.DatabaseSchema.Tables.DocumentVersion; + + [Column("id")] + [PrimaryKeyColumn(AutoIncrement = false)] + [ForeignKey(typeof(ContentVersionDto))] + public int Id { get; set; } + + [Column("templateId")] + [NullSetting(NullSetting = NullSettings.Null)] + [ForeignKey(typeof(TemplateDto), Column = "nodeId")] + public int? TemplateId { get; set; } + + [Column("published")] + public bool Published { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne)] + public ContentVersionDto ContentVersionDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/DomainDto.cs b/src/Umbraco.Core/Persistence/Dtos/DomainDto.cs similarity index 86% rename from src/Umbraco.Core/Models/Rdbms/DomainDto.cs rename to src/Umbraco.Core/Persistence/Dtos/DomainDto.cs index e43c1bdeae..7ed20defb6 100644 --- a/src/Umbraco.Core/Models/Rdbms/DomainDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/DomainDto.cs @@ -1,33 +1,33 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoDomains")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class DomainDto - { - [Column("id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - [Column("domainDefaultLanguage")] - [NullSetting(NullSetting = NullSettings.Null)] - public int? DefaultLanguage { get; set; } - - [Column("domainRootStructureID")] - [NullSetting(NullSetting = NullSettings.Null)] - [ForeignKey(typeof(NodeDto))] - public int? RootStructureId { get; set; } - - [Column("domainName")] - public string DomainName { get; set; } - - /// - /// Used for a result on the query to get the associated language for a domain if there is one - /// - [ResultColumn("languageISOCode")] - public string IsoCode { get; set; } - } -} \ No newline at end of file +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Domain)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class DomainDto + { + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("domainDefaultLanguage")] + [NullSetting(NullSetting = NullSettings.Null)] + public int? DefaultLanguage { get; set; } + + [Column("domainRootStructureID")] + [NullSetting(NullSetting = NullSettings.Null)] + [ForeignKey(typeof(NodeDto))] + public int? RootStructureId { get; set; } + + [Column("domainName")] + public string DomainName { get; set; } + + /// + /// Used for a result on the query to get the associated language for a domain if there is one + /// + [ResultColumn("languageISOCode")] + public string IsoCode { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs b/src/Umbraco.Core/Persistence/Dtos/ExternalLoginDto.cs similarity index 86% rename from src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs rename to src/Umbraco.Core/Persistence/Dtos/ExternalLoginDto.cs index 803c25fdfc..1b774854a6 100644 --- a/src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ExternalLoginDto.cs @@ -1,11 +1,11 @@ -using System; -using Umbraco.Core.Persistence; +using System; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -namespace Umbraco.Core.Models.Rdbms +namespace Umbraco.Core.Persistence.Dtos { - [TableName("umbracoExternalLogin")] + [TableName(Constants.DatabaseSchema.Tables.ExternalLogin)] [ExplicitColumns] [PrimaryKey("Id")] internal class ExternalLoginDto @@ -30,7 +30,5 @@ namespace Umbraco.Core.Models.Rdbms [Column("createDate")] [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime CreateDate { get; set; } - - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Dtos/KeyValueDto.cs b/src/Umbraco.Core/Persistence/Dtos/KeyValueDto.cs new file mode 100644 index 0000000000..b74039c388 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/KeyValueDto.cs @@ -0,0 +1,26 @@ +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.KeyValue)] + [PrimaryKey("key", AutoIncrement = false)] + [ExplicitColumns] + internal class KeyValueDto + { + [Column("key")] + [Length(256)] + [PrimaryKeyColumn(AutoIncrement = false, Clustered = true)] + public string Key { get; set; } + + [Column("value")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Value { get; set; } + + [Column("updated")] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime Updated { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs b/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs new file mode 100644 index 0000000000..78e5670830 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs @@ -0,0 +1,40 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Language)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class LanguageDto + { + [Column("id")] + [PrimaryKeyColumn(IdentitySeed = 2)] + public short Id { get; set; } + + [Column("languageISOCode")] + [Index(IndexTypes.UniqueNonClustered)] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(10)] + public string IsoCode { get; set; } + + [Column("languageCultureName")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(100)] + public string CultureName { get; set; } + + /// + /// Defines if this language is the default variant language when language variants are in use + /// + [Column("isDefaultVariantLang")] + [Constraint(Default = "0")] + public bool IsDefaultVariantLanguage { get; set; } + + /// + /// If true, a variant node cannot be published unless this language variant is created + /// + [Column("mandatory")] + [Constraint(Default = "0")] + public bool Mandatory { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs b/src/Umbraco.Core/Persistence/Dtos/LanguageTextDto.cs similarity index 82% rename from src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs rename to src/Umbraco.Core/Persistence/Dtos/LanguageTextDto.cs index 87329fbd4c..ff5592a929 100644 --- a/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/LanguageTextDto.cs @@ -1,28 +1,28 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsLanguageText")] - [PrimaryKey("pk")] - [ExplicitColumns] - internal class LanguageTextDto - { - [Column("pk")] - [PrimaryKeyColumn] - public int PrimaryKey { get; set; } - - [Column("languageId")] - [ForeignKey(typeof(LanguageDto), Column = "id")] - public int LanguageId { get; set; } - - [Column("UniqueId")] - [ForeignKey(typeof(DictionaryDto), Column = "id")] - public Guid UniqueId { get; set; } - - [Column("value")] - [Length(1000)] - public string Value { get; set; } - } -} \ No newline at end of file +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.DictionaryValue)] + [PrimaryKey("pk")] + [ExplicitColumns] + internal class LanguageTextDto + { + [Column("pk")] + [PrimaryKeyColumn] + public int PrimaryKey { get; set; } + + [Column("languageId")] + [ForeignKey(typeof(LanguageDto), Column = "id")] + public int LanguageId { get; set; } + + [Column("UniqueId")] + [ForeignKey(typeof(DictionaryDto), Column = "id")] + public Guid UniqueId { get; set; } + + [Column("value")] + [Length(1000)] + public string Value { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/LockDto.cs b/src/Umbraco.Core/Persistence/Dtos/LockDto.cs new file mode 100644 index 0000000000..833d262e26 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/LockDto.cs @@ -0,0 +1,24 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Lock)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class LockDto + { + [Column("id")] + [PrimaryKeyColumn(Name = "PK_umbracoLock")] + public int Id { get; set; } + + [Column("value")] + [NullSetting(NullSetting = NullSettings.NotNull)] + public int Value { get; set; } = 1; + + [Column("name")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Length(64)] + public string Name { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/LogDto.cs b/src/Umbraco.Core/Persistence/Dtos/LogDto.cs similarity index 81% rename from src/Umbraco.Core/Models/Rdbms/LogDto.cs rename to src/Umbraco.Core/Persistence/Dtos/LogDto.cs index 08fc602152..3f8d511074 100644 --- a/src/Umbraco.Core/Models/Rdbms/LogDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/LogDto.cs @@ -1,37 +1,37 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoLog")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class LogDto - { - [Column("id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - [Column("userId")] - public int UserId { get; set; } - - [Column("NodeId")] - [Index(IndexTypes.NonClustered, Name = "IX_umbracoLog")] - public int NodeId { get; set; } - - [Column("Datestamp")] - [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime Datestamp { get; set; } - - [Column("logHeader")] - [Length(50)] - public string Header { get; set; } - - [Column("logComment")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(4000)] - public string Comment { get; set; } - } -} \ No newline at end of file +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Log)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class LogDto + { + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("userId")] + public int UserId { get; set; } + + [Column("NodeId")] + [Index(IndexTypes.NonClustered, Name = "IX_umbracoLog")] + public int NodeId { get; set; } + + [Column("Datestamp")] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime Datestamp { get; set; } + + [Column("logHeader")] + [Length(50)] + public string Header { get; set; } + + [Column("logComment")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(4000)] + public string Comment { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/MacroDto.cs b/src/Umbraco.Core/Persistence/Dtos/MacroDto.cs similarity index 85% rename from src/Umbraco.Core/Models/Rdbms/MacroDto.cs rename to src/Umbraco.Core/Persistence/Dtos/MacroDto.cs index ba2cee9356..821f092b38 100644 --- a/src/Umbraco.Core/Models/Rdbms/MacroDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/MacroDto.cs @@ -1,68 +1,70 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsMacro")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class MacroDto - { - [Column("id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - [Column("uniqueId")] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacro_UniqueId")] - public Guid UniqueId { get; set; } - - [Column("macroUseInEditor")] - [Constraint(Default = "0")] - public bool UseInEditor { get; set; } - - [Column("macroRefreshRate")] - [Constraint(Default = "0")] - public int RefreshRate { get; set; } - - [Column("macroAlias")] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacroPropertyAlias")] - public string Alias { get; set; } - - [Column("macroName")] - [NullSetting(NullSetting = NullSettings.Null)] - public string Name { get; set; } - - [Column("macroScriptType")] - [NullSetting(NullSetting = NullSettings.Null)] - public string ScriptType { get; set; } - - [Column("macroScriptAssembly")] - [NullSetting(NullSetting = NullSettings.Null)] - public string ScriptAssembly { get; set; } - - [Column("macroXSLT")] - [NullSetting(NullSetting = NullSettings.Null)] - public string Xslt { get; set; } - - [Column("macroCacheByPage")] - [Constraint(Default = "1")] - public bool CacheByPage { get; set; } - - [Column("macroCachePersonalized")] - [Constraint(Default = "0")] - public bool CachePersonalized { get; set; } - - [Column("macroDontRender")] - [Constraint(Default = "0")] - public bool DontRender { get; set; } - - [Column("macroPython")] - [NullSetting(NullSetting = NullSettings.Null)] - public string Python { get; set; } - - [ResultColumn] - public List MacroPropertyDtos { get; set; } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Macro)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class MacroDto + { + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("uniqueId")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacro_UniqueId")] + public Guid UniqueId { get; set; } + + [Column("macroUseInEditor")] + [Constraint(Default = "0")] + public bool UseInEditor { get; set; } + + [Column("macroRefreshRate")] + [Constraint(Default = "0")] + public int RefreshRate { get; set; } + + [Column("macroAlias")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacroPropertyAlias")] + public string Alias { get; set; } + + [Column("macroName")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Name { get; set; } + + [Column("macroScriptType")] + [NullSetting(NullSetting = NullSettings.Null)] + public string ScriptType { get; set; } + + [Column("macroScriptAssembly")] + [NullSetting(NullSetting = NullSettings.Null)] + public string ScriptAssembly { get; set; } + + [Column("macroXSLT")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Xslt { get; set; } + + [Column("macroCacheByPage")] + [Constraint(Default = "1")] + public bool CacheByPage { get; set; } + + [Column("macroCachePersonalized")] + [Constraint(Default = "0")] + public bool CachePersonalized { get; set; } + + [Column("macroDontRender")] + [Constraint(Default = "0")] + public bool DontRender { get; set; } + + //TODO: Rename this column! - actually please revamp all of the macros! :) + [Column("macroPython")] + [NullSetting(NullSetting = NullSettings.Null)] + public string MacroFilePath { get; set; } + + [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "Macro")] + public List MacroPropertyDtos { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/MacroPropertyDto.cs b/src/Umbraco.Core/Persistence/Dtos/MacroPropertyDto.cs similarity index 85% rename from src/Umbraco.Core/Models/Rdbms/MacroPropertyDto.cs rename to src/Umbraco.Core/Persistence/Dtos/MacroPropertyDto.cs index 7cfe6c3da7..ae8528aec4 100644 --- a/src/Umbraco.Core/Models/Rdbms/MacroPropertyDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/MacroPropertyDto.cs @@ -1,40 +1,40 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsMacroProperty")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class MacroPropertyDto - { - [Column("id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - // important to use column name != cmsMacro.uniqueId (fix in v8) - [Column("uniquePropertyId")] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacroProperty_UniquePropertyId")] - public Guid UniqueId { get; set; } - - [Column("editorAlias")] - public string EditorAlias { get; set; } - - [Column("macro")] - [ForeignKey(typeof(MacroDto))] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacroProperty_Alias", ForColumns = "macro, macroPropertyAlias")] - public int Macro { get; set; } - - [Column("macroPropertySortOrder")] - [Constraint(Default = "0")] - public byte SortOrder { get; set; } - - [Column("macroPropertyAlias")] - [Length(50)] - public string Alias { get; set; } - - [Column("macroPropertyName")] - public string Name { get; set; } - } -} \ No newline at end of file +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.MacroProperty)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class MacroPropertyDto + { + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + // important to use column name != cmsMacro.uniqueId (fix in v8) + [Column("uniquePropertyId")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacroProperty_UniquePropertyId")] + public Guid UniqueId { get; set; } + + [Column("editorAlias")] + public string EditorAlias { get; set; } + + [Column("macro")] + [ForeignKey(typeof(MacroDto))] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacroProperty_Alias", ForColumns = "macro, macroPropertyAlias")] + public int Macro { get; set; } + + [Column("macroPropertySortOrder")] + [Constraint(Default = "0")] + public byte SortOrder { get; set; } + + [Column("macroPropertyAlias")] + [Length(50)] + public string Alias { get; set; } + + [Column("macroPropertyName")] + public string Name { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/MediaDto.cs b/src/Umbraco.Core/Persistence/Dtos/MediaDto.cs new file mode 100644 index 0000000000..6990a891c4 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/MediaDto.cs @@ -0,0 +1,21 @@ +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + // this is a special Dto that does not have a corresponding table + // and is only used in our code to represent a media item, similar + // to document items. + + internal class MediaDto + { + public int NodeId { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] + public ContentDto ContentDto { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne)] + public MediaVersionDto MediaVersionDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/MediaVersionDto.cs b/src/Umbraco.Core/Persistence/Dtos/MediaVersionDto.cs new file mode 100644 index 0000000000..e27a99a2ae --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/MediaVersionDto.cs @@ -0,0 +1,25 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.MediaVersion)] + [PrimaryKey("id", AutoIncrement = false)] + [ExplicitColumns] + internal class MediaVersionDto + { + [Column("id")] + [PrimaryKeyColumn(AutoIncrement = false)] + [ForeignKey(typeof(ContentVersionDto))] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_" + Constants.DatabaseSchema.Tables.MediaVersion, ForColumns = "id, path")] + public int Id { get; set; } + + [Column("path")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Path { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne)] + public ContentVersionDto ContentVersionDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/Member2MemberGroupDto.cs b/src/Umbraco.Core/Persistence/Dtos/Member2MemberGroupDto.cs new file mode 100644 index 0000000000..6524555966 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/Member2MemberGroupDto.cs @@ -0,0 +1,20 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Member2MemberGroup)] + [PrimaryKey("Member", AutoIncrement = false)] + [ExplicitColumns] + internal class Member2MemberGroupDto + { + [Column("Member")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsMember2MemberGroup", OnColumns = "Member, MemberGroup")] + [ForeignKey(typeof(MemberDto))] + public int Member { get; set; } + + [Column("MemberGroup")] + [ForeignKey(typeof(NodeDto))] + public int MemberGroup { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/MemberDto.cs b/src/Umbraco.Core/Persistence/Dtos/MemberDto.cs new file mode 100644 index 0000000000..df4c9e2542 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/MemberDto.cs @@ -0,0 +1,42 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(TableName)] + [PrimaryKey("nodeId", AutoIncrement = false)] + [ExplicitColumns] + internal class MemberDto + { + private const string TableName = Constants.DatabaseSchema.Tables.Member; + + [Column("nodeId")] + [PrimaryKeyColumn(AutoIncrement = false)] + [ForeignKey(typeof(ContentDto))] + public int NodeId { get; set; } + + [Column("Email")] + [Length(1000)] + [Constraint(Default = "''")] + public string Email { get; set; } + + [Column("LoginName")] + [Length(1000)] + [Constraint(Default = "''")] + [Index(IndexTypes.NonClustered, Name = "IX_cmsMember_LoginName")] + public string LoginName { get; set; } + + [Column("Password")] + [Length(1000)] + [Constraint(Default = "''")] + public string Password { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] + public ContentDto ContentDto { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] + public ContentVersionDto ContentVersionDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/MemberTypeDto.cs similarity index 75% rename from src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs rename to src/Umbraco.Core/Persistence/Dtos/MemberTypeDto.cs index c9432652af..545f92bb82 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/MemberTypeDto.cs @@ -1,31 +1,35 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsMemberType")] - [PrimaryKey("pk")] - [ExplicitColumns] - internal class MemberTypeDto - { - [Column("pk")] - [PrimaryKeyColumn] - public int PrimaryKey { get; set; } - - [Column("NodeId")] - [ForeignKey(typeof(NodeDto))] - [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] - public int NodeId { get; set; } - - [Column("propertytypeId")] - public int PropertyTypeId { get; set; } - - [Column("memberCanEdit")] - [Constraint(Default = "0")] - public bool CanEdit { get; set; } - - [Column("viewOnProfile")] - [Constraint(Default = "0")] - public bool ViewOnProfile { get; set; } - } -} \ No newline at end of file +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.MemberType)] + [PrimaryKey("pk")] + [ExplicitColumns] + internal class MemberTypeDto + { + [Column("pk")] + [PrimaryKeyColumn] + public int PrimaryKey { get; set; } + + [Column("NodeId")] + [ForeignKey(typeof(NodeDto))] + [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] + public int NodeId { get; set; } + + [Column("propertytypeId")] + public int PropertyTypeId { get; set; } + + [Column("memberCanEdit")] + [Constraint(Default = "0")] + public bool CanEdit { get; set; } + + [Column("viewOnProfile")] + [Constraint(Default = "0")] + public bool ViewOnProfile { get; set; } + + [Column("isSensitive")] + [Constraint(Default = "0")] + public bool IsSensitive { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs b/src/Umbraco.Core/Persistence/Dtos/MemberTypeReadOnlyDto.cs similarity index 87% rename from src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs rename to src/Umbraco.Core/Persistence/Dtos/MemberTypeReadOnlyDto.cs index 4833cb33f0..f9e70cce18 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/MemberTypeReadOnlyDto.cs @@ -1,78 +1,80 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Persistence; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoNode")] - [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] - public List PropertyTypes { get; set; } - - /* PropertyTypeGroups */ - //TODO Add PropertyTypeGroupDto ReadOnly list - [ResultColumn] - public List PropertyTypeGroups { get; set; } - } -} \ No newline at end of file +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/NodeDto.cs b/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs new file mode 100644 index 0000000000..38e8ed0900 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs @@ -0,0 +1,64 @@ +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(TableName)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class NodeDto + { + private const string TableName = Constants.DatabaseSchema.Tables.Node; + public const int NodeIdSeed = 1060; + + [Column("id")] + [PrimaryKeyColumn(IdentitySeed = NodeIdSeed)] + public int NodeId { get; set; } + + [Column("uniqueId")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_UniqueId")] + [Constraint(Default = SystemMethods.NewGuid)] + public Guid UniqueId { get; set; } + + [Column("parentId")] + [ForeignKey(typeof(NodeDto))] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ParentId")] + public int ParentId { get; set; } + + [Column("level")] + public short Level { get; set; } + + [Column("path")] + [Length(150)] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Path")] + public string Path { get; set; } + + [Column("sortOrder")] + public int SortOrder { get; set; } + + [Column("trashed")] + [Constraint(Default = "0")] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Trashed")] + public bool Trashed { get; set; } + + [Column("nodeUser")] // fixme dbfix rename userId + [NullSetting(NullSetting = NullSettings.Null)] + public int? UserId { get; set; } + + [Column("text")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Text { get; set; } + + [Column("nodeObjectType")] // fixme dbfix rename objectType + [NullSetting(NullSetting = NullSettings.Null)] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType")] + public Guid? NodeObjectType { get; set; } // fixme dbfix rename ObjectType + + [Column("createDate")] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime CreateDate { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/PreviewXmlDto.cs b/src/Umbraco.Core/Persistence/Dtos/PreviewXmlDto.cs new file mode 100644 index 0000000000..40cc50b00a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/PreviewXmlDto.cs @@ -0,0 +1,23 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.NodePreviewXml)] + [PrimaryKey("nodeId", AutoIncrement = false)] + [ExplicitColumns] + internal class PreviewXmlDto + { + [Column("nodeId")] + [PrimaryKeyColumn(AutoIncrement = false)] + [ForeignKey(typeof(ContentDto), Column = "nodeId")] + public int NodeId { get; set; } + + [Column("xml")] + [SpecialDbType(SpecialDbTypes.NTEXT)] + public string Xml { get; set; } + + [Column("rv")] + public long Rv { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs new file mode 100644 index 0000000000..cb47784d92 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs @@ -0,0 +1,135 @@ +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(TableName)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class PropertyDataDto + { + private const string TableName = Constants.DatabaseSchema.Tables.PropertyData; + public const int VarcharLength = 512; + public const int SegmentLength = 256; + + private decimal? _decimalValue; + + // pk, not used at the moment (never updating) + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("versionId")] + [ForeignKey(typeof(ContentVersionDto))] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_VersionId", ForColumns = "versionId,propertyTypeId,languageId,segment")] + public int VersionId { get; set; } + + [Column("propertyTypeId")] + [ForeignKey(typeof(PropertyTypeDto))] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_PropertyTypeId")] + public int PropertyTypeId { get; set; } + + [Column("languageId")] + [ForeignKey(typeof(LanguageDto))] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_LanguageId")] + [NullSetting(NullSetting = NullSettings.Null)] + public int? LanguageId { get; set; } + + [Column("segment")] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Segment")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(SegmentLength)] + public string Segment { get; set; } + + [Column("intValue")] + [NullSetting(NullSetting = NullSettings.Null)] + public int? IntegerValue { get; set; } + + [Column("decimalValue")] + [NullSetting(NullSetting = NullSettings.Null)] + public decimal? DecimalValue + { + get => _decimalValue; + set => _decimalValue = value?.Normalize(); + } + + [Column("dateValue")] + [NullSetting(NullSetting = NullSettings.Null)] + public DateTime? DateValue { get; set; } + + [Column("varcharValue")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(VarcharLength)] + public string VarcharValue { get; set; } + + [Column("textValue")] + [NullSetting(NullSetting = NullSettings.Null)] + [SpecialDbType(SpecialDbTypes.NTEXT)] + public string TextValue { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "PropertyTypeId")] + public PropertyTypeDto PropertyTypeDto { get; set; } + + [Ignore] + public object Value + { + get + { + if (IntegerValue.HasValue) + return IntegerValue.Value; + + if (DecimalValue.HasValue) + return DecimalValue.Value; + + if (DateValue.HasValue) + return DateValue.Value; + + if (!string.IsNullOrEmpty(VarcharValue)) + return VarcharValue; + + if (!string.IsNullOrEmpty(TextValue)) + return TextValue; + + return null; + } + } + + public PropertyDataDto Clone(int versionId) + { + return new PropertyDataDto + { + VersionId = versionId, + PropertyTypeId = PropertyTypeId, + LanguageId = LanguageId, + Segment = Segment, + IntegerValue = IntegerValue, + DecimalValue = DecimalValue, + DateValue = DateValue, + VarcharValue = VarcharValue, + TextValue = TextValue, + PropertyTypeDto = PropertyTypeDto + }; + } + + protected bool Equals(PropertyDataDto other) + { + return Id == other.Id; + } + + public override bool Equals(object other) + { + return + !ReferenceEquals(null, other) // other is not null + && (ReferenceEquals(this, other) // and either ref-equals, or same id + || other is PropertyDataDto pdata && pdata.Id == Id); + } + + public override int GetHashCode() + { + // ReSharper disable once NonReadonlyMemberInGetHashCode + return Id; + } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs similarity index 85% rename from src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs rename to src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs index 7e28e5f556..8c52aa1e15 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs @@ -1,64 +1,69 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsPropertyType")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class PropertyTypeDto - { - [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 50)] - public int Id { get; set; } - - [Column("dataTypeId")] - [ForeignKey(typeof(DataTypeDto), Column = "nodeId")] - public int DataTypeId { get; set; } - - [Column("contentTypeId")] - [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] - public int ContentTypeId { get; set; } - - [Column("propertyTypeGroupId")] - [NullSetting(NullSetting = NullSettings.Null)] - [ForeignKey(typeof(PropertyTypeGroupDto))] - public int? PropertyTypeGroupId { get; set; } - - [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyTypeAlias")] - [Column("Alias")] - public string Alias { get; set; } - - [Column("Name")] - [NullSetting(NullSetting = NullSettings.Null)] - public string Name { get; set; } - - [Column("sortOrder")] - [Constraint(Default = "0")] - public int SortOrder { get; set; } - - [Column("mandatory")] - [Constraint(Default = "0")] - public bool Mandatory { get; set; } - - [Column("validationRegExp")] - [NullSetting(NullSetting = NullSettings.Null)] - public string ValidationRegExp { get; set; } - - [Column("Description")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(2000)] - public string Description { get; set; } - - [ResultColumn] - public DataTypeDto DataTypeDto { get; set; } - - [Column("UniqueID")] - [NullSetting(NullSetting = NullSettings.NotNull)] - [Constraint(Default = SystemMethods.NewGuid)] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyTypeUniqueID")] - public Guid UniqueId { get; set; } - } -} \ No newline at end of file +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.PropertyType)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class PropertyTypeDto + { + [Column("id")] + [PrimaryKeyColumn(IdentitySeed = 50)] + public int Id { get; set; } + + [Column("dataTypeId")] + [ForeignKey(typeof(DataTypeDto), Column = "nodeId")] + public int DataTypeId { get; set; } + + [Column("contentTypeId")] + [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] + public int ContentTypeId { get; set; } + + [Column("propertyTypeGroupId")] + [NullSetting(NullSetting = NullSettings.Null)] + [ForeignKey(typeof(PropertyTypeGroupDto))] + public int? PropertyTypeGroupId { get; set; } + + [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyTypeAlias")] + [Column("Alias")] + public string Alias { get; set; } + + [Column("Name")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Name { get; set; } + + [Column("sortOrder")] + [Constraint(Default = "0")] + public int SortOrder { get; set; } + + [Column("mandatory")] + [Constraint(Default = "0")] + public bool Mandatory { get; set; } + + [Column("validationRegExp")] + [NullSetting(NullSetting = NullSettings.Null)] + public string ValidationRegExp { get; set; } + + [Column("Description")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(2000)] + public string Description { get; set; } + + [Column("variations")] + [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] + public byte Variations { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "DataTypeId")] + public DataTypeDto DataTypeDto { get; set; } + + [Column("UniqueID")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Constraint(Default = SystemMethods.NewGuid)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyTypeUniqueID")] + public Guid UniqueId { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeGroupDto.cs similarity index 79% rename from src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs rename to src/Umbraco.Core/Persistence/Dtos/PropertyTypeGroupDto.cs index 68de420a97..c48a6697ef 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeGroupDto.cs @@ -1,37 +1,38 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsPropertyTypeGroup")] - [PrimaryKey("id", autoIncrement = true)] - [ExplicitColumns] - internal class PropertyTypeGroupDto - { - [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 12)] - public int Id { get; set; } - - [Column("contenttypeNodeId")] - [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] - public int ContentTypeNodeId { get; set; } - - [Column("text")] - public string Text { get; set; } - - [Column("sortorder")] - public int SortOrder { get; set; } - - [ResultColumn] - public List PropertyTypeDtos { get; set; } - - [Column("uniqueID")] - [NullSetting(NullSetting = NullSettings.NotNull)] - [Constraint(Default = SystemMethods.NewGuid)] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyTypeGroupUniqueID")] - public Guid UniqueId { get; set; } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.PropertyTypeGroup)] + [PrimaryKey("id", AutoIncrement = true)] + [ExplicitColumns] + internal class PropertyTypeGroupDto + { + [Column("id")] + [PrimaryKeyColumn(IdentitySeed = 12)] + public int Id { get; set; } + + [Column("contenttypeNodeId")] + [ForeignKey(typeof(ContentTypeDto), Column = "nodeId")] + public int ContentTypeNodeId { get; set; } + + [Column("text")] + public string Text { get; set; } + + [Column("sortorder")] + public int SortOrder { get; set; } + + [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "PropertyTypeGroupId")] + public List PropertyTypeDtos { get; set; } + + [Column("uniqueID")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Constraint(Default = SystemMethods.NewGuid)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyTypeGroupUniqueID")] + public Guid UniqueId { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeGroupReadOnlyDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeGroupReadOnlyDto.cs new file mode 100644 index 0000000000..f812bd48aa --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeGroupReadOnlyDto.cs @@ -0,0 +1,26 @@ +using System; +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.PropertyTypeGroup)] + [PrimaryKey("id", AutoIncrement = true)] + [ExplicitColumns] + internal class PropertyTypeGroupReadOnlyDto + { + [Column("PropertyTypeGroupId")] + public int? Id { get; set; } + + [Column("PropertyGroupName")] + public string Text { get; set; } + + [Column("PropertyGroupSortOrder")] + public int SortOrder { get; set; } + + [Column("contenttypeNodeId")] + public int ContentTypeNodeId { get; set; } + + [Column("PropertyGroupUniqueID")] + public Guid UniqueId { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs similarity index 84% rename from src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs rename to src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs index d85baa2884..c68dee42b5 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs @@ -1,58 +1,61 @@ -using System; -using Umbraco.Core.Persistence; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsPropertyType")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class PropertyTypeReadOnlyDto - { - [Column("PropertyTypeId")] - public int? Id { get; set; } - - [Column("dataTypeId")] - public int DataTypeId { get; set; } - - [Column("contentTypeId")] - public int ContentTypeId { get; set; } - - [Column("PropertyTypesGroupId")] - public int? PropertyTypeGroupId { get; set; } - - [Column("Alias")] - public string Alias { get; set; } - - [Column("Name")] - public string Name { get; set; } - - [Column("PropertyTypeSortOrder")] - public int SortOrder { get; set; } - - [Column("mandatory")] - public bool Mandatory { get; set; } - - [Column("validationRegExp")] - public string ValidationRegExp { get; set; } - - [Column("Description")] - public string Description { get; set; } - - /* cmsMemberType */ - [Column("memberCanEdit")] - public bool CanEdit { get; set; } - - [Column("viewOnProfile")] - public bool ViewOnProfile { get; set; } - - /* cmsDataType */ - [Column("propertyEditorAlias")] - public string PropertyEditorAlias { get; set; } - - [Column("dbType")] - public string DbType { get; set; } - - [Column("UniqueID")] - public Guid UniqueId { get; set; } - } -} \ No newline at end of file +using System; +using NPoco; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.PropertyType)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class PropertyTypeReadOnlyDto + { + [Column("PropertyTypeId")] + public int? Id { get; set; } + + [Column("dataTypeId")] + public int DataTypeId { get; set; } + + [Column("contentTypeId")] + public int ContentTypeId { get; set; } + + [Column("PropertyTypesGroupId")] + public int? PropertyTypeGroupId { get; set; } + + [Column("Alias")] + public string Alias { get; set; } + + [Column("Name")] + public string Name { get; set; } + + [Column("PropertyTypeSortOrder")] + public int SortOrder { get; set; } + + [Column("mandatory")] + public bool Mandatory { get; set; } + + [Column("validationRegExp")] + public string ValidationRegExp { get; set; } + + [Column("Description")] + public string Description { get; set; } + + /* cmsMemberType */ + [Column("memberCanEdit")] + public bool CanEdit { get; set; } + + [Column("viewOnProfile")] + public bool ViewOnProfile { get; set; } + + [Column("isSensitive")] + public bool IsSensitive { get; set; } + + /* DataType */ + [Column("propertyEditorAlias")] + public string PropertyEditorAlias { get; set; } + + [Column("dbType")] + public string DbType { get; set; } + + [Column("UniqueID")] + public Guid UniqueId { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs b/src/Umbraco.Core/Persistence/Dtos/RedirectUrlDto.cs similarity index 90% rename from src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs rename to src/Umbraco.Core/Persistence/Dtos/RedirectUrlDto.cs index ca65e4b9a1..b2bc990f6b 100644 --- a/src/Umbraco.Core/Models/Rdbms/RedirectUrlDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/RedirectUrlDto.cs @@ -1,11 +1,11 @@ using System; -using Umbraco.Core.Persistence; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; -namespace Umbraco.Core.Models.Rdbms +namespace Umbraco.Core.Persistence.Dtos { - [TableName("umbracoRedirectUrl")] - [PrimaryKey("id", autoIncrement = false)] + [TableName(Constants.DatabaseSchema.Tables.RedirectUrl)] + [PrimaryKey("id", AutoIncrement = false)] [ExplicitColumns] class RedirectUrlDto { diff --git a/src/Umbraco.Core/Models/Rdbms/RelationDto.cs b/src/Umbraco.Core/Persistence/Dtos/RelationDto.cs similarity index 88% rename from src/Umbraco.Core/Models/Rdbms/RelationDto.cs rename to src/Umbraco.Core/Persistence/Dtos/RelationDto.cs index d3d741a191..f1fd3007d7 100644 --- a/src/Umbraco.Core/Models/Rdbms/RelationDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/RelationDto.cs @@ -1,38 +1,38 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoRelation")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class RelationDto - { - [Column("id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - [Column("parentId")] - [ForeignKey(typeof(NodeDto), Name = "FK_umbracoRelation_umbracoNode")] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelation_parentChildType", ForColumns = "parentId,childId,relType")] - public int ParentId { get; set; } - - [Column("childId")] - [ForeignKey(typeof(NodeDto), Name = "FK_umbracoRelation_umbracoNode1")] - public int ChildId { get; set; } - - [Column("relType")] - [ForeignKey(typeof(RelationTypeDto))] - public int RelationType { get; set; } - - [Column("datetime")] - [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime Datetime { get; set; } - - [Column("comment")] - [Length(1000)] - public string Comment { get; set; } - } -} \ No newline at end of file +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Relation)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class RelationDto + { + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("parentId")] + [ForeignKey(typeof(NodeDto), Name = "FK_umbracoRelation_umbracoNode")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelation_parentChildType", ForColumns = "parentId,childId,relType")] + public int ParentId { get; set; } + + [Column("childId")] + [ForeignKey(typeof(NodeDto), Name = "FK_umbracoRelation_umbracoNode1")] + public int ChildId { get; set; } + + [Column("relType")] + [ForeignKey(typeof(RelationTypeDto))] + public int RelationType { get; set; } + + [Column("datetime")] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime Datetime { get; set; } + + [Column("comment")] + [Length(1000)] + public string Comment { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs similarity index 88% rename from src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs rename to src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs index d13ce33520..5ce408fc44 100644 --- a/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs @@ -1,41 +1,41 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoRelationType")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class RelationTypeDto - { - public const int NodeIdSeed = 3; - - [Column("id")] - [PrimaryKeyColumn(IdentitySeed = NodeIdSeed)] - public int Id { get; set; } - - [Column("typeUniqueId")] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_UniqueId")] - public Guid UniqueId { get; set; } - - [Column("dual")] - public bool Dual { get; set; } - - [Column("parentObjectType")] - public Guid ParentObjectType { get; set; } - - [Column("childObjectType")] - public Guid ChildObjectType { get; set; } - - [Column("name")] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")] - public string Name { get; set; } - - [Column("alias")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(100)] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")] - public string Alias { get; set; } - } -} \ No newline at end of file +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.RelationType)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class RelationTypeDto + { + public const int NodeIdSeed = 3; + + [Column("id")] + [PrimaryKeyColumn(IdentitySeed = NodeIdSeed)] + public int Id { get; set; } + + [Column("typeUniqueId")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_UniqueId")] + public Guid UniqueId { get; set; } + + [Column("dual")] + public bool Dual { get; set; } + + [Column("parentObjectType")] + public Guid ParentObjectType { get; set; } + + [Column("childObjectType")] + public Guid ChildObjectType { get; set; } + + [Column("name")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")] + public string Name { get; set; } + + [Column("alias")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(100)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")] + public string Alias { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs b/src/Umbraco.Core/Persistence/Dtos/ServerRegistrationDto.cs similarity index 88% rename from src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs rename to src/Umbraco.Core/Persistence/Dtos/ServerRegistrationDto.cs index 2a3751c083..ccf9d26414 100644 --- a/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ServerRegistrationDto.cs @@ -1,40 +1,40 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoServer")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class ServerRegistrationDto - { - [Column("id")] - [PrimaryKeyColumn(AutoIncrement = true)] - public int Id { get; set; } - - [Column("address")] - [Length(500)] - public string ServerAddress { get; set; } - - [Column("computerName")] - [Length(255)] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_computerName")] // server identity is unique - public string ServerIdentity { get; set; } - - [Column("registeredDate")] - [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime DateRegistered { get; set; } - - [Column("lastNotifiedDate")] - public DateTime DateAccessed { get; set; } - - [Column("isActive")] - [Index(IndexTypes.NonClustered)] - public bool IsActive { get; set; } - - [Column("isMaster")] - public bool IsMaster { get; set; } - } -} \ No newline at end of file +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Server)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class ServerRegistrationDto + { + [Column("id")] + [PrimaryKeyColumn(AutoIncrement = true)] + public int Id { get; set; } + + [Column("address")] + [Length(500)] + public string ServerAddress { get; set; } + + [Column("computerName")] + [Length(255)] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_computerName")] // server identity is unique + public string ServerIdentity { get; set; } + + [Column("registeredDate")] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime DateRegistered { get; set; } + + [Column("lastNotifiedDate")] + public DateTime DateAccessed { get; set; } + + [Column("isActive")] + [Index(IndexTypes.NonClustered)] + public bool IsActive { get; set; } + + [Column("isMaster")] + public bool IsMaster { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs b/src/Umbraco.Core/Persistence/Dtos/StylesheetDto.cs similarity index 87% rename from src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs rename to src/Umbraco.Core/Persistence/Dtos/StylesheetDto.cs index bbc63d950b..f340d34d16 100644 --- a/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/StylesheetDto.cs @@ -1,24 +1,24 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [Obsolete("This is no longer used and will be removed from Umbraco in future versions")] - internal class StylesheetDto - { - [Column("nodeId")] - [PrimaryKeyColumn(AutoIncrement = false)] - [ForeignKey(typeof(NodeDto))] - public int NodeId { get; set; } - - [Column("filename")] - [Length(100)] - public string Filename { get; set; } - - [Column("content")] - [SpecialDbType(SpecialDbTypes.NTEXT)] - [NullSetting(NullSetting = NullSettings.Null)] - public string Content { get; set; } - } -} \ No newline at end of file +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [Obsolete("This is no longer used and will be removed from Umbraco in future versions")] + internal class StylesheetDto + { + [Column("nodeId")] + [PrimaryKeyColumn(AutoIncrement = false)] + [ForeignKey(typeof(NodeDto))] + public int NodeId { get; set; } + + [Column("filename")] + [Length(100)] + public string Filename { get; set; } + + [Column("content")] + [SpecialDbType(SpecialDbTypes.NTEXT)] + [NullSetting(NullSetting = NullSettings.Null)] + public string Content { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs b/src/Umbraco.Core/Persistence/Dtos/StylesheetPropertyDto.cs similarity index 89% rename from src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs rename to src/Umbraco.Core/Persistence/Dtos/StylesheetPropertyDto.cs index bff327d286..5ef1aed9cc 100644 --- a/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/StylesheetPropertyDto.cs @@ -1,28 +1,28 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [Obsolete("This is no longer used and will be removed from Umbraco in future versions")] - internal class StylesheetPropertyDto - { - [Column("nodeId")] - [PrimaryKeyColumn(AutoIncrement = false)] - public int NodeId { get; set; } - - [Column("stylesheetPropertyEditor")] - [NullSetting(NullSetting = NullSettings.Null)] - public bool? Editor { get; set; } - - [Column("stylesheetPropertyAlias")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(50)] - public string Alias { get; set; } - - [Column("stylesheetPropertyValue")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(400)] - public string Value { get; set; } - } -} \ No newline at end of file +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [Obsolete("This is no longer used and will be removed from Umbraco in future versions")] + internal class StylesheetPropertyDto + { + [Column("nodeId")] + [PrimaryKeyColumn(AutoIncrement = false)] + public int NodeId { get; set; } + + [Column("stylesheetPropertyEditor")] + [NullSetting(NullSetting = NullSettings.Null)] + public bool? Editor { get; set; } + + [Column("stylesheetPropertyAlias")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(50)] + public string Alias { get; set; } + + [Column("stylesheetPropertyValue")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(400)] + public string Value { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/TagDto.cs b/src/Umbraco.Core/Persistence/Dtos/TagDto.cs new file mode 100644 index 0000000000..15c309d9e5 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/TagDto.cs @@ -0,0 +1,32 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Tag)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class TagDto + { + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("group")] + [Length(100)] + public string Group { get; set; } + + [Column("tag")] + [Length(200)] + [Index(IndexTypes.UniqueNonClustered, ForColumns = "group,tag", Name = "IX_cmsTags")] + public string Text { get; set; } + + //[Column("key")] + //[Length(301)] // de-normalized "{group}/{tag}" + //public string Key { get; set; } + + // queries result column + [ResultColumn("NodeCount")] + public int NodeCount { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/TagRelationshipDto.cs b/src/Umbraco.Core/Persistence/Dtos/TagRelationshipDto.cs similarity index 79% rename from src/Umbraco.Core/Models/Rdbms/TagRelationshipDto.cs rename to src/Umbraco.Core/Persistence/Dtos/TagRelationshipDto.cs index 4fc936713a..4a07b16a07 100644 --- a/src/Umbraco.Core/Models/Rdbms/TagRelationshipDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/TagRelationshipDto.cs @@ -1,24 +1,24 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsTagRelationship")] - [PrimaryKey("nodeId", autoIncrement = false)] - [ExplicitColumns] - internal class TagRelationshipDto - { - [Column("nodeId")] - [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsTagRelationship", OnColumns = "nodeId, propertyTypeId, tagId")] - [ForeignKey(typeof(ContentDto), Name = "FK_cmsTagRelationship_cmsContent", Column = "nodeId")] - public int NodeId { get; set; } - - [Column("tagId")] - [ForeignKey(typeof(TagDto))] - public int TagId { get; set; } - - [Column("propertyTypeId")] - [ForeignKey(typeof(PropertyTypeDto), Name = "FK_cmsTagRelationship_cmsPropertyType")] - public int PropertyTypeId { get; set; } - } -} \ No newline at end of file +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.TagRelationship)] + [PrimaryKey("nodeId", AutoIncrement = false)] + [ExplicitColumns] + internal class TagRelationshipDto + { + [Column("nodeId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsTagRelationship", OnColumns = "nodeId, propertyTypeId, tagId")] + [ForeignKey(typeof(ContentDto), Name = "FK_cmsTagRelationship_cmsContent", Column = "nodeId")] + public int NodeId { get; set; } + + [Column("tagId")] + [ForeignKey(typeof(TagDto))] + public int TagId { get; set; } + + [Column("propertyTypeId")] + [ForeignKey(typeof(PropertyTypeDto), Name = "FK_cmsTagRelationship_cmsPropertyType")] + public int PropertyTypeId { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/TaskDto.cs b/src/Umbraco.Core/Persistence/Dtos/TaskDto.cs similarity index 87% rename from src/Umbraco.Core/Models/Rdbms/TaskDto.cs rename to src/Umbraco.Core/Persistence/Dtos/TaskDto.cs index e27f7c0a93..9531ffb7b9 100644 --- a/src/Umbraco.Core/Models/Rdbms/TaskDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/TaskDto.cs @@ -1,49 +1,50 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsTask")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class TaskDto - { - [Column("closed")] - [Constraint(Default = "0")] - public bool Closed { get; set; } - - [Column("id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - [Column("taskTypeId")] - [ForeignKey(typeof(TaskTypeDto))] - public byte TaskTypeId { get; set; } - - [Column("nodeId")] - [ForeignKey(typeof(NodeDto))] - public int NodeId { get; set; } - - [Column("parentUserId")] - [ForeignKey(typeof(UserDto), Name = "FK_cmsTask_umbracoUser")] - public int ParentUserId { get; set; } - - [Column("userId")] - [ForeignKey(typeof(UserDto), Name = "FK_cmsTask_umbracoUser1")] - public int UserId { get; set; } - - [Column("DateTime")] - [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime DateTime { get; set; } - - [Column("Comment")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(500)] - public string Comment { get; set; } - - [ResultColumn] - public TaskTypeDto TaskTypeDto { get; set; } - } -} \ No newline at end of file +using System; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Task)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class TaskDto + { + [Column("closed")] + [Constraint(Default = "0")] + public bool Closed { get; set; } + + [Column("id")] + [PrimaryKeyColumn] + public int Id { get; set; } + + [Column("taskTypeId")] + [ForeignKey(typeof(TaskTypeDto))] + public byte TaskTypeId { get; set; } + + [Column("nodeId")] + [ForeignKey(typeof(NodeDto))] + public int NodeId { get; set; } + + [Column("parentUserId")] + [ForeignKey(typeof(UserDto), Name = "FK_cmsTask_umbracoUser")] + public int ParentUserId { get; set; } + + [Column("userId")] + [ForeignKey(typeof(UserDto), Name = "FK_cmsTask_umbracoUser1")] + public int UserId { get; set; } + + [Column("DateTime")] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime DateTime { get; set; } + + [Column("Comment")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(500)] + public string Comment { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "TaskTypeId")] + public TaskTypeDto TaskTypeDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/TaskTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/TaskTypeDto.cs new file mode 100644 index 0000000000..ca731c29c9 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/TaskTypeDto.cs @@ -0,0 +1,19 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.TaskType)] + [PrimaryKey("id")] + [ExplicitColumns] + internal class TaskTypeDto + { + [Column("id")] + [PrimaryKeyColumn(IdentitySeed = 2)] + public byte Id { get; set; } + + [Column("alias")] + [Index(IndexTypes.NonClustered)] + public string Alias { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs b/src/Umbraco.Core/Persistence/Dtos/TemplateDto.cs similarity index 76% rename from src/Umbraco.Core/Models/Rdbms/TemplateDto.cs rename to src/Umbraco.Core/Persistence/Dtos/TemplateDto.cs index 8e2fe0b9f5..2f0d149ee7 100644 --- a/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/TemplateDto.cs @@ -1,32 +1,33 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("cmsTemplate")] - [PrimaryKey("pk")] - [ExplicitColumns] - internal class TemplateDto - { - [Column("pk")] - [PrimaryKeyColumn] - public int PrimaryKey { get; set; } - - [Column("nodeId")] - [Index(IndexTypes.UniqueNonClustered)] - [ForeignKey(typeof(NodeDto), Name = "FK_cmsTemplate_umbracoNode")] - public int NodeId { get; set; } - - [Column("alias")] - [Length(100)] - [NullSetting(NullSetting = NullSettings.Null)] - public string Alias { get; set; } - - [Column("design")] - [SpecialDbType(SpecialDbTypes.NTEXT)] - public string Design { get; set; } - - [ResultColumn] - public NodeDto NodeDto { get; set; } - } -} \ No newline at end of file +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.Template)] + [PrimaryKey("pk")] + [ExplicitColumns] + internal class TemplateDto + { + [Column("pk")] + [PrimaryKeyColumn] + public int PrimaryKey { get; set; } + + [Column("nodeId")] + [Index(IndexTypes.UniqueNonClustered)] + [ForeignKey(typeof(NodeDto), Name = "FK_cmsTemplate_umbracoNode")] + public int NodeId { get; set; } + + [Column("alias")] + [Length(100)] + [NullSetting(NullSetting = NullSettings.Null)] + public string Alias { get; set; } + + [Column("design")] + [SpecialDbType(SpecialDbTypes.NTEXT)] + public string Design { get; set; } + + [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "NodeId")] + public NodeDto NodeDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/User2NodeNotifyDto.cs b/src/Umbraco.Core/Persistence/Dtos/User2NodeNotifyDto.cs similarity index 76% rename from src/Umbraco.Core/Models/Rdbms/User2NodeNotifyDto.cs rename to src/Umbraco.Core/Persistence/Dtos/User2NodeNotifyDto.cs index 298ad920d9..d7c54351b0 100644 --- a/src/Umbraco.Core/Models/Rdbms/User2NodeNotifyDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/User2NodeNotifyDto.cs @@ -1,25 +1,25 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoUser2NodeNotify")] - [PrimaryKey("userId", autoIncrement = false)] - [ExplicitColumns] - internal class User2NodeNotifyDto - { - [Column("userId")] - [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_umbracoUser2NodeNotify", OnColumns = "userId, nodeId, action")] - [ForeignKey(typeof(UserDto))] - public int UserId { get; set; } - - [Column("nodeId")] - [ForeignKey(typeof(NodeDto))] - public int NodeId { get; set; } - - [Column("action")] - [SpecialDbType(SpecialDbTypes.NCHAR)] - [Length(1)] - public string Action { get; set; } - } -} \ No newline at end of file +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.User2NodeNotify)] + [PrimaryKey("userId", AutoIncrement = false)] + [ExplicitColumns] + internal class User2NodeNotifyDto + { + [Column("userId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_umbracoUser2NodeNotify", OnColumns = "userId, nodeId, action")] + [ForeignKey(typeof(UserDto))] + public int UserId { get; set; } + + [Column("nodeId")] + [ForeignKey(typeof(NodeDto))] + public int NodeId { get; set; } + + [Column("action")] + [SpecialDbType(SpecialDbTypes.NCHAR)] + [Length(1)] + public string Action { get; set; } + } +} diff --git a/src/Umbraco.Core/Persistence/Dtos/User2UserGroupDto.cs b/src/Umbraco.Core/Persistence/Dtos/User2UserGroupDto.cs new file mode 100644 index 0000000000..6705153ffb --- /dev/null +++ b/src/Umbraco.Core/Persistence/Dtos/User2UserGroupDto.cs @@ -0,0 +1,37 @@ +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.User2UserGroup)] + [ExplicitColumns] + internal class User2UserGroupDto + { + [Column("userId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_user2userGroup", OnColumns = "userId, userGroupId")] + [ForeignKey(typeof(UserDto))] + public int UserId { get; set; } + + [Column("userGroupId")] + [ForeignKey(typeof(UserGroupDto))] + public int UserGroupId { get; set; } + } + + [TableName(Constants.DatabaseSchema.Tables.User2UserGroup)] + [ExplicitColumns] + internal class User2UserGroupReadOnlyDto + { + [Column("userId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_user2userGroup", OnColumns = "userId, userGroupId")] + [ForeignKey(typeof(UserDto))] + public int UserId { get; set; } + + [Column("userGroupId")] + [ForeignKey(typeof(UserGroupDto))] + public int UserGroupId { get; set; } + + [ResultColumn] + [Reference(ReferenceType.Foreign)] // fixme + public UserGroupDto UserGroupDto { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/UserDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserDto.cs similarity index 92% rename from src/Umbraco.Core/Models/Rdbms/UserDto.cs rename to src/Umbraco.Core/Persistence/Dtos/UserDto.cs index 44cc520998..c6cc889ab0 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/UserDto.cs @@ -1,121 +1,123 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoUser")] - [PrimaryKey("id", autoIncrement = true)] - [ExplicitColumns] - internal class UserDto - { - public UserDto() - { - UserGroupDtos = new List(); - UserStartNodeDtos = new HashSet(); - } - - [Column("id")] - [PrimaryKeyColumn(Name = "PK_user")] - public int Id { get; set; } - - [Column("userDisabled")] - [Constraint(Default = "0")] - public bool Disabled { get; set; } - - [Column("userNoConsole")] - [Constraint(Default = "0")] - public bool NoConsole { get; set; } - - [Column("userName")] - public string UserName { get; set; } - - [Column("userLogin")] - [Length(125)] - [Index(IndexTypes.NonClustered)] - public string Login { get; set; } - - [Column("userPassword")] - [Length(500)] - public string Password { get; set; } - - /// - /// This will represent a JSON structure of how the password has been created (i.e hash algorithm, iterations) - /// - [Column("passwordConfig")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(500)] - public string PasswordConfig { get; set; } - - [Column("userEmail")] - public string Email { get; set; } - - [Column("userLanguage")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(10)] - public string UserLanguage { get; set; } - - [Column("securityStampToken")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(255)] - public string SecurityStampToken { get; set; } - - [Column("failedLoginAttempts")] - [NullSetting(NullSetting = NullSettings.Null)] - public int? FailedLoginAttempts { get; set; } - - [Column("lastLockoutDate")] - [NullSetting(NullSetting = NullSettings.Null)] - public DateTime? LastLockoutDate { get; set; } - - [Column("lastPasswordChangeDate")] - [NullSetting(NullSetting = NullSettings.Null)] - public DateTime? LastPasswordChangeDate { get; set; } - - [Column("lastLoginDate")] - [NullSetting(NullSetting = NullSettings.Null)] - public DateTime? LastLoginDate { get; set; } - - [Column("emailConfirmedDate")] - [NullSetting(NullSetting = NullSettings.Null)] - public DateTime? EmailConfirmedDate { get; set; } - - [Column("invitedDate")] - [NullSetting(NullSetting = NullSettings.Null)] - public DateTime? InvitedDate { get; set; } - - [Column("createDate")] - [NullSetting(NullSetting = NullSettings.NotNull)] - [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime CreateDate { get; set; } - - [Column("updateDate")] - [NullSetting(NullSetting = NullSettings.NotNull)] - [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime UpdateDate { get; set; } - - /// - /// Will hold the media file system relative path of the users custom avatar if they uploaded one - /// - [Column("avatar")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(500)] - public string Avatar { get; set; } +using System; +using System.Collections.Generic; +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; - /// - /// A Json blob stored for recording tour data for a user - /// - [Column("tourData")] - [NullSetting(NullSetting = NullSettings.Null)] - [SpecialDbType(SpecialDbTypes.NTEXT)] - public string TourData { get; set; } - - [ResultColumn] - public List UserGroupDtos { get; set; } - - [ResultColumn] - public HashSet UserStartNodeDtos { get; set; } - } -} \ No newline at end of file +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.User)] + [PrimaryKey("id", AutoIncrement = true)] + [ExplicitColumns] + internal class UserDto + { + public UserDto() + { + UserGroupDtos = new List(); + UserStartNodeDtos = new HashSet(); + } + + [Column("id")] + [PrimaryKeyColumn(Name = "PK_user")] + public int Id { get; set; } + + [Column("userDisabled")] + [Constraint(Default = "0")] + public bool Disabled { get; set; } + + [Column("userNoConsole")] + [Constraint(Default = "0")] + public bool NoConsole { get; set; } + + [Column("userName")] + public string UserName { get; set; } + + [Column("userLogin")] + [Length(125)] + [Index(IndexTypes.NonClustered)] + public string Login { get; set; } + + [Column("userPassword")] + [Length(500)] + public string Password { get; set; } + + /// + /// This will represent a JSON structure of how the password has been created (i.e hash algorithm, iterations) + /// + [Column("passwordConfig")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(500)] + public string PasswordConfig { get; set; } + + [Column("userEmail")] + public string Email { get; set; } + + [Column("userLanguage")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(10)] + public string UserLanguage { get; set; } + + [Column("securityStampToken")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(255)] + public string SecurityStampToken { get; set; } + + [Column("failedLoginAttempts")] + [NullSetting(NullSetting = NullSettings.Null)] + public int? FailedLoginAttempts { get; set; } + + [Column("lastLockoutDate")] + [NullSetting(NullSetting = NullSettings.Null)] + public DateTime? LastLockoutDate { get; set; } + + [Column("lastPasswordChangeDate")] + [NullSetting(NullSetting = NullSettings.Null)] + public DateTime? LastPasswordChangeDate { get; set; } + + [Column("lastLoginDate")] + [NullSetting(NullSetting = NullSettings.Null)] + public DateTime? LastLoginDate { get; set; } + + [Column("emailConfirmedDate")] + [NullSetting(NullSetting = NullSettings.Null)] + public DateTime? EmailConfirmedDate { get; set; } + + [Column("invitedDate")] + [NullSetting(NullSetting = NullSettings.Null)] + public DateTime? InvitedDate { get; set; } + + [Column("createDate")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime CreateDate { get; set; } + + [Column("updateDate")] + [NullSetting(NullSetting = NullSettings.NotNull)] + [Constraint(Default = SystemMethods.CurrentDateTime)] + public DateTime UpdateDate { get; set; } + + /// + /// Will hold the media file system relative path of the users custom avatar if they uploaded one + /// + [Column("avatar")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(500)] + public string Avatar { get; set; } + + /// + /// A Json blob stored for recording tour data for a user + /// + [Column("tourData")] + [NullSetting(NullSetting = NullSettings.Null)] + [SpecialDbType(SpecialDbTypes.NTEXT)] + public string TourData { get; set; } + + [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "UserId")] + public List UserGroupDtos { get; set; } + + [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "UserId")] + public HashSet UserStartNodeDtos { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroup2AppDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserGroup2AppDto.cs similarity index 78% rename from src/Umbraco.Core/Models/Rdbms/UserGroup2AppDto.cs rename to src/Umbraco.Core/Persistence/Dtos/UserGroup2AppDto.cs index 14fc60e197..c0ac48b1c5 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserGroup2AppDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/UserGroup2AppDto.cs @@ -1,9 +1,9 @@ -using Umbraco.Core.Persistence; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; -namespace Umbraco.Core.Models.Rdbms +namespace Umbraco.Core.Persistence.Dtos { - [TableName("umbracoUserGroup2App")] + [TableName(Constants.DatabaseSchema.Tables.UserGroup2App)] [ExplicitColumns] internal class UserGroup2AppDto { @@ -16,4 +16,4 @@ namespace Umbraco.Core.Models.Rdbms [Length(50)] public string AppAlias { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroup2NodePermissionDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserGroup2NodePermissionDto.cs similarity index 82% rename from src/Umbraco.Core/Models/Rdbms/UserGroup2NodePermissionDto.cs rename to src/Umbraco.Core/Persistence/Dtos/UserGroup2NodePermissionDto.cs index 8c75dcf4f7..bcb2034054 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserGroup2NodePermissionDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/UserGroup2NodePermissionDto.cs @@ -1,23 +1,23 @@ -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseAnnotations; - -namespace Umbraco.Core.Models.Rdbms -{ - [TableName("umbracoUserGroup2NodePermission")] - [ExplicitColumns] - internal class UserGroup2NodePermissionDto - { - [Column("userGroupId")] - [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_umbracoUserGroup2NodePermission", OnColumns = "userGroupId, nodeId, permission")] - [ForeignKey(typeof(UserGroupDto))] - public int UserGroupId { get; set; } - - [Column("nodeId")] - [ForeignKey(typeof(NodeDto))] - [Index(IndexTypes.NonClustered, Name = "IX_umbracoUser2NodePermission_nodeId")] - public int NodeId { get; set; } - - [Column("permission")] - public string Permission { get; set; } - } -} \ No newline at end of file +using NPoco; +using Umbraco.Core.Persistence.DatabaseAnnotations; + +namespace Umbraco.Core.Persistence.Dtos +{ + [TableName(Constants.DatabaseSchema.Tables.UserGroup2NodePermission)] + [ExplicitColumns] + internal class UserGroup2NodePermissionDto + { + [Column("userGroupId")] + [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_umbracoUserGroup2NodePermission", OnColumns = "userGroupId, nodeId, permission")] + [ForeignKey(typeof(UserGroupDto))] + public int UserGroupId { get; set; } + + [Column("nodeId")] + [ForeignKey(typeof(NodeDto))] + [Index(IndexTypes.NonClustered, Name = "IX_umbracoUser2NodePermission_nodeId")] + public int NodeId { get; set; } + + [Column("permission")] + public string Permission { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserGroupDto.cs similarity index 90% rename from src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs rename to src/Umbraco.Core/Persistence/Dtos/UserGroupDto.cs index f6bb09066d..3383ed9e3d 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/UserGroupDto.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; -using Umbraco.Core.Persistence; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -namespace Umbraco.Core.Models.Rdbms +namespace Umbraco.Core.Persistence.Dtos { - [TableName("umbracoUserGroup")] + [TableName(Constants.DatabaseSchema.Tables.UserGroup)] [PrimaryKey("id")] [ExplicitColumns] internal class UserGroupDto @@ -17,7 +17,7 @@ namespace Umbraco.Core.Models.Rdbms } [Column("id")] - [PrimaryKeyColumn(IdentitySeed = 5)] + [PrimaryKeyColumn(IdentitySeed = 6)] public int Id { get; set; } [Column("userGroupAlias")] @@ -60,6 +60,7 @@ namespace Umbraco.Core.Models.Rdbms public int? StartMediaId { get; set; } [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "UserGroupId")] public List UserGroup2AppDtos { get; set; } /// @@ -68,4 +69,4 @@ namespace Umbraco.Core.Models.Rdbms [ResultColumn] public int UserCount { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Rdbms/UserLoginDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs similarity index 84% rename from src/Umbraco.Core/Models/Rdbms/UserLoginDto.cs rename to src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs index 0aee639197..86d306b06a 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserLoginDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs @@ -1,11 +1,11 @@ using System; -using Umbraco.Core.Persistence; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; -namespace Umbraco.Core.Models.Rdbms +namespace Umbraco.Core.Persistence.Dtos { - [TableName("umbracoUserLogin")] - [PrimaryKey("sessionId", autoIncrement = false)] + [TableName(Constants.DatabaseSchema.Tables.UserLogin)] + [PrimaryKey("sessionId", AutoIncrement = false)] [ExplicitColumns] internal class UserLoginDto { @@ -14,7 +14,7 @@ namespace Umbraco.Core.Models.Rdbms public Guid SessionId { get; set; } [Column("userId")] - [ForeignKey(typeof(UserDto), Name = "FK_umbracoUserLogin_umbracoUser_id")] + [ForeignKey(typeof(UserDto), Name = "FK_" + Constants.DatabaseSchema.Tables.UserLogin + "_umbracoUser_id")] public int UserId { get; set; } /// @@ -49,4 +49,4 @@ namespace Umbraco.Core.Models.Rdbms [NullSetting(NullSetting = NullSettings.Null)] public string IpAddress { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserStartNodeDto.cs similarity index 89% rename from src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs rename to src/Umbraco.Core/Persistence/Dtos/UserStartNodeDto.cs index fa8af80759..d8b2206658 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserStartNodeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/UserStartNodeDto.cs @@ -1,11 +1,11 @@ using System; -using Umbraco.Core.Persistence; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; -namespace Umbraco.Core.Models.Rdbms +namespace Umbraco.Core.Persistence.Dtos { - [TableName("umbracoUserStartNode")] - [PrimaryKey("id", autoIncrement = true)] + [TableName(Constants.DatabaseSchema.Tables.UserStartNode)] + [PrimaryKey("id", AutoIncrement = true)] [ExplicitColumns] internal class UserStartNodeDto : IEquatable { @@ -45,7 +45,7 @@ namespace Umbraco.Core.Models.Rdbms { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; + if (obj.GetType() != GetType()) return false; return Equals((UserStartNodeDto) obj); } @@ -64,4 +64,4 @@ namespace Umbraco.Core.Models.Rdbms return !Equals(left, right); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/EntityNotFoundException.cs b/src/Umbraco.Core/Persistence/EntityNotFoundException.cs index 1cc66a2ba2..49e85046a0 100644 --- a/src/Umbraco.Core/Persistence/EntityNotFoundException.cs +++ b/src/Umbraco.Core/Persistence/EntityNotFoundException.cs @@ -39,7 +39,7 @@ namespace Umbraco.Core.Persistence if (Id != null) { - return "Umbraco entity (id: " + Id + ") not found. " + result; + return "Umbraco entity (id: " + Id + ") not found. " + result; } return result; diff --git a/src/Umbraco.Core/Persistence/Factories/AuditEntryFactory.cs b/src/Umbraco.Core/Persistence/Factories/AuditEntryFactory.cs new file mode 100644 index 0000000000..bbf6058055 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/AuditEntryFactory.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal static class AuditEntryFactory + { + public static IEnumerable BuildEntities(IEnumerable dtos) + { + return dtos.Select(BuildEntity).ToList(); + } + + public static IAuditEntry BuildEntity(AuditEntryDto dto) + { + var entity = new AuditEntry + { + Id = dto.Id, + PerformingUserId = dto.PerformingUserId, + PerformingDetails = dto.PerformingDetails, + PerformingIp = dto.PerformingIp, + EventDateUtc = dto.EventDateUtc, + AffectedUserId = dto.AffectedUserId, + AffectedDetails = dto.AffectedDetails, + EventType = dto.EventType, + EventDetails = dto.EventDetails + }; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + entity.ResetDirtyProperties(false); + return entity; + } + + public static AuditEntryDto BuildDto(IAuditEntry entity) + { + return new AuditEntryDto + { + Id = entity.Id, + PerformingUserId = entity.PerformingUserId, + PerformingDetails = entity.PerformingDetails, + PerformingIp = entity.PerformingIp, + EventDateUtc = entity.EventDateUtc, + AffectedUserId = entity.AffectedUserId, + AffectedDetails = entity.AffectedDetails, + EventType = entity.EventType, + EventDetails = entity.EventDetails + }; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/ConsentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ConsentFactory.cs new file mode 100644 index 0000000000..5c3b90fee8 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/ConsentFactory.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal static class ConsentFactory + { + public static IEnumerable BuildEntities(IEnumerable dtos) + { + var ix = new Dictionary(); + var output = new List(); + + foreach (var dto in dtos) + { + var k = dto.Source + "::" + dto.Context + "::" + dto.Action; + + var consent = new Consent + { + Id = dto.Id, + Current = dto.Current, + CreateDate = dto.CreateDate, + Source = dto.Source, + Context = dto.Context, + Action = dto.Action, + State = (ConsentState) dto.State, // assume value is valid + Comment = dto.Comment + }; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + consent.ResetDirtyProperties(false); + + if (ix.TryGetValue(k, out var current)) + { + if (current.HistoryInternal == null) + current.HistoryInternal = new List(); + current.HistoryInternal.Add(consent); + } + else + { + ix[k] = consent; + output.Add(consent); + } + } + + return output; + } + + public static ConsentDto BuildDto(IConsent entity) + { + return new ConsentDto + { + Id = entity.Id, + Current = entity.Current, + CreateDate = entity.CreateDate, + Source = entity.Source, + Context = entity.Context, + Action = entity.Action, + State = (int) entity.State, + Comment = entity.Comment + }; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs new file mode 100644 index 0000000000..b1a0a35432 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -0,0 +1,319 @@ +using System; +using System.Text.RegularExpressions; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class ContentBaseFactory + { + private static readonly Regex MediaPathPattern = new Regex(@"(/media/.+?)(?:['""]|$)", RegexOptions.Compiled); + + /// + /// Builds an IContent item from a dto and content type. + /// + public static Content BuildEntity(DocumentDto dto, IContentType contentType) + { + var contentDto = dto.ContentDto; + var nodeDto = contentDto.NodeDto; + var documentVersionDto = dto.DocumentVersionDto; + var contentVersionDto = documentVersionDto.ContentVersionDto; + var publishedVersionDto = dto.PublishedVersionDto; + + var content = new Content(nodeDto.Text, nodeDto.ParentId, contentType); + + try + { + content.DisableChangeTracking(); + + content.Id = dto.NodeId; + content.Key = nodeDto.UniqueId; + content.VersionId = contentVersionDto.Id; + + content.Name = contentVersionDto.Text; + content.NodeName = contentVersionDto.Text; + + content.Path = nodeDto.Path; + content.Level = nodeDto.Level; + content.ParentId = nodeDto.ParentId; + content.SortOrder = nodeDto.SortOrder; + content.Trashed = nodeDto.Trashed; + + content.CreatorId = nodeDto.UserId ?? 0; + content.WriterId = contentVersionDto.UserId; + content.CreateDate = nodeDto.CreateDate; + content.UpdateDate = contentVersionDto.VersionDate; + + content.Published = dto.Published; + content.Edited = dto.Edited; + content.ExpireDate = dto.ExpiresDate; + content.ReleaseDate = dto.ReleaseDate; + + // fixme - shall we get published infos or not? + //if (dto.Published) + if (publishedVersionDto != null) + { + content.PublishedVersionId = publishedVersionDto.Id; + content.PublishDate = publishedVersionDto.ContentVersionDto.VersionDate; + content.PublishName = publishedVersionDto.ContentVersionDto.Text; + content.PublisherId = publishedVersionDto.ContentVersionDto.UserId; + } + + // templates = ignored, managed by the repository + + // reset dirty initial properties (U4-1946) + content.ResetDirtyProperties(false); + return content; + } + finally + { + content.EnableChangeTracking(); + } + } + + /// + /// Builds an IMedia item from a dto and content type. + /// + public static Models.Media BuildEntity(ContentDto dto, IMediaType contentType) + { + var nodeDto = dto.NodeDto; + var contentVersionDto = dto.ContentVersionDto; + + var content = new Models.Media(nodeDto.Text, nodeDto.ParentId, contentType); + + try + { + content.DisableChangeTracking(); + + content.Id = dto.NodeId; + content.Key = nodeDto.UniqueId; + content.VersionId = contentVersionDto.Id; + + // fixme missing names? + + content.Path = nodeDto.Path; + content.Level = nodeDto.Level; + content.ParentId = nodeDto.ParentId; + content.SortOrder = nodeDto.SortOrder; + content.Trashed = nodeDto.Trashed; + + content.CreatorId = nodeDto.UserId ?? 0; + content.WriterId = contentVersionDto.UserId; + content.CreateDate = nodeDto.CreateDate; + content.UpdateDate = contentVersionDto.VersionDate; + + // reset dirty initial properties (U4-1946) + content.ResetDirtyProperties(false); + return content; + } + finally + { + content.EnableChangeTracking(); + } + } + + /// + /// Builds an IMedia item from a dto and content type. + /// + public static Member BuildEntity(MemberDto dto, IMemberType contentType) + { + var nodeDto = dto.ContentDto.NodeDto; + var contentVersionDto = dto.ContentVersionDto; + + var content = new Member(nodeDto.Text, dto.Email, dto.LoginName, dto.Password, contentType); + + try + { + content.DisableChangeTracking(); + + content.Id = dto.NodeId; + content.Key = nodeDto.UniqueId; + content.VersionId = contentVersionDto.Id; + + // fixme missing names? + + content.Path = nodeDto.Path; + content.Level = nodeDto.Level; + content.ParentId = nodeDto.ParentId; + content.SortOrder = nodeDto.SortOrder; + content.Trashed = nodeDto.Trashed; + + content.CreatorId = nodeDto.UserId ?? 0; + content.WriterId = contentVersionDto.UserId; + content.CreateDate = nodeDto.CreateDate; + content.UpdateDate = contentVersionDto.VersionDate; + + content.ProviderUserKey = content.Key; // fixme explain + + // reset dirty initial properties (U4-1946) + content.ResetDirtyProperties(false); + return content; + } + finally + { + content.EnableChangeTracking(); + } + } + + /// + /// Buils a dto from an IContent item. + /// + public static DocumentDto BuildDto(IContent entity, Guid objectType) + { + var contentBase = (Content) entity; + var contentDto = BuildContentDto(contentBase, objectType); + + var dto = new DocumentDto + { + NodeId = entity.Id, + Published = entity.Published, + ReleaseDate = entity.ReleaseDate, + ExpiresDate = entity.ExpireDate, + + ContentDto = contentDto, + DocumentVersionDto = BuildDocumentVersionDto(contentBase, contentDto) + }; + + return dto; + } + + /// + /// Buils a dto from an IMedia item. + /// + public static MediaDto BuildDto(IMedia entity) + { + var contentBase = (Models.Media) entity; + var contentDto = BuildContentDto(contentBase, Constants.ObjectTypes.Media); + + var dto = new MediaDto + { + NodeId = entity.Id, + ContentDto = contentDto, + MediaVersionDto = BuildMediaVersionDto(contentBase, contentDto) + }; + + return dto; + } + + /// + /// Buils a dto from an IMember item. + /// + public static MemberDto BuildDto(IMember entity) + { + var member = (Member) entity; + var contentDto = BuildContentDto(member, Constants.ObjectTypes.Member); + + var dto = new MemberDto + { + Email = entity.Email, + LoginName = entity.Username, + NodeId = entity.Id, + Password = entity.RawPasswordValue, + + ContentDto = contentDto, + ContentVersionDto = BuildContentVersionDto(member, contentDto) + }; + return dto; + } + + private static ContentDto BuildContentDto(ContentBase entity, Guid objectType) + { + var dto = new ContentDto + { + NodeId = entity.Id, + ContentTypeId = entity.ContentTypeId, + + NodeDto = BuildNodeDto(entity, objectType) + }; + + return dto; + } + + private static NodeDto BuildNodeDto(ContentBase entity, Guid objectType) + { + var dto = new NodeDto + { + NodeId = entity.Id, + UniqueId = entity.Key, + ParentId = entity.ParentId, + Level = Convert.ToInt16(entity.Level), + Path = entity.Path, + SortOrder = entity.SortOrder, + Trashed = entity.Trashed, + UserId = entity.CreatorId, + Text = entity.Name, + NodeObjectType = objectType, + CreateDate = entity.CreateDate + }; + + return dto; + } + + // always build the current / VersionPk dto + // we're never going to build / save old versions (which are immutable) + private static ContentVersionDto BuildContentVersionDto(ContentBase entity, ContentDto contentDto) + { + var dto = new ContentVersionDto + { + Id = entity.VersionId, + NodeId = entity.Id, + VersionDate = entity.UpdateDate, + UserId = entity.WriterId, + Current = true, // always building the current one + Text = entity.Name, + + ContentDto = contentDto + }; + + return dto; + } + + // always build the current / VersionPk dto + // we're never going to build / save old versions (which are immutable) + private static DocumentVersionDto BuildDocumentVersionDto(Content entity, ContentDto contentDto) + { + var dto = new DocumentVersionDto + { + Id = entity.VersionId, + TemplateId = entity.Template?.Id, + Published = false, // always building the current, unpublished one + + ContentVersionDto = BuildContentVersionDto(entity, contentDto) + }; + + return dto; + } + + private static MediaVersionDto BuildMediaVersionDto(Models.Media entity, ContentDto contentDto) + { + // try to get a path from the string being stored for media + // fixme - only considering umbracoFile ?! + + TryMatch(entity.GetValue("umbracoFile"), out var path); + + var dto = new MediaVersionDto + { + Id = entity.VersionId, + Path = path, + + ContentVersionDto = BuildContentVersionDto(entity, contentDto) + }; + + return dto; + } + + // fixme - this should NOT be here?! + // more dark magic ;-( + internal static bool TryMatch(string text, out string path) + { + path = null; + if (string.IsNullOrWhiteSpace(text)) return false; + + var m = MediaPathPattern.Match(text); + if (!m.Success || m.Groups.Count != 2) return false; + + path = m.Groups[1].Value; + return true; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs deleted file mode 100644 index f5df0c7c55..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class ContentFactory - { - private readonly IContentType _contentType; - private readonly Guid _nodeObjectTypeId; - private readonly int _id; - private int _primaryKey; - - public ContentFactory(IContentType contentType, Guid nodeObjectTypeId, int id) - { - _contentType = contentType; - _nodeObjectTypeId = nodeObjectTypeId; - _id = id; - } - - public ContentFactory(Guid nodeObjectTypeId, int id) - { - _nodeObjectTypeId = nodeObjectTypeId; - _id = id; - } - - #region Implementation of IEntityFactory - - /// - /// Builds a IContent item from the dto(s) and content type - /// - /// - /// This DTO can contain all of the information to build an IContent item, however in cases where multiple entities are being built, - /// a separate publishedDto entity will be supplied in place of the 's own - /// ResultColumn DocumentPublishedReadOnlyDto - /// - /// - /// - /// When querying for multiple content items the main DTO will not contain the ResultColumn DocumentPublishedReadOnlyDto and a separate publishedDto instance will be supplied - /// - /// - public static IContent BuildEntity(DocumentDto dto, IContentType contentType, DocumentPublishedReadOnlyDto publishedDto = null) - { - var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, contentType); - - try - { - content.DisableChangeTracking(); - - content.Id = dto.NodeId; - content.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId; - content.Name = dto.Text; - content.NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text; - content.Path = dto.ContentVersionDto.ContentDto.NodeDto.Path; - content.CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value; - content.WriterId = dto.WriterUserId; - content.Level = dto.ContentVersionDto.ContentDto.NodeDto.Level; - content.ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId; - content.SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder; - content.Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed; - content.Published = dto.Published; - content.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate; - content.UpdateDate = dto.ContentVersionDto.VersionDate; - content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null; - content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null; - content.Version = dto.ContentVersionDto.VersionId; - - content.PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished; - - //Check if the publishedDto has been supplied, if not the use the dto's own DocumentPublishedReadOnlyDto value - content.PublishedVersionGuid = publishedDto == null - ? (dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId) - : publishedDto.VersionId; - content.PublishedDate = publishedDto == null - ? (dto.DocumentPublishedReadOnlyDto == null ? default(DateTime) : dto.DocumentPublishedReadOnlyDto.VersionDate) - : publishedDto.VersionDate; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - content.ResetDirtyProperties(false); - return content; - } - finally - { - content.EnableChangeTracking(); - } - - } - - [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")] - public IContent BuildEntity(DocumentDto dto) - { - return BuildEntity(dto, _contentType); - } - - public DocumentDto BuildDto(IContent entity) - { - //NOTE Currently doesn't add Alias (legacy that eventually will go away) - var documentDto = new DocumentDto - { - Newest = true, - NodeId = entity.Id, - Published = entity.Published, - Text = entity.Name, - UpdateDate = entity.UpdateDate, - WriterUserId = entity.WriterId, - VersionId = entity.Version, - ExpiresDate = null, - ReleaseDate = null, - ContentVersionDto = BuildContentVersionDto(entity) - }; - - if (entity.Template != null && entity.Template.Id > 0) - documentDto.TemplateId = entity.Template.Id; - - if (entity.ExpireDate.HasValue) - documentDto.ExpiresDate = entity.ExpireDate.Value; - - if (entity.ReleaseDate.HasValue) - documentDto.ReleaseDate = entity.ReleaseDate.Value; - - return documentDto; - } - - #endregion - - public void SetPrimaryKey(int primaryKey) - { - _primaryKey = primaryKey; - } - - private ContentVersionDto BuildContentVersionDto(IContent entity) - { - //TODO: Change this once the Language property is public on IContent - var content = entity as Content; - var lang = content == null ? string.Empty : content.Language; - - var contentVersionDto = new ContentVersionDto - { - NodeId = entity.Id, - VersionDate = entity.UpdateDate, - VersionId = entity.Version, - ContentDto = BuildContentDto(entity) - }; - return contentVersionDto; - } - - private ContentDto BuildContentDto(IContent entity) - { - var contentDto = new ContentDto - { - NodeId = entity.Id, - ContentTypeId = entity.ContentTypeId, - NodeDto = BuildNodeDto(entity) - }; - - if (_primaryKey > 0) - { - contentDto.PrimaryKey = _primaryKey; - } - - return contentDto; - } - - private NodeDto BuildNodeDto(IContent entity) - { - //TODO: Change this once the Language property is public on IContent - var nodeName = entity.Name; - - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectTypeId, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = nodeName, - Trashed = entity.Trashed, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; - - return nodeDto; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 3672f80873..f592bfddcb 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { @@ -14,7 +14,7 @@ namespace Umbraco.Core.Persistence.Factories // IMediaType (media types) // IMemberType (member types) // - internal class ContentTypeFactory + internal class ContentTypeFactory { #region IContentType @@ -28,8 +28,7 @@ namespace Umbraco.Core.Persistence.Factories BuildCommonEntity(contentType, dto); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) contentType.ResetDirtyProperties(false); return contentType; } @@ -52,8 +51,7 @@ namespace Umbraco.Core.Persistence.Factories BuildCommonEntity(contentType, dto); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) contentType.ResetDirtyProperties(false); } finally @@ -84,7 +82,8 @@ namespace Umbraco.Core.Persistence.Factories NodeId = entity.Id, PropertyTypeId = x.Id, CanEdit = memberType.MemberCanEditProperty(x.Alias), - ViewOnProfile = memberType.MemberCanViewProperty(x.Alias) + ViewOnProfile = memberType.MemberCanViewProperty(x.Alias), + IsSensitive = memberType.IsSensitiveProperty(x.Alias) }).ToList(); return dtos; } @@ -110,17 +109,18 @@ namespace Umbraco.Core.Persistence.Factories entity.AllowedAsRoot = dto.AllowAtRoot; entity.IsContainer = dto.IsContainer; entity.Trashed = dto.NodeDto.Trashed; + entity.Variations = (ContentVariation) dto.Variations; } public ContentTypeDto BuildContentTypeDto(IContentTypeBase entity) { Guid nodeObjectType; if (entity is IContentType) - nodeObjectType = Constants.ObjectTypes.DocumentTypeGuid; + nodeObjectType = Constants.ObjectTypes.DocumentType; else if (entity is IMediaType) - nodeObjectType = Constants.ObjectTypes.MediaTypeGuid; + nodeObjectType = Constants.ObjectTypes.MediaType; else if (entity is IMemberType) - nodeObjectType = Constants.ObjectTypes.MemberTypeGuid; + nodeObjectType = Constants.ObjectTypes.MemberType; else throw new Exception("Invalid entity."); @@ -133,6 +133,7 @@ namespace Umbraco.Core.Persistence.Factories NodeId = entity.Id, AllowAtRoot = entity.AllowedAsRoot, IsContainer = entity.IsContainer, + Variations = (byte) entity.Variations, NodeDto = BuildNodeDto(entity, nodeObjectType) }; return contentTypeDto; @@ -159,4 +160,4 @@ namespace Umbraco.Core.Persistence.Factories #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs deleted file mode 100644 index cf487fa1a2..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class DataTypeDefinitionFactory - { - private readonly Guid _nodeObjectTypeId; - private int _primaryKey; - - public DataTypeDefinitionFactory(Guid nodeObjectTypeId) - { - _nodeObjectTypeId = nodeObjectTypeId; - } - - #region Implementation of IEntityFactory - - public IDataTypeDefinition BuildEntity(DataTypeDto dto) - { - var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias); - - - try - { - dataTypeDefinition.DisableChangeTracking(); - - dataTypeDefinition.CreateDate = dto.NodeDto.CreateDate; - dataTypeDefinition.DatabaseType = dto.DbType.EnumParse(true); - dataTypeDefinition.Id = dto.DataTypeId; - dataTypeDefinition.Key = dto.NodeDto.UniqueId; - dataTypeDefinition.Level = dto.NodeDto.Level; - dataTypeDefinition.UpdateDate = dto.NodeDto.CreateDate; - dataTypeDefinition.Name = dto.NodeDto.Text; - dataTypeDefinition.ParentId = dto.NodeDto.ParentId; - dataTypeDefinition.Path = dto.NodeDto.Path; - dataTypeDefinition.SortOrder = dto.NodeDto.SortOrder; - dataTypeDefinition.Trashed = dto.NodeDto.Trashed; - dataTypeDefinition.CreatorId = dto.NodeDto.UserId.Value; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - dataTypeDefinition.ResetDirtyProperties(false); - return dataTypeDefinition; - } - finally - { - dataTypeDefinition.EnableChangeTracking(); - } - } - - public DataTypeDto BuildDto(IDataTypeDefinition entity) - { - var dataTypeDto = new DataTypeDto - { - PropertyEditorAlias = entity.PropertyEditorAlias, - DataTypeId = entity.Id, - DbType = entity.DatabaseType.ToString(), - NodeDto = BuildNodeDto(entity) - }; - - if (_primaryKey > 0) - { - dataTypeDto.PrimaryKey = _primaryKey; - } - - return dataTypeDto; - } - - #endregion - - public void SetPrimaryKey(int primaryKey) - { - _primaryKey = primaryKey; - } - - private NodeDto BuildNodeDto(IDataTypeDefinition entity) - { - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectTypeId, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = entity.Name, - Trashed = entity.Trashed, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; - - return nodeDto; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs new file mode 100644 index 0000000000..226aa07483 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs @@ -0,0 +1,83 @@ +using System; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Persistence.Factories +{ + internal static class DataTypeFactory + { + public static IDataType BuildEntity(DataTypeDto dto, PropertyEditorCollection editors) + { + if (!editors.TryGet(dto.EditorAlias, out var editor)) + throw new InvalidOperationException($"Could not find an editor with alias \"{dto.EditorAlias}\"."); + + var dataType = new DataType(editor); + + try + { + dataType.DisableChangeTracking(); + + dataType.CreateDate = dto.NodeDto.CreateDate; + dataType.DatabaseType = dto.DbType.EnumParse(true); + dataType.Id = dto.NodeId; + dataType.Key = dto.NodeDto.UniqueId; + dataType.Level = dto.NodeDto.Level; + dataType.UpdateDate = dto.NodeDto.CreateDate; + dataType.Name = dto.NodeDto.Text; + dataType.ParentId = dto.NodeDto.ParentId; + dataType.Path = dto.NodeDto.Path; + dataType.SortOrder = dto.NodeDto.SortOrder; + dataType.Trashed = dto.NodeDto.Trashed; + dataType.CreatorId = dto.NodeDto.UserId ?? 0; + + dataType.SetLazyConfiguration(dto.Configuration); + + // reset dirty initial properties (U4-1946) + dataType.ResetDirtyProperties(false); + return dataType; + } + finally + { + dataType.EnableChangeTracking(); + } + } + + public static DataTypeDto BuildDto(IDataType entity) + { + var dataTypeDto = new DataTypeDto + { + EditorAlias = entity.EditorAlias, + NodeId = entity.Id, + DbType = entity.DatabaseType.ToString(), + Configuration = entity.Configuration == null ? null : JsonConvert.SerializeObject(entity.Configuration, ConfigurationEditor.ConfigurationJsonSettings), + NodeDto = BuildNodeDto(entity) + }; + + return dataTypeDto; + } + + private static NodeDto BuildNodeDto(IDataType entity) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = Convert.ToInt16(entity.Level), + NodeObjectType = Constants.ObjectTypes.DataType, + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = entity.Name, + Trashed = entity.Trashed, + UniqueId = entity.Key, + UserId = entity.CreatorId + }; + + return nodeDto; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs index 4bcfea58ca..e84b1c96e2 100644 --- a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal class DictionaryItemFactory + internal class DictionaryItemFactory { #region Implementation of IEntityFactory @@ -18,8 +18,8 @@ namespace Umbraco.Core.Persistence.Factories item.Id = dto.PrimaryKey; item.Key = dto.UniqueId; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + + // reset dirty initial properties (U4-1946) item.ResetDirtyProperties(false); return item; } @@ -63,4 +63,4 @@ namespace Umbraco.Core.Persistence.Factories return list; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs index 2a931f6069..a4075e03e2 100644 --- a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs @@ -1,10 +1,10 @@ using System; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal class DictionaryTranslationFactory + internal class DictionaryTranslationFactory { private readonly Guid _uniqueId; @@ -25,8 +25,7 @@ namespace Umbraco.Core.Persistence.Factories item.Id = dto.PrimaryKey; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) item.ResetDirtyProperties(false); return item; } @@ -53,4 +52,4 @@ namespace Umbraco.Core.Persistence.Factories #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs b/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs index 4882df3202..2a8c340462 100644 --- a/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs @@ -1,5 +1,5 @@ -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { @@ -9,8 +9,7 @@ namespace Umbraco.Core.Persistence.Factories { var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId, dto.CreateDate); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); return entity; } @@ -23,10 +22,10 @@ namespace Umbraco.Core.Persistence.Factories CreateDate = entity.CreateDate, LoginProvider = entity.LoginProvider, ProviderKey = entity.ProviderKey, - UserId = entity.UserId + UserId = entity.UserId }; return dto; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs index 54c8aa30a8..f79dab1bbd 100644 --- a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs @@ -1,27 +1,26 @@ using System.Globalization; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal class LanguageFactory + internal class LanguageFactory { public ILanguage BuildEntity(LanguageDto dto) { - var lang = new Language(dto.IsoCode) { CultureName = dto.CultureName, Id = dto.Id }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + var lang = new Language(dto.IsoCode) { CultureName = dto.CultureName, Id = dto.Id, IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage, Mandatory = dto.Mandatory }; + // reset dirty initial properties (U4-1946) lang.ResetDirtyProperties(false); return lang; } public LanguageDto BuildDto(ILanguage entity) { - var dto = new LanguageDto { CultureName = entity.CultureName, IsoCode = entity.IsoCode }; + var dto = new LanguageDto { CultureName = entity.CultureName, IsoCode = entity.IsoCode, IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage, Mandatory = entity.Mandatory }; if (entity.HasIdentity) dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); return dto; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs index 964668e854..a854a3a65f 100644 --- a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs @@ -1,28 +1,26 @@ using System.Collections.Generic; using System.Globalization; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - - internal class MacroFactory + internal class MacroFactory { public IMacro BuildEntity(MacroDto dto) { - var model = new Macro(dto.Id, dto.UniqueId, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.ScriptType, dto.ScriptAssembly, dto.Xslt, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.Python); + var model = new Macro(dto.Id, dto.UniqueId, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.ScriptType, dto.ScriptAssembly, dto.Xslt, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.MacroFilePath); try { model.DisableChangeTracking(); - foreach (var p in dto.MacroPropertyDtos) + foreach (var p in dto.MacroPropertyDtos.EmptyNull()) { model.Properties.Add(new MacroProperty(p.Id, p.UniqueId, p.Alias, p.Name, p.SortOrder, p.EditorAlias)); } - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) model.ResetDirtyProperties(false); return model; } @@ -42,7 +40,7 @@ namespace Umbraco.Core.Persistence.Factories CachePersonalized = entity.CacheByMember, DontRender = entity.DontRender, Name = entity.Name, - Python = entity.ScriptPath, + MacroFilePath = entity.ScriptPath, RefreshRate = entity.CacheDuration, ScriptAssembly = entity.ControlAssembly, ScriptType = entity.ControlType, diff --git a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs deleted file mode 100644 index 693aef594a..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System; -using System.Globalization; -using System.Text.RegularExpressions; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class MediaFactory - { - private readonly IMediaType _contentType; - private readonly Guid _nodeObjectTypeId; - private int _primaryKey; - - public MediaFactory(IMediaType contentType, Guid nodeObjectTypeId) - { - _contentType = contentType; - _nodeObjectTypeId = nodeObjectTypeId; - } - - public MediaFactory(Guid nodeObjectTypeId) - { - _nodeObjectTypeId = nodeObjectTypeId; - } - - #region Implementation of IEntityFactory - - public static IMedia BuildEntity(ContentVersionDto dto, IMediaType contentType) - { - var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, contentType); - - try - { - media.DisableChangeTracking(); - - media.Id = dto.NodeId; - media.Key = dto.ContentDto.NodeDto.UniqueId; - media.Path = dto.ContentDto.NodeDto.Path; - media.CreatorId = dto.ContentDto.NodeDto.UserId.Value; - media.Level = dto.ContentDto.NodeDto.Level; - media.ParentId = dto.ContentDto.NodeDto.ParentId; - media.SortOrder = dto.ContentDto.NodeDto.SortOrder; - media.Trashed = dto.ContentDto.NodeDto.Trashed; - media.CreateDate = dto.ContentDto.NodeDto.CreateDate; - media.UpdateDate = dto.VersionDate; - media.Version = dto.VersionId; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - media.ResetDirtyProperties(false); - return media; - } - finally - { - media.EnableChangeTracking(); - } - - } - - [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")] - public IMedia BuildEntity(ContentVersionDto dto) - { - return BuildEntity(dto, _contentType); - } - - public MediaDto BuildDto(IMedia entity) - { - var versionDto = new ContentVersionDto - { - NodeId = entity.Id, - VersionDate = entity.UpdateDate, - VersionId = entity.Version, - ContentDto = BuildContentDto(entity) - }; - - //Extract the media path for storage - string mediaPath; - TryMatch(entity.GetValue("umbracoFile"), out mediaPath); - - var dto = new MediaDto() - { - NodeId = entity.Id, - ContentVersionDto = versionDto, - MediaPath = mediaPath, - VersionId = entity.Version - }; - return dto; - } - - #endregion - - public void SetPrimaryKey(int primaryKey) - { - _primaryKey = primaryKey; - } - - private ContentDto BuildContentDto(IMedia entity) - { - var contentDto = new ContentDto - { - NodeId = entity.Id, - ContentTypeId = entity.ContentTypeId, - NodeDto = BuildNodeDto(entity) - }; - - if (_primaryKey > 0) - { - contentDto.PrimaryKey = _primaryKey; - } - - return contentDto; - } - - private NodeDto BuildNodeDto(IMedia entity) - { - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectTypeId, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = entity.Name, - Trashed = entity.Trashed, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; - - return nodeDto; - } - - private static readonly Regex MediaPathPattern = new Regex(@"(/media/.+?)(?:['""]|$)", RegexOptions.Compiled); - - /// - /// Try getting a media path out of the string being stored for media - /// - /// - /// - /// - internal static bool TryMatch(string text, out string mediaPath) - { - mediaPath = null; - - if (string.IsNullOrWhiteSpace(text)) - return false; - - var match = MediaPathPattern.Match(text); - if (match.Success == false || match.Groups.Count != 2) - return false; - - - var url = match.Groups[1].Value; - mediaPath = url; - return true; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs deleted file mode 100644 index 7b28808429..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/MemberFactory.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Globalization; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class MemberFactory - { - private readonly IMemberType _contentType; - private readonly Guid _nodeObjectTypeId; - private readonly int _id; - private int _primaryKey; - - public MemberFactory(IMemberType contentType, Guid nodeObjectTypeId, int id) - { - _contentType = contentType; - _nodeObjectTypeId = nodeObjectTypeId; - _id = id; - } - - public MemberFactory(Guid nodeObjectTypeId, int id) - { - _nodeObjectTypeId = nodeObjectTypeId; - _id = id; - } - - #region Implementation of IEntityFactory - - public static IMember BuildEntity(MemberDto dto, IMemberType contentType) - { - var member = new Member( - dto.ContentVersionDto.ContentDto.NodeDto.Text, - dto.Email, dto.LoginName, dto.Password, contentType); - - try - { - member.DisableChangeTracking(); - - member.Id = dto.NodeId; - member.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId; - member.Path = dto.ContentVersionDto.ContentDto.NodeDto.Path; - member.CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value; - member.Level = dto.ContentVersionDto.ContentDto.NodeDto.Level; - member.ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId; - member.SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder; - member.Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed; - member.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate; - member.UpdateDate = dto.ContentVersionDto.VersionDate; - member.Version = dto.ContentVersionDto.VersionId; - - member.ProviderUserKey = member.Key; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - member.ResetDirtyProperties(false); - return member; - } - finally - { - member.EnableChangeTracking(); - } - } - - [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")] - public IMember BuildEntity(MemberDto dto) - { - return BuildEntity(dto, _contentType); - } - - public MemberDto BuildDto(IMember entity) - { - var dto = new MemberDto - { - ContentVersionDto = new ContentVersionDto - { - NodeId = entity.Id, - VersionDate = entity.UpdateDate, - VersionId = entity.Version, - ContentDto = BuildContentDto(entity) - }, - Email = entity.Email, - LoginName = entity.Username, - NodeId = entity.Id, - Password = entity.RawPasswordValue - }; - return dto; - } - - #endregion - - public void SetPrimaryKey(int primaryKey) - { - _primaryKey = primaryKey; - } - - private ContentDto BuildContentDto(IMember entity) - { - var contentDto = new ContentDto - { - NodeId = entity.Id, - ContentTypeId = entity.ContentTypeId, - NodeDto = BuildNodeDto(entity) - }; - - if (_primaryKey > 0) - { - contentDto.PrimaryKey = _primaryKey; - } - - return contentDto; - } - - private NodeDto BuildNodeDto(IMember entity) - { - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectTypeId, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = entity.Name, - Trashed = entity.Trashed, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; - - return nodeDto; - } - } - -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs index a4e069e85c..96c0b2fcef 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberGroupFactory.cs @@ -1,17 +1,17 @@ using System; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal class MemberGroupFactory + internal class MemberGroupFactory { - + private readonly Guid _nodeObjectTypeId; public MemberGroupFactory() { - _nodeObjectTypeId = new Guid(Constants.ObjectTypes.MemberGroup); + _nodeObjectTypeId = Constants.ObjectTypes.MemberGroup; } #region Implementation of IEntityFactory @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Factories public IMemberGroup BuildEntity(NodeDto dto) { var group = new MemberGroup(); - + try { group.DisableChangeTracking(); @@ -29,8 +29,7 @@ namespace Umbraco.Core.Persistence.Factories group.Key = dto.UniqueId; group.Name = dto.Text; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) group.ResetDirtyProperties(false); return group; } @@ -69,4 +68,4 @@ namespace Umbraco.Core.Persistence.Factories #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index ba7a47165b..6f79f03c73 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -2,12 +2,13 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; namespace Umbraco.Core.Persistence.Factories { - internal class MemberTypeReadOnlyFactory + internal class MemberTypeReadOnlyFactory { public IMemberType BuildEntity(MemberTypeReadOnlyDto dto, out bool needsSaving) { @@ -43,7 +44,7 @@ namespace Umbraco.Core.Persistence.Factories var propertyTypes = GetPropertyTypes(dto, memberType, standardPropertyTypes); - //By Convention we add 9 stnd PropertyTypes - This is only here to support loading of types that didn't have these conventions before. + //By Convention we add 9 stnd 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; @@ -56,10 +57,10 @@ namespace Umbraco.Core.Persistence.Factories //Add the standard PropertyType to the current list propertyTypes.Add(standardPropertyType.Value); - - //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType + + //Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, - new MemberTypePropertyProfileAccess(false, false)); + new MemberTypePropertyProfileAccess(false, false, false)); } memberType.NoGroupPropertyTypes = propertyTypes; @@ -78,7 +79,7 @@ namespace Umbraco.Core.Persistence.Factories var propertyGroups = new PropertyGroupCollection(); foreach (var groupDto in dto.PropertyTypeGroups.Where(x => x.Id.HasValue)) { - var group = new PropertyGroup(); + var group = new PropertyGroup(MemberType.IsPublishingConst); // if the group is defined on the current member type, // assign its identifier, else it will be zero @@ -93,7 +94,7 @@ namespace Umbraco.Core.Persistence.Factories group.Key = groupDto.UniqueId; group.Name = groupDto.Text; group.SortOrder = groupDto.SortOrder; - group.PropertyTypes = new PropertyTypeCollection(); + group.PropertyTypes = new PropertyTypeCollection(MemberType.IsPublishingConst); //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded var localGroupDto = groupDto; @@ -102,7 +103,7 @@ namespace Umbraco.Core.Persistence.Factories { //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType memberType.MemberTypePropertyTypes.Add(typeDto.Alias, - new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit)); + new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive)); var tempGroupDto = groupDto; @@ -110,9 +111,9 @@ namespace Umbraco.Core.Persistence.Factories //what the underlying data type is var propDbType = MemberTypeRepository.GetDbTypeForBuiltInProperty( typeDto.Alias, - typeDto.DbType.EnumParse(true), + typeDto.DbType.EnumParse(true), standardProps); - + var propertyType = new PropertyType( typeDto.PropertyEditorAlias, propDbType.Result, @@ -121,7 +122,7 @@ namespace Umbraco.Core.Persistence.Factories propDbType.Success, typeDto.Alias) { - DataTypeDefinitionId = typeDto.DataTypeId, + DataTypeId = typeDto.DataTypeId, Description = typeDto.Description, Id = typeDto.Id.Value, Name = typeDto.Name, @@ -133,13 +134,13 @@ namespace Umbraco.Core.Persistence.Factories UpdateDate = memberType.UpdateDate, Key = typeDto.UniqueId }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + + // reset dirty initial properties (U4-1946) propertyType.ResetDirtyProperties(false); group.PropertyTypes.Add(propertyType); } - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + + // reset dirty initial properties (U4-1946) group.ResetDirtyProperties(false); propertyGroups.Add(group); } @@ -157,13 +158,13 @@ namespace Umbraco.Core.Persistence.Factories { //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType memberType.MemberTypePropertyTypes.Add(typeDto.Alias, - new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit)); + 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), + typeDto.DbType.EnumParse(true), standardProps); var propertyType = new PropertyType( @@ -174,7 +175,7 @@ namespace Umbraco.Core.Persistence.Factories propDbType.Success, typeDto.Alias) { - DataTypeDefinitionId = typeDto.DataTypeId, + DataTypeId = typeDto.DataTypeId, Description = typeDto.Description, Id = typeDto.Id.Value, Mandatory = typeDto.Mandatory, @@ -186,7 +187,7 @@ namespace Umbraco.Core.Persistence.Factories UpdateDate = dto.CreateDate, Key = typeDto.UniqueId }; - + propertyTypes.Add(propertyType); } return propertyTypes; @@ -196,6 +197,6 @@ namespace Umbraco.Core.Persistence.Factories { throw new System.NotImplementedException(); } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/MigrationEntryFactory.cs b/src/Umbraco.Core/Persistence/Factories/MigrationEntryFactory.cs deleted file mode 100644 index 1cb7000293..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/MigrationEntryFactory.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using Semver; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class MigrationEntryFactory - { - public MigrationEntry BuildEntity(MigrationDto dto) - { - SemVersion parsed; - if (SemVersion.TryParse(dto.Version, out parsed) == false) - { - throw new FormatException("Cannot parse the version string in the database to a SemVersion object: " + dto.Version); - } - - var model = new MigrationEntry(dto.Id, dto.CreateDate, dto.Name, parsed); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - model.ResetDirtyProperties(false); - return model; - } - - public MigrationDto BuildDto(IMigrationEntry entity) - { - var dto = new MigrationDto - { - CreateDate = entity.CreateDate, - Name = entity.MigrationName, - Version = entity.Version.ToString() - }; - - if (entity.HasIdentity) - dto.Id = entity.Id; - - return dto; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/ModelFactoryConfiguration.cs b/src/Umbraco.Core/Persistence/Factories/ModelFactoryConfiguration.cs index 2aadb46084..0a7372087d 100644 --- a/src/Umbraco.Core/Persistence/Factories/ModelFactoryConfiguration.cs +++ b/src/Umbraco.Core/Persistence/Factories/ModelFactoryConfiguration.cs @@ -1,6 +1,6 @@ using AutoMapper; -using Umbraco.Core.Models.Mapping; +// fixme what is this?! namespace Umbraco.Core.Persistence.Factories { @@ -13,8 +13,8 @@ namespace Umbraco.Core.Persistence.Factories //{ // public override void ConfigureMappings(IConfiguration config) // { - + // UserSectionFactory.ConfigureMappings(config); // } //} -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index f202d8c321..4e2789bab7 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -2,54 +2,33 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal class PropertyFactory + internal static class PropertyFactory { - private readonly PropertyType[] _compositionTypeProperties; - private readonly Guid _version; - private readonly int _id; - private readonly DateTime _createDate; - private readonly DateTime _updateDate; - - public PropertyFactory(PropertyType[] compositionTypeProperties, Guid version, int id) - { - _compositionTypeProperties = compositionTypeProperties; - _version = version; - _id = id; - } - - public PropertyFactory(PropertyType[] compositionTypeProperties, Guid version, int id, DateTime createDate, DateTime updateDate) - { - _compositionTypeProperties = compositionTypeProperties; - _version = version; - _id = id; - _createDate = createDate; - _updateDate = updateDate; - } - - public static IEnumerable BuildEntity(IReadOnlyCollection dtos, PropertyType[] compositionTypeProperties, DateTime createDate, DateTime updateDate) + public static IEnumerable BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection dtos, int publishedVersionId) { var properties = new List(); + var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable) x); - foreach (var propertyType in compositionTypeProperties) + foreach (var propertyType in propertyTypes) { - var propertyDataDto = dtos.LastOrDefault(x => x.PropertyTypeId == propertyType.Id); - var property = propertyDataDto == null - ? propertyType.CreatePropertyFromValue(null) - : propertyType.CreatePropertyFromRawValue(propertyDataDto.GetValue, - propertyDataDto.VersionId.Value, - propertyDataDto.Id); + var property = propertyType.CreateProperty(); + try { - //on initial construction we don't want to have dirty properties tracked property.DisableChangeTracking(); - property.CreateDate = createDate; - property.UpdateDate = updateDate; - // http://issues.umbraco.org/issue/U4-1946 + // see notes in BuildDtos - we always have edit+published dtos + + if (xdtos.TryGetValue(propertyType.Id, out var propDtos)) + { + foreach (var propDto in propDtos) + property.FactorySetValue(propDto.LanguageId, propDto.Segment, propDto.VersionId == publishedVersionId, propDto.Value); + } + property.ResetDirtyProperties(false); properties.Add(property); } @@ -57,78 +36,100 @@ namespace Umbraco.Core.Persistence.Factories { property.EnableChangeTracking(); } - } return properties; } - [Obsolete("Use the static method instead, there's no reason to allocate one of these classes everytime we want to map values")] - public IEnumerable BuildEntity(PropertyDataDto[] dtos) + private static PropertyDataDto BuildDto(int versionId, Property property, int? nLanguageId, string segment, object value) { - return BuildEntity(dtos, _compositionTypeProperties, _createDate, _updateDate); + var dto = new PropertyDataDto { VersionId = versionId, PropertyTypeId = property.PropertyTypeId }; + + if (nLanguageId.HasValue) + dto.LanguageId = nLanguageId; + + if (segment != null) + dto.Segment = segment; + + if (property.ValueStorageType == ValueStorageType.Integer) + { + if (value is bool || property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.Boolean) + { + dto.IntegerValue = value != null && string.IsNullOrEmpty(value.ToString()) ? 0 : Convert.ToInt32(value); + } + else if (value != null && string.IsNullOrWhiteSpace(value.ToString()) == false && int.TryParse(value.ToString(), out var val)) + { + dto.IntegerValue = val; + } + } + else if (property.ValueStorageType == ValueStorageType.Decimal && value != null) + { + if (decimal.TryParse(value.ToString(), out var val)) + { + dto.DecimalValue = val; // property value should be normalized already + } + } + else if (property.ValueStorageType == ValueStorageType.Date && value != null && string.IsNullOrWhiteSpace(value.ToString()) == false) + { + if (DateTime.TryParse(value.ToString(), out var date)) + { + dto.DateValue = date; + } + } + else if (property.ValueStorageType == ValueStorageType.Ntext && value != null) + { + dto.TextValue = value.ToString(); + } + else if (property.ValueStorageType == ValueStorageType.Nvarchar && value != null) + { + dto.VarcharValue = value.ToString(); + } + + return dto; } - public IEnumerable BuildDto(IEnumerable properties) + public static IEnumerable BuildDtos(int currentVersionId, int publishedVersionId, IEnumerable properties, out bool edited) { var propertyDataDtos = new List(); + edited = false; foreach (var property in properties) { - var dto = new PropertyDataDto { NodeId = _id, PropertyTypeId = property.PropertyTypeId, VersionId = _version }; + if (property.PropertyType.IsPublishing) + { + // publishing = deal with edit and published values + foreach (var propertyValue in property.Values) + { + // deal with published value + if (propertyValue.PublishedValue != null && publishedVersionId > 0) + propertyDataDtos.Add(BuildDto(publishedVersionId, property, propertyValue.LanguageId, propertyValue.Segment, propertyValue.PublishedValue)); - //Check if property has an Id and set it, so that it can be updated if it already exists - if (property.HasIdentity) - { - dto.Id = property.Id; - } + // deal with edit value + if (propertyValue.EditedValue != null) + propertyDataDtos.Add(BuildDto(currentVersionId, property, propertyValue.LanguageId, propertyValue.Segment, propertyValue.EditedValue)); - if (property.DataTypeDatabaseType == DataTypeDatabaseType.Integer) - { - if (property.Value is bool || property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.TrueFalseAlias) - { - dto.Integer = property.Value != null && string.IsNullOrEmpty(property.Value.ToString()) - ? 0 - : Convert.ToInt32(property.Value); - } - else - { - int val; - if ((property.Value != null && string.IsNullOrWhiteSpace(property.Value.ToString()) == false) && int.TryParse(property.Value.ToString(), out val)) - { - dto.Integer = val; - } - } - } - else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Decimal && property.Value != null) - { - decimal val; - if (decimal.TryParse(property.Value.ToString(), out val)) - { - dto.Decimal = val; // property value should be normalized already - } - } - else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Date && property.Value != null && string.IsNullOrWhiteSpace(property.Value.ToString()) == false) - { - DateTime date; - if (DateTime.TryParse(property.Value.ToString(), out date)) - { - dto.Date = date; - } - } - else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Ntext && property.Value != null) - { - dto.Text = property.Value.ToString(); - } - else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Nvarchar && property.Value != null) - { - dto.VarChar = property.Value.ToString(); - } + // deal with missing edit value (fix inconsistencies) + else if (propertyValue.PublishedValue != null) + propertyDataDtos.Add(BuildDto(currentVersionId, property, propertyValue.LanguageId, propertyValue.Segment, propertyValue.PublishedValue)); - propertyDataDtos.Add(dto); + // use explicit equals here, else object comparison fails at comparing eg strings + var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); + edited |= !sameValues; + } + } + else + { + foreach (var propertyValue in property.Values) + { + // not publishing = only deal with edit values + if (propertyValue.EditedValue != null) + propertyDataDtos.Add(BuildDto(currentVersionId, property, propertyValue.LanguageId, propertyValue.Segment, propertyValue.EditedValue)); + } + edited = true; + } } + return propertyDataDtos; } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index e1db8dcebf..59bed3cede 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -2,17 +2,17 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal class PropertyGroupFactory + internal class PropertyGroupFactory { private readonly int _contentTypeId; private readonly DateTime _createDate; private readonly DateTime _updateDate; //a callback to create a property type which can be injected via a contructor - private readonly Func _propertyTypeCtor; + private readonly Func _propertyTypeCtor; public PropertyGroupFactory(int contentTypeId) { @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Factories _propertyTypeCtor = (propertyEditorAlias, dbType, alias) => new PropertyType(propertyEditorAlias, dbType); } - public PropertyGroupFactory(int contentTypeId, DateTime createDate, DateTime updateDate, Func propertyTypeCtor) + public PropertyGroupFactory(int contentTypeId, DateTime createDate, DateTime updateDate, Func propertyTypeCtor) { _contentTypeId = contentTypeId; _createDate = createDate; @@ -30,14 +30,14 @@ namespace Umbraco.Core.Persistence.Factories #region Implementation of IEntityFactory,IEnumerable> - public IEnumerable BuildEntity(IEnumerable groupDtos) + public IEnumerable BuildEntity(IEnumerable groupDtos, bool isPublishing) { // groupDtos contains all the groups, those that are defined on the current // content type, and those that are inherited from composition content types var propertyGroups = new PropertyGroupCollection(); foreach (var groupDto in groupDtos) { - var group = new PropertyGroup(); + var group = new PropertyGroup(isPublishing); try { @@ -50,7 +50,7 @@ namespace Umbraco.Core.Persistence.Factories group.Name = groupDto.Text; group.SortOrder = groupDto.SortOrder; - group.PropertyTypes = new PropertyTypeCollection(); + group.PropertyTypes = new PropertyTypeCollection(isPublishing); group.Key = groupDto.UniqueId; //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded @@ -58,8 +58,8 @@ namespace Umbraco.Core.Persistence.Factories foreach (var typeDto in typeDtos) { var tempGroupDto = groupDto; - var propertyType = _propertyTypeCtor(typeDto.DataTypeDto.PropertyEditorAlias, - typeDto.DataTypeDto.DbType.EnumParse(true), + var propertyType = _propertyTypeCtor(typeDto.DataTypeDto.EditorAlias, + typeDto.DataTypeDto.DbType.EnumParse(true), typeDto.Alias); try @@ -67,7 +67,7 @@ namespace Umbraco.Core.Persistence.Factories propertyType.DisableChangeTracking(); propertyType.Alias = typeDto.Alias; - propertyType.DataTypeDefinitionId = typeDto.DataTypeId; + propertyType.DataTypeId = typeDto.DataTypeId; propertyType.Description = typeDto.Description; propertyType.Id = typeDto.Id; propertyType.Key = typeDto.UniqueId; @@ -78,9 +78,9 @@ namespace Umbraco.Core.Persistence.Factories propertyType.PropertyGroupId = new Lazy(() => tempGroupDto.Id); propertyType.CreateDate = _createDate; propertyType.UpdateDate = _updateDate; + propertyType.Variations = (ContentVariation) typeDto.Variations; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) propertyType.ResetDirtyProperties(false); group.PropertyTypes.Add(propertyType); } @@ -89,8 +89,8 @@ namespace Umbraco.Core.Persistence.Factories propertyType.EnableChangeTracking(); } } - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + + // reset dirty initial properties (U4-1946) group.ResetDirtyProperties(false); propertyGroups.Add(group); } @@ -134,16 +134,17 @@ namespace Umbraco.Core.Persistence.Factories { Alias = propertyType.Alias, ContentTypeId = _contentTypeId, - DataTypeId = propertyType.DataTypeDefinitionId, + DataTypeId = propertyType.DataTypeId, Description = propertyType.Description, Mandatory = propertyType.Mandatory, Name = propertyType.Name, SortOrder = propertyType.SortOrder, ValidationRegExp = propertyType.ValidationRegExp, - UniqueId = propertyType.Key + UniqueId = propertyType.Key, + Variations = (byte) propertyType.Variations }; - if (tabId != default(int)) + if (tabId != default) { propertyTypeDto.PropertyTypeGroupId = tabId; } @@ -158,4 +159,4 @@ namespace Umbraco.Core.Persistence.Factories return propertyTypeDto; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/PublicAccessEntryFactory.cs b/src/Umbraco.Core/Persistence/Factories/PublicAccessEntryFactory.cs index a576ef31c4..90ce4f68bb 100644 --- a/src/Umbraco.Core/Persistence/Factories/PublicAccessEntryFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PublicAccessEntryFactory.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Factories { public PublicAccessEntry BuildEntity(AccessDto dto) { - var entity = new PublicAccessEntry(dto.Id, dto.NodeId, dto.LoginNodeId, dto.NoAccessNodeId, + var entity = new PublicAccessEntry(dto.Id, dto.NodeId, dto.LoginNodeId, dto.NoAccessNodeId, dto.Rules.Select(x => new PublicAccessRule(x.Id, x.AccessId) { RuleValue = x.RuleValue, @@ -22,8 +22,7 @@ namespace Umbraco.Core.Persistence.Factories UpdateDate = dto.UpdateDate }; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); return entity; } @@ -52,4 +51,4 @@ namespace Umbraco.Core.Persistence.Factories return dto; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs index 47adf75d89..8a556486a9 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs @@ -1,9 +1,9 @@ using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal class RelationFactory + internal class RelationFactory { private readonly IRelationType _relationType; @@ -27,8 +27,7 @@ namespace Umbraco.Core.Persistence.Factories entity.Id = dto.Id; entity.UpdateDate = dto.Datetime; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); return entity; } @@ -57,4 +56,4 @@ namespace Umbraco.Core.Persistence.Factories #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs index b112535817..6b35452bfa 100644 --- a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs @@ -1,9 +1,9 @@ using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal class RelationTypeFactory + internal class RelationTypeFactory { #region Implementation of IEntityFactory @@ -20,8 +20,7 @@ namespace Umbraco.Core.Persistence.Factories entity.IsBidirectional = dto.Dual; entity.Name = dto.Name; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); return entity; } @@ -52,4 +51,4 @@ namespace Umbraco.Core.Persistence.Factories #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs index 9c315aef46..5b0a42ad7b 100644 --- a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs @@ -1,15 +1,14 @@ using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal class ServerRegistrationFactory + internal class ServerRegistrationFactory { public ServerRegistration BuildEntity(ServerRegistrationDto dto) { var model = new ServerRegistration(dto.Id, dto.ServerAddress, dto.ServerIdentity, dto.DateRegistered, dto.DateAccessed, dto.IsActive, dto.IsMaster); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) model.ResetDirtyProperties(false); return model; } @@ -32,4 +31,4 @@ namespace Umbraco.Core.Persistence.Factories return dto; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/TagFactory.cs b/src/Umbraco.Core/Persistence/Factories/TagFactory.cs index 0aa60733de..bd93d1d3b6 100644 --- a/src/Umbraco.Core/Persistence/Factories/TagFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TagFactory.cs @@ -1,28 +1,27 @@ using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal class TagFactory + internal static class TagFactory { - public ITag BuildEntity(TagDto dto) + public static ITag BuildEntity(TagDto dto) { - var model = new Tag(dto.Id, dto.Tag, dto.Group, dto.NodeCount); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - model.ResetDirtyProperties(false); - return model; + var entity = new Tag(dto.Id, dto.Group, dto.Text) { NodeCount = dto.NodeCount }; + // reset dirty initial properties (U4-1946) + entity.ResetDirtyProperties(false); + return entity; } - public TagDto BuildDto(ITag entity) + public static TagDto BuildDto(ITag entity) { return new TagDto - { - Id = entity.Id, - Group = entity.Group, - Tag = entity.Text, - NodeCount = entity.NodeCount, - }; + { + Id = entity.Id, + Group = entity.Group, + Text = entity.Text, + //Key = entity.Group + "/" + entity.Text // de-normalize + }; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs b/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs index d60403abea..98ab898cd4 100644 --- a/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TaskFactory.cs @@ -1,7 +1,5 @@ -using AutoMapper; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { @@ -25,8 +23,8 @@ namespace Umbraco.Core.Persistence.Factories entity.EntityId = dto.NodeId; entity.Id = dto.Id; entity.OwnerUserId = dto.ParentUserId; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + + // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); return entity; } @@ -49,8 +47,8 @@ namespace Umbraco.Core.Persistence.Factories TaskTypeId = (byte)entity.TaskType.Id, UserId = entity.AssigneeUserId }; - + return dto; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/TaskTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/TaskTypeFactory.cs index a17abebad6..f4851c192f 100644 --- a/src/Umbraco.Core/Persistence/Factories/TaskTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TaskTypeFactory.cs @@ -1,5 +1,5 @@ -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { @@ -8,8 +8,7 @@ namespace Umbraco.Core.Persistence.Factories public TaskType BuildEntity(TaskTypeDto dto) { var entity = new TaskType(dto.Alias) {Id = dto.Id}; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); return entity; } @@ -24,4 +23,4 @@ namespace Umbraco.Core.Persistence.Factories return dto; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs index ecff14d99c..a5be4d05bc 100644 --- a/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/TemplateFactory.cs @@ -5,8 +5,8 @@ using System.Linq; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { @@ -17,7 +17,7 @@ namespace Umbraco.Core.Persistence.Factories public TemplateFactory() { - + } public TemplateFactory(Guid nodeObjectTypeId) @@ -51,8 +51,7 @@ namespace Umbraco.Core.Persistence.Factories if (dto.NodeDto.ParentId > 0) template.MasterTemplateId = new Lazy(() => dto.NodeDto.ParentId); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) template.ResetDirtyProperties(false); return template; } @@ -105,4 +104,4 @@ namespace Umbraco.Core.Persistence.Factories return nodeDto; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs deleted file mode 100644 index 86594836db..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Strings; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class UmbracoEntityFactory - { - internal void AddAdditionalData(UmbracoEntity entity, IDictionary originalEntityProperties) - { - var entityProps = typeof(IUmbracoEntity).GetPublicProperties().Select(x => x.Name).ToArray(); - - //figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data - foreach (var k in originalEntityProperties.Keys - .Select(x => new { orig = x, title = x.ToCleanString(CleanStringType.PascalCase | CleanStringType.Ascii | CleanStringType.ConvertCase) }) - .Where(x => entityProps.InvariantContains(x.title) == false)) - { - entity.AdditionalData[k.title] = originalEntityProperties[k.orig]; - } - } - - internal UmbracoEntity BuildEntityFromDynamic(dynamic d) - { - var asDictionary = (IDictionary)d; - - var entity = new UmbracoEntity(d.trashed); - - try - { - entity.DisableChangeTracking(); - - entity.CreateDate = d.createDate; - entity.CreatorId = d.nodeUser == null ? 0 : d.nodeUser; - entity.Id = d.id; - entity.Key = d.uniqueID; - entity.Level = d.level; - entity.Name = d.text; - entity.NodeObjectTypeId = d.nodeObjectType; - entity.ParentId = d.parentID; - entity.Path = d.path; - entity.SortOrder = d.sortOrder; - entity.HasChildren = d.children > 0; - entity.ContentTypeAlias = asDictionary.ContainsKey("alias") ? (d.alias ?? string.Empty) : string.Empty; - entity.ContentTypeIcon = asDictionary.ContainsKey("icon") ? (d.icon ?? string.Empty) : string.Empty; - entity.ContentTypeThumbnail = asDictionary.ContainsKey("thumbnail") ? (d.thumbnail ?? string.Empty) : string.Empty; - - var publishedVersion = default(Guid); - //some content items don't have a published/newest version - if (asDictionary.ContainsKey("publishedVersion") && asDictionary["publishedVersion"] != null) - { - Guid.TryParse(d.publishedVersion.ToString(), out publishedVersion); - } - var newestVersion = default(Guid); - if (asDictionary.ContainsKey("newestVersion") && d.newestVersion != null) - { - Guid.TryParse(d.newestVersion.ToString(), out newestVersion); - } - - entity.IsPublished = publishedVersion != default(Guid) || (newestVersion != default(Guid) && publishedVersion == newestVersion); - entity.IsDraft = newestVersion != default(Guid) && (publishedVersion == default(Guid) || publishedVersion != newestVersion); - entity.HasPendingChanges = (publishedVersion != default(Guid) && newestVersion != default(Guid)) && publishedVersion != newestVersion; - - //Now we can assign the additional data! - AddAdditionalData(entity, asDictionary); - - return entity; - } - finally - { - entity.EnableChangeTracking(); - } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index 5ab34991cb..394477cb51 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -3,17 +3,17 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { - internal static class UserFactory + internal static class UserFactory { public static IUser BuildEntity(UserDto dto) { - var guidId = dto.Id.ToGuid(); - - var user = new User(dto.Id, dto.UserName, dto.Email, dto.Login,dto.Password, + var guidId = dto.Id.ToGuid(); + + var user = new User(dto.Id, dto.UserName, dto.Email, dto.Login,dto.Password, dto.UserGroupDtos.Select(x => x.ToReadOnlyGroup()).ToArray(), dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Content).Select(x => x.StartNode).ToArray(), dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Media).Select(x => x.StartNode).ToArray()); @@ -21,7 +21,7 @@ namespace Umbraco.Core.Persistence.Factories try { user.DisableChangeTracking(); - + user.Key = guidId; user.IsLockedOut = dto.NoConsole; user.IsApproved = dto.Disabled == false; @@ -38,8 +38,7 @@ namespace Umbraco.Core.Persistence.Factories user.InvitedDate = dto.InvitedDate; user.TourData = dto.TourData; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 + // reset dirty initial properties (U4-1946) user.ResetDirtyProperties(false); return user; @@ -53,7 +52,7 @@ namespace Umbraco.Core.Persistence.Factories public static UserDto BuildDto(IUser entity) { var dto = new UserDto - { + { Disabled = entity.IsApproved == false, Email = entity.Email, Login = entity.Username, @@ -67,8 +66,8 @@ namespace Umbraco.Core.Persistence.Factories LastLoginDate = entity.LastLoginDate == DateTime.MinValue ? (DateTime?)null : entity.LastLoginDate, LastPasswordChangeDate = entity.LastPasswordChangeDate == DateTime.MinValue ? (DateTime?)null : entity.LastPasswordChangeDate, CreateDate = entity.CreateDate, - UpdateDate = entity.UpdateDate, - Avatar = entity.Avatar, + UpdateDate = entity.UpdateDate, + Avatar = entity.Avatar, EmailConfirmedDate = entity.EmailConfirmedDate, InvitedDate = entity.InvitedDate, TourData = entity.TourData @@ -100,6 +99,6 @@ namespace Umbraco.Core.Persistence.Factories } return dto; - } + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs index f4ecffca54..d854ac3769 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Linq; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { @@ -19,11 +19,11 @@ namespace Umbraco.Core.Persistence.Factories try { userGroup.DisableChangeTracking(); - userGroup.Id = dto.Id; + userGroup.Id = dto.Id; userGroup.CreateDate = dto.CreateDate; - userGroup.UpdateDate = dto.UpdateDate; - userGroup.StartContentId = dto.StartContentId; - userGroup.StartMediaId = dto.StartMediaId; + userGroup.UpdateDate = dto.UpdateDate; + userGroup.StartContentId = dto.StartContentId; + userGroup.StartMediaId = dto.StartMediaId; if (dto.UserGroup2AppDtos != null) { foreach (var app in dto.UserGroup2AppDtos) @@ -77,4 +77,4 @@ namespace Umbraco.Core.Persistence.Factories } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/ITransientErrorDetectionStrategy.cs b/src/Umbraco.Core/Persistence/FaultHandling/ITransientErrorDetectionStrategy.cs index 1dd558f3d4..3c76d46f6a 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/ITransientErrorDetectionStrategy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/ITransientErrorDetectionStrategy.cs @@ -14,4 +14,4 @@ namespace Umbraco.Core.Persistence.FaultHandling /// True if the specified exception is considered as transient, otherwise false. bool IsTransient(Exception ex); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryDbConnection.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryDbConnection.cs new file mode 100644 index 0000000000..ed438ccbc1 --- /dev/null +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryDbConnection.cs @@ -0,0 +1,238 @@ +using System; +using System.Data; +using System.Data.Common; +using NPoco; +using Transaction = System.Transactions.Transaction; + +namespace Umbraco.Core.Persistence.FaultHandling +{ + class RetryDbConnection : DbConnection + { + private DbConnection _inner; + private readonly RetryPolicy _conRetryPolicy; + private readonly RetryPolicy _cmdRetryPolicy; + + public RetryDbConnection(DbConnection connection, RetryPolicy conRetryPolicy, RetryPolicy cmdRetryPolicy) + { + _inner = connection; + _inner.StateChange += StateChangeHandler; + + _conRetryPolicy = conRetryPolicy ?? RetryPolicy.NoRetry; + _cmdRetryPolicy = cmdRetryPolicy; + } + + public DbConnection Inner { get { return _inner; } } + + public override string ConnectionString { get { return _inner.ConnectionString; } set { _inner.ConnectionString = value; } } + + protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) + { + return _inner.BeginTransaction(isolationLevel); + } + + protected override bool CanRaiseEvents + { + get { return true; } + } + + public override void ChangeDatabase(string databaseName) + { + _inner.ChangeDatabase(databaseName); + } + + public override void Close() + { + _inner.Close(); + } + + public override int ConnectionTimeout + { + get { return _inner.ConnectionTimeout; } + } + + protected override DbCommand CreateDbCommand() + { + return new FaultHandlingDbCommand(this, _inner.CreateCommand(), _cmdRetryPolicy); + } + + public override string DataSource + { + get { return _inner.DataSource; } + } + + public override string Database + { + get { return _inner.Database; } + } + + protected override void Dispose(bool disposing) + { + if (disposing && _inner != null) + { + _inner.StateChange -= StateChangeHandler; + _inner.Dispose(); + } + _inner = null; + base.Dispose(disposing); + } + + public override void EnlistTransaction(Transaction transaction) + { + _inner.EnlistTransaction(transaction); + } + + public override DataTable GetSchema() + { + return _inner.GetSchema(); + } + + public override DataTable GetSchema(string collectionName) + { + return _inner.GetSchema(collectionName); + } + + public override DataTable GetSchema(string collectionName, string[] restrictionValues) + { + return _inner.GetSchema(collectionName, restrictionValues); + } + + public override void Open() + { + _conRetryPolicy.ExecuteAction(_inner.Open); + } + + public override string ServerVersion + { + get { return _inner.ServerVersion; } + } + + public override ConnectionState State + { + get { return _inner.State; } + } + + private void StateChangeHandler(object sender, StateChangeEventArgs stateChangeEventArguments) + { + OnStateChange(stateChangeEventArguments); + } + + public void Ensure() + { + // verify whether or not the connection is valid and is open. This code may be retried therefore + // it is important to ensure that a connection is re-established should it have previously failed + if (State != ConnectionState.Open) + Open(); + } + } + + class FaultHandlingDbCommand : DbCommand + { + private RetryDbConnection _connection; + private DbCommand _inner; + private readonly RetryPolicy _cmdRetryPolicy; + + public FaultHandlingDbCommand(RetryDbConnection connection, DbCommand command, RetryPolicy cmdRetryPolicy) + { + _connection = connection; + _inner = command; + _cmdRetryPolicy = cmdRetryPolicy ?? RetryPolicy.NoRetry; + } + + public DbCommand Inner => _inner; + + protected override void Dispose(bool disposing) + { + if (disposing) + _inner?.Dispose(); + _inner = null; + base.Dispose(disposing); + } + + public override void Cancel() + { + _inner.Cancel(); + } + + public override string CommandText + { + get => _inner.CommandText; + set => _inner.CommandText = value; + } + + public override int CommandTimeout + { + get => _inner.CommandTimeout; + set => _inner.CommandTimeout = value; + } + + public override CommandType CommandType + { + get => _inner.CommandType; + set => _inner.CommandType = value; + } + + protected override DbConnection DbConnection + { + get => _connection; + set + { + if (value == null) throw new ArgumentNullException(nameof(value)); + var connection = value as RetryDbConnection; + if (connection == null) throw new ArgumentException("Value is not a FaultHandlingDbConnection instance."); + if (_connection != null && _connection != connection) throw new Exception("Value is another FaultHandlingDbConnection instance."); + _connection = connection; + _inner.Connection = connection.Inner; + } + } + + protected override DbParameter CreateDbParameter() + { + return _inner.CreateParameter(); + } + + protected override DbParameterCollection DbParameterCollection => _inner.Parameters; + + protected override DbTransaction DbTransaction + { + get => _inner.Transaction; + set => _inner.Transaction = value; + } + + public override bool DesignTimeVisible { get; set; } + + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) + { + return Execute(() => _inner.ExecuteReader(behavior)); + } + + public override int ExecuteNonQuery() + { + return Execute(() => _inner.ExecuteNonQuery()); + } + + public override object ExecuteScalar() + { + return Execute(() => _inner.ExecuteScalar()); + } + + private T Execute(Func f) + { + return _cmdRetryPolicy.ExecuteAction(() => + { + _connection.Ensure(); + return f(); + }); + } + + public override void Prepare() + { + _inner.Prepare(); + } + + public override UpdateRowSource UpdatedRowSource + { + get => _inner.UpdatedRowSource; + set => _inner.UpdatedRowSource = value; + } + } +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs index 90b7f2902c..e3a72313f0 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs @@ -59,4 +59,4 @@ namespace Umbraco.Core.Persistence.FaultHandling { } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicy.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicy.cs index 2f8a37dc41..6d99f9f1e5 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicy.cs @@ -173,7 +173,7 @@ namespace Umbraco.Core.Persistence.FaultHandling } } - // Perform an extra check in the delay interval. Should prevent from accidentally ending up with the value of -1 that will block a thread indefinitely. + // Perform an extra check in the delay interval. Should prevent from accidentally ending up with the value of -1 that will block a thread indefinitely. // In addition, any other negative numbers will cause an ArgumentOutOfRangeException fault that will be thrown by Thread.Sleep. if (delay.TotalMilliseconds < 0) { @@ -198,7 +198,7 @@ namespace Umbraco.Core.Persistence.FaultHandling protected virtual void OnRetrying(int retryCount, Exception lastError, TimeSpan delay) { if (this.Retrying != null) - { + { this.Retrying(this, new RetryingEventArgs(retryCount, delay, lastError)); } } @@ -237,4 +237,4 @@ namespace Umbraco.Core.Persistence.FaultHandling } #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicyFactory.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicyFactory.cs index cacb46b580..0729d67ee0 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicyFactory.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryPolicyFactory.cs @@ -54,4 +54,4 @@ namespace Umbraco.Core.Persistence.FaultHandling return retryPolicy; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryStrategy.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryStrategy.cs index 1cf6029984..0f79e78a7f 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/RetryStrategy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryStrategy.cs @@ -80,7 +80,7 @@ namespace Umbraco.Core.Persistence.FaultHandling public static readonly RetryStrategy DefaultExponential = new ExponentialBackoff(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultClientBackoff); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name of the retry strategy. /// a value indicating whether or not the very first retry attempt will be made immediately @@ -108,4 +108,4 @@ namespace Umbraco.Core.Persistence.FaultHandling /// The ShouldRetry delegate. public abstract ShouldRetry GetShouldRetry(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryingEventArgs.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryingEventArgs.cs index 2767249f31..99eeb29688 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/RetryingEventArgs.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryingEventArgs.cs @@ -37,4 +37,4 @@ namespace Umbraco.Core.Persistence.FaultHandling /// public Exception LastException { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/ExponentialBackoff.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/ExponentialBackoff.cs index d5c43f798c..e8954d334a 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/ExponentialBackoff.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/ExponentialBackoff.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies private readonly TimeSpan deltaBackoff; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public ExponentialBackoff() : this(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultClientBackoff) @@ -21,7 +21,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The maximum number of retry attempts. /// The minimum back-off time @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name of the retry strategy. /// The maximum number of retry attempts. @@ -46,7 +46,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name of the retry strategy. /// The maximum number of retry attempts. @@ -54,7 +54,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies /// The maximum back-off time. /// The value that will be used for calculating a random delta in the exponential delay between retries. /// - /// Indicates whether or not the very first retry attempt will be made immediately + /// Indicates whether or not the very first retry attempt will be made immediately /// whereas the subsequent retries will remain subject to retry interval. /// public ExponentialBackoff(string name, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff, bool firstFastRetry) @@ -97,4 +97,4 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies }; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/FixedInterval.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/FixedInterval.cs index 5c75aef559..289d9d7a90 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/FixedInterval.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/FixedInterval.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies private readonly TimeSpan retryInterval; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public FixedInterval() : this(DefaultClientRetryCount) @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The number of retry attempts. public FixedInterval(int retryCount) @@ -28,7 +28,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The number of retry attempts. /// The time interval between retries. @@ -38,7 +38,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The retry strategy name. /// The number of retry attempts. @@ -49,7 +49,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The retry strategy name. /// The number of retry attempts. @@ -93,4 +93,4 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies }; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/Incremental.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/Incremental.cs index b3ab72b6a3..18f2575fea 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/Incremental.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/Incremental.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies private readonly TimeSpan increment; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public Incremental() : this(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement) @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The number of retry attempts. /// The initial interval that will apply for the first retry. @@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The retry strategy name. /// The number of retry attempts. @@ -43,7 +43,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The retry strategy name. /// The number of retry attempts. @@ -83,4 +83,4 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies }; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs index 152d1388f8..ea28f5f8b7 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/NetworkConnectivityErrorDetectionStrategy.cs @@ -17,8 +17,8 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies switch (sqlException.Number) { // SQL Error Code: 11001 - // A network-related or instance-specific error occurred while establishing a connection to SQL Server. - // The server was not found or was not accessible. Verify that the instance name is correct and that SQL + // A network-related or instance-specific error occurred while establishing a connection to SQL Server. + // The server was not found or was not accessible. Verify that the instance name is correct and that SQL // Server is configured to allow remote connections. (provider: TCP Provider, error: 0 - No such host is known.) case 11001: return true; @@ -28,4 +28,4 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies return false; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs index 190b33c3de..1164719538 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs @@ -105,32 +105,32 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies // An established connection was aborted by the software in your host machine. case 10053: // SQL Error Code: 10054 - // A transport-level error has occurred when sending the request to the server. + // A transport-level error has occurred when sending the request to the server. // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) case 10054: // SQL Error Code: 10060 - // A network-related or instance-specific error occurred while establishing a connection to SQL Server. - // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server - // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed - // because the connected party did not properly respond after a period of time, or established connection failed + // A network-related or instance-specific error occurred while establishing a connection to SQL Server. + // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server + // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed + // because the connected party did not properly respond after a period of time, or established connection failed // because connected host has failed to respond.)"} case 10060: // SQL Error Code: 40613 - // Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer + // Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer // support, and provide them the session tracing ID of ZZZZZ. case 40613: // SQL Error Code: 40143 // The service has encountered an error processing your request. Please try again. case 40143: // SQL Error Code: 233 - // The client was unable to establish a connection because of an error during connection initialization process before login. - // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy - // to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server. + // The client was unable to establish a connection because of an error during connection initialization process before login. + // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy + // to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server. // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) case 233: // SQL Error Code: 64 - // A connection was successfully established with the server, but then an error occurred during the login process. - // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) + // A connection was successfully established with the server, but then an error occurred during the login process. + // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) case 64: // DBNETLIB Error Code: 20 // The instance of SQL Server you attempted to connect to does not support encryption. @@ -158,4 +158,4 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/FaultHandling/ThrottlingCondition.cs b/src/Umbraco.Core/Persistence/FaultHandling/ThrottlingCondition.cs index 7aa27355ac..6b623cd5ce 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/ThrottlingCondition.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/ThrottlingCondition.cs @@ -50,8 +50,8 @@ namespace Umbraco.Core.Persistence.FaultHandling None = 0, /// - /// Corresponds to a Soft throttling type. Soft throttling is applied when machine resources such as, CPU, IO, storage, and worker threads exceed - /// predefined safety thresholds despite the load balancer’s best efforts. + /// Corresponds to a Soft throttling type. Soft throttling is applied when machine resources such as, CPU, IO, storage, and worker threads exceed + /// predefined safety thresholds despite the load balancer’s best efforts. /// Soft = 1, @@ -171,7 +171,7 @@ namespace Umbraco.Core.Persistence.FaultHandling /// public bool IsThrottledOnDataSpace { - get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.PhysicalDatabaseSpace).Count() > 0; } + get { return throttledResources.Any(x => x.Item1 == ThrottledResourceType.PhysicalDatabaseSpace); } } /// @@ -179,7 +179,7 @@ namespace Umbraco.Core.Persistence.FaultHandling /// public bool IsThrottledOnLogSpace { - get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.PhysicalLogSpace).Count() > 0; } + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.PhysicalLogSpace); } } /// @@ -187,7 +187,7 @@ namespace Umbraco.Core.Persistence.FaultHandling /// public bool IsThrottledOnLogWrite { - get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.LogWriteIoDelay).Count() > 0; } + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.LogWriteIoDelay); } } /// @@ -195,7 +195,7 @@ namespace Umbraco.Core.Persistence.FaultHandling /// public bool IsThrottledOnDataRead { - get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.DataReadIoDelay).Count() > 0; } + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.DataReadIoDelay); } } /// @@ -203,7 +203,7 @@ namespace Umbraco.Core.Persistence.FaultHandling /// public bool IsThrottledOnCpu { - get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.Cpu).Count() > 0; } + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.Cpu); } } /// @@ -211,7 +211,7 @@ namespace Umbraco.Core.Persistence.FaultHandling /// public bool IsThrottledOnDatabaseSize { - get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.DatabaseSize).Count() > 0; } + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.DatabaseSize); } } /// @@ -219,7 +219,7 @@ namespace Umbraco.Core.Persistence.FaultHandling /// public bool IsThrottledOnWorkerThreads { - get { return this.throttledResources.Where(x => x.Item1 == ThrottledResourceType.WorkerThreads).Count() > 0; } + get { return this.throttledResources.Any(x => x.Item1 == ThrottledResourceType.WorkerThreads); } } /// @@ -327,4 +327,4 @@ namespace Umbraco.Core.Persistence.FaultHandling return result.ToString(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/IDatabaseFactory.cs b/src/Umbraco.Core/Persistence/IDatabaseFactory.cs deleted file mode 100644 index 8def46b0fb..0000000000 --- a/src/Umbraco.Core/Persistence/IDatabaseFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.ComponentModel; - -namespace Umbraco.Core.Persistence -{ - [Obsolete("Use IDatabaseFactory2 instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public interface IDatabaseFactory : IDisposable - { - /// - /// gets or creates the ambient database - /// - /// - UmbracoDatabase CreateDatabase(); - } - - /// - /// Used to create the UmbracoDatabase for use in the DatabaseContext - /// -#pragma warning disable 618 - public interface IDatabaseFactory2 : IDatabaseFactory -#pragma warning restore 618 - { - /// - /// creates a new database - /// - /// - UmbracoDatabase CreateNewDatabase(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/IQueryRepository.cs b/src/Umbraco.Core/Persistence/IQueryRepository.cs new file mode 100644 index 0000000000..3e23b72186 --- /dev/null +++ b/src/Umbraco.Core/Persistence/IQueryRepository.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence +{ + /// + /// Defines the base implementation of a querying repository. + /// + public interface IQueryRepository : IRepository + { + /// + /// Gets entities. + /// + IEnumerable Get(IQuery query); + + /// + /// Counts entities. + /// + int Count(IQuery query); + } +} diff --git a/src/Umbraco.Core/Persistence/IReadRepository.cs b/src/Umbraco.Core/Persistence/IReadRepository.cs new file mode 100644 index 0000000000..8b2a4c2533 --- /dev/null +++ b/src/Umbraco.Core/Persistence/IReadRepository.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Persistence +{ + /// + /// Defines the base implementation of a reading repository. + /// + public interface IReadRepository : IRepository + { + /// + /// Gets an entity. + /// + TEntity Get(TId id); + + /// + /// Gets entities. + /// + IEnumerable GetMany(params TId[] ids); + + /// + /// Gets a value indicating whether an entity exists. + /// + bool Exists(TId id); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/IReadWriteQueryRepository.cs b/src/Umbraco.Core/Persistence/IReadWriteQueryRepository.cs new file mode 100644 index 0000000000..aaaf276e0b --- /dev/null +++ b/src/Umbraco.Core/Persistence/IReadWriteQueryRepository.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Persistence +{ + /// + /// Defines the base implementation of a reading, writing and querying repository. + /// + public interface IReadWriteQueryRepository : IReadRepository, IWriteRepository, IQueryRepository + { } +} diff --git a/src/Umbraco.Core/Persistence/IRepository.cs b/src/Umbraco.Core/Persistence/IRepository.cs new file mode 100644 index 0000000000..9c51a8affd --- /dev/null +++ b/src/Umbraco.Core/Persistence/IRepository.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Persistence +{ + /// + /// Defines the base implementation of a repository. + /// + public interface IRepository + { } +} diff --git a/src/Umbraco.Core/Persistence/ISqlContext.cs b/src/Umbraco.Core/Persistence/ISqlContext.cs new file mode 100644 index 0000000000..f8a6bfd0fb --- /dev/null +++ b/src/Umbraco.Core/Persistence/ISqlContext.cs @@ -0,0 +1,53 @@ +using NPoco; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence +{ + /// + /// Specifies the Sql context. + /// + public interface ISqlContext + { + /// + /// Gets the Sql syntax provider. + /// + ISqlSyntaxProvider SqlSyntax { get; } + + /// + /// Gets the database type. + /// + DatabaseType DatabaseType { get; } + + /// + /// Creates a new Sql expression. + /// + Sql Sql(); + + /// + /// Creates a new Sql expression. + /// + Sql Sql(string sql, params object[] args); + + /// + /// Creates a new query expression. + /// + IQuery Query(); + + /// + /// Gets the Sql templates. + /// + SqlTemplates Templates { get; } + + /// + /// Gets the Poco data factory. + /// + IPocoDataFactory PocoDataFactory { get; } + + /// + /// Gets the mappers. + /// + IMapperCollection Mappers { get; } + } +} diff --git a/src/Umbraco.Core/Persistence/IUmbracoDatabase.cs b/src/Umbraco.Core/Persistence/IUmbracoDatabase.cs new file mode 100644 index 0000000000..899a1d0de6 --- /dev/null +++ b/src/Umbraco.Core/Persistence/IUmbracoDatabase.cs @@ -0,0 +1,24 @@ +using NPoco; + +namespace Umbraco.Core.Persistence +{ + public interface IUmbracoDatabase : IDatabase + { + /// + /// Gets the Sql context. + /// + ISqlContext SqlContext { get; } + + /// + /// Gets the database instance unique identifier as a string. + /// + /// UmbracoDatabase returns the first eight digits of its unique Guid and, in some + /// debug mode, the underlying database connection identifier (if any). + string InstanceId { get; } + + /// + /// Gets a value indicating whether the database is currently in a transaction. + /// + bool InTransaction { get; } + } +} diff --git a/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs new file mode 100644 index 0000000000..9ef320e187 --- /dev/null +++ b/src/Umbraco.Core/Persistence/IUmbracoDatabaseFactory.cs @@ -0,0 +1,41 @@ +using System; + +namespace Umbraco.Core.Persistence +{ + /// + /// Creates and manages the "ambient" database. + /// + public interface IUmbracoDatabaseFactory : IDisposable + { + /// + /// Creates a new database. + /// + /// The new database must be disposed after being used. + IUmbracoDatabase CreateDatabase(); + + /// + /// Gets a value indicating whether the database factory is configured. + /// + bool Configured { get; } + + /// + /// Gets a value indicating whether the database can connect. + /// + bool CanConnect { get; } + + /// + /// Configures the database factory. + /// + void Configure(string connectionString, string providerName); + + /// + /// Gets the Sql context. + /// + ISqlContext SqlContext { get; } + + /// + /// Configures the database factory for upgrades. + /// + void ConfigureForUpgrade(); + } +} diff --git a/src/Umbraco.Core/Persistence/IWriteRepository.cs b/src/Umbraco.Core/Persistence/IWriteRepository.cs new file mode 100644 index 0000000000..26687648b9 --- /dev/null +++ b/src/Umbraco.Core/Persistence/IWriteRepository.cs @@ -0,0 +1,19 @@ +namespace Umbraco.Core.Persistence +{ + /// + /// Defines the base implementation of a writing repository. + /// + public interface IWriteRepository : IRepository + { + /// + /// Saves an entity. + /// + void Save(TEntity entity); + + /// + /// Deletes an entity. + /// + /// + void Delete(TEntity entity); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/LocalDb.cs b/src/Umbraco.Core/Persistence/LocalDb.cs index 4dcc56a9a4..995508ecf7 100644 --- a/src/Umbraco.Core/Persistence/LocalDb.cs +++ b/src/Umbraco.Core/Persistence/LocalDb.cs @@ -106,7 +106,7 @@ namespace Umbraco.Core.Persistence // detect 14, 13, 12, 11 for (var i = 14; i > 10; i--) { - var exe = Path.Combine(programFiles, string.Format(@"Microsoft SQL Server\{0}0\Tools\Binn\SqlLocalDB.exe", i)); + var exe = Path.Combine(programFiles, $@"Microsoft SQL Server\{i}0\Tools\Binn\SqlLocalDB.exe"); if (File.Exists(exe) == false) continue; _version = i; _exe = exe; @@ -126,8 +126,7 @@ namespace Umbraco.Core.Persistence public string[] GetInstances() { EnsureAvailable(); - string output, error; - var rc = ExecuteSqlLocalDb("i", out output, out error); // info + var rc = ExecuteSqlLocalDb("i", out var output, out var error); // info if (rc != 0 || error != string.Empty) return null; return output.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); } @@ -154,8 +153,7 @@ namespace Umbraco.Core.Persistence public bool CreateInstance(string instanceName) { EnsureAvailable(); - string output, error; - return ExecuteSqlLocalDb(string.Format("c \"{0}\"", instanceName), out output, out error) == 0 && error == string.Empty; + return ExecuteSqlLocalDb($"c \"{instanceName}\"", out _, out var error) == 0 && error == string.Empty; } /// @@ -176,9 +174,8 @@ namespace Umbraco.Core.Persistence instance.DropDatabases(); // else the files remain // -i force NOWAIT, -k kills - string output, error; - return ExecuteSqlLocalDb(string.Format("p \"{0}\" -i", instanceName), out output, out error) == 0 && error == string.Empty - && ExecuteSqlLocalDb(string.Format("d \"{0}\"", instanceName), out output, out error) == 0 && error == string.Empty; + return ExecuteSqlLocalDb($"p \"{instanceName}\" -i", out _, out var error) == 0 && error == string.Empty + && ExecuteSqlLocalDb($"d \"{instanceName}\"", out _, out error) == 0 && error == string.Empty; } /// @@ -196,8 +193,7 @@ namespace Umbraco.Core.Persistence if (InstanceExists(instanceName) == false) return true; // -i force NOWAIT, -k kills - string output, error; - return ExecuteSqlLocalDb(string.Format("p \"{0}\" -i", instanceName), out output, out error) == 0 && error == string.Empty; + return ExecuteSqlLocalDb($"p \"{instanceName}\" -i", out _, out var error) == 0 && error == string.Empty; } /// @@ -213,8 +209,7 @@ namespace Umbraco.Core.Persistence { EnsureAvailable(); if (InstanceExists(instanceName) == false) return false; - string output, error; - return ExecuteSqlLocalDb(string.Format("s \"{0}\"", instanceName), out output, out error) == 0 && error == string.Empty; + return ExecuteSqlLocalDb($"s \"{instanceName}\"", out _, out var error) == 0 && error == string.Empty; } /// @@ -246,7 +241,7 @@ namespace Umbraco.Core.Persistence /// /// Gets the name of the instance. /// - public string InstanceName { get; private set; } + public string InstanceName { get; } /// /// Initializes a new instance of the class. @@ -255,7 +250,7 @@ namespace Umbraco.Core.Persistence public Instance(string instanceName) { InstanceName = instanceName; - _masterCstr = string.Format(@"Server=(localdb)\{0};Integrated Security=True;", instanceName); + _masterCstr = $@"Server=(localdb)\{instanceName};Integrated Security=True;"; } /// @@ -268,7 +263,7 @@ namespace Umbraco.Core.Persistence /// public string GetConnectionString(string databaseName) { - return _masterCstr + string.Format(@"Database={0};", databaseName); + return _masterCstr + $@"Database={databaseName};"; } /// @@ -284,10 +279,9 @@ namespace Umbraco.Core.Persistence /// public string GetAttachedConnectionString(string databaseName, string filesPath) { - string logName, baseFilename, baseLogFilename, mdfFilename, ldfFilename; - GetDatabaseFiles(databaseName, filesPath, out logName, out baseFilename, out baseLogFilename, out mdfFilename, out ldfFilename); + GetDatabaseFiles(databaseName, filesPath, out _, out _, out _, out var mdfFilename, out _); - return _masterCstr + string.Format(@"AttachDbFileName='{0}';", mdfFilename); + return _masterCstr + $@"AttachDbFileName='{mdfFilename}';"; } /// @@ -383,8 +377,7 @@ namespace Umbraco.Core.Persistence /// public bool CreateDatabase(string databaseName, string filesPath) { - string logName, baseFilename, baseLogFilename, mdfFilename, ldfFilename; - GetDatabaseFiles(databaseName, filesPath, out logName, out baseFilename, out baseLogFilename, out mdfFilename, out ldfFilename); + GetDatabaseFiles(databaseName, filesPath, out var logName, out _, out _, out var mdfFilename, out var ldfFilename); using (var conn = new SqlConnection(_masterCstr)) using (var cmd = conn.CreateCommand()) @@ -396,13 +389,10 @@ namespace Umbraco.Core.Persistence // cannot use parameters on CREATE DATABASE // ie "CREATE DATABASE @0 ..." does not work - SetCommand(cmd, string.Format(@" - CREATE DATABASE {0} - ON (NAME=N{1}, FILENAME={2}) - LOG ON (NAME=N{3}, FILENAME={4})", - QuotedName(databaseName), - QuotedName(databaseName, '\''), QuotedName(mdfFilename, '\''), - QuotedName(logName, '\''), QuotedName(ldfFilename, '\''))); + SetCommand(cmd, $@" + CREATE DATABASE {QuotedName(databaseName)} + ON (NAME=N{QuotedName(databaseName, '\'')}, FILENAME={QuotedName(mdfFilename, '\'')}) + LOG ON (NAME=N{QuotedName(logName, '\'')}, FILENAME={QuotedName(ldfFilename, '\'')})"); var unused = cmd.ExecuteNonQuery(); } @@ -624,9 +614,8 @@ namespace Umbraco.Core.Persistence { // cannot use parameters on ALTER DATABASE // ie "ALTER DATABASE @0 ..." does not work - SetCommand(cmd, string.Format(@" - ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE", - QuotedName(databaseName))); + SetCommand(cmd, $@" + ALTER DATABASE {QuotedName(databaseName)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"); var unused1 = cmd.ExecuteNonQuery(); } @@ -647,9 +636,8 @@ namespace Umbraco.Core.Persistence // cannot use parameters on DROP DATABASE // ie "DROP DATABASE @0 ..." does not work - SetCommand(cmd, string.Format(@" - DROP DATABASE {0}", - QuotedName(databaseName))); + SetCommand(cmd, $@" + DROP DATABASE {QuotedName(databaseName)}"); var unused2 = cmd.ExecuteNonQuery(); @@ -667,7 +655,7 @@ namespace Umbraco.Core.Persistence private static string GetLogFilename(string mdfFilename) { if (mdfFilename.EndsWith(".mdf") == false) - throw new ArgumentException("Not a valid MDF filename (no .mdf extension).", "mdfFilename"); + throw new ArgumentException("Not a valid MDF filename (no .mdf extension).", nameof(mdfFilename)); return mdfFilename.Substring(0, mdfFilename.Length - ".mdf".Length) + "_log.ldf"; } @@ -680,9 +668,8 @@ namespace Umbraco.Core.Persistence { // cannot use parameters on ALTER DATABASE // ie "ALTER DATABASE @0 ..." does not work - SetCommand(cmd, string.Format(@" - ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE", - QuotedName(databaseName))); + SetCommand(cmd, $@" + ALTER DATABASE {QuotedName(databaseName)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"); var unused1 = cmd.ExecuteNonQuery(); @@ -701,19 +688,16 @@ namespace Umbraco.Core.Persistence /// The directory containing database files. private static void AttachDatabase(SqlCommand cmd, string databaseName, string filesPath) { - string logName, baseFilename, baseLogFilename, mdfFilename, ldfFilename; - GetDatabaseFiles(databaseName, filesPath, out logName, out baseFilename, out baseLogFilename, out mdfFilename, out ldfFilename); + GetDatabaseFiles(databaseName, filesPath, + out var logName, out _, out _, out var mdfFilename, out var ldfFilename); // cannot use parameters on CREATE DATABASE // ie "CREATE DATABASE @0 ..." does not work - SetCommand(cmd, string.Format(@" - CREATE DATABASE {0} - ON (NAME=N{1}, FILENAME={2}) - LOG ON (NAME=N{3}, FILENAME={4}) - FOR ATTACH", - QuotedName(databaseName), - QuotedName(databaseName, '\''), QuotedName(mdfFilename, '\''), - QuotedName(logName, '\''), QuotedName(ldfFilename, '\''))); + SetCommand(cmd, $@" + CREATE DATABASE {QuotedName(databaseName)} + ON (NAME=N{QuotedName(databaseName, '\'')}, FILENAME={QuotedName(mdfFilename, '\'')}) + LOG ON (NAME=N{QuotedName(logName, '\'')}, FILENAME={QuotedName(ldfFilename, '\'')}) + FOR ATTACH"); var unused = cmd.ExecuteNonQuery(); } @@ -806,9 +790,8 @@ namespace Umbraco.Core.Persistence && (sourceExtension == null && targetExtension == null || sourceExtension == targetExtension); if (nop && delete == false) return; - string logName, baseFilename, baseLogFilename, mdfFilename, ldfFilename; GetDatabaseFiles(databaseName, filesPath, - out logName, out baseFilename, out baseLogFilename, out mdfFilename, out ldfFilename); + out _, out _, out _, out var mdfFilename, out var ldfFilename); if (sourceExtension != null) { @@ -825,9 +808,8 @@ namespace Umbraco.Core.Persistence else { // copy or copy+delete ie move - string targetLogName, targetBaseFilename, targetLogFilename, targetMdfFilename, targetLdfFilename; GetDatabaseFiles(targetDatabaseName ?? databaseName, targetFilesPath ?? filesPath, - out targetLogName, out targetBaseFilename, out targetLogFilename, out targetMdfFilename, out targetLdfFilename); + out _, out _, out _, out var targetMdfFilename, out var targetLdfFilename); if (targetExtension != null) { @@ -862,9 +844,8 @@ namespace Umbraco.Core.Persistence /// public bool DatabaseFilesExist(string databaseName, string filesPath, string extension = null) { - string logName, baseFilename, baseLogFilename, mdfFilename, ldfFilename; GetDatabaseFiles(databaseName, filesPath, - out logName, out baseFilename, out baseLogFilename, out mdfFilename, out ldfFilename); + out _, out _, out _, out var mdfFilename, out var ldfFilename); if (extension != null) { diff --git a/src/Umbraco.Core/Persistence/LockedRepository.cs b/src/Umbraco.Core/Persistence/LockedRepository.cs deleted file mode 100644 index c092a3329f..0000000000 --- a/src/Umbraco.Core/Persistence/LockedRepository.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence -{ - internal class LockedRepository - where TRepository : IDisposable, IRepository - { - public LockedRepository(IDatabaseUnitOfWork unitOfWork, TRepository repository) - { - UnitOfWork = unitOfWork; - Repository = repository; - } - - public LockedRepository(Transaction transaction, IDatabaseUnitOfWork unitOfWork, TRepository repository) - { - //Transaction = transaction; - UnitOfWork = unitOfWork; - Repository = repository; - } - - //public Transaction Transaction { get; private set; } - public IDatabaseUnitOfWork UnitOfWork { get; private set; } - public TRepository Repository { get; private set; } - - //public void Commit() - //{ - // UnitOfWork.Commit(); - // Transaction.Complete(); - //} - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/LockingRepository.cs b/src/Umbraco.Core/Persistence/LockingRepository.cs deleted file mode 100644 index 6d3d5d54c1..0000000000 --- a/src/Umbraco.Core/Persistence/LockingRepository.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence -{ - internal class LockingRepository - where TRepository : IDisposable, IRepository - { - private readonly IScopeUnitOfWorkProvider _uowProvider; - private readonly Func _repositoryFactory; - private readonly int[] _readLockIds, _writeLockIds; - - public LockingRepository(IScopeUnitOfWorkProvider uowProvider, Func repositoryFactory, - IEnumerable readLockIds, IEnumerable writeLockIds) - { - Mandate.ParameterNotNull(uowProvider, "uowProvider"); - Mandate.ParameterNotNull(repositoryFactory, "repositoryFactory"); - - _uowProvider = uowProvider; - _repositoryFactory = repositoryFactory; - _readLockIds = readLockIds == null ? new int[0] : readLockIds.ToArray(); - _writeLockIds = writeLockIds == null ? new int[0] : writeLockIds.ToArray(); - } - - public void WithReadLocked(Action> action, bool autoCommit = true) - { - using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) - { - // getting the database creates a scope and a transaction - // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) - // and will throw if outer scope (if any) has a lower isolation level - - foreach (var lockId in _readLockIds) - uow.Database.AcquireLockNodeReadLock(lockId); - - using (var repository = _repositoryFactory(uow)) - { - action(new LockedRepository(uow, repository)); - if (autoCommit == false) return; - uow.Commit(); - - } // dispose repository => dispose uow => complete (or not) scope - } // dispose uow again => nothing - } - - public TResult WithReadLocked(Func, TResult> func, bool autoCommit = true) - { - using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) - { - // getting the database creates a scope and a transaction - // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) - // and will throw if outer scope (if any) has a lower isolation level - - foreach (var lockId in _readLockIds) - uow.Database.AcquireLockNodeReadLock(lockId); - - using (var repository = _repositoryFactory(uow)) - { - var ret = func(new LockedRepository(uow, repository)); - if (autoCommit == false) return ret; - uow.Commit(); - return ret; - - } // dispose repository => dispose uow => complete (or not) scope - } // dispose uow again => nothing - } - - public void WithWriteLocked(Action> action, bool autoCommit = true) - { - using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) - { - // getting the database creates a scope and a transaction - // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) - // and will throw if outer scope (if any) has a lower isolation level - - foreach (var lockId in _writeLockIds) - uow.Database.AcquireLockNodeWriteLock(lockId); - - using (var repository = _repositoryFactory(uow)) - { - action(new LockedRepository(uow, repository)); - if (autoCommit == false) return; - uow.Commit(); - - } // dispose repository => dispose uow => complete (or not) scope - } // dispose uow again => nothing - } - - public TResult WithWriteLocked(Func, TResult> func, bool autoCommit = true) - { - using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead)) - { - // getting the database creates a scope and a transaction - // the scope is IsolationLevel.RepeatableRead (because UnitOfWork is) - // and will throw if outer scope (if any) has a lower isolation level - - foreach (var lockId in _writeLockIds) - uow.Database.AcquireLockNodeReadLock(lockId); - - using (var repository = _repositoryFactory(uow)) - { - var ret = func(new LockedRepository(uow, repository)); - if (autoCommit == false) return ret; - uow.Commit(); - return ret; - - } // dispose repository => dispose uow => complete (or not) scope - } // dispose uow again => nothing - } - } -} diff --git a/src/Umbraco.Core/Persistence/Mappers/AccessMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AccessMapper.cs index 170a8f0fc6..0465086810 100644 --- a/src/Umbraco.Core/Persistence/Mappers/AccessMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/AccessMapper.cs @@ -1,6 +1,6 @@ using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { @@ -9,19 +9,9 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - public AccessMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Key, dto => dto.Id); CacheMap(src => src.LoginNodeId, dto => dto.LoginNodeId); @@ -30,7 +20,5 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.CreateDate, dto => dto.CreateDate); CacheMap(src => src.UpdateDate, dto => dto.UpdateDate); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/AuditEntryMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AuditEntryMapper.cs new file mode 100644 index 0000000000..28c7c1eeec --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/AuditEntryMapper.cs @@ -0,0 +1,31 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a mapper for audit entry entities. + /// + [MapperFor(typeof(IAuditEntry))] + [MapperFor(typeof(AuditEntry))] + public sealed class AuditEntryMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + 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); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs new file mode 100644 index 0000000000..ad1964ee07 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs @@ -0,0 +1,24 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof(AuditItem))] + [MapperFor(typeof(IAuditItem))] + public sealed class AuditItemMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + 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); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/AuditMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AuditMapper.cs deleted file mode 100644 index c30b72fc64..0000000000 --- a/src/Umbraco.Core/Persistence/Mappers/AuditMapper.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Mappers -{ - [MapperFor(typeof(AuditItem))] - [MapperFor(typeof(IAuditItem))] - public sealed class AuditMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - public AuditMapper(ISqlSyntaxProvider sqlSyntax) : base(sqlSyntax) - { - - } - - //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 AuditMapper() - { - BuildMap(); - } - - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() - { - if (PropertyInfoCache.IsEmpty) - { - 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); - } - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs index 99f9cebf6b..794daae4ff 100644 --- a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs @@ -2,43 +2,37 @@ using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; -using umbraco.interfaces; +using NPoco; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Composing; namespace Umbraco.Core.Persistence.Mappers { public abstract class BaseMapper : IDiscoverable { - private readonly ISqlSyntaxProvider _sqlSyntax; - - protected BaseMapper() : this(SqlSyntaxContext.SqlSyntaxProvider) - { - } - - protected BaseMapper(ISqlSyntaxProvider sqlSyntax) + protected BaseMapper() { - _sqlSyntax = sqlSyntax; + Build(); } internal abstract ConcurrentDictionary PropertyInfoCache { get; } - internal abstract void BuildMap(); - - internal string Map(string propertyName, bool throws = false) + private void Build() { - DtoMapModel dtoTypeProperty; - if (PropertyInfoCache.TryGetValue(propertyName, out dtoTypeProperty)) - { - return GetColumnName(dtoTypeProperty.Type, dtoTypeProperty.PropertyInfo); - } - else - { - 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; - } + 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) @@ -50,28 +44,26 @@ namespace Umbraco.Core.Persistence.Mappers internal DtoMapModel ResolveMapping(Expression> sourceMember, Expression> destinationMember) { var source = ExpressionHelper.FindProperty(sourceMember); - var destination = (PropertyInfo)ExpressionHelper.FindProperty(destinationMember); + var destination = (PropertyInfo) ExpressionHelper.FindProperty(destinationMember).Item1; if (destination == null) { throw new InvalidOperationException("The 'destination' returned was null, cannot resolve the mapping"); } - return new DtoMapModel(typeof(TDestination), destination, source.Name); + return new DtoMapModel(typeof(TDestination), destination, source.Item1.Name); } - internal virtual string GetColumnName(Type dtoType, PropertyInfo dtoProperty) + internal virtual string GetColumnName(ISqlSyntaxProvider sqlSyntax, Type dtoType, PropertyInfo dtoProperty) { var tableNameAttribute = dtoType.FirstAttribute(); - string tableName = tableNameAttribute.Value; + var tableName = tableNameAttribute.Value; var columnAttribute = dtoProperty.FirstAttribute(); - string columnName = columnAttribute.Name; + var columnName = columnAttribute.Name; - string columnMap = string.Format("{0}.{1}", - _sqlSyntax.GetQuotedTableName(tableName), - _sqlSyntax.GetQuotedColumnName(columnName)); + var columnMap = sqlSyntax.GetQuotedTableName(tableName) + "." + sqlSyntax.GetQuotedColumnName(columnName); return columnMap; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/ConsentMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ConsentMapper.cs new file mode 100644 index 0000000000..063197d1a2 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/ConsentMapper.cs @@ -0,0 +1,30 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a mapper for consent entities. + /// + [MapperFor(typeof(IConsent))] + [MapperFor(typeof(Consent))] + public sealed class ConsentMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + 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); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs index 5db0d1d3b0..5c89faf80a 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ContentMapper.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(Content))] @@ -17,52 +14,38 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - public ContentMapper(ISqlSyntaxProvider sqlSyntax) : base(sqlSyntax) - { - - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - //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 ContentMapper() + protected override void BuildMap() { - BuildMap(); - } + if (PropertyInfoCache.IsEmpty == false) return; - #region Overrides of BaseMapper + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.Key, dto => dto.UniqueId); - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } + CacheMap(src => src.VersionId, dto => dto.Id); - internal override void BuildMap() - { - if(PropertyInfoCache.IsEmpty) - { - 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.NodeName, 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.ContentTypeId, dto => dto.ContentTypeId); - CacheMap(src => src.UpdateDate, dto => dto.VersionDate); - CacheMap(src => src.Version, dto => dto.VersionId); - CacheMap(src => src.Name, dto => dto.Text); - CacheMap(src => src.ExpireDate, dto => dto.ExpiresDate); - CacheMap(src => src.ReleaseDate, dto => dto.ReleaseDate); - CacheMap(src => src.Published, dto => dto.Published); - //CacheMap(src => src.Name, dto => dto.Alias); - //CacheMap(src => src, dto => dto.Newest); - //CacheMap(src => src.Template, dto => dto.TemplateId); - } + CacheMap(src => src.NodeName, dto => dto.Text); + CacheMap(src => src.Name, dto => dto.Text); + + 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); + + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.CreatorId, dto => dto.UserId); + CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); + + CacheMap(src => src.UpdateDate, dto => dto.VersionDate); + CacheMap(src => src.ExpireDate, dto => dto.ExpiresDate); + CacheMap(src => src.ReleaseDate, dto => dto.ReleaseDate); + CacheMap(src => src.Published, dto => dto.Published); + + //CacheMap(src => src.Name, dto => dto.Alias); + //CacheMap(src => src, dto => dto.Newest); + //CacheMap(src => src.Template, dto => dto.TemplateId); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs index 943b42b2ba..3d62c0eab1 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs @@ -1,11 +1,11 @@ using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(ContentType))] @@ -14,43 +14,28 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 ContentTypeMapper() + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() { - BuildMap(); + 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.Thumbnail, dto => dto.Thumbnail); } - - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() - { - if (PropertyInfoCache.IsEmpty) - { - 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.Thumbnail, dto => dto.Thumbnail); - } - } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/DataTypeDefinitionMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DataTypeDefinitionMapper.cs deleted file mode 100644 index ea27c8699f..0000000000 --- a/src/Umbraco.Core/Persistence/Mappers/DataTypeDefinitionMapper.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents a to DTO mapper used to translate the properties of the public api - /// implementation to that of the database's DTO as sql: [tableName].[columnName]. - /// - [MapperFor(typeof(DataTypeDefinition))] - [MapperFor(typeof(IDataTypeDefinition))] - public sealed class DataTypeDefinitionMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - //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 DataTypeDefinitionMapper() - { - BuildMap(); - } - - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() - { - 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.PropertyEditorAlias, dto => dto.PropertyEditorAlias); - CacheMap(src => src.DatabaseType, dto => dto.DbType); - - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/DataTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DataTypeMapper.cs new file mode 100644 index 0000000000..a67f9301b2 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/DataTypeMapper.cs @@ -0,0 +1,36 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(DataType))] + [MapperFor(typeof(IDataType))] + public sealed class DataTypeMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() + { + 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); + + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs index fddbee347a..62974d7e49 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DictionaryMapper.cs @@ -1,13 +1,11 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(DictionaryItem))] @@ -16,28 +14,14 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 DictionaryMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { 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); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs index ebd39d95a3..af2d91e56c 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DictionaryTranslationMapper.cs @@ -1,43 +1,27 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(DictionaryTranslation))] [MapperFor(typeof(IDictionaryTranslation))] - public class DictionaryTranslationMapper : BaseMapper + public sealed class DictionaryTranslationMapper : BaseMapper { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 DictionaryTranslationMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { 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); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs index bc38f72dab..0bce8d7538 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs @@ -1,7 +1,6 @@ using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { @@ -11,17 +10,9 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - public DomainMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.RootContentId, dto => dto.RootStructureId); @@ -29,4 +20,4 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.DomainName, dto => dto.DomainName); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs b/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs index d351b8abb3..51c6520b7f 100644 --- a/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs +++ b/src/Umbraco.Core/Persistence/Mappers/DtoMapModel.cs @@ -16,4 +16,4 @@ namespace Umbraco.Core.Persistence.Mappers public Type Type { get; private set; } public PropertyInfo PropertyInfo { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs index c4226797e2..d3fb24273b 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs @@ -1,6 +1,6 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using Umbraco.Core.Models.Identity; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { @@ -16,12 +16,9 @@ namespace Umbraco.Core.Persistence.Mappers #region Overrides of BaseMapper - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.CreateDate, dto => dto.CreateDate); @@ -32,4 +29,4 @@ namespace Umbraco.Core.Persistence.Mappers #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/IMapperCollection.cs b/src/Umbraco.Core/Persistence/Mappers/IMapperCollection.cs new file mode 100644 index 0000000000..dfa6a7ca5b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/IMapperCollection.cs @@ -0,0 +1,10 @@ +using System; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Persistence.Mappers +{ + public interface IMapperCollection : IBuilderCollection + { + BaseMapper this[Type type] { get; } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs b/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs index 0832aace89..b2d3c87adf 100644 --- a/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/LanguageMapper.cs @@ -1,13 +1,11 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(ILanguage))] @@ -16,25 +14,13 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - public LanguageMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.IsoCode, dto => dto.IsoCode); CacheMap(src => src.CultureName, dto => dto.CultureName); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs index 05246ab59d..c60feea9b8 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MacroMapper.cs @@ -1,6 +1,6 @@ using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { @@ -10,21 +10,9 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 MacroMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.Alias, dto => dto.Alias); @@ -35,11 +23,9 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.DontRender, dto => dto.DontRender); CacheMap(src => src.Name, dto => dto.Name); CacheMap(src => src.CacheDuration, dto => dto.RefreshRate); - CacheMap(src => src.ScriptPath, dto => dto.Python); + CacheMap(src => src.ScriptPath, dto => dto.MacroFilePath); CacheMap(src => src.UseInEditor, dto => dto.UseInEditor); CacheMap(src => src.XsltPath, dto => dto.Xslt); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MapperCollection.cs b/src/Umbraco.Core/Persistence/Mappers/MapperCollection.cs new file mode 100644 index 0000000000..343406378a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/MapperCollection.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Persistence.Mappers +{ + public class MapperCollection : BuilderCollectionBase, IMapperCollection + { + public MapperCollection(IEnumerable items) + : base(items) + { } + + // maintain our own index for faster lookup + private readonly ConcurrentDictionary _index = new ConcurrentDictionary(); + + public BaseMapper this[Type type] + { + get + { + return _index.GetOrAdd(type, t => + { + // check if any of the mappers are assigned to this type + var mapper = this.FirstOrDefault(x => x.GetType() + .GetCustomAttributes(false) + .Any(m => m.EntityType == type)); + + if (mapper != null) return mapper; + + throw new Exception($"Could not find a mapper matching type {type.FullName}."); + }); + } + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs new file mode 100644 index 0000000000..82085027ab --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs @@ -0,0 +1,62 @@ +using LightInject; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Persistence.Mappers +{ + public class MapperCollectionBuilder : LazyCollectionBuilderBase + { + public MapperCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override MapperCollectionBuilder This => this; + + protected override void Initialize() + { + base.Initialize(); + + // default initializer registers + // - service MapperCollectionBuilder, returns MapperCollectionBuilder + // - service MapperCollection, returns MapperCollectionBuilder's collection + // we want to register extra + // - service IMapperCollection, returns MappersCollectionBuilder's collection + + Container.Register(factory => factory.GetInstance()); + } + + public MapperCollectionBuilder AddCore() + { + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + Add(); + return this; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MapperForAttribute.cs b/src/Umbraco.Core/Persistence/Mappers/MapperForAttribute.cs index 44ee135b07..63686f2476 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MapperForAttribute.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MapperForAttribute.cs @@ -16,4 +16,4 @@ namespace Umbraco.Core.Persistence.Mappers } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs deleted file mode 100644 index a4dee60ee9..0000000000 --- a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Core.Persistence.Mappers -{ - internal class MappingResolver : LazyManyObjectsResolverBase - { - /// - /// Constructor accepting a list of BaseMapper types that are attributed with the MapperFor attribute - /// - /// - /// - /// - public MappingResolver(IServiceProvider serviceProvider, ILogger logger, Func> assignedMapperTypes) - : base(serviceProvider, logger, assignedMapperTypes) - { - - } - - /// - /// Caches the type -> mapper so that we don't have to type check each time we want one or lookup the attribute - /// - private readonly ConcurrentDictionary _mapperCache = new ConcurrentDictionary(); - - /// - /// Return a mapper by type - /// - /// - /// - public virtual BaseMapper ResolveMapperByType(Type type) - { - return _mapperCache.GetOrAdd(type, type1 => - { - - //first check if we can resolve it by attribute - - var byAttribute = TryGetMapperByAttribute(type); - if (byAttribute.Success) - { - return byAttribute.Result; - } - throw new Exception("Invalid Type: A Mapper could not be resolved based on the passed in Type " + type); - }); - } - - /// - /// Check the entity type to see if it has a mapper attribute assigned and try to instantiate it - /// - /// - /// - private Attempt TryGetMapperByAttribute(Type entityType) - { - //check if any of the mappers are assigned to this type - var mapper = Values.FirstOrDefault( - x => x.GetType().GetCustomAttributes(false) - .Any(m => m.EntityType == entityType)); - - if (mapper == null) - { - return Attempt.Fail(); - } - - return Attempt.Succeed(mapper); - } - - public virtual string GetMapping(Type type, string propertyName) - { - var mapper = ResolveMapperByType(type); - var result = mapper.Map(propertyName); - if(string.IsNullOrEmpty(result)) - throw new Exception("Invalid mapping: The passed in property name could not be mapped using the passed in type"); - - return result; - } - - } - -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs index 66f6258527..c379155dd6 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MediaMapper.cs @@ -1,13 +1,11 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(IMedia))] @@ -16,40 +14,27 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 MediaMapper() + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() { - BuildMap(); + if (PropertyInfoCache.IsEmpty == false) return; + + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.Key, dto => dto.UniqueId); + + 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); } - - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() - { - if (PropertyInfoCache.IsEmpty) - { - 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.ContentTypeId, dto => dto.ContentTypeId); - CacheMap(src => src.UpdateDate, dto => dto.VersionDate); - CacheMap(src => src.Version, dto => dto.VersionId); - } - } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs index 70e4b5c861..5d433d3959 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs @@ -1,11 +1,11 @@ using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(IMediaType))] @@ -14,43 +14,28 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 MediaTypeMapper() + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() { - BuildMap(); + 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.Thumbnail, dto => dto.Thumbnail); } - - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() - { - if (PropertyInfoCache.IsEmpty) - { - 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.Thumbnail, dto => dto.Thumbnail); - } - } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberGroupMapper.cs index da57738403..0358b9e6d2 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberGroupMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberGroupMapper.cs @@ -1,6 +1,6 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { @@ -10,19 +10,9 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 MemberGroupMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.NodeId); CacheMap(src => src.CreateDate, dto => dto.CreateDate); @@ -31,4 +21,4 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Key, dto => dto.UniqueId); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs index b8c5f1f3c1..9137df341b 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberMapper.cs @@ -1,12 +1,12 @@ using System.Collections.Concurrent; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(IMember))] @@ -15,21 +15,9 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 MemberMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.NodeId); CacheMap(src => src.CreateDate, dto => dto.CreateDate); @@ -44,31 +32,28 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.ContentTypeId, dto => dto.ContentTypeId); CacheMap(src => src.ContentTypeAlias, dto => dto.Alias); CacheMap(src => src.UpdateDate, dto => dto.VersionDate); - CacheMap(src => src.Version, dto => dto.VersionId); CacheMap(src => src.Email, dto => dto.Email); CacheMap(src => src.Username, dto => dto.LoginName); CacheMap(src => src.RawPasswordValue, dto => dto.Password); - CacheMap(src => src.IsApproved, dto => dto.Integer); - CacheMap(src => src.IsLockedOut, dto => dto.Integer); - CacheMap(src => src.Comments, dto => dto.Text); - CacheMap(src => src.RawPasswordAnswerValue, dto => dto.VarChar); - CacheMap(src => src.PasswordQuestion, dto => dto.VarChar); - CacheMap(src => src.FailedPasswordAttempts, dto => dto.Integer); - CacheMap(src => src.LastLockoutDate, dto => dto.Date); - CacheMap(src => src.LastLoginDate, dto => dto.Date); - CacheMap(src => src.LastPasswordChangeDate, dto => dto.Date); + 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); /* Internal experiment */ - CacheMap(src => src.DateTimePropertyValue, dto => dto.Date); - CacheMap(src => src.IntegerPropertyValue, dto => dto.Integer); - CacheMap(src => src.BoolPropertyValue, dto => dto.Integer); - CacheMap(src => src.LongStringPropertyValue, dto => dto.Text); - CacheMap(src => src.ShortStringPropertyValue, dto => dto.VarChar); + 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); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs index 69a5190b46..b3beb17ef7 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs @@ -1,57 +1,41 @@ using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof (MemberType))] [MapperFor(typeof (IMemberType))] public sealed class MemberTypeMapper : BaseMapper { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = - new ConcurrentDictionary(); + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 MemberTypeMapper() + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() { - BuildMap(); + 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.Thumbnail, dto => dto.Thumbnail); } - - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() - { - if (PropertyInfoCache.IsEmpty) - { - 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.Thumbnail, dto => dto.Thumbnail); - } - } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/MigrationEntryMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MigrationEntryMapper.cs deleted file mode 100644 index 5c0838ede9..0000000000 --- a/src/Umbraco.Core/Persistence/Mappers/MigrationEntryMapper.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Concurrent; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Mappers -{ - [MapperFor(typeof(MigrationEntry))] - [MapperFor(typeof(IMigrationEntry))] - internal sealed class MigrationEntryMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - //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 MigrationEntryMapper() - { - BuildMap(); - } - - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.UpdateDate, dto => dto.CreateDate); - CacheMap(src => src.Version, dto => dto.Version); - CacheMap(src => src.MigrationName, dto => dto.Name); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/PetaPocoMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PetaPocoMapper.cs deleted file mode 100644 index fd516073ce..0000000000 --- a/src/Umbraco.Core/Persistence/Mappers/PetaPocoMapper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Reflection; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents the PetaPocoMapper, which is the implementation of the IMapper interface. - /// This is currently only used to ensure that nullable dates are not saved to the database. - /// - public class PetaPocoMapper : IMapper - { - public void GetTableInfo(Type t, TableInfo ti) - { - } - - public bool MapPropertyToColumn(Type t, PropertyInfo pi, ref string columnName, ref bool resultColumn) - { - return true; - } - - public Func GetFromDbConverter(PropertyInfo pi, Type sourceType) - { - return null; - } - - public Func GetToDbConverter(Type sourceType) - { - //We need this check to ensure that PetaPoco doesn't try to insert an invalid - //date from a nullable DateTime property. - if (sourceType == typeof(DateTime)) - { - return datetimeVal => - { - var datetime = datetimeVal as DateTime?; - if (datetime.HasValue && datetime.Value > DateTime.MinValue) - return datetime.Value; - - return null; - }; - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/PocoMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PocoMapper.cs new file mode 100644 index 0000000000..f531a684d7 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/PocoMapper.cs @@ -0,0 +1,31 @@ +using System; +using System.Reflection; +using NPoco; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Extends NPoco default mapper and ensures that nullable dates are not saved to the database. + /// + public class PocoMapper : DefaultMapper + { + public override Func GetToDbConverter(Type destType, MemberInfo sourceMemberInfo) + { + // ensures that NPoco does not try to insert an invalid date + // from a nullable DateTime property + if (sourceMemberInfo.GetMemberInfoType() == typeof(DateTime)) + { + return datetimeVal => + { + var datetime = datetimeVal as DateTime?; + if (datetime.HasValue && datetime.Value > DateTime.MinValue) + return datetime.Value; + + return null; + }; + } + + return null; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs index 655c40fc61..77ed355437 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyGroupMapper.cs @@ -1,13 +1,11 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(PropertyGroup))] @@ -15,28 +13,14 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 PropertyGroupMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { 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); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs index 5d7fd74dc9..0f29f7b951 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyMapper.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { @@ -11,27 +9,12 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 PropertyMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Version, dto => dto.VersionId); CacheMap(src => src.PropertyTypeId, dto => dto.PropertyTypeId); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs index 7bbd8cf798..2355d68c6a 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs @@ -1,13 +1,11 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(PropertyType))] @@ -15,38 +13,23 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 PropertyTypeMapper() + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() { - BuildMap(); + 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); } - - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() - { - if(PropertyInfoCache.IsEmpty) - { - CacheMap(src => src.Key, dto => dto.UniqueId); - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Alias, dto => dto.Alias); - CacheMap(src => src.DataTypeDefinitionId, 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.PropertyEditorAlias); - CacheMap(src => src.DataTypeDatabaseType, dto => dto.DbType); - } - } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs b/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs index 944e30c381..abcf198635 100644 --- a/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/RelationMapper.cs @@ -1,13 +1,11 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(IRelation))] @@ -16,21 +14,9 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 RelationMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.ChildId, dto => dto.ChildId); @@ -39,7 +25,5 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.ParentId, dto => dto.ParentId); CacheMap(src => src.RelationTypeId, dto => dto.RelationType); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs index 8748bd9033..c8a352a644 100644 --- a/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/RelationTypeMapper.cs @@ -1,13 +1,11 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(RelationType))] @@ -16,21 +14,9 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 RelationTypeMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.Alias, dto => dto.Alias); @@ -39,7 +25,5 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Name, dto => dto.Name); CacheMap(src => src.ParentObjectType, dto => dto.ParentObjectType); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs index 54d7e8cdc1..eaa19855ee 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ServerRegistrationMapper.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { @@ -12,21 +10,9 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 ServerRegistrationMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.IsActive, dto => dto.IsActive); @@ -36,7 +22,5 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.UpdateDate, dto => dto.DateAccessed); CacheMap(src => src.ServerIdentity, dto => dto.ServerIdentity); } - - #endregion } -} \ 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 74a0a05063..5141e8f79b 100644 --- a/src/Umbraco.Core/Persistence/Mappers/TagMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/TagMapper.cs @@ -1,11 +1,11 @@ using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(Tag))] @@ -14,30 +14,15 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 TagMapper() + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + + protected override void BuildMap() { - BuildMap(); + 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); } - - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() - { - if (PropertyInfoCache.IsEmpty) - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.Text, dto => dto.Tag); - CacheMap(src => src.Group, dto => dto.Group); - } - } - - #endregion } } diff --git a/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs index 4399fd323b..f503fc1684 100644 --- a/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/TaskTypeMapper.cs @@ -1,11 +1,11 @@ using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(TaskType))] @@ -13,26 +13,12 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 TaskTypeMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.Alias, dto => dto.Alias); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs b/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs index 0d5b485ffa..f402081e08 100644 --- a/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/TemplateMapper.cs @@ -1,16 +1,11 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(Template))] @@ -19,32 +14,17 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 TemplateMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache + protected override void BuildMap() { - get { return PropertyInfoCacheInstance; } - } + if (PropertyInfoCache.IsEmpty == false) return; - internal override void BuildMap() - { - if(PropertyInfoCache.IsEmpty) - { - 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); - CacheMap(src => src.Content, dto => dto.Design); - } + 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); + CacheMap(src => src.Content, dto => dto.Design); } - - #endregion } } diff --git a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs index 248d5ebc42..b5fb374d78 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; +using System.Collections.Concurrent; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { @@ -11,21 +9,9 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 UmbracoEntityMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.NodeId); CacheMap(src => src.CreateDate, dto => dto.CreateDate); @@ -38,7 +24,5 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Key, dto => dto.UniqueId); CacheMap(src => src.CreatorId, dto => dto.UserId); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs index cd376e79a9..92e2abd03e 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs @@ -1,11 +1,11 @@ using System.Collections.Concurrent; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { /// - /// Represents a to DTO mapper used to translate the properties of the public api + /// Represents a to DTO mapper used to translate the properties of the public api /// implementation to that of the database's DTO as sql: [tableName].[columnName]. /// [MapperFor(typeof(IUserGroup))] @@ -23,12 +23,9 @@ namespace Umbraco.Core.Persistence.Mappers #region Overrides of BaseMapper - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.Alias, dto => dto.Alias); @@ -40,4 +37,4 @@ namespace Umbraco.Core.Persistence.Mappers #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs index 9d30f571b4..9dace89f3a 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Collections.Concurrent; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Mappers { @@ -12,21 +10,9 @@ namespace Umbraco.Core.Persistence.Mappers { private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - //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 UserMapper() - { - BuildMap(); - } + internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - #region Overrides of BaseMapper - - internal override ConcurrentDictionary PropertyInfoCache - { - get { return PropertyInfoCacheInstance; } - } - - internal override void BuildMap() + protected override void BuildMap() { CacheMap(src => src.Id, dto => dto.Id); CacheMap(src => src.Email, dto => dto.Email); @@ -34,7 +20,7 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.RawPasswordValue, dto => dto.Password); CacheMap(src => src.Name, dto => dto.UserName); //NOTE: This column in the db is *not* used! - //CacheMap(src => src.DefaultPermissions, dto => dto.DefaultPermissions); + //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); @@ -45,7 +31,5 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.LastPasswordChangeDate, dto => dto.LastPasswordChangeDate); CacheMap(src => src.SecurityStamp, dto => dto.SecurityStampToken); } - - #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/UserSectionMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserSectionMapper.cs index 29b739df2a..af1ccb628c 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserSectionMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserSectionMapper.cs @@ -1,6 +1,5 @@ using System.Collections.Concurrent; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Mappers { @@ -31,4 +30,4 @@ namespace Umbraco.Core.Persistence.Mappers // #endregion //} -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Mappers/UserTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserTypeMapper.cs new file mode 100644 index 0000000000..ee493a327e --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/UserTypeMapper.cs @@ -0,0 +1,28 @@ +//using System.Collections.Concurrent; +//using Umbraco.Core.Models.Membership; +//using Umbraco.Core.Models.Rdbms; + +//namespace Umbraco.Core.Persistence.Mappers +//{ +// /// +// /// Represents a to DTO mapper used to translate the properties of the public api +// /// implementation to that of the database's DTO as sql: [tableName].[columnName]. +// /// +// [MapperFor(typeof(IUserType))] +// [MapperFor(typeof(UserType))] +// public sealed class UserTypeMapper : BaseMapper +// { +// private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + +// internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; + +// protected override void BuildMap() +// { +// CacheMap(src => src.Id, dto => dto.Id); +// CacheMap(src => src.Alias, dto => dto.Alias); +// CacheMap(src => src.Name, dto => dto.Name); +// CacheMap(src => src.Permissions, dto => dto.DefaultPermissions); +// } +// } +//} +// fixme remoev this file diff --git a/src/Umbraco.Core/Persistence/Migrations/IMigration.cs b/src/Umbraco.Core/Persistence/Migrations/IMigration.cs deleted file mode 100644 index 8d0adcf4bc..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/IMigration.cs +++ /dev/null @@ -1,13 +0,0 @@ -using umbraco.interfaces; - -namespace Umbraco.Core.Persistence.Migrations -{ - /// - /// Marker interface for database migrations - /// - public interface IMigration : IDiscoverable - { - void Up(); - void Down(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/IMigrationContext.cs b/src/Umbraco.Core/Persistence/Migrations/IMigrationContext.cs deleted file mode 100644 index 159efb6ff8..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/IMigrationContext.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Persistence.Migrations -{ - public interface IMigrationContext - { - ICollection Expressions { get; set; } - DatabaseProviders CurrentDatabaseProvider { get; } - Database Database { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/IMigrationExpression.cs b/src/Umbraco.Core/Persistence/Migrations/IMigrationExpression.cs deleted file mode 100644 index c8d36de915..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/IMigrationExpression.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations -{ - /// - /// Marker interface for migration expressions - /// - public interface IMigrationExpression - { - string Process(Database database); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs deleted file mode 100644 index 1f94d30b0f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ /dev/null @@ -1,340 +0,0 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Migrations.Initial -{ - /// - /// Represents the initial data creation by running Insert for the base data. - /// - internal class BaseDataCreation - { - private readonly Database _database; - private readonly ILogger _logger; - - public BaseDataCreation(Database database, ILogger logger) - { - _database = database; - _logger = logger; - } - - /// - /// Initialize the base data creation by inserting the data foundation for umbraco - /// specific to a table - /// - /// Name of the table to create base data for - public void InitializeBaseData(string tableName) - { - _logger.Info(string.Format("Creating data in table {0}", tableName)); - - if(tableName.Equals("umbracoNode")) - { - CreateUmbracNodeData(); - } - - if (tableName.Equals("umbracoLock")) - { - CreateUmbracoLockData(); - } - - if (tableName.Equals("cmsContentType")) - { - CreateCmsContentTypeData(); - } - - if (tableName.Equals("umbracoUser")) - { - CreateUmbracoUserData(); - } - - if (tableName.Equals("umbracoUserGroup")) - { - CreateUmbracoUserGroupData(); - } - - if (tableName.Equals("umbracoUser2UserGroup")) - { - CreateUmbracoUser2UserGroupData(); - } - - if (tableName.Equals("umbracoUserGroup2App")) - { - CreateUmbracoUserGroup2AppData(); - } - - if (tableName.Equals("cmsPropertyTypeGroup")) - { - CreateCmsPropertyTypeGroupData(); - } - - if (tableName.Equals("cmsPropertyType")) - { - CreateCmsPropertyTypeData(); - } - - if (tableName.Equals("umbracoLanguage")) - { - CreateUmbracoLanguageData(); - } - - if (tableName.Equals("cmsContentTypeAllowedContentType")) - { - CreateCmsContentTypeAllowedContentTypeData(); - } - - if(tableName.Equals("cmsDataType")) - { - CreateCmsDataTypeData(); - } - - if (tableName.Equals("cmsDataTypePreValues")) - { - CreateCmsDataTypePreValuesData(); - } - - if (tableName.Equals("umbracoRelationType")) - { - CreateUmbracoRelationTypeData(); - } - - if (tableName.Equals("cmsTaskType")) - { - CreateCmsTaskTypeData(); - } - - if (tableName.Equals("umbracoMigration")) - { - CreateUmbracoMigrationData(); - } - - _logger.Info(string.Format("Done creating data in table {0}", tableName)); - } - - private void CreateUmbracNodeData() - { - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = new Guid(Constants.ObjectTypes.SystemRoot), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.ContentRecycleBin), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.MediaRecycleBin), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -92, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-92", SortOrder = 35, UniqueId = new Guid("f0bc4bfb-b499-40d6-ba86-058885a5178c"), Text = "Label", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "True/false", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = new Guid("fbaf13a8-4036-41f2-93a3-974f678c312a"), Text = "Checkbox list", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -42, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-42", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultContentListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMembersListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = new Guid(Constants.ObjectTypes.MemberType), CreateDate = DateTime.Now }); - - //New UDI pickers with newer Ids - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Related Links", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - - //TODO: We're not creating these for 7.0 - //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1039, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1039", SortOrder = 2, UniqueId = new Guid("06f349a9-c949-4b6a-8660-59c10451af42"), Text = "Ultimate Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1038, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1038", SortOrder = 2, UniqueId = new Guid("1251c96c-185c-4e9b-93f4-b48205573cbd"), Text = "Simple Editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - //_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1042, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1042", SortOrder = 2, UniqueId = new Guid("0a452bd5-83f9-4bc3-8403-1286e13fb77e"), Text = "Macro Container", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - } - - private void CreateUmbracoLockData() - { - // all lock objects - _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" }); - } - - private void CreateCmsContentTypeData() - { - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user" }); - } - - private void CreateUmbracoUserData() - { - _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en-US", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); - } - - private void CreateUmbracoUserGroupData() - { - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); - } - - private void CreateUmbracoUser2UserGroupData() - { - _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 }); - } - - private void CreateUmbracoUserGroup2AppData() - { - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Content }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Media }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Members }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Forms }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Content }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Media }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Forms }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 4, AppAlias = Constants.Applications.Translation }); - } - - private void CreateCmsPropertyTypeGroupData() - { - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); - //membership property group - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); - } - - private void CreateCmsPropertyTypeData() - { - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - //membership property types - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); - - } - - private void CreateUmbracoLanguageData() - { - _database.Insert("umbracoLanguage", "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "en-US" }); - } - - private void CreateCmsContentTypeAllowedContentTypeData() - { - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1031 }); - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1032 }); - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1033 }); - } - - private void CreateCmsDataTypeData() - { - //TODO Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors. - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 1, DataTypeId = -49, PropertyEditorAlias = Constants.PropertyEditors.TrueFalseAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 2, DataTypeId = -51, PropertyEditorAlias = Constants.PropertyEditors.IntegerAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 3, DataTypeId = -87, PropertyEditorAlias = Constants.PropertyEditors.TinyMCEAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 4, DataTypeId = -88, PropertyEditorAlias = Constants.PropertyEditors.TextboxAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 5, DataTypeId = -89, PropertyEditorAlias = Constants.PropertyEditors.TextboxMultipleAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 6, DataTypeId = -90, PropertyEditorAlias = Constants.PropertyEditors.UploadFieldAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 7, DataTypeId = -92, PropertyEditorAlias = Constants.PropertyEditors.NoEditAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 8, DataTypeId = -36, PropertyEditorAlias = Constants.PropertyEditors.DateTimeAlias, DbType = "Date" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 9, DataTypeId = -37, PropertyEditorAlias = Constants.PropertyEditors.ColorPickerAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListMultipleAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 12, DataTypeId = -40, PropertyEditorAlias = Constants.PropertyEditors.RadioButtonListAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 13, DataTypeId = -41, PropertyEditorAlias = Constants.PropertyEditors.DateAlias, DbType = "Date" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 22, DataTypeId = 1041, PropertyEditorAlias = Constants.PropertyEditors.TagsAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 24, DataTypeId = 1043, PropertyEditorAlias = Constants.PropertyEditors.ImageCropperAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -26, DataTypeId = Constants.System.DefaultContentListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -27, DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -28, DataTypeId = Constants.System.DefaultMembersListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - - //New UDI pickers with newer Ids - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 26, DataTypeId = 1046, PropertyEditorAlias = Constants.PropertyEditors.ContentPicker2Alias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 27, DataTypeId = 1047, PropertyEditorAlias = Constants.PropertyEditors.MemberPicker2Alias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 28, DataTypeId = 1048, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 29, DataTypeId = 1049, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 30, DataTypeId = 1050, PropertyEditorAlias = Constants.PropertyEditors.RelatedLinks2Alias, DbType = "Ntext" }); - - //TODO: We're not creating these for 7.0 - //_database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 19, DataTypeId = 1038, PropertyEditorAlias = Constants.PropertyEditors.MarkdownEditorAlias, DbType = "Ntext" }); - //_database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 20, DataTypeId = 1039, PropertyEditorAlias = Constants.PropertyEditors.UltimatePickerAlias, DbType = "Ntext" }); - //_database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 23, DataTypeId = 1042, PropertyEditorAlias = Constants.PropertyEditors.MacroContainerAlias, DbType = "Ntext" }); - } - - private void CreateCmsDataTypePreValuesData() - { - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 3, Alias = "", SortOrder = 0, DataTypeNodeId = -87, Value = ",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 4, Alias = "group", SortOrder = 0, DataTypeNodeId = 1041, Value = "default" }); - - //defaults for the member list - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -1, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "10" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "username" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -3, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "asc" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); - - //layouts for the list view - var cardLayout = "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": 1, \"selected\": true}"; - var listLayout = "{\"name\": \"List\",\"path\": \"views/propertyeditors/listview/layouts/list/list.html\",\"icon\": \"icon-list\", \"isSystem\": 1,\"selected\": true}"; - - //defaults for the media list - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -5, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "100" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "updateDate" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -7, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "desc" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -8, Alias = "layouts", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[" + cardLayout + "," + listLayout + "]" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); - - //default's for MultipleMediaPickerAlias picker - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 6, Alias = "multiPicker", SortOrder = 0, DataTypeNodeId = 1049, Value = "1" }); - } - - private void CreateUmbracoRelationTypeData() - { - var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); - _database.Insert("umbracoRelationType", "id", false, relationType); - relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); - _database.Insert("umbracoRelationType", "id", false, relationType); - } - - private void CreateCmsTaskTypeData() - { - _database.Insert("cmsTaskType", "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); - } - - private void CreateUmbracoMigrationData() - { - var dto = new MigrationDto - { - Id = 1, - Name = Constants.System.UmbracoMigrationName, - Version = UmbracoVersion.GetSemanticVersion().ToString(), - CreateDate = DateTime.Now - }; - - _database.Insert("umbracoMigration", "pk", false, dto); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs deleted file mode 100644 index d1741b4ee0..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ /dev/null @@ -1,381 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Initial -{ - /// - /// Represents the initial database schema creation by running CreateTable for all DTOs against the db. - /// - public class DatabaseSchemaCreation - { - /// - /// Constructor - /// - /// - /// - /// - public DatabaseSchemaCreation(Database database, ILogger logger, ISqlSyntaxProvider sqlSyntaxProvider) - { - _database = database; - _logger = logger; - _sqlSyntaxProvider = sqlSyntaxProvider; - _schemaHelper = new DatabaseSchemaHelper(database, logger, sqlSyntaxProvider); - } - - #region Private Members - - private readonly DatabaseSchemaHelper _schemaHelper; - private readonly Database _database; - private readonly ILogger _logger; - private readonly ISqlSyntaxProvider _sqlSyntaxProvider; - - private static readonly Dictionary OrderedTables = new Dictionary - { - {0, typeof (NodeDto)}, - {1, typeof (ContentTypeDto)}, - {2, typeof (TemplateDto)}, - {3, typeof (ContentDto)}, - {4, typeof (ContentVersionDto)}, - {5, typeof (DocumentDto)}, - {6, typeof (MediaDto)}, - - {7, typeof (ContentTypeTemplateDto)}, - {8, typeof (DataTypeDto)}, - {9, typeof (DataTypePreValueDto)}, - {10, typeof (DictionaryDto)}, - {11, typeof (LanguageDto)}, - {12, typeof (LanguageTextDto)}, - {13, typeof (DomainDto)}, - {14, typeof (LogDto)}, - {15, typeof (MacroDto)}, - {16, typeof (MacroPropertyDto)}, - {17, typeof (MemberTypeDto)}, - {18, typeof (MemberDto)}, - {19, typeof (Member2MemberGroupDto)}, - {20, typeof (ContentXmlDto)}, - {21, typeof (PreviewXmlDto)}, - {22, typeof (PropertyTypeGroupDto)}, - {23, typeof (PropertyTypeDto)}, - {24, typeof (PropertyDataDto)}, - {25, typeof (RelationTypeDto)}, - {26, typeof (RelationDto)}, - - {28, typeof (TagDto)}, - {29, typeof (TagRelationshipDto)}, - // Removed in 7.6 {31, typeof (UserTypeDto)}, - {32, typeof (UserDto)}, - {33, typeof (TaskTypeDto)}, - {34, typeof (TaskDto)}, - {35, typeof (ContentType2ContentTypeDto)}, - {36, typeof (ContentTypeAllowedContentTypeDto)}, - // Removed in 7.6 {37, typeof (User2AppDto)}, - {38, typeof (User2NodeNotifyDto)}, - // Removed in 7.6 {39, typeof (User2NodePermissionDto)}, - {40, typeof (ServerRegistrationDto)}, - {41, typeof (AccessDto)}, - {42, typeof (AccessRuleDto)}, - {43, typeof (CacheInstructionDto)}, - {44, typeof (ExternalLoginDto)}, - {45, typeof (MigrationDto)}, - //46, removed: UmbracoDeployChecksumDto - //47, removed: UmbracoDeployDependencyDto - {48, typeof (RedirectUrlDto) }, - {49, typeof (LockDto) }, - {50, typeof (UserGroupDto) }, - {51, typeof (User2UserGroupDto) }, - {52, typeof (UserGroup2NodePermissionDto) }, - {53, typeof (UserGroup2AppDto) }, - {54, typeof (UserStartNodeDto) }, - {55, typeof (UserLoginDto)} - }; - #endregion - - /// - /// Drops all Umbraco tables in the db - /// - internal void UninstallDatabaseSchema() - { - _logger.Info("Start UninstallDatabaseSchema"); - - foreach (var item in OrderedTables.OrderByDescending(x => x.Key)) - { - var tableNameAttribute = item.Value.FirstAttribute(); - - string tableName = tableNameAttribute == null ? item.Value.Name : tableNameAttribute.Value; - - _logger.Info("Uninstall" + tableName); - - try - { - if (_schemaHelper.TableExist(tableName)) - { - _schemaHelper.DropTable(tableName); - } - } - catch (Exception ex) - { - //swallow this for now, not sure how best to handle this with diff databases... though this is internal - // and only used for unit tests. If this fails its because the table doesn't exist... generally! - _logger.Error("Could not drop table " + tableName, ex); - } - } - } - - - - /// - /// Initialize the database by creating the umbraco db schema - /// - public void InitializeDatabaseSchema() - { - var e = new DatabaseCreationEventArgs(); - FireBeforeCreation(e); - - if (!e.Cancel) - { - foreach (var item in OrderedTables.OrderBy(x => x.Key)) - { - _schemaHelper.CreateTable(false, item.Value); - } - } - - FireAfterCreation(e); - } - - /// - /// Validates the schema of the current database - /// - public DatabaseSchemaResult ValidateSchema() - { - var result = new DatabaseSchemaResult(); - - //get the db index defs - result.DbIndexDefinitions = _sqlSyntaxProvider.GetDefinedIndexes(_database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - - foreach (var item in OrderedTables.OrderBy(x => x.Key)) - { - var tableDefinition = DefinitionFactory.GetTableDefinition(_sqlSyntaxProvider, item.Value); - result.TableDefinitions.Add(tableDefinition); - } - - ValidateDbTables(result); - - ValidateDbColumns(result); - - ValidateDbIndexes(result); - - ValidateDbConstraints(result); - - return result; - } - - private void ValidateDbConstraints(DatabaseSchemaResult result) - { - //MySql doesn't conform to the "normal" naming of constraints, so there is currently no point in doing these checks. - //TODO: At a later point we do other checks for MySql, but ideally it should be necessary to do special checks for different providers. - // ALso note that to get the constraints for MySql we have to open a connection which we currently have not. - if (_sqlSyntaxProvider is MySqlSyntaxProvider) - return; - - //Check constraints in configured database against constraints in schema - var constraintsInDatabase = _sqlSyntaxProvider.GetConstraintsPerColumn(_database).DistinctBy(x => x.Item3).ToList(); - var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("FK_")).Select(x => x.Item3).ToList(); - var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("PK_")).Select(x => x.Item3).ToList(); - var indexesInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("IX_")).Select(x => x.Item3).ToList(); - var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); - var unknownConstraintsInDatabase = - constraintsInDatabase.Where( - x => - x.Item3.InvariantStartsWith("FK_") == false && x.Item3.InvariantStartsWith("PK_") == false && - x.Item3.InvariantStartsWith("IX_") == false).Select(x => x.Item3).ToList(); - var foreignKeysInSchema = result.TableDefinitions.SelectMany(x => x.ForeignKeys.Select(y => y.Name)).ToList(); - var primaryKeysInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => y.PrimaryKeyName)) - .Where(x => x.IsNullOrWhiteSpace() == false).ToList(); - - //Add valid and invalid foreign key differences to the result object - // We'll need to do invariant contains with case insensitivity because foreign key, primary key, and even index naming w/ MySQL is not standardized - // In theory you could have: FK_ or fk_ ...or really any standard that your development department (or developer) chooses to use. - foreach (var unknown in unknownConstraintsInDatabase) - { - if (foreignKeysInSchema.InvariantContains(unknown) || primaryKeysInSchema.InvariantContains(unknown) || indexesInSchema.InvariantContains(unknown)) - { - result.ValidConstraints.Add(unknown); - } - else - { - result.Errors.Add(new Tuple("Unknown", unknown)); - } - } - - //Foreign keys: - - var validForeignKeyDifferences = foreignKeysInDatabase.Intersect(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var foreignKey in validForeignKeyDifferences) - { - result.ValidConstraints.Add(foreignKey); - } - var invalidForeignKeyDifferences = - foreignKeysInDatabase.Except(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(foreignKeysInSchema.Except(foreignKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var foreignKey in invalidForeignKeyDifferences) - { - result.Errors.Add(new Tuple("Constraint", foreignKey)); - } - - - //Primary keys: - - //Add valid and invalid primary key differences to the result object - var validPrimaryKeyDifferences = primaryKeysInDatabase.Intersect(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var primaryKey in validPrimaryKeyDifferences) - { - result.ValidConstraints.Add(primaryKey); - } - var invalidPrimaryKeyDifferences = - primaryKeysInDatabase.Except(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(primaryKeysInSchema.Except(primaryKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var primaryKey in invalidPrimaryKeyDifferences) - { - result.Errors.Add(new Tuple("Constraint", primaryKey)); - } - - //Constaints: - - //NOTE: SD: The colIndex checks above should really take care of this but I need to keep this here because it was here before - // and some schema validation checks might rely on this data remaining here! - //Add valid and invalid index differences to the result object - var validIndexDifferences = indexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var index in validIndexDifferences) - { - result.ValidConstraints.Add(index); - } - var invalidIndexDifferences = - indexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(indexesInSchema.Except(indexesInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var index in invalidIndexDifferences) - { - result.Errors.Add(new Tuple("Constraint", index)); - } - } - - private void ValidateDbColumns(DatabaseSchemaResult result) - { - //Check columns in configured database against columns in schema - var columnsInDatabase = _sqlSyntaxProvider.GetColumnsInSchema(_database); - var columnsPerTableInDatabase = columnsInDatabase.Select(x => string.Concat(x.TableName, ",", x.ColumnName)).ToList(); - var columnsPerTableInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => string.Concat(y.TableName, ",", y.Name))).ToList(); - //Add valid and invalid column differences to the result object - var validColumnDifferences = columnsPerTableInDatabase.Intersect(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var column in validColumnDifferences) - { - result.ValidColumns.Add(column); - } - - var invalidColumnDifferences = - columnsPerTableInDatabase.Except(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(columnsPerTableInSchema.Except(columnsPerTableInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var column in invalidColumnDifferences) - { - result.Errors.Add(new Tuple("Column", column)); - } - } - - private void ValidateDbTables(DatabaseSchemaResult result) - { - //Check tables in configured database against tables in schema - var tablesInDatabase = _sqlSyntaxProvider.GetTablesInSchema(_database).ToList(); - var tablesInSchema = result.TableDefinitions.Select(x => x.Name).ToList(); - //Add valid and invalid table differences to the result object - var validTableDifferences = tablesInDatabase.Intersect(tablesInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var tableName in validTableDifferences) - { - result.ValidTables.Add(tableName); - } - - var invalidTableDifferences = - tablesInDatabase.Except(tablesInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(tablesInSchema.Except(tablesInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var tableName in invalidTableDifferences) - { - result.Errors.Add(new Tuple("Table", tableName)); - } - } - - private void ValidateDbIndexes(DatabaseSchemaResult result) - { - //These are just column indexes NOT constraints or Keys - //var colIndexesInDatabase = result.DbIndexDefinitions.Where(x => x.IndexName.InvariantStartsWith("IX_")).Select(x => x.IndexName).ToList(); - var colIndexesInDatabase = result.DbIndexDefinitions.Select(x => x.IndexName).ToList(); - var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); - - //Add valid and invalid index differences to the result object - var validColIndexDifferences = colIndexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var index in validColIndexDifferences) - { - result.ValidIndexes.Add(index); - } - - var invalidColIndexDifferences = - colIndexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(indexesInSchema.Except(colIndexesInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var index in invalidColIndexDifferences) - { - result.Errors.Add(new Tuple("Index", index)); - } - } - - #region Events - - /// - /// The save event handler - /// - internal delegate void DatabaseEventHandler(DatabaseCreationEventArgs e); - - /// - /// Occurs when [before save]. - /// - internal static event DatabaseEventHandler BeforeCreation; - /// - /// Raises the event. - /// - /// The instance containing the event data. - internal virtual void FireBeforeCreation(DatabaseCreationEventArgs e) - { - if (BeforeCreation != null) - { - BeforeCreation(e); - } - } - - /// - /// Occurs when [after save]. - /// - internal static event DatabaseEventHandler AfterCreation; - /// - /// Raises the event. - /// - /// The instance containing the event data. - internal virtual void FireAfterCreation(DatabaseCreationEventArgs e) - { - if (AfterCreation != null) - { - AfterCreation(e); - } - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs b/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs deleted file mode 100644 index 5efcfc70e8..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/LocalMigrationContext.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Linq; -using System.Runtime.Remoting.Contexts; -using System.Text; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Migrations.Syntax.Alter; -using Umbraco.Core.Persistence.Migrations.Syntax.Create; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete; -using Umbraco.Core.Persistence.Migrations.Syntax.Execute; -using Umbraco.Core.Persistence.Migrations.Syntax.Update; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations -{ - internal class LocalMigrationContext : MigrationContext - { - private readonly ISqlSyntaxProvider _sqlSyntax; - - public LocalMigrationContext(DatabaseProviders databaseProvider, Database database, ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(databaseProvider, database, logger) - { - _sqlSyntax = sqlSyntax; - } - - public IExecuteBuilder Execute - { - get { return new ExecuteBuilder(this, _sqlSyntax); } - } - - public IDeleteBuilder Delete - { - get { return new DeleteBuilder(this, _sqlSyntax); } - } - - public IUpdateBuilder Update - { - get { return new UpdateBuilder(this, _sqlSyntax); } - } - - public IAlterSyntaxBuilder Alter - { - get { return new AlterSyntaxBuilder(this, _sqlSyntax); } - } - - public ICreateBuilder Create - { - get { return new CreateBuilder(this, _sqlSyntax); } - } - - public string GetSql() - { - var sb = new StringBuilder(); - foreach (var sql in Expressions.Select(x => x.Process(Database))) - { - sb.Append(sql); - sb.AppendLine(); - sb.AppendLine("GO"); - } - return sb.ToString(); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationAttribute.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationAttribute.cs deleted file mode 100644 index b42b4aa6b8..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationAttribute.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; - -namespace Umbraco.Core.Persistence.Migrations -{ - /// - /// Represents the Migration attribute, which is used to mark classes as - /// database migrations with Up/Down methods for pushing changes UP or pulling them DOWN. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public sealed class MigrationAttribute : Attribute - { - public MigrationAttribute(string targetVersion, int sortOrder, string product) - { - TargetVersion = new Version(targetVersion); - SortOrder = sortOrder; - ProductName = product; - } - - public MigrationAttribute(string minimumCurrentVersion, string targetVersion, int sortOrder, string product) - { - TargetVersion = new Version(targetVersion); - MinimumCurrentVersion = new Version(minimumCurrentVersion); - SortOrder = sortOrder; - ProductName = product; - } - - /// - /// Gets the minimum current version for which this migration is allowed to execute - /// - public Version MinimumCurrentVersion { get; private set; } - - /// - /// Gets the target version of this migration. - /// - public Version TargetVersion { get; private set; } - - /// - /// Gets or sets the sort order, which is the order this migration will be run in. - /// - public int SortOrder { get; private set; } - - /// - /// Gets or sets the name of the product, which this migration belongs to. - /// - public string ProductName { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs deleted file mode 100644 index 486f84b444..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Migrations.Syntax.Alter; -using Umbraco.Core.Persistence.Migrations.Syntax.Create; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete; -using Umbraco.Core.Persistence.Migrations.Syntax.Execute; -using Umbraco.Core.Persistence.Migrations.Syntax.IfDatabase; -using Umbraco.Core.Persistence.Migrations.Syntax.Insert; -using Umbraco.Core.Persistence.Migrations.Syntax.Rename; -using Umbraco.Core.Persistence.Migrations.Syntax.Update; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations -{ - public abstract class MigrationBase : IMigration - { - public ISqlSyntaxProvider SqlSyntax { get; private set; } - public ILogger Logger { get; private set; } - - protected MigrationBase(ISqlSyntaxProvider sqlSyntax, ILogger logger) - { - SqlSyntax = sqlSyntax; - Logger = logger; - } - - public IMigrationContext Context; - - public abstract void Up(); - public abstract void Down(); - - public virtual void GetUpExpressions(IMigrationContext context) - { - Context = context; - Up(); - } - - public virtual void GetDownExpressions(IMigrationContext context) - { - Context = context; - Down(); - } - - public IAlterSyntaxBuilder Alter - { - get { return new AlterSyntaxBuilder(Context, SqlSyntax); } - } - - public ICreateBuilder Create - { - get { return new CreateBuilder(Context, SqlSyntax); } - } - - public IDeleteBuilder Delete - { - get { return new DeleteBuilder(Context, SqlSyntax); } - } - - public IExecuteBuilder Execute - { - get { return new ExecuteBuilder(Context, SqlSyntax); } - } - - public IInsertBuilder Insert - { - get { return new InsertBuilder(Context, SqlSyntax); } - } - - public IRenameBuilder Rename - { - get { return new RenameBuilder(Context, SqlSyntax); } - } - - public IUpdateBuilder Update - { - get { return new UpdateBuilder(Context, SqlSyntax); } - } - - public IIfDatabaseBuilder IfDatabase(params DatabaseProviders[] databaseProviders) - { - return new IfDatabaseBuilder(Context, SqlSyntax, databaseProviders); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationContext.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationContext.cs deleted file mode 100644 index 07fc3db387..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Persistence.Migrations -{ - internal class MigrationContext : IMigrationContext - { - public MigrationContext(DatabaseProviders databaseProvider, Database database, ILogger logger) - { - Expressions = new Collection(); - CurrentDatabaseProvider = databaseProvider; - Database = database; - Logger = logger; - } - - public ICollection Expressions { get; set; } - - public DatabaseProviders CurrentDatabaseProvider { get; private set; } - - public Database Database { get; private set; } - - public ILogger Logger { get; private set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs deleted file mode 100644 index 35d199d5f0..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations -{ - public abstract class MigrationExpressionBase : IMigrationExpression - { - //[Obsolete("Use the other constructors specifying an ISqlSyntaxProvider instead")] - //protected MigrationExpressionBase() - // : this(SqlSyntaxContext.SqlSyntaxProvider) - //{ - //} - - //[Obsolete("Use the other constructors specifying an ISqlSyntaxProvider instead")] - //protected MigrationExpressionBase(DatabaseProviders current, DatabaseProviders[] databaseProviders) - // : this(current, databaseProviders, SqlSyntaxContext.SqlSyntaxProvider) - //{ - //} - - //protected MigrationExpressionBase(ISqlSyntaxProvider sqlSyntax) - //{ - // SqlSyntax = sqlSyntax; - //} - - protected MigrationExpressionBase(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - { - SupportedDatabaseProviders = databaseProviders; - SqlSyntax = sqlSyntax; - CurrentDatabaseProvider = current; - } - - public virtual DatabaseProviders[] SupportedDatabaseProviders { get; private set; } - public ISqlSyntaxProvider SqlSyntax { get; private set; } - public virtual DatabaseProviders CurrentDatabaseProvider { get; private set; } - - public bool IsExpressionSupported() - { - if (SupportedDatabaseProviders == null || SupportedDatabaseProviders.Any() == false) - return true; - - return SupportedDatabaseProviders.Any(x => x == CurrentDatabaseProvider); - } - - public virtual string Process(Database database) - { - return this.ToString(); - } - - /// - /// This might be useful in the future if we add it to the interface, but for now it's used to hack the DeleteAppTables & DeleteForeignKeyExpression - /// to ensure they are not executed twice. - /// - internal string Name { get; set; } - - protected string GetQuotedValue(object val) - { - if (val == null) return "NULL"; - - var type = val.GetType(); - - switch (Type.GetTypeCode(type)) - { - case TypeCode.Boolean: - return ((bool)val) ? "1" : "0"; - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - case TypeCode.SByte: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Byte: - case TypeCode.UInt16: - case TypeCode.UInt32: - case TypeCode.UInt64: - return val.ToString(); - case TypeCode.DateTime: - return SqlSyntax.GetQuotedValue(SqlSyntax.FormatDateTime((DateTime) val)); - default: - return SqlSyntax.GetQuotedValue(val.ToString()); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationResolver.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationResolver.cs deleted file mode 100644 index 6174d599dd..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationResolver.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations -{ - /// - /// A resolver to return all IMigrations - /// - internal class MigrationResolver : LazyManyObjectsResolverBase - { - /// - /// Constructor - /// - /// - /// - /// - /// Use transient objects as we don't want these as singletons and take up memory that is not required - /// - public MigrationResolver(ILogger logger, Func> migrations) - : base(new MigrationServiceProvider(), logger, migrations, ObjectLifetimeScope.Transient) - { - } - - /// - /// Gets the migrations - /// - public IEnumerable Migrations - { - get { return Values; } - } - - - /// - /// This will ctor the IMigration instances - /// - /// - /// This is like a super crappy DI - in v8 we have real DI - /// - private class MigrationServiceProvider : IServiceProvider - { - public object GetService(Type serviceType) - { - var normalArgs = new[] {typeof (ISqlSyntaxProvider), typeof (ILogger)}; - var found = serviceType.GetConstructor(normalArgs); - if (found != null) - return found.Invoke(new object[] - { - ApplicationContext.Current.DatabaseContext.SqlSyntax, - ApplicationContext.Current.ProfilingLogger.Logger - }); - //use normal ctor - return Activator.CreateInstance(serviceType); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs deleted file mode 100644 index 51751b07ad..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ /dev/null @@ -1,315 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Text; -using log4net; -using Semver; -using Umbraco.Core.Configuration; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Migrations.Syntax.IfDatabase; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Persistence.Migrations -{ - /// - /// Represents the Migration Runner, which is used to apply migrations to - /// the umbraco database. - /// - public class MigrationRunner - { - private readonly IMigrationEntryService _migrationEntryService; - private readonly ILogger _logger; - private readonly SemVersion _currentVersion; - private readonly SemVersion _targetVersion; - private readonly string _productName; - private readonly IMigration[] _migrations; - - [Obsolete("Use the ctor that specifies all dependencies instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public MigrationRunner(Version currentVersion, Version targetVersion, string productName) - : this(LoggerResolver.Current.Logger, currentVersion, targetVersion, productName) - { - } - - [Obsolete("Use the ctor that specifies all dependencies instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public MigrationRunner(ILogger logger, Version currentVersion, Version targetVersion, string productName) - : this(logger, currentVersion, targetVersion, productName, null) - { - } - - [Obsolete("Use the ctor that specifies all dependencies instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public MigrationRunner(ILogger logger, Version currentVersion, Version targetVersion, string productName, params IMigration[] migrations) - : this(ApplicationContext.Current.Services.MigrationEntryService, logger, new SemVersion(currentVersion), new SemVersion(targetVersion), productName, migrations) - { - - } - - public MigrationRunner(IMigrationEntryService migrationEntryService, ILogger logger, SemVersion currentVersion, SemVersion targetVersion, string productName, params IMigration[] migrations) - { - if (migrationEntryService == null) throw new ArgumentNullException("migrationEntryService"); - if (logger == null) throw new ArgumentNullException("logger"); - if (currentVersion == null) throw new ArgumentNullException("currentVersion"); - if (targetVersion == null) throw new ArgumentNullException("targetVersion"); - Mandate.ParameterNotNullOrEmpty(productName, "productName"); - - _migrationEntryService = migrationEntryService; - _logger = logger; - _currentVersion = currentVersion; - _targetVersion = targetVersion; - _productName = productName; - //ensure this is null if there aren't any - _migrations = migrations == null || migrations.Length == 0 ? null : migrations; - } - - /// - /// Executes the migrations against the database. - /// - /// The PetaPoco Database, which the migrations will be run against - /// Boolean indicating whether this is an upgrade or downgrade - /// True if migrations were applied, otherwise False - public virtual bool Execute(Database database, bool isUpgrade = true) - { - return Execute(database, database.GetDatabaseProvider(), isUpgrade); - } - - /// - /// Executes the migrations against the database. - /// - /// The PetaPoco Database, which the migrations will be run against - /// - /// Boolean indicating whether this is an upgrade or downgrade - /// True if migrations were applied, otherwise False - public virtual bool Execute(Database database, DatabaseProviders databaseProvider, bool isUpgrade = true) - { - _logger.Info("Initializing database migrations"); - - var foundMigrations = FindMigrations(); - - //filter all non-schema migrations - var migrations = isUpgrade - ? OrderedUpgradeMigrations(foundMigrations).ToList() - : OrderedDowngradeMigrations(foundMigrations).ToList(); - - - if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(migrations, _currentVersion, _targetVersion, _productName, true), this)) - { - _logger.Warn("Migration was cancelled by an event"); - return false; - } - - //Loop through migrations to generate sql - var migrationContext = InitializeMigrations(migrations, database, databaseProvider, isUpgrade); - - try - { - ExecuteMigrations(migrationContext, database); - } - catch (Exception ex) - { - //if this fails then the transaction will be rolled back, BUT if we are using MySql this is not the case, - //since it does not support schema changes in a transaction, see: http://dev.mysql.com/doc/refman/5.0/en/implicit-commit.html - //so in that case we have to downgrade - if (databaseProvider == DatabaseProviders.MySql) - { - throw new DataLossException( - "An error occurred running a schema migration but the changes could not be rolled back. Error: " + ex.Message + ". In some cases, it may be required that the database be restored to it's original state before running this upgrade process again.", - ex); - } - - //continue throwing the exception - throw; - } - - Migrated.RaiseEvent(new MigrationEventArgs(migrations, migrationContext, _currentVersion, _targetVersion, _productName, false), this); - - return true; - } - - /// - /// Filters and orders migrations based on the migrations listed and the currently configured version and the target installation version - /// - /// - /// - public IEnumerable OrderedUpgradeMigrations(IEnumerable foundMigrations) - { - //get the version instance to compare with the migrations, this will be a normal c# Version object with only 3 parts - var targetVersionToCompare = _targetVersion.GetVersion(3); - var currentVersionToCompare = _currentVersion.GetVersion(3); - - var migrations = (from migration in foundMigrations - let migrationAttributes = migration.GetType().GetCustomAttributes(false) - from migrationAttribute in migrationAttributes - where migrationAttribute != null - where migrationAttribute.TargetVersion > currentVersionToCompare && - migrationAttribute.TargetVersion <= targetVersionToCompare && - migrationAttribute.ProductName == _productName && - //filter if the migration specifies a minimum current version for which to execute - (migrationAttribute.MinimumCurrentVersion == null || currentVersionToCompare >= migrationAttribute.MinimumCurrentVersion) - orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder ascending - select migration).Distinct(); - return migrations; - } - - /// - /// Filters and orders migrations based on the migrations listed and the currently configured version and the target installation version - /// - /// - /// - public IEnumerable OrderedDowngradeMigrations(IEnumerable foundMigrations) - { - //get the version instance to compare with the migrations, this will be a normal c# Version object with only 3 parts - var targetVersionToCompare = _targetVersion.GetVersion(3); - var currentVersionToCompare = _currentVersion.GetVersion(3); - - var migrations = (from migration in foundMigrations - let migrationAttributes = migration.GetType().GetCustomAttributes(false) - from migrationAttribute in migrationAttributes - where migrationAttribute != null - where - migrationAttribute.TargetVersion > currentVersionToCompare && - migrationAttribute.TargetVersion <= targetVersionToCompare && - migrationAttribute.ProductName == _productName && - //filter if the migration specifies a minimum current version for which to execute - (migrationAttribute.MinimumCurrentVersion == null || currentVersionToCompare >= migrationAttribute.MinimumCurrentVersion) - orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder descending - select migration).Distinct(); - return migrations; - } - - /// - /// Find all migrations that are available through the - /// - /// An array of - protected virtual IMigration[] FindMigrations() - { - //MCH NOTE: Consider adding the ProductName filter to the Resolver so we don't get a bunch of irrelevant migrations - return _migrations ?? MigrationResolver.Current.Migrations.ToArray(); - } - - internal MigrationContext InitializeMigrations(List migrations, Database database, DatabaseProviders databaseProvider, bool isUpgrade = true) - { - //Loop through migrations to generate sql - var context = new MigrationContext(databaseProvider, database, _logger); - - foreach (var migration in migrations) - { - var baseMigration = migration as MigrationBase; - if (baseMigration != null) - { - if (isUpgrade) - { - baseMigration.GetUpExpressions(context); - _logger.Info(string.Format("Added UPGRADE migration '{0}' to context", baseMigration.GetType().Name)); - } - else - { - baseMigration.GetDownExpressions(context); - _logger.Info(string.Format("Added DOWNGRADE migration '{0}' to context", baseMigration.GetType().Name)); - } - } - else - { - //this is just a normal migration so we can only call Up/Down - if (isUpgrade) - { - migration.Up(); - _logger.Info(string.Format("Added UPGRADE migration '{0}' to context", migration.GetType().Name)); - } - else - { - migration.Down(); - _logger.Info(string.Format("Added DOWNGRADE migration '{0}' to context", migration.GetType().Name)); - } - } - } - - return context; - } - - private void ExecuteMigrations(IMigrationContext context, Database database) - { - //Transactional execution of the sql that was generated from the found migrations - using (var transaction = database.GetTransaction()) - { - int i = 1; - foreach (var expression in context.Expressions) - { - var sql = expression.Process(database); - if (string.IsNullOrEmpty(sql)) - { - i++; - continue; - } - - //TODO: We should output all of these SQL calls to files in a migration folder in App_Data/TEMP - // so if people want to executed them manually on another environment, they can. - - //The following ensures the multiple statement sare executed one at a time, this is a requirement - // of SQLCE, it's unfortunate but necessary. - // http://stackoverflow.com/questions/13665491/sql-ce-inconsistent-with-multiple-statements - var sb = new StringBuilder(); - using (var reader = new StringReader(sql)) - { - string line; - while ((line = reader.ReadLine()) != null) - { - line = line.Trim(); - if (line.Equals("GO", StringComparison.OrdinalIgnoreCase)) - { - //Execute the SQL up to the point of a GO statement - var exeSql = sb.ToString(); - _logger.Info("Executing sql statement " + i + ": " + exeSql); - database.Execute(exeSql); - - //restart the string builder - sb.Remove(0, sb.Length); - } - else - { - sb.AppendLine(line); - } - } - //execute anything remaining - if (sb.Length > 0) - { - var exeSql = sb.ToString(); - _logger.Info("Executing sql statement " + i + ": " + exeSql); - database.Execute(exeSql); - } - } - - i++; - } - - transaction.Complete(); - - //Now that this is all complete, we need to add an entry to the migrations table flagging that migrations - // for this version have executed. - //NOTE: We CANNOT do this as part of the transaction!!! This is because when upgrading to 7.3, we cannot - // create the migrations table and then add data to it in the same transaction without issuing things like GO - // commands and since we need to support all Dbs, we need to just do this after the fact. - var exists = _migrationEntryService.FindEntry(_productName, _targetVersion); - if (exists == null) - { - _migrationEntryService.CreateEntry(_productName, _targetVersion); - } - - } - } - - /// - /// Occurs before Migration - /// - public static event TypedEventHandler Migrating; - - /// - /// Occurs after Migration - /// - public static event TypedEventHandler Migrated; - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/AlterSyntaxBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/AlterSyntaxBuilder.cs deleted file mode 100644 index fc1971858f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/AlterSyntaxBuilder.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column; -using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions; -using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter -{ - public class AlterSyntaxBuilder : IAlterSyntaxBuilder - { - private readonly IMigrationContext _context; - private readonly ISqlSyntaxProvider _sqlSyntax; - private readonly DatabaseProviders[] _databaseProviders; - - public AlterSyntaxBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) - { - _context = context; - _sqlSyntax = sqlSyntax; - _databaseProviders = databaseProviders; - } - - public IAlterTableSyntax Table(string tableName) - { - var expression = new AlterTableExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; - //_context.Expressions.Add(expression); - return new AlterTableBuilder(_context, _databaseProviders, expression); - } - - /// - /// The problem with this is that only under particular circumstances is the expression added to the context - /// so you wouldn't actually know if you are using it correctly or not and chances are you are not and therefore - /// the statement won't even execute whereas using the IAlterTableSyntax to modify a column is guaranteed to add - /// the expression to the context. - /// - /// - /// - [Obsolete("Use the IAlterTableSyntax to modify a column instead, this will be removed in future versions")] - public IAlterColumnSyntax Column(string columnName) - { - var expression = new AlterColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) {Column = {Name = columnName}}; - //_context.Expressions.Add(expression); - return new AlterColumnBuilder(_context, _databaseProviders, expression); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/AlterColumnBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/AlterColumnBuilder.cs deleted file mode 100644 index a7077f2d35..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/AlterColumnBuilder.cs +++ /dev/null @@ -1,245 +0,0 @@ -using System.Data; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions; -using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column -{ - public class AlterColumnBuilder : ExpressionBuilder, - IAlterColumnSyntax, - IAlterColumnTypeSyntax, - IAlterColumnOptionForeignKeyCascadeSyntax - { - private readonly IMigrationContext _context; - private readonly DatabaseProviders[] _databaseProviders; - - public AlterColumnBuilder(IMigrationContext context, DatabaseProviders[] databaseProviders, AlterColumnExpression expression) - : base(expression) - { - _context = context; - _databaseProviders = databaseProviders; - } - - public ForeignKeyDefinition CurrentForeignKey { get; set; } - - public override ColumnDefinition GetColumnForType() - { - return Expression.Column; - } - - public IAlterColumnTypeSyntax OnTable(string name) - { - Expression.TableName = name; - return this; - } - - public IAlterColumnOptionSyntax WithDefault(SystemMethods method) - { - var dc = new AlterDefaultConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax) - { - TableName = Expression.TableName, - SchemaName = Expression.SchemaName, - ColumnName = Expression.Column.Name, - DefaultValue = method - }; - - _context.Expressions.Add(dc); - - Expression.Column.DefaultValue = method; - - return this; - } - - - public IAlterColumnOptionSyntax WithDefaultValue(object value) - { - var dc = new AlterDefaultConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax) - { - TableName = Expression.TableName, - SchemaName = Expression.SchemaName, - ColumnName = Expression.Column.Name, - DefaultValue = value - }; - - _context.Expressions.Add(dc); - - Expression.Column.DefaultValue = value; - - return this; - } - - public IAlterColumnOptionSyntax Identity() - { - Expression.Column.IsIdentity = true; - return this; - } - - public IAlterColumnOptionSyntax Indexed() - { - return Indexed(null); - } - - public IAlterColumnOptionSyntax Indexed(string indexName) - { - Expression.Column.IsIndexed = true; - - var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName - }); - - index.Index.Columns.Add(new IndexColumnDefinition - { - Name = Expression.Column.Name - }); - - _context.Expressions.Add(index); - - return this; - } - - public IAlterColumnOptionSyntax PrimaryKey() - { - Expression.Column.IsPrimaryKey = true; - return this; - } - - public IAlterColumnOptionSyntax PrimaryKey(string primaryKeyName) - { - Expression.Column.IsPrimaryKey = true; - Expression.Column.PrimaryKeyName = primaryKeyName; - return this; - } - - public IAlterColumnOptionSyntax Nullable() - { - Expression.Column.IsNullable = true; - return this; - } - - public IAlterColumnOptionSyntax NotNullable() - { - Expression.Column.IsNullable = false; - return this; - } - - public IAlterColumnOptionSyntax Unique() - { - return Unique(null); - } - - public IAlterColumnOptionSyntax Unique(string indexName) - { - Expression.Column.IsUnique = true; - - var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName, - IsUnique = true - }); - - index.Index.Columns.Add(new IndexColumnDefinition - { - Name = Expression.Column.Name - }); - - _context.Expressions.Add(index); - - return this; - } - - public IAlterColumnOptionForeignKeyCascadeSyntax ForeignKey(string primaryTableName, string primaryColumnName) - { - return ForeignKey(null, null, primaryTableName, primaryColumnName); - } - - public IAlterColumnOptionForeignKeyCascadeSyntax ForeignKey(string foreignKeyName, string primaryTableName, - string primaryColumnName) - { - return ForeignKey(foreignKeyName, null, primaryTableName, primaryColumnName); - } - - public IAlterColumnOptionForeignKeyCascadeSyntax ForeignKey(string foreignKeyName, string primaryTableSchema, - string primaryTableName, string primaryColumnName) - { - Expression.Column.IsForeignKey = true; - - var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = primaryTableName, - PrimaryTableSchema = primaryTableSchema, - ForeignTable = Expression.TableName, - ForeignTableSchema = Expression.SchemaName - }); - - fk.ForeignKey.PrimaryColumns.Add(primaryColumnName); - fk.ForeignKey.ForeignColumns.Add(Expression.Column.Name); - - _context.Expressions.Add(fk); - CurrentForeignKey = fk.ForeignKey; - return this; - } - - public IAlterColumnOptionForeignKeyCascadeSyntax ForeignKey() - { - Expression.Column.IsForeignKey = true; - return this; - } - - public IAlterColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignTableName, string foreignColumnName) - { - return ReferencedBy(null, null, foreignTableName, foreignColumnName); - } - - public IAlterColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableName, - string foreignColumnName) - { - return ReferencedBy(foreignKeyName, null, foreignTableName, foreignColumnName); - } - - public IAlterColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableSchema, - string foreignTableName, string foreignColumnName) - { - var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = Expression.TableName, - PrimaryTableSchema = Expression.SchemaName, - ForeignTable = foreignTableName, - ForeignTableSchema = foreignTableSchema - }); - - fk.ForeignKey.PrimaryColumns.Add(Expression.Column.Name); - fk.ForeignKey.ForeignColumns.Add(foreignColumnName); - - _context.Expressions.Add(fk); - CurrentForeignKey = fk.ForeignKey; - return this; - } - - public IAlterColumnOptionForeignKeyCascadeSyntax OnDelete(Rule rule) - { - CurrentForeignKey.OnDelete = rule; - return this; - } - - public IAlterColumnOptionForeignKeyCascadeSyntax OnUpdate(Rule rule) - { - CurrentForeignKey.OnUpdate = rule; - return this; - } - - public IAlterColumnOptionSyntax OnDeleteOrUpdate(Rule rule) - { - OnDelete(rule); - OnUpdate(rule); - return this; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnOptionForeignKeyCascadeSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnOptionForeignKeyCascadeSyntax.cs deleted file mode 100644 index c6c3aae29e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnOptionForeignKeyCascadeSyntax.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column -{ - public interface IAlterColumnOptionForeignKeyCascadeSyntax : - IAlterColumnOptionSyntax, - IForeignKeyCascadeSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnOptionSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnOptionSyntax.cs deleted file mode 100644 index 2dd4578f1d..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnOptionSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column -{ - public interface IAlterColumnOptionSyntax : IColumnOptionSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnSyntax.cs deleted file mode 100644 index 194db11ba3..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column -{ - public interface IAlterColumnSyntax : IFluentSyntax - { - IAlterColumnTypeSyntax OnTable(string name); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnTypeSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnTypeSyntax.cs deleted file mode 100644 index 0a621ed7d7..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Column/IAlterColumnTypeSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column -{ - public interface IAlterColumnTypeSyntax : IColumnTypeSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs deleted file mode 100644 index a40e7be320..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions -{ - public class AlterColumnExpression : MigrationExpressionBase - { - //public AlterColumnExpression(ISqlSyntaxProvider sqlSyntax) - // : base(sqlSyntax) - //{ - // Column = new ColumnDefinition() { ModificationType = ModificationType.Alter }; - //} - - public AlterColumnExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - Column = new ColumnDefinition() { ModificationType = ModificationType.Alter }; - } - - //[Obsolete("Use the constructor specifying an ISqlSyntaxProvider instead")] - //public AlterColumnExpression() - // : this(SqlSyntaxContext.SqlSyntaxProvider) - //{ - //} - - //[Obsolete("Use the constructor specifying an ISqlSyntaxProvider instead")] - //public AlterColumnExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders) - // : this(current, databaseProviders, SqlSyntaxContext.SqlSyntaxProvider) - //{ - //} - - - - public virtual string SchemaName { get; set; } - public virtual string TableName { get; set; } - public virtual ColumnDefinition Column { get; set; } - - public override string ToString() - { - //string columnNameFormat = string.Format("{0} {1}", - // SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(Column.Name), - // SqlSyntaxContext.SqlSyntaxProvider.Format(Column)); - - return string.Format(SqlSyntax.AlterColumn, - SqlSyntax.GetQuotedTableName(TableName), - SqlSyntax.Format(Column)); - - //return string.Format(SqlSyntaxContext.SqlSyntaxProvider.AlterColumn, - // SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(TableName), - // SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(Column.Name)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterDefaultConstraintExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterDefaultConstraintExpression.cs deleted file mode 100644 index cd83f55a35..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterDefaultConstraintExpression.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions -{ - public class AlterDefaultConstraintExpression : MigrationExpressionBase - { - public AlterDefaultConstraintExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - } - - public virtual string SchemaName { get; set; } - public virtual string TableName { get; set; } - public virtual string ColumnName { get; set; } - public virtual string ConstraintName { get; set; } - public virtual object DefaultValue { get; set; } - - public override string ToString() - { - //NOTE Should probably investigate if Deleting a Default Constraint is different from deleting a 'regular' constraint - - return string.Format(SqlSyntax.DeleteConstraint, - SqlSyntax.GetQuotedTableName(TableName), - SqlSyntax.GetQuotedName(ConstraintName)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterTableExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterTableExpression.cs deleted file mode 100644 index b5906ed324..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterTableExpression.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions -{ - public class AlterTableExpression : MigrationExpressionBase - { - - public AlterTableExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - } - - public virtual string SchemaName { get; set; } - public virtual string TableName { get; set; } - - public override string ToString() - { - return string.Format("ALTER TABLE {0}", TableName); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/IAlterSyntaxBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/IAlterSyntaxBuilder.cs deleted file mode 100644 index 77db6b8cde..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/IAlterSyntaxBuilder.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column; -using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter -{ - public interface IAlterSyntaxBuilder : IFluentSyntax - { - IAlterTableSyntax Table(string tableName); - - /// - /// The problem with this is that only under particular circumstances is the expression added to the context - /// so you wouldn't actually know if you are using it correctly or not and chances are you are not and therefore - /// the statement won't even execute whereas using the IAlterTableSyntax to modify a column is guaranteed to add - /// the expression to the context. - /// - /// - /// - [Obsolete("Use the IAlterTableSyntax to modify a column instead, this will be removed in future versions")] - IAlterColumnSyntax Column(string columnName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/AlterTableBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/AlterTableBuilder.cs deleted file mode 100644 index 7a6ab8d47a..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/AlterTableBuilder.cs +++ /dev/null @@ -1,262 +0,0 @@ -using System.Data; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions; -using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table -{ - public class AlterTableBuilder : ExpressionBuilder, - IAlterTableColumnTypeSyntax, - IAlterTableColumnOptionForeignKeyCascadeSyntax - { - private readonly IMigrationContext _context; - private readonly DatabaseProviders[] _databaseProviders; - - public AlterTableBuilder(IMigrationContext context, DatabaseProviders[] databaseProviders, AlterTableExpression expression) - : base(expression) - { - _context = context; - _databaseProviders = databaseProviders; - } - - public ColumnDefinition CurrentColumn { get; set; } - - public ForeignKeyDefinition CurrentForeignKey { get; set; } - - public override ColumnDefinition GetColumnForType() - { - return CurrentColumn; - } - - public IAlterTableColumnOptionSyntax WithDefault(SystemMethods method) - { - CurrentColumn.DefaultValue = method; - return this; - } - - public IAlterTableColumnOptionSyntax WithDefaultValue(object value) - { - if (CurrentColumn.ModificationType == ModificationType.Alter) - { - var dc = new AlterDefaultConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax) - { - TableName = Expression.TableName, - SchemaName = Expression.SchemaName, - ColumnName = CurrentColumn.Name, - DefaultValue = value - }; - - _context.Expressions.Add(dc); - } - - CurrentColumn.DefaultValue = value; - return this; - } - - public IAlterTableColumnOptionSyntax Identity() - { - CurrentColumn.IsIdentity = true; - return this; - } - - public IAlterTableColumnOptionSyntax Indexed() - { - return Indexed(null); - } - - public IAlterTableColumnOptionSyntax Indexed(string indexName) - { - CurrentColumn.IsIndexed = true; - - var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName - }); - - index.Index.Columns.Add(new IndexColumnDefinition - { - Name = CurrentColumn.Name - }); - - _context.Expressions.Add(index); - - return this; - } - - public IAlterTableColumnOptionSyntax PrimaryKey() - { - CurrentColumn.IsPrimaryKey = true; - return this; - } - - public IAlterTableColumnOptionSyntax PrimaryKey(string primaryKeyName) - { - CurrentColumn.IsPrimaryKey = true; - CurrentColumn.PrimaryKeyName = primaryKeyName; - return this; - } - - public IAlterTableColumnOptionSyntax Nullable() - { - CurrentColumn.IsNullable = true; - return this; - } - - public IAlterTableColumnOptionSyntax NotNullable() - { - CurrentColumn.IsNullable = false; - return this; - } - - public IAlterTableColumnOptionSyntax Unique() - { - return Unique(null); - } - - public IAlterTableColumnOptionSyntax Unique(string indexName) - { - CurrentColumn.IsUnique = true; - - var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName, - IsUnique = true - }); - - index.Index.Columns.Add(new IndexColumnDefinition - { - Name = CurrentColumn.Name - }); - - _context.Expressions.Add(index); - - return this; - } - - public IAlterTableColumnOptionForeignKeyCascadeSyntax ForeignKey(string primaryTableName, string primaryColumnName) - { - return ForeignKey(null, null, primaryTableName, primaryColumnName); - } - - public IAlterTableColumnOptionForeignKeyCascadeSyntax ForeignKey(string foreignKeyName, string primaryTableName, - string primaryColumnName) - { - return ForeignKey(foreignKeyName, null, primaryTableName, primaryColumnName); - } - - public IAlterTableColumnOptionForeignKeyCascadeSyntax ForeignKey(string foreignKeyName, string primaryTableSchema, - string primaryTableName, string primaryColumnName) - { - CurrentColumn.IsForeignKey = true; - - var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = primaryTableName, - PrimaryTableSchema = primaryTableSchema, - ForeignTable = Expression.TableName, - ForeignTableSchema = Expression.SchemaName - }); - - fk.ForeignKey.PrimaryColumns.Add(primaryColumnName); - fk.ForeignKey.ForeignColumns.Add(CurrentColumn.Name); - - _context.Expressions.Add(fk); - CurrentForeignKey = fk.ForeignKey; - return this; - } - - public IAlterTableColumnOptionForeignKeyCascadeSyntax ForeignKey() - { - CurrentColumn.IsForeignKey = true; - return this; - } - - public IAlterTableColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignTableName, string foreignColumnName) - { - return ReferencedBy(null, null, foreignTableName, foreignColumnName); - } - - public IAlterTableColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableName, - string foreignColumnName) - { - return ReferencedBy(foreignKeyName, null, foreignTableName, foreignColumnName); - } - - public IAlterTableColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableSchema, - string foreignTableName, string foreignColumnName) - { - var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = Expression.TableName, - PrimaryTableSchema = Expression.SchemaName, - ForeignTable = foreignTableName, - ForeignTableSchema = foreignTableSchema - }); - - fk.ForeignKey.PrimaryColumns.Add(CurrentColumn.Name); - fk.ForeignKey.ForeignColumns.Add(foreignColumnName); - - _context.Expressions.Add(fk); - CurrentForeignKey = fk.ForeignKey; - return this; - } - - public IAlterTableColumnTypeSyntax AddColumn(string name) - { - var column = new ColumnDefinition { Name = name, ModificationType = ModificationType.Create }; - var createColumn = new CreateColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax) - { - Column = column, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName - }; - - CurrentColumn = column; - - _context.Expressions.Add(createColumn); - return this; - } - - public IAlterTableColumnTypeSyntax AlterColumn(string name) - { - var column = new ColumnDefinition { Name = name, ModificationType = ModificationType.Alter }; - var alterColumn = new AlterColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax) - { - Column = column, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName - }; - - CurrentColumn = column; - - _context.Expressions.Add(alterColumn); - return this; - } - - public IAlterTableColumnOptionForeignKeyCascadeSyntax OnDelete(Rule rule) - { - CurrentForeignKey.OnDelete = rule; - return this; - } - - public IAlterTableColumnOptionForeignKeyCascadeSyntax OnUpdate(Rule rule) - { - CurrentForeignKey.OnUpdate = rule; - return this; - } - - public IAlterTableColumnOptionSyntax OnDeleteOrUpdate(Rule rule) - { - OnDelete(rule); - OnUpdate(rule); - return this; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableColumnOptionForeignKeyCascadeSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableColumnOptionForeignKeyCascadeSyntax.cs deleted file mode 100644 index e9035c195c..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableColumnOptionForeignKeyCascadeSyntax.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table -{ - public interface IAlterTableColumnOptionForeignKeyCascadeSyntax : - IAlterTableColumnOptionSyntax, - IForeignKeyCascadeSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableColumnOptionSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableColumnOptionSyntax.cs deleted file mode 100644 index 956f9ee999..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableColumnOptionSyntax.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table -{ - public interface IAlterTableColumnOptionSyntax : - IColumnOptionSyntax, - IAlterTableSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableColumnTypeSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableColumnTypeSyntax.cs deleted file mode 100644 index b744836c1b..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableColumnTypeSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table -{ - public interface IAlterTableColumnTypeSyntax : IColumnTypeSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableSyntax.cs deleted file mode 100644 index 3692e2d07a..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Table/IAlterTableSyntax.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table -{ - public interface IAlterTableSyntax : IFluentSyntax - { - IAlterTableColumnTypeSyntax AddColumn(string name); - IAlterTableColumnTypeSyntax AlterColumn(string name); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/CreateColumnBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/CreateColumnBuilder.cs deleted file mode 100644 index d004cd1176..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/CreateColumnBuilder.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System.Data; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Column -{ - public class CreateColumnBuilder : ExpressionBuilder, - ICreateColumnOnTableSyntax, - ICreateColumnTypeSyntax, - ICreateColumnOptionForeignKeyCascadeSyntax - { - private readonly IMigrationContext _context; - private readonly DatabaseProviders[] _databaseProviders; - - public CreateColumnBuilder(IMigrationContext context, DatabaseProviders[] databaseProviders, CreateColumnExpression expression) - : base(expression) - { - _context = context; - _databaseProviders = databaseProviders; - } - - public ForeignKeyDefinition CurrentForeignKey { get; set; } - - public override ColumnDefinition GetColumnForType() - { - return Expression.Column; - } - - public ICreateColumnTypeSyntax OnTable(string name) - { - Expression.TableName = name; - return this; - } - - public ICreateColumnOptionSyntax WithDefault(SystemMethods method) - { - Expression.Column.DefaultValue = method; - return this; - } - - public ICreateColumnOptionSyntax WithDefaultValue(object value) - { - Expression.Column.DefaultValue = value; - return this; - } - - public ICreateColumnOptionSyntax Identity() - { - return Indexed(null); - } - - public ICreateColumnOptionSyntax Indexed() - { - return Indexed(null); - } - - public ICreateColumnOptionSyntax Indexed(string indexName) - { - Expression.Column.IsIndexed = true; - - var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName - }); - - index.Index.Columns.Add(new IndexColumnDefinition - { - Name = Expression.Column.Name - }); - - _context.Expressions.Add(index); - - return this; - } - - public ICreateColumnOptionSyntax PrimaryKey() - { - Expression.Column.IsPrimaryKey = true; - return this; - } - - public ICreateColumnOptionSyntax PrimaryKey(string primaryKeyName) - { - Expression.Column.IsPrimaryKey = true; - Expression.Column.PrimaryKeyName = primaryKeyName; - return this; - } - - public ICreateColumnOptionSyntax Nullable() - { - Expression.Column.IsNullable = true; - return this; - } - - public ICreateColumnOptionSyntax NotNullable() - { - Expression.Column.IsNullable = false; - return this; - } - - public ICreateColumnOptionSyntax Unique() - { - return Unique(null); - } - - public ICreateColumnOptionSyntax Unique(string indexName) - { - Expression.Column.IsUnique = true; - - var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName, - IsUnique = true - }); - - index.Index.Columns.Add(new IndexColumnDefinition - { - Name = Expression.Column.Name - }); - - _context.Expressions.Add(index); - - return this; - } - - public ICreateColumnOptionForeignKeyCascadeSyntax ForeignKey(string primaryTableName, string primaryColumnName) - { - return ForeignKey(null, null, primaryTableName, primaryColumnName); - } - - public ICreateColumnOptionForeignKeyCascadeSyntax ForeignKey(string foreignKeyName, string primaryTableName, - string primaryColumnName) - { - return ForeignKey(foreignKeyName, null, primaryTableName, primaryColumnName); - } - - public ICreateColumnOptionForeignKeyCascadeSyntax ForeignKey(string foreignKeyName, string primaryTableSchema, - string primaryTableName, string primaryColumnName) - { - Expression.Column.IsForeignKey = true; - - var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = primaryTableName, - PrimaryTableSchema = primaryTableSchema, - ForeignTable = Expression.TableName, - ForeignTableSchema = Expression.SchemaName - }); - - fk.ForeignKey.PrimaryColumns.Add(primaryColumnName); - fk.ForeignKey.ForeignColumns.Add(Expression.Column.Name); - - _context.Expressions.Add(fk); - CurrentForeignKey = fk.ForeignKey; - return this; - } - - public ICreateColumnOptionForeignKeyCascadeSyntax ForeignKey() - { - Expression.Column.IsForeignKey = true; - return this; - } - - public ICreateColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignTableName, string foreignColumnName) - { - return ReferencedBy(null, null, foreignTableName, foreignColumnName); - } - - public ICreateColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableName, - string foreignColumnName) - { - return ReferencedBy(foreignKeyName, null, foreignTableName, foreignColumnName); - } - - public ICreateColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableSchema, - string foreignTableName, string foreignColumnName) - { - var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = Expression.TableName, - PrimaryTableSchema = Expression.SchemaName, - ForeignTable = foreignTableName, - ForeignTableSchema = foreignTableSchema - }); - - fk.ForeignKey.PrimaryColumns.Add(Expression.Column.Name); - fk.ForeignKey.ForeignColumns.Add(foreignColumnName); - - _context.Expressions.Add(fk); - CurrentForeignKey = fk.ForeignKey; - return this; - } - - public ICreateColumnOptionForeignKeyCascadeSyntax OnDelete(Rule rule) - { - CurrentForeignKey.OnDelete = rule; - return this; - } - - public ICreateColumnOptionForeignKeyCascadeSyntax OnUpdate(Rule rule) - { - CurrentForeignKey.OnUpdate = rule; - return this; - } - - public ICreateColumnOptionSyntax OnDeleteOrUpdate(Rule rule) - { - OnDelete(rule); - OnUpdate(rule); - return this; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnOnTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnOnTableSyntax.cs deleted file mode 100644 index 530cd9740a..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnOnTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Column -{ - public interface ICreateColumnOnTableSyntax : IColumnTypeSyntax - { - ICreateColumnTypeSyntax OnTable(string name); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnOptionForeignKeyCascadeSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnOptionForeignKeyCascadeSyntax.cs deleted file mode 100644 index 7f30ff4f7a..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnOptionForeignKeyCascadeSyntax.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Column -{ - public interface ICreateColumnOptionForeignKeyCascadeSyntax : ICreateColumnOptionSyntax, - IForeignKeyCascadeSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnOptionSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnOptionSyntax.cs deleted file mode 100644 index 3699dcda89..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnOptionSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Column -{ - public interface ICreateColumnOptionSyntax : IColumnOptionSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnTypeSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnTypeSyntax.cs deleted file mode 100644 index 97ef046b8b..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Column/ICreateColumnTypeSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Column -{ - public interface ICreateColumnTypeSyntax : IColumnTypeSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Constraint/CreateConstraintBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Constraint/CreateConstraintBuilder.cs deleted file mode 100644 index acb1a3d392..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Constraint/CreateConstraintBuilder.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Constraint -{ - public class CreateConstraintBuilder : ExpressionBuilderBase, - ICreateConstraintOnTableSyntax, - ICreateConstraintColumnsSyntax - { - public CreateConstraintBuilder(CreateConstraintExpression expression) : base(expression) - { - } - - public ICreateConstraintColumnsSyntax OnTable(string tableName) - { - Expression.Constraint.TableName = tableName; - return this; - } - - public void Column(string columnName) - { - Expression.Constraint.Columns.Add(columnName); - } - - public void Columns(string[] columnNames) - { - foreach (var columnName in columnNames) - { - Expression.Constraint.Columns.Add(columnName); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Constraint/ICreateConstraintColumnsSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Constraint/ICreateConstraintColumnsSyntax.cs deleted file mode 100644 index a037b40605..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Constraint/ICreateConstraintColumnsSyntax.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Constraint -{ - public interface ICreateConstraintColumnsSyntax : IFluentSyntax - { - void Column(string columnName); - void Columns(string[] columnNames); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Constraint/ICreateConstraintOnTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Constraint/ICreateConstraintOnTableSyntax.cs deleted file mode 100644 index f42d2fcc74..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Constraint/ICreateConstraintOnTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Constraint -{ - public interface ICreateConstraintOnTableSyntax : IFluentSyntax - { - ICreateConstraintColumnsSyntax OnTable(string tableName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs deleted file mode 100644 index 6afb0a4ca7..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/CreateBuilder.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Column; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Constraint; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Expressions; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.ForeignKey; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Index; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Table; -using Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions; -using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create -{ - public class CreateBuilder : ICreateBuilder - { - private readonly IMigrationContext _context; - private readonly ISqlSyntaxProvider _sqlSyntax; - private readonly DatabaseProviders[] _databaseProviders; - - public CreateBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) - { - if (context == null) throw new ArgumentNullException("context"); - if (sqlSyntax == null) throw new ArgumentNullException("sqlSyntax"); - - _context = context; - _sqlSyntax = sqlSyntax; - _databaseProviders = databaseProviders; - } - - public void Table() - { - var tableDefinition = DefinitionFactory.GetTableDefinition(_sqlSyntax, typeof(T)); - - AddSql(_sqlSyntax.Format(tableDefinition)); - AddSql(_sqlSyntax.FormatPrimaryKey(tableDefinition)); - foreach (var sql in _sqlSyntax.Format(tableDefinition.ForeignKeys)) - AddSql(sql); - foreach (var sql in _sqlSyntax.Format(tableDefinition.Indexes)) - AddSql(sql); - } - - private void AddSql(string sql) - { - var expression = new ExecuteSqlStatementExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { SqlStatement = sql }; - _context.Expressions.Add(expression); - } - - public ICreateTableWithColumnSyntax Table(string tableName) - { - var expression = new CreateTableExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; - _context.Expressions.Add(expression); - return new CreateTableBuilder(_context, _databaseProviders, expression); - } - - public ICreateColumnOnTableSyntax Column(string columnName) - { - var expression = new CreateColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { Column = { Name = columnName } }; - _context.Expressions.Add(expression); - return new CreateColumnBuilder(_context, _databaseProviders, expression); - } - - public ICreateForeignKeyFromTableSyntax ForeignKey() - { - var expression = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax); - _context.Expressions.Add(expression); - return new CreateForeignKeyBuilder(expression); - } - - public ICreateForeignKeyFromTableSyntax ForeignKey(string foreignKeyName) - { - var expression = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { ForeignKey = { Name = foreignKeyName } }; - _context.Expressions.Add(expression); - return new CreateForeignKeyBuilder(expression); - } - - public ICreateIndexForTableSyntax Index() - { - var expression = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax); - _context.Expressions.Add(expression); - return new CreateIndexBuilder(expression); - } - - public ICreateIndexForTableSyntax Index(string indexName) - { - var expression = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { Index = { Name = indexName } }; - _context.Expressions.Add(expression); - return new CreateIndexBuilder(expression); - } - - public ICreateConstraintOnTableSyntax PrimaryKey() - { - var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.PrimaryKey); - _context.Expressions.Add(expression); - return new CreateConstraintBuilder(expression); - } - - public ICreateConstraintOnTableSyntax PrimaryKey(string primaryKeyName) - { - var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.PrimaryKey); - expression.Constraint.ConstraintName = primaryKeyName; - _context.Expressions.Add(expression); - return new CreateConstraintBuilder(expression); - } - - public ICreateConstraintOnTableSyntax UniqueConstraint() - { - var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.Unique); - _context.Expressions.Add(expression); - return new CreateConstraintBuilder(expression); - } - - public ICreateConstraintOnTableSyntax UniqueConstraint(string constraintName) - { - var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.Unique); - expression.Constraint.ConstraintName = constraintName; - _context.Expressions.Add(expression); - return new CreateConstraintBuilder(expression); - } - - public ICreateConstraintOnTableSyntax Constraint(string constraintName) - { - var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.NonUnique); - expression.Constraint.ConstraintName = constraintName; - _context.Expressions.Add(expression); - return new CreateConstraintBuilder(expression); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateConstraintExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateConstraintExpression.cs deleted file mode 100644 index a06775929b..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateConstraintExpression.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Expressions -{ - public class CreateConstraintExpression : MigrationExpressionBase - { - public CreateConstraintExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax, ConstraintType constraint) - : base(current, databaseProviders, sqlSyntax) - { - Constraint = new ConstraintDefinition(constraint); - } - - - public ConstraintDefinition Constraint { get; private set; } - - public override string ToString() - { - var constraintType = (Constraint.IsPrimaryKeyConstraint) ? "PRIMARY KEY" : "UNIQUE"; - - if (Constraint.IsPrimaryKeyConstraint && SqlSyntax.SupportsClustered()) - constraintType += " CLUSTERED"; - - if (Constraint.IsNonUniqueConstraint) - constraintType = string.Empty; - - var columns = new string[Constraint.Columns.Count]; - - for (int i = 0; i < Constraint.Columns.Count; i++) - { - columns[i] = SqlSyntax.GetQuotedColumnName(Constraint.Columns.ElementAt(i)); - } - - return string.Format(SqlSyntax.CreateConstraint, - SqlSyntax.GetQuotedTableName(Constraint.TableName), - SqlSyntax.GetQuotedName(Constraint.ConstraintName), - constraintType, - string.Join(", ", columns)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateTableExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateTableExpression.cs deleted file mode 100644 index 8cf37e74d2..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Expressions/CreateTableExpression.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Expressions -{ - public class CreateTableExpression : MigrationExpressionBase - { - //[Obsolete("Use the other constructors specifying an ISqlSyntaxProvider instead")] - //public CreateTableExpression() - //{ - // Columns = new List(); - //} - - //[Obsolete("Use the other constructors specifying an ISqlSyntaxProvider instead")] - //public CreateTableExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders) - // : base(current, databaseProviders) - //{ - // Columns = new List(); - //} - - //public CreateTableExpression(ISqlSyntaxProvider sqlSyntax) - // : base(sqlSyntax) - //{ - // Columns = new List(); - //} - - public CreateTableExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - Columns = new List(); - } - - public virtual string SchemaName { get; set; } - public virtual string TableName { get; set; } - public virtual IList Columns { get; set; } - - public override string ToString() - { - var table = new TableDefinition { Name = TableName, SchemaName = SchemaName, Columns = Columns }; - - return string.Format(SqlSyntax.Format(table)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/CreateForeignKeyBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/CreateForeignKeyBuilder.cs deleted file mode 100644 index 73fcb9571f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/CreateForeignKeyBuilder.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Data; -using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.ForeignKey -{ - public class CreateForeignKeyBuilder : ExpressionBuilderBase, - ICreateForeignKeyFromTableSyntax, - ICreateForeignKeyForeignColumnSyntax, - ICreateForeignKeyToTableSyntax, - ICreateForeignKeyPrimaryColumnSyntax, - ICreateForeignKeyCascadeSyntax - { - public CreateForeignKeyBuilder(CreateForeignKeyExpression expression) : base(expression) - { - } - - public ICreateForeignKeyForeignColumnSyntax FromTable(string table) - { - Expression.ForeignKey.ForeignTable = table; - return this; - } - - public ICreateForeignKeyToTableSyntax ForeignColumn(string column) - { - Expression.ForeignKey.ForeignColumns.Add(column); - return this; - } - - public ICreateForeignKeyToTableSyntax ForeignColumns(params string[] columns) - { - foreach (var column in columns) - Expression.ForeignKey.ForeignColumns.Add(column); - return this; - } - - public ICreateForeignKeyPrimaryColumnSyntax ToTable(string table) - { - Expression.ForeignKey.PrimaryTable = table; - return this; - } - - public ICreateForeignKeyCascadeSyntax PrimaryColumn(string column) - { - Expression.ForeignKey.PrimaryColumns.Add(column); - return this; - } - - public ICreateForeignKeyCascadeSyntax PrimaryColumns(params string[] columns) - { - foreach (var column in columns) - Expression.ForeignKey.PrimaryColumns.Add(column); - return this; - } - - public ICreateForeignKeyCascadeSyntax OnDelete(Rule rule) - { - Expression.ForeignKey.OnDelete = rule; - return this; - } - - public ICreateForeignKeyCascadeSyntax OnUpdate(Rule rule) - { - Expression.ForeignKey.OnUpdate = rule; - return this; - } - - public void OnDeleteOrUpdate(Rule rule) - { - Expression.ForeignKey.OnDelete = rule; - Expression.ForeignKey.OnUpdate = rule; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyCascadeSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyCascadeSyntax.cs deleted file mode 100644 index 7671249c2e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyCascadeSyntax.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Data; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.ForeignKey -{ - public interface ICreateForeignKeyCascadeSyntax : IFluentSyntax - { - ICreateForeignKeyCascadeSyntax OnDelete(Rule rule); - ICreateForeignKeyCascadeSyntax OnUpdate(Rule rule); - void OnDeleteOrUpdate(Rule rule); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyForeignColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyForeignColumnSyntax.cs deleted file mode 100644 index 92a7769f8d..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyForeignColumnSyntax.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.ForeignKey -{ - public interface ICreateForeignKeyForeignColumnSyntax : IFluentSyntax - { - ICreateForeignKeyToTableSyntax ForeignColumn(string column); - ICreateForeignKeyToTableSyntax ForeignColumns(params string[] columns); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyFromTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyFromTableSyntax.cs deleted file mode 100644 index 36be02faae..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyFromTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.ForeignKey -{ - public interface ICreateForeignKeyFromTableSyntax : IFluentSyntax - { - ICreateForeignKeyForeignColumnSyntax FromTable(string table); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyPrimaryColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyPrimaryColumnSyntax.cs deleted file mode 100644 index bad288b81e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyPrimaryColumnSyntax.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.ForeignKey -{ - public interface ICreateForeignKeyPrimaryColumnSyntax : IFluentSyntax - { - ICreateForeignKeyCascadeSyntax PrimaryColumn(string column); - ICreateForeignKeyCascadeSyntax PrimaryColumns(params string[] columns); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyToTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyToTableSyntax.cs deleted file mode 100644 index ea2589a2c2..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ForeignKey/ICreateForeignKeyToTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.ForeignKey -{ - public interface ICreateForeignKeyToTableSyntax : IFluentSyntax - { - ICreateForeignKeyPrimaryColumnSyntax ToTable(string table); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ICreateBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ICreateBuilder.cs deleted file mode 100644 index 3cb2fe7e9f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/ICreateBuilder.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Column; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Constraint; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.ForeignKey; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Index; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Table; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create -{ - public interface ICreateBuilder : IFluentSyntax - { - void Table(); - - ICreateTableWithColumnSyntax Table(string tableName); - ICreateColumnOnTableSyntax Column(string columnName); - - ICreateForeignKeyFromTableSyntax ForeignKey(); - ICreateForeignKeyFromTableSyntax ForeignKey(string foreignKeyName); - - ICreateIndexForTableSyntax Index(); - ICreateIndexForTableSyntax Index(string indexName); - - ICreateConstraintOnTableSyntax PrimaryKey(); - ICreateConstraintOnTableSyntax PrimaryKey(string primaryKeyName); - - ICreateConstraintOnTableSyntax UniqueConstraint(); - ICreateConstraintOnTableSyntax UniqueConstraint(string constraintName); - ICreateConstraintOnTableSyntax Constraint(string constraintName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/CreateIndexBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/CreateIndexBuilder.cs deleted file mode 100644 index 4d1396ff79..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/CreateIndexBuilder.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Index -{ - public class CreateIndexBuilder : ExpressionBuilderBase, - ICreateIndexForTableSyntax, - ICreateIndexOnColumnSyntax, - ICreateIndexColumnOptionsSyntax, - ICreateIndexOptionsSyntax - { - public CreateIndexBuilder(CreateIndexExpression expression) : base(expression) - { - } - - public IndexColumnDefinition CurrentColumn { get; set; } - - public ICreateIndexOnColumnSyntax OnTable(string tableName) - { - Expression.Index.TableName = tableName; - return this; - } - - public ICreateIndexColumnOptionsSyntax OnColumn(string columnName) - { - CurrentColumn = new IndexColumnDefinition { Name = columnName }; - Expression.Index.Columns.Add(CurrentColumn); - return this; - } - - public ICreateIndexOptionsSyntax WithOptions() - { - return this; - } - - public ICreateIndexOnColumnSyntax Ascending() - { - CurrentColumn.Direction = Direction.Ascending; - return this; - } - - public ICreateIndexOnColumnSyntax Descending() - { - CurrentColumn.Direction = Direction.Descending; - return this; - } - - ICreateIndexOnColumnSyntax ICreateIndexColumnOptionsSyntax.Unique() - { - Expression.Index.IsUnique = true; - //if it is Unique then it must be unique nonclustered and set the other flags - Expression.Index.IndexType = IndexTypes.UniqueNonClustered; - Expression.Index.IsClustered = false; - return this; - } - - public ICreateIndexOnColumnSyntax NonClustered() - { - Expression.Index.IndexType = IndexTypes.NonClustered; - Expression.Index.IsClustered = false; - Expression.Index.IndexType = IndexTypes.NonClustered; - Expression.Index.IsUnique = false; - return this; - } - - public ICreateIndexOnColumnSyntax Clustered() - { - Expression.Index.IndexType = IndexTypes.Clustered; - Expression.Index.IsClustered = true; - //if it is clustered then we have to change the index type set the other flags - Expression.Index.IndexType = IndexTypes.Clustered; - Expression.Index.IsClustered = true; - Expression.Index.IsUnique = false; - return this; - } - - ICreateIndexOnColumnSyntax ICreateIndexOptionsSyntax.Unique() - { - Expression.Index.IndexType = IndexTypes.UniqueNonClustered; - Expression.Index.IsUnique = true; - return this; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexColumnOptionsSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexColumnOptionsSyntax.cs deleted file mode 100644 index f8055376aa..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexColumnOptionsSyntax.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Index -{ - public interface ICreateIndexColumnOptionsSyntax : IFluentSyntax - { - ICreateIndexOnColumnSyntax Ascending(); - ICreateIndexOnColumnSyntax Descending(); - ICreateIndexOnColumnSyntax Unique(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexForTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexForTableSyntax.cs deleted file mode 100644 index 0ac954990b..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexForTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Index -{ - public interface ICreateIndexForTableSyntax : IFluentSyntax - { - ICreateIndexOnColumnSyntax OnTable(string tableName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexOnColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexOnColumnSyntax.cs deleted file mode 100644 index 0c829aa4ef..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexOnColumnSyntax.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Index -{ - public interface ICreateIndexOnColumnSyntax : IFluentSyntax - { - ICreateIndexColumnOptionsSyntax OnColumn(string columnName); - ICreateIndexOptionsSyntax WithOptions(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexOptionsSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexOptionsSyntax.cs deleted file mode 100644 index 4253b48088..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Index/ICreateIndexOptionsSyntax.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Index -{ - public interface ICreateIndexOptionsSyntax : IFluentSyntax - { - ICreateIndexOnColumnSyntax Unique(); - ICreateIndexOnColumnSyntax NonClustered(); - ICreateIndexOnColumnSyntax Clustered(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/CreateTableBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/CreateTableBuilder.cs deleted file mode 100644 index 86760b71bc..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/CreateTableBuilder.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System.Data; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Create.Expressions; -using Umbraco.Core.Persistence.Migrations.Syntax.Expressions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table -{ - public class CreateTableBuilder : ExpressionBuilder, - ICreateTableWithColumnSyntax, - ICreateTableColumnAsTypeSyntax, - ICreateTableColumnOptionForeignKeyCascadeSyntax - { - private readonly IMigrationContext _context; - private readonly DatabaseProviders[] _databaseProviders; - - public CreateTableBuilder(IMigrationContext context, DatabaseProviders[] databaseProviders, CreateTableExpression expression) - : base(expression) - { - _context = context; - _databaseProviders = databaseProviders; - } - - public ColumnDefinition CurrentColumn { get; set; } - - public ForeignKeyDefinition CurrentForeignKey { get; set; } - - public override ColumnDefinition GetColumnForType() - { - return CurrentColumn; - } - - public ICreateTableColumnAsTypeSyntax WithColumn(string name) - { - var column = new ColumnDefinition { Name = name, TableName = Expression.TableName, ModificationType = ModificationType.Create }; - Expression.Columns.Add(column); - CurrentColumn = column; - return this; - } - - public ICreateTableColumnOptionSyntax WithDefault(SystemMethods method) - { - CurrentColumn.DefaultValue = method; - return this; - } - - public ICreateTableColumnOptionSyntax WithDefaultValue(object value) - { - CurrentColumn.DefaultValue = value; - return this; - } - - public ICreateTableColumnOptionSyntax Identity() - { - CurrentColumn.IsIdentity = true; - return this; - } - - public ICreateTableColumnOptionSyntax Indexed() - { - return Indexed(null); - } - - public ICreateTableColumnOptionSyntax Indexed(string indexName) - { - CurrentColumn.IsIndexed = true; - - var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName - }); - - index.Index.Columns.Add(new IndexColumnDefinition - { - Name = CurrentColumn.Name - }); - - _context.Expressions.Add(index); - - return this; - } - - public ICreateTableColumnOptionSyntax PrimaryKey() - { - CurrentColumn.IsPrimaryKey = true; - - //For MySQL, the PK will be created WITH the create table expression, however for - // SQL Server, the PK get's created in a different Alter table expression afterwords. - // MySQL will choke if the same constraint is again added afterword - // TODO: This is a super hack, I'd rather not add another property like 'CreatesPkInCreateTableDefinition' to check - // for this, but I don't see another way around. MySQL doesn't support checking for a constraint before creating - // it... except in a very strange way but it doesn't actually provider error feedback if it doesn't work so we cannot use - // it. For now, this is what I'm doing - if (Expression.CurrentDatabaseProvider != DatabaseProviders.MySql) - { - var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, ConstraintType.PrimaryKey) - { - Constraint = - { - TableName = CurrentColumn.TableName, - Columns = new[] { CurrentColumn.Name } - } - }; - _context.Expressions.Add(expression); - } - - return this; - } - - public ICreateTableColumnOptionSyntax PrimaryKey(string primaryKeyName) - { - CurrentColumn.IsPrimaryKey = true; - CurrentColumn.PrimaryKeyName = primaryKeyName; - - //For MySQL, the PK will be created WITH the create table expression, however for - // SQL Server, the PK get's created in a different Alter table expression afterwords. - // MySQL will choke if the same constraint is again added afterword - // TODO: This is a super hack, I'd rather not add another property like 'CreatesPkInCreateTableDefinition' to check - // for this, but I don't see another way around. MySQL doesn't support checking for a constraint before creating - // it... except in a very strange way but it doesn't actually provider error feedback if it doesn't work so we cannot use - // it. For now, this is what I'm doing - - if (Expression.CurrentDatabaseProvider != DatabaseProviders.MySql) - { - var expression = new CreateConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, ConstraintType.PrimaryKey) - { - Constraint = - { - ConstraintName = primaryKeyName, - TableName = CurrentColumn.TableName, - Columns = new[] { CurrentColumn.Name } - } - }; - _context.Expressions.Add(expression); - } - - return this; - } - - public ICreateTableColumnOptionSyntax Nullable() - { - CurrentColumn.IsNullable = true; - return this; - } - - public ICreateTableColumnOptionSyntax NotNullable() - { - CurrentColumn.IsNullable = false; - return this; - } - - public ICreateTableColumnOptionSyntax Unique() - { - return Unique(null); - } - - public ICreateTableColumnOptionSyntax Unique(string indexName) - { - CurrentColumn.IsUnique = true; - - var index = new CreateIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new IndexDefinition - { - Name = indexName, - SchemaName = Expression.SchemaName, - TableName = Expression.TableName, - IsUnique = true - }); - - index.Index.Columns.Add(new IndexColumnDefinition - { - Name = CurrentColumn.Name - }); - - _context.Expressions.Add(index); - - return this; - } - - public ICreateTableColumnOptionForeignKeyCascadeSyntax ForeignKey(string primaryTableName, string primaryColumnName) - { - return ForeignKey(null, null, primaryTableName, primaryColumnName); - } - - public ICreateTableColumnOptionForeignKeyCascadeSyntax ForeignKey(string foreignKeyName, string primaryTableName, - string primaryColumnName) - { - return ForeignKey(foreignKeyName, null, primaryTableName, primaryColumnName); - } - - public ICreateTableColumnOptionForeignKeyCascadeSyntax ForeignKey(string foreignKeyName, string primaryTableSchema, - string primaryTableName, string primaryColumnName) - { - CurrentColumn.IsForeignKey = true; - - var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = primaryTableName, - PrimaryTableSchema = primaryTableSchema, - ForeignTable = Expression.TableName, - ForeignTableSchema = Expression.SchemaName - }); - - fk.ForeignKey.PrimaryColumns.Add(primaryColumnName); - fk.ForeignKey.ForeignColumns.Add(CurrentColumn.Name); - - _context.Expressions.Add(fk); - CurrentForeignKey = fk.ForeignKey; - return this; - } - - public ICreateTableColumnOptionForeignKeyCascadeSyntax ForeignKey() - { - CurrentColumn.IsForeignKey = true; - return this; - } - - public ICreateTableColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignTableName, string foreignColumnName) - { - return ReferencedBy(null, null, foreignTableName, foreignColumnName); - } - - public ICreateTableColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableName, - string foreignColumnName) - { - return ReferencedBy(foreignKeyName, null, foreignTableName, foreignColumnName); - } - - public ICreateTableColumnOptionForeignKeyCascadeSyntax ReferencedBy(string foreignKeyName, string foreignTableSchema, - string foreignTableName, string foreignColumnName) - { - var fk = new CreateForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, Expression.SqlSyntax, new ForeignKeyDefinition - { - Name = foreignKeyName, - PrimaryTable = Expression.TableName, - PrimaryTableSchema = Expression.SchemaName, - ForeignTable = foreignTableName, - ForeignTableSchema = foreignTableSchema - }); - - fk.ForeignKey.PrimaryColumns.Add(CurrentColumn.Name); - fk.ForeignKey.ForeignColumns.Add(foreignColumnName); - - _context.Expressions.Add(fk); - CurrentForeignKey = fk.ForeignKey; - return this; - } - - public ICreateTableColumnOptionForeignKeyCascadeSyntax OnDelete(Rule rule) - { - CurrentForeignKey.OnDelete = rule; - return this; - } - - public ICreateTableColumnOptionForeignKeyCascadeSyntax OnUpdate(Rule rule) - { - CurrentForeignKey.OnUpdate = rule; - return this; - } - - public ICreateTableColumnOptionSyntax OnDeleteOrUpdate(Rule rule) - { - OnDelete(rule); - OnUpdate(rule); - return this; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableColumnAsTypeSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableColumnAsTypeSyntax.cs deleted file mode 100644 index 26e65eb73f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableColumnAsTypeSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table -{ - public interface ICreateTableColumnAsTypeSyntax : IColumnTypeSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableColumnOptionForeignKeyCascadeSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableColumnOptionForeignKeyCascadeSyntax.cs deleted file mode 100644 index fa6af935d9..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableColumnOptionForeignKeyCascadeSyntax.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table -{ - public interface ICreateTableColumnOptionForeignKeyCascadeSyntax : - ICreateTableColumnOptionSyntax, - IForeignKeyCascadeSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableColumnOptionSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableColumnOptionSyntax.cs deleted file mode 100644 index 68b1aff248..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableColumnOptionSyntax.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table -{ - public interface ICreateTableColumnOptionSyntax : - IColumnOptionSyntax, - ICreateTableWithColumnSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableWithColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableWithColumnSyntax.cs deleted file mode 100644 index 1edf13e847..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Create/Table/ICreateTableWithColumnSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Create.Table -{ - public interface ICreateTableWithColumnSyntax : IFluentSyntax - { - ICreateTableColumnAsTypeSyntax WithColumn(string name); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Column/DeleteColumnBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Column/DeleteColumnBuilder.cs deleted file mode 100644 index c8d13b97cd..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Column/DeleteColumnBuilder.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Column -{ - public class DeleteColumnBuilder : ExpressionBuilderBase, IDeleteColumnFromTableSyntax - { - public DeleteColumnBuilder(DeleteColumnExpression expression) : base(expression) - { - } - - public void FromTable(string tableName) - { - Expression.TableName = tableName; - } - - public IDeleteColumnFromTableSyntax Column(string columnName) - { - Expression.ColumnNames.Add(columnName); - return this; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Column/IDeleteColumnFromTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Column/IDeleteColumnFromTableSyntax.cs deleted file mode 100644 index cdeb259e25..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Column/IDeleteColumnFromTableSyntax.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Column -{ - public interface IDeleteColumnFromTableSyntax : IFluentSyntax - { - void FromTable(string tableName); - IDeleteColumnFromTableSyntax Column(string columnName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Constraint/DeleteConstraintBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Constraint/DeleteConstraintBuilder.cs deleted file mode 100644 index 49aa1ed0e0..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Constraint/DeleteConstraintBuilder.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Constraint -{ - public class DeleteConstraintBuilder : ExpressionBuilderBase, IDeleteConstraintOnTableSyntax - { - public DeleteConstraintBuilder(DeleteConstraintExpression expression) : base(expression) - { - } - - public void FromTable(string tableName) - { - Expression.Constraint.TableName = tableName; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Constraint/IDeleteConstraintOnTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Constraint/IDeleteConstraintOnTableSyntax.cs deleted file mode 100644 index bcbb886ccb..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Constraint/IDeleteConstraintOnTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Constraint -{ - public interface IDeleteConstraintOnTableSyntax : IFluentSyntax - { - void FromTable(string tableName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs deleted file mode 100644 index c2af83ad9b..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.DefaultConstraint -{ - public class DeleteDefaultConstraintBuilder : ExpressionBuilderBase, - IDeleteDefaultConstraintOnTableSyntax, - IDeleteDefaultConstraintOnColumnSyntax - { - public DeleteDefaultConstraintBuilder(DeleteDefaultConstraintExpression expression) - : base(expression) - { - } - - public IDeleteDefaultConstraintOnColumnSyntax OnTable(string tableName) - { - Expression.TableName = tableName; - return this; - } - - public void OnColumn(string columnName) - { - Expression.ColumnName = columnName; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DefaultConstraint/IDeleteDefaultConstraintOnColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DefaultConstraint/IDeleteDefaultConstraintOnColumnSyntax.cs deleted file mode 100644 index a8055d55e0..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DefaultConstraint/IDeleteDefaultConstraintOnColumnSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.DefaultConstraint -{ - public interface IDeleteDefaultConstraintOnColumnSyntax : IFluentSyntax - { - void OnColumn(string columnName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DefaultConstraint/IDeleteDefaultConstraintOnTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DefaultConstraint/IDeleteDefaultConstraintOnTableSyntax.cs deleted file mode 100644 index 95eb4675b1..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DefaultConstraint/IDeleteDefaultConstraintOnTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.DefaultConstraint -{ - public interface IDeleteDefaultConstraintOnTableSyntax : IFluentSyntax - { - IDeleteDefaultConstraintOnColumnSyntax OnTable(string tableName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs deleted file mode 100644 index 18ea700ed2..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Column; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Constraint; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.DefaultConstraint; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.ForeignKey; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete -{ - public class DeleteBuilder : IDeleteBuilder - { - private readonly IMigrationContext _context; - private readonly ISqlSyntaxProvider _sqlSyntax; - private readonly DatabaseProviders[] _databaseProviders; - - public DeleteBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) - { - _context = context; - _sqlSyntax = sqlSyntax; - _databaseProviders = databaseProviders; - } - - public void Table(string tableName) - { - var expression = new DeleteTableExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; - _context.Expressions.Add(expression); - } - - public IDeleteColumnFromTableSyntax Column(string columnName) - { - var expression = new DeleteColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) {ColumnNames = {columnName}}; - _context.Expressions.Add(expression); - return new DeleteColumnBuilder(expression); - } - - public IDeleteForeignKeyFromTableSyntax ForeignKey() - { - var expression = new DeleteForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax); - _context.Expressions.Add(expression); - return new DeleteForeignKeyBuilder(expression); - } - - public IDeleteForeignKeyOnTableSyntax ForeignKey(string foreignKeyName) - { - var expression = new DeleteForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) {ForeignKey = {Name = foreignKeyName}}; - _context.Expressions.Add(expression); - return new DeleteForeignKeyBuilder(expression); - } - - public IDeleteDataSyntax FromTable(string tableName) - { - var expression = new DeleteDataExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; - _context.Expressions.Add(expression); - return new DeleteDataBuilder(expression); - } - - public IDeleteIndexForTableSyntax Index() - { - var expression = new DeleteIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax); - _context.Expressions.Add(expression); - return new DeleteIndexBuilder(expression); - } - - public IDeleteIndexForTableSyntax Index(string indexName) - { - var expression = new DeleteIndexExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { Index = { Name = indexName } }; - _context.Expressions.Add(expression); - return new DeleteIndexBuilder(expression); - } - - public IDeleteConstraintOnTableSyntax PrimaryKey(string primaryKeyName) - { - var expression = new DeleteConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.PrimaryKey) - { - Constraint = { ConstraintName = primaryKeyName } - }; - _context.Expressions.Add(expression); - return new DeleteConstraintBuilder(expression); - } - - public IDeleteConstraintOnTableSyntax UniqueConstraint(string constraintName) - { - var expression = new DeleteConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax, ConstraintType.Unique) - { - Constraint = { ConstraintName = constraintName } - }; - _context.Expressions.Add(expression); - return new DeleteConstraintBuilder(expression); - } - - public IDeleteDefaultConstraintOnTableSyntax DefaultConstraint() - { - var expression = new DeleteDefaultConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax); - _context.Expressions.Add(expression); - return new DeleteDefaultConstraintBuilder(expression); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteDataBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteDataBuilder.cs deleted file mode 100644 index 149e73b078..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteDataBuilder.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete -{ - public class DeleteDataBuilder : IDeleteDataSyntax - { - private readonly DeleteDataExpression _expression; - - public DeleteDataBuilder(DeleteDataExpression expression) - { - _expression = expression; - } - - public void IsNull(string columnName) - { - _expression.Rows.Add(new DeletionDataDefinition - { - new KeyValuePair(columnName, null) - }); - } - - public IDeleteDataSyntax Row(object dataAsAnonymousType) - { - _expression.Rows.Add(GetData(dataAsAnonymousType)); - return this; - } - - public IDeleteDataSyntax InSchema(string schemaName) - { - _expression.SchemaName = schemaName; - return this; - } - - public void AllRows() - { - _expression.IsAllRows = true; - } - - private static DeletionDataDefinition GetData(object dataAsAnonymousType) - { - var data = new DeletionDataDefinition(); - var properties = TypeDescriptor.GetProperties(dataAsAnonymousType); - - foreach (PropertyDescriptor property in properties) - { - data.Add(new KeyValuePair(property.Name, property.GetValue(dataAsAnonymousType))); - } - - return data; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteColumnExpression.cs deleted file mode 100644 index b02bca6f12..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteColumnExpression.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions -{ - public class DeleteColumnExpression : MigrationExpressionBase - { - //public DeleteColumnExpression() - //{ - // ColumnNames = new List(); - //} - - public DeleteColumnExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - ColumnNames = new List(); - } - - public virtual string SchemaName { get; set; } - public virtual string TableName { get; set; } - public ICollection ColumnNames { get; set; } - - public override string ToString() - { - if (IsExpressionSupported() == false) - return string.Empty; - - var sb = new StringBuilder(); - foreach (string columnName in ColumnNames) - { - if (ColumnNames.First() != columnName) sb.AppendLine(";"); - sb.AppendFormat(SqlSyntax.DropColumn, - SqlSyntax.GetQuotedTableName(TableName), - SqlSyntax.GetQuotedColumnName(columnName)); - } - - return sb.ToString(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs deleted file mode 100644 index 21ef6bb517..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions -{ - public class DeleteConstraintExpression : MigrationExpressionBase - { - - public DeleteConstraintExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax, ConstraintType type) - : base(current, databaseProviders, sqlSyntax) - { - Constraint = new ConstraintDefinition(type); - } - - public ConstraintDefinition Constraint { get; private set; } - - public override string ToString() - { - // Test for MySQL primary key situation. - if (CurrentDatabaseProvider == DatabaseProviders.MySql) - { - if (Constraint.IsPrimaryKeyConstraint) - { - return string.Format(SqlSyntax.DeleteConstraint, - SqlSyntax.GetQuotedTableName(Constraint.TableName), - "PRIMARY KEY", - ""); - } - else - { - return string.Format(SqlSyntax.DeleteConstraint, - SqlSyntax.GetQuotedTableName(Constraint.TableName), - "FOREIGN KEY", - ""); - } - } - else - { - return string.Format(SqlSyntax.DeleteConstraint, - SqlSyntax.GetQuotedTableName(Constraint.TableName), - SqlSyntax.GetQuotedName(Constraint.ConstraintName)); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDataExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDataExpression.cs deleted file mode 100644 index 97c601a2a1..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDataExpression.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions -{ - public class DeleteDataExpression : MigrationExpressionBase - { - private readonly List _rows = new List(); - - public DeleteDataExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - } - - public virtual string SchemaName { get; set; } - public string TableName { get; set; } - public virtual bool IsAllRows { get; set; } - - public List Rows - { - get { return _rows; } - } - - public override string ToString() - { - var deleteItems = new List(); - - if (IsAllRows) - { - deleteItems.Add(string.Format(SqlSyntax.DeleteData, SqlSyntax.GetQuotedTableName(TableName), "1 = 1")); - } - else - { - foreach (var row in Rows) - { - var whereClauses = new List(); - foreach (KeyValuePair item in row) - { - whereClauses.Add(string.Format("{0} {1} {2}", - SqlSyntax.GetQuotedColumnName(item.Key), - item.Value == null ? "IS" : "=", - GetQuotedValue(item.Value))); - } - - deleteItems.Add(string.Format(SqlSyntax.DeleteData, - SqlSyntax.GetQuotedTableName(TableName), - String.Join(" AND ", whereClauses.ToArray()))); - } - } - - return String.Join("; ", deleteItems.ToArray()); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDefaultConstraintExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDefaultConstraintExpression.cs deleted file mode 100644 index 70a74da28e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteDefaultConstraintExpression.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions -{ - public class DeleteDefaultConstraintExpression : MigrationExpressionBase - { - public DeleteDefaultConstraintExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - } - - public virtual string SchemaName { get; set; } - public virtual string TableName { get; set; } - public virtual string ColumnName { get; set; } - - public override string ToString() - { - if (IsExpressionSupported() == false) - return string.Empty; - - return string.Format(SqlSyntax.DeleteDefaultConstraint, - TableName, - ColumnName); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs deleted file mode 100644 index e0fef06f3f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions -{ - public class DeleteForeignKeyExpression : MigrationExpressionBase - { - //public DeleteForeignKeyExpression(ISqlSyntaxProvider sqlSyntax) - // : base(sqlSyntax) - //{ - // ForeignKey = new ForeignKeyDefinition(); - //} - - public DeleteForeignKeyExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - ForeignKey = new ForeignKeyDefinition(); - } - - public ForeignKeyDefinition ForeignKey { get; set; } - - public override string ToString() - { - if (IsExpressionSupported() == false) - return string.Empty; - - if (ForeignKey.ForeignTable == null) - throw new ArgumentNullException("Table name not specified, ensure you have appended the OnTable extension. Format should be Delete.ForeignKey(KeyName).OnTable(TableName)"); - - if (CurrentDatabaseProvider == DatabaseProviders.MySql) - { - //MySql naming "convention" for foreignkeys, which aren't explicitly named - if (string.IsNullOrEmpty(ForeignKey.Name)) - ForeignKey.Name = string.Format("{0}_ibfk_1", ForeignKey.ForeignTable.ToLower()); - - return string.Format(SqlSyntax.DeleteConstraint, - SqlSyntax.GetQuotedTableName(ForeignKey.ForeignTable), - "FOREIGN KEY", - SqlSyntax.GetQuotedName(ForeignKey.Name)); - } - - if (string.IsNullOrEmpty(ForeignKey.Name)) - { - ForeignKey.Name = string.Format("FK_{0}_{1}_{2}", ForeignKey.ForeignTable, ForeignKey.PrimaryTable, ForeignKey.PrimaryColumns.First()); - } - - return string.Format(SqlSyntax.DeleteConstraint, - SqlSyntax.GetQuotedTableName(ForeignKey.ForeignTable), - SqlSyntax.GetQuotedName(ForeignKey.Name)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteIndexExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteIndexExpression.cs deleted file mode 100644 index e504223669..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteIndexExpression.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions -{ - public class DeleteIndexExpression : MigrationExpressionBase - { - - public DeleteIndexExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - Index = new IndexDefinition(); - } - - public DeleteIndexExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax, IndexDefinition index) - : base(current, databaseProviders, sqlSyntax) - { - Index = index; - } - - public IndexDefinition Index { get; private set; } - - public override string ToString() - { - return string.Format(SqlSyntax.DropIndex, - SqlSyntax.GetQuotedName(Index.Name), - SqlSyntax.GetQuotedTableName(Index.TableName)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteTableExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteTableExpression.cs deleted file mode 100644 index 0456091a4b..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteTableExpression.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions -{ - public class DeleteTableExpression : MigrationExpressionBase - { - - public DeleteTableExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - } - - public virtual string SchemaName { get; set; } - public virtual string TableName { get; set; } - - public override string ToString() - { - return string.Format(SqlSyntax.DropTable, - SqlSyntax.GetQuotedTableName(TableName)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/DeleteForeignKeyBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/DeleteForeignKeyBuilder.cs deleted file mode 100644 index f6c9ee6c07..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/DeleteForeignKeyBuilder.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.ForeignKey -{ - public class DeleteForeignKeyBuilder : ExpressionBuilderBase, - IDeleteForeignKeyFromTableSyntax, - IDeleteForeignKeyForeignColumnSyntax, - IDeleteForeignKeyToTableSyntax, - IDeleteForeignKeyPrimaryColumnSyntax, - IDeleteForeignKeyOnTableSyntax - { - public DeleteForeignKeyBuilder(DeleteForeignKeyExpression expression) : base(expression) - { - } - - public IDeleteForeignKeyForeignColumnSyntax FromTable(string foreignTableName) - { - Expression.ForeignKey.ForeignTable = foreignTableName; - return this; - } - - public IDeleteForeignKeyToTableSyntax ForeignColumn(string column) - { - Expression.ForeignKey.ForeignColumns.Add(column); - return this; - } - - public IDeleteForeignKeyToTableSyntax ForeignColumns(params string[] columns) - { - foreach (var column in columns) - { - Expression.ForeignKey.ForeignColumns.Add(column); - } - - return this; - } - - public IDeleteForeignKeyPrimaryColumnSyntax ToTable(string table) - { - Expression.ForeignKey.PrimaryTable = table; - return this; - } - - public void PrimaryColumn(string column) - { - Expression.ForeignKey.PrimaryColumns.Add(column); - } - - public void PrimaryColumns(params string[] columns) - { - foreach (var column in columns) - { - Expression.ForeignKey.PrimaryColumns.Add(column); - } - } - - public void OnTable(string foreignTableName) - { - Expression.ForeignKey.ForeignTable = foreignTableName; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyForeignColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyForeignColumnSyntax.cs deleted file mode 100644 index ae2fbfb70e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyForeignColumnSyntax.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.ForeignKey -{ - public interface IDeleteForeignKeyForeignColumnSyntax : IFluentSyntax - { - IDeleteForeignKeyToTableSyntax ForeignColumn(string column); - IDeleteForeignKeyToTableSyntax ForeignColumns(params string[] columns); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyFromTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyFromTableSyntax.cs deleted file mode 100644 index aa8de0673e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyFromTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.ForeignKey -{ - public interface IDeleteForeignKeyFromTableSyntax : IFluentSyntax - { - IDeleteForeignKeyForeignColumnSyntax FromTable(string foreignTableName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyOnTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyOnTableSyntax.cs deleted file mode 100644 index 91e26d38b4..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyOnTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.ForeignKey -{ - public interface IDeleteForeignKeyOnTableSyntax : IFluentSyntax - { - void OnTable(string foreignTableName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyPrimaryColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyPrimaryColumnSyntax.cs deleted file mode 100644 index a2f256e644..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyPrimaryColumnSyntax.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.ForeignKey -{ - public interface IDeleteForeignKeyPrimaryColumnSyntax : IFluentSyntax - { - void PrimaryColumn(string column); - void PrimaryColumns(params string[] columns); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyToTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyToTableSyntax.cs deleted file mode 100644 index 46b32c6237..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/ForeignKey/IDeleteForeignKeyToTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.ForeignKey -{ - public interface IDeleteForeignKeyToTableSyntax : IFluentSyntax - { - IDeleteForeignKeyPrimaryColumnSyntax ToTable(string table); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/IDeleteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/IDeleteBuilder.cs deleted file mode 100644 index 4a31c3bf37..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/IDeleteBuilder.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Column; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Constraint; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.DefaultConstraint; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.ForeignKey; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete -{ - public interface IDeleteBuilder : IFluentSyntax - { - void Table(string tableName); - IDeleteColumnFromTableSyntax Column(string columnName); - IDeleteForeignKeyFromTableSyntax ForeignKey(); - IDeleteForeignKeyOnTableSyntax ForeignKey(string foreignKeyName); - IDeleteDataSyntax FromTable(string tableName); - IDeleteIndexForTableSyntax Index(); - IDeleteIndexForTableSyntax Index(string indexName); - IDeleteConstraintOnTableSyntax PrimaryKey(string primaryKeyName); - IDeleteConstraintOnTableSyntax UniqueConstraint(string constraintName); - IDeleteDefaultConstraintOnTableSyntax DefaultConstraint(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/IDeleteDataSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/IDeleteDataSyntax.cs deleted file mode 100644 index c17ba0f212..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/IDeleteDataSyntax.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete -{ - public interface IDeleteDataSyntax : IFluentSyntax - { - IDeleteDataSyntax Row(object dataAsAnonymousType); - void AllRows(); - void IsNull(string columnName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs deleted file mode 100644 index 93661c9ece..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index -{ - public class DeleteIndexBuilder : ExpressionBuilderBase, IDeleteIndexForTableSyntax, IDeleteIndexOnColumnSyntax - { - public DeleteIndexBuilder(DeleteIndexExpression expression) : base(expression) - { - } - - public IndexColumnDefinition CurrentColumn { get; set; } - - public IDeleteIndexOnColumnSyntax OnTable(string tableName) - { - Expression.Index.TableName = tableName; - return this; - } - - [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] - public void OnColumn(string columnName) - { - var column = new IndexColumnDefinition { Name = columnName }; - Expression.Index.Columns.Add(column); - } - - [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] - public void OnColumns(params string[] columnNames) - { - foreach (string columnName in columnNames) - { - Expression.Index.Columns.Add(new IndexColumnDefinition { Name = columnName }); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexForTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexForTableSyntax.cs deleted file mode 100644 index c6d7a227cb..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexForTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index -{ - public interface IDeleteIndexForTableSyntax : IFluentSyntax - { - IDeleteIndexOnColumnSyntax OnTable(string tableName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs deleted file mode 100644 index fcf5038a86..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index -{ - public interface IDeleteIndexOnColumnSyntax : IFluentSyntax - { - [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] - void OnColumn(string columnName); - - [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] - void OnColumns(params string[] columnNames); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Table/DeleteTableBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Table/DeleteTableBuilder.cs deleted file mode 100644 index 01f94201b4..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Table/DeleteTableBuilder.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Table -{ - public class DeleteTableBuilder : ExpressionBuilderBase - { - public DeleteTableBuilder(DeleteTableExpression expression) : base(expression) - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs deleted file mode 100644 index 549a4a0c66..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute -{ - public class ExecuteBuilder : IExecuteBuilder - { - private readonly IMigrationContext _context; - private readonly ISqlSyntaxProvider _sqlSyntax; - private readonly DatabaseProviders[] _databaseProviders; - - public ExecuteBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) - { - _context = context; - _sqlSyntax = sqlSyntax; - _databaseProviders = databaseProviders; - } - - public void Sql(string sqlStatement) - { - var expression = new ExecuteSqlStatementExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) {SqlStatement = sqlStatement}; - _context.Expressions.Add(expression); - } - - public void Code(Func codeStatement) - { - var expression = new ExecuteCodeStatementExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { CodeStatement = codeStatement }; - _context.Expressions.Add(expression); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs deleted file mode 100644 index e5f487d15d..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions -{ - public class ExecuteCodeStatementExpression : MigrationExpressionBase - { - public ExecuteCodeStatementExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - } - - public virtual Func CodeStatement { get; set; } - - public override string Process(Database database) - { - if(CodeStatement != null) - return CodeStatement(database); - - return base.Process(database); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteSqlStatementExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteSqlStatementExpression.cs deleted file mode 100644 index 1164ba7caa..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteSqlStatementExpression.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions -{ - public class ExecuteSqlStatementExpression : MigrationExpressionBase - { - - public ExecuteSqlStatementExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - } - - public virtual string SqlStatement { get; set; } - - public override string ToString() - { - if (IsExpressionSupported() == false) - return string.Empty; - - return SqlStatement; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs deleted file mode 100644 index 46717e0a3f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute -{ - public interface IExecuteBuilder : IFluentSyntax - { - void Sql(string sqlStatement); - void Code(Func codeStatement); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/ExpressionBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/ExpressionBuilder.cs deleted file mode 100644 index 1d2c76b6a7..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/ExpressionBuilder.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System.Data; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax -{ - public abstract class ExpressionBuilder : ExpressionBuilderBase - where ExpressionT : IMigrationExpression - where NextT : IFluentSyntax - { - protected ExpressionBuilder(ExpressionT expression) - : base(expression) - { - } - - public abstract ColumnDefinition GetColumnForType(); - - private ColumnDefinition Column - { - get { return GetColumnForType(); } - } - - public NextT AsAnsiString() - { - Column.Type = DbType.AnsiString; - return (NextT)(object)this; - } - - public NextT AsAnsiString(int size) - { - Column.Type = DbType.AnsiString; - Column.Size = size; - return (NextT)(object)this; - } - - public NextT AsBinary() - { - Column.Type = DbType.Binary; - return (NextT)(object)this; - } - - public NextT AsBinary(int size) - { - Column.Type = DbType.Binary; - Column.Size = size; - return (NextT)(object)this; - } - - public NextT AsBoolean() - { - Column.Type = DbType.Boolean; - return (NextT)(object)this; - } - - public NextT AsByte() - { - Column.Type = DbType.Byte; - return (NextT)(object)this; - } - - public NextT AsCurrency() - { - Column.Type = DbType.Currency; - return (NextT)(object)this; - } - - public NextT AsDate() - { - Column.Type = DbType.Date; - return (NextT)(object)this; - } - - public NextT AsDateTime() - { - Column.Type = DbType.DateTime; - return (NextT)(object)this; - } - - public NextT AsDecimal() - { - Column.Type = DbType.Decimal; - return (NextT)(object)this; - } - - public NextT AsDecimal(int size, int precision) - { - Column.Type = DbType.Decimal; - Column.Size = size; - Column.Precision = precision; - return (NextT)(object)this; - } - - public NextT AsDouble() - { - Column.Type = DbType.Double; - return (NextT)(object)this; - } - - public NextT AsFixedLengthString(int size) - { - Column.Type = DbType.StringFixedLength; - Column.Size = size; - return (NextT)(object)this; - } - - public NextT AsFixedLengthAnsiString(int size) - { - Column.Type = DbType.AnsiStringFixedLength; - Column.Size = size; - return (NextT)(object)this; - } - - public NextT AsFloat() - { - Column.Type = DbType.Single; - return (NextT)(object)this; - } - - public NextT AsGuid() - { - Column.Type = DbType.Guid; - return (NextT)(object)this; - } - - public NextT AsInt16() - { - Column.Type = DbType.Int16; - return (NextT)(object)this; - } - - public NextT AsInt32() - { - Column.Type = DbType.Int32; - return (NextT)(object)this; - } - - public NextT AsInt64() - { - Column.Type = DbType.Int64; - return (NextT)(object)this; - } - - public NextT AsString() - { - Column.Type = DbType.String; - return (NextT)(object)this; - } - - public NextT AsString(int size) - { - Column.Type = DbType.String; - Column.Size = size; - return (NextT)(object)this; - } - - public NextT AsTime() - { - Column.Type = DbType.Time; - return (NextT)(object)this; - } - - public NextT AsXml() - { - Column.Type = DbType.Xml; - return (NextT)(object)this; - } - - public NextT AsXml(int size) - { - Column.Type = DbType.Xml; - Column.Size = size; - return (NextT)(object)this; - } - - public NextT AsCustom(string customType) - { - Column.Type = null; - Column.CustomType = customType; - return (NextT)(object)this; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/ExpressionBuilderBase.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/ExpressionBuilderBase.cs deleted file mode 100644 index da75ab8541..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/ExpressionBuilderBase.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax -{ - public abstract class ExpressionBuilderBase - where T : IMigrationExpression - { - public T Expression { get; private set; } - - protected ExpressionBuilderBase(T expression) - { - Expression = expression; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateColumnExpression.cs deleted file mode 100644 index ad16da5286..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateColumnExpression.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Expressions -{ - public class CreateColumnExpression : MigrationExpressionBase - { - - public CreateColumnExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - Column = new ColumnDefinition { ModificationType = ModificationType.Create }; - } - - public string SchemaName { get; set; } - public string TableName { get; set; } - public ColumnDefinition Column { get; set; } - - public override string ToString() - { - if (IsExpressionSupported() == false) - return string.Empty; - - if (string.IsNullOrEmpty(Column.TableName)) - Column.TableName = TableName; - - return string.Format(SqlSyntax.AddColumn, - SqlSyntax.GetQuotedTableName(Column.TableName), - SqlSyntax.Format(Column)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateForeignKeyExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateForeignKeyExpression.cs deleted file mode 100644 index 45e40e961f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateForeignKeyExpression.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Expressions -{ - public class CreateForeignKeyExpression : MigrationExpressionBase - { - public CreateForeignKeyExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax, ForeignKeyDefinition fkDef) - : base(current, databaseProviders, sqlSyntax) - { - ForeignKey = fkDef; - } - - public CreateForeignKeyExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - ForeignKey = new ForeignKeyDefinition(); - } - - public ForeignKeyDefinition ForeignKey { get; set; } - - public override string ToString() - { - if (IsExpressionSupported() == false) - return string.Empty; - - return SqlSyntax.Format(ForeignKey); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateIndexExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateIndexExpression.cs deleted file mode 100644 index 7fa38cd3ae..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Expressions/CreateIndexExpression.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Expressions -{ - public class CreateIndexExpression : MigrationExpressionBase - { - - public CreateIndexExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax, IndexDefinition index) - : base(current, databaseProviders, sqlSyntax) - { - Index = index; - } - - public CreateIndexExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - Index = new IndexDefinition(); - } - - public IndexDefinition Index { get; set; } - - public override string ToString() - { - return SqlSyntax.Format(Index); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/IColumnOptionSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/IColumnOptionSyntax.cs deleted file mode 100644 index 5d96a7ca17..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/IColumnOptionSyntax.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax -{ - public interface IColumnOptionSyntax : IFluentSyntax - where TNext : IFluentSyntax - where TNextFk : IFluentSyntax - { - TNext WithDefault(SystemMethods method); - TNext WithDefaultValue(object value); - TNext Identity(); - TNext Indexed(); - TNext Indexed(string indexName); - - TNext PrimaryKey(); - TNext PrimaryKey(string primaryKeyName); - TNext Nullable(); - TNext NotNullable(); - TNext Unique(); - TNext Unique(string indexName); - - TNextFk ForeignKey(string primaryTableName, string primaryColumnName); - TNextFk ForeignKey(string foreignKeyName, string primaryTableName, string primaryColumnName); - TNextFk ForeignKey(string foreignKeyName, string primaryTableSchema, string primaryTableName, string primaryColumnName); - TNextFk ForeignKey(); - - TNextFk ReferencedBy(string foreignTableName, string foreignColumnName); - TNextFk ReferencedBy(string foreignKeyName, string foreignTableName, string foreignColumnName); - TNextFk ReferencedBy(string foreignKeyName, string foreignTableSchema, string foreignTableName, string foreignColumnName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/IColumnTypeSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/IColumnTypeSyntax.cs deleted file mode 100644 index 406e6962bb..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/IColumnTypeSyntax.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax -{ - public interface IColumnTypeSyntax : IFluentSyntax - where TNext : IFluentSyntax - { - TNext AsAnsiString(); - TNext AsAnsiString(int size); - TNext AsBinary(); - TNext AsBinary(int size); - TNext AsBoolean(); - TNext AsByte(); - TNext AsCurrency(); - TNext AsDate(); - TNext AsDateTime(); - TNext AsDecimal(); - TNext AsDecimal(int size, int precision); - TNext AsDouble(); - TNext AsGuid(); - TNext AsFixedLengthString(int size); - TNext AsFixedLengthAnsiString(int size); - TNext AsFloat(); - TNext AsInt16(); - TNext AsInt32(); - TNext AsInt64(); - TNext AsString(); - TNext AsString(int size); - TNext AsTime(); - TNext AsXml(); - TNext AsXml(int size); - TNext AsCustom(string customType); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/IFluentSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/IFluentSyntax.cs deleted file mode 100644 index f6d1f67f2b..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/IFluentSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax -{ - public interface IFluentSyntax - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/IForeignKeyCascadeSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/IForeignKeyCascadeSyntax.cs deleted file mode 100644 index eb75ef7c3d..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/IForeignKeyCascadeSyntax.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Data; - -namespace Umbraco.Core.Persistence.Migrations.Syntax -{ - public interface IForeignKeyCascadeSyntax : IFluentSyntax - where TNext : IFluentSyntax - where TNextFk : IFluentSyntax - { - TNextFk OnDelete(Rule rule); - TNextFk OnUpdate(Rule rule); - TNext OnDeleteOrUpdate(Rule rule); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/IfDatabase/IIfDatabaseBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/IfDatabase/IIfDatabaseBuilder.cs deleted file mode 100644 index b274c215ea..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/IfDatabase/IIfDatabaseBuilder.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Create; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete; -using Umbraco.Core.Persistence.Migrations.Syntax.Execute; -using Umbraco.Core.Persistence.Migrations.Syntax.Rename; -using Umbraco.Core.Persistence.Migrations.Syntax.Update; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.IfDatabase -{ - public interface IIfDatabaseBuilder : IFluentSyntax - { - ICreateBuilder Create { get; } - IExecuteBuilder Execute { get; } - IDeleteBuilder Delete { get; } - IRenameBuilder Rename { get; } - IUpdateBuilder Update { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/IfDatabase/IfDatabaseBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/IfDatabase/IfDatabaseBuilder.cs deleted file mode 100644 index e0d20c4875..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/IfDatabase/IfDatabaseBuilder.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Create; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete; -using Umbraco.Core.Persistence.Migrations.Syntax.Execute; -using Umbraco.Core.Persistence.Migrations.Syntax.Rename; -using Umbraco.Core.Persistence.Migrations.Syntax.Update; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.IfDatabase -{ - public class IfDatabaseBuilder : IIfDatabaseBuilder - { - private readonly IMigrationContext _context; - private readonly ISqlSyntaxProvider _sqlSyntax; - private readonly DatabaseProviders[] _databaseProviders; - - public IfDatabaseBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) - { - _context = context; - _sqlSyntax = sqlSyntax; - _databaseProviders = databaseProviders; - } - - public ICreateBuilder Create - { - get { return new CreateBuilder(_context, _sqlSyntax, _databaseProviders); } - } - - public IExecuteBuilder Execute - { - get { return new ExecuteBuilder(_context, _sqlSyntax, _databaseProviders); } - } - - public IDeleteBuilder Delete - { - get { return new DeleteBuilder(_context, _sqlSyntax, _databaseProviders); } - } - - public IRenameBuilder Rename - { - get { return new RenameBuilder(_context, _sqlSyntax, _databaseProviders); } - } - - public IUpdateBuilder Update - { - get { return new UpdateBuilder(_context, _sqlSyntax, _databaseProviders); } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs deleted file mode 100644 index 28b8c71d39..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/Expressions/InsertDataExpression.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Insert.Expressions -{ - public class InsertDataExpression : MigrationExpressionBase - { - private readonly List _rows = new List(); - - public InsertDataExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - } - - public string SchemaName { get; set; } - public string TableName { get; set; } - public bool EnabledIdentityInsert { get; set; } - - public List Rows - { - get { return _rows; } - } - - public override string ToString() - { - if (IsExpressionSupported() == false) - return string.Empty; - - var sb = new StringBuilder(); - - if (EnabledIdentityInsert && SqlSyntax.SupportsIdentityInsert()) - { - sb.AppendLine(string.Format("SET IDENTITY_INSERT {0} ON;", SqlSyntax.GetQuotedTableName(TableName))); - if (CurrentDatabaseProvider == DatabaseProviders.SqlServer || CurrentDatabaseProvider == DatabaseProviders.SqlServerCE) - { - sb.AppendLine("GO"); - } - } - - try - { - foreach (var item in Rows) - { - var cols = ""; - var vals = ""; - foreach (var keyVal in item) - { - cols += SqlSyntax.GetQuotedColumnName(keyVal.Key) + ","; - vals += GetQuotedValue(keyVal.Value) + ","; - } - cols = cols.TrimEnd(','); - vals = vals.TrimEnd(','); - - - var sql = string.Format(SqlSyntax.InsertData, - SqlSyntax.GetQuotedTableName(TableName), - cols, vals); - - sb.AppendLine(string.Format("{0};", sql)); - if (CurrentDatabaseProvider == DatabaseProviders.SqlServer || CurrentDatabaseProvider == DatabaseProviders.SqlServerCE) - { - sb.AppendLine("GO"); - } - } - } - finally - { - if (EnabledIdentityInsert && SqlSyntax.SupportsIdentityInsert()) - { - sb.AppendLine(string.Format("SET IDENTITY_INSERT {0} OFF;", SqlSyntax.GetQuotedTableName(TableName))); - if (CurrentDatabaseProvider == DatabaseProviders.SqlServer || CurrentDatabaseProvider == DatabaseProviders.SqlServerCE) - { - sb.AppendLine("GO"); - } - } - } - - return sb.ToString(); - } - - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertBuilder.cs deleted file mode 100644 index f57d28e2f4..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertBuilder.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Insert -{ - public interface IInsertBuilder : IFluentSyntax - { - IInsertDataSyntax IntoTable(string tableName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertDataSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertDataSyntax.cs deleted file mode 100644 index 5791f0cb83..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/IInsertDataSyntax.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Insert -{ - public interface IInsertDataSyntax : IFluentSyntax - { - IInsertDataSyntax EnableIdentityInsert(); - IInsertDataSyntax Row(object dataAsAnonymousType); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertBuilder.cs deleted file mode 100644 index 2d3ce9c64d..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertBuilder.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Insert.Expressions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Insert -{ - public class InsertBuilder : IInsertBuilder - { - private readonly IMigrationContext _context; - private readonly ISqlSyntaxProvider _sqlSyntax; - private readonly DatabaseProviders[] _databaseProviders; - - public InsertBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) - { - _context = context; - _sqlSyntax = sqlSyntax; - _databaseProviders = databaseProviders; - } - - public IInsertDataSyntax IntoTable(string tableName) - { - var expression = new InsertDataExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; - _context.Expressions.Add(expression); - return new InsertDataBuilder(expression); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertDataBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertDataBuilder.cs deleted file mode 100644 index 4f8bf2dfcc..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Insert/InsertDataBuilder.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Insert.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Insert -{ - public class InsertDataBuilder : IInsertDataSyntax - { - private readonly InsertDataExpression _expression; - - public InsertDataBuilder(InsertDataExpression expression) - { - _expression = expression; - } - - public IInsertDataSyntax EnableIdentityInsert() - { - _expression.EnabledIdentityInsert = true; - return this; - } - - public IInsertDataSyntax Row(object dataAsAnonymousType) - { - _expression.Rows.Add(GetData(dataAsAnonymousType)); - return this; - } - - private static InsertionDataDefinition GetData(object dataAsAnonymousType) - { - var data = new InsertionDataDefinition(); - var properties = TypeDescriptor.GetProperties(dataAsAnonymousType); - - foreach (PropertyDescriptor property in properties) - { - data.Add(new KeyValuePair(property.Name, property.GetValue(dataAsAnonymousType))); - } - - return data; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Column/IRenameColumnTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Column/IRenameColumnTableSyntax.cs deleted file mode 100644 index 5792ac8756..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Column/IRenameColumnTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Column -{ - public interface IRenameColumnTableSyntax : IFluentSyntax - { - IRenameColumnToSyntax OnTable(string tableName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Column/IRenameColumnToSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Column/IRenameColumnToSyntax.cs deleted file mode 100644 index 10a0f5cee5..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Column/IRenameColumnToSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Column -{ - public interface IRenameColumnToSyntax : IFluentSyntax - { - void To(string name); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Column/RenameColumnBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Column/RenameColumnBuilder.cs deleted file mode 100644 index 4f001a76fd..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Column/RenameColumnBuilder.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Rename.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Column -{ - public class RenameColumnBuilder : ExpressionBuilderBase, IRenameColumnToSyntax, IRenameColumnTableSyntax - { - public RenameColumnBuilder(RenameColumnExpression expression) : base(expression) - { - } - - public void To(string name) - { - Expression.NewName = name; - } - - public IRenameColumnToSyntax OnTable(string tableName) - { - Expression.TableName = tableName; - return this; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs deleted file mode 100644 index 1f84401923..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Expressions -{ - public class RenameColumnExpression : MigrationExpressionBase - { - public RenameColumnExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - } - - public virtual string SchemaName { get; set; } - public virtual string TableName { get; set; } - public virtual string OldName { get; set; } - public virtual string NewName { get; set; } - - public override string Process(Database database) - { - if (CurrentDatabaseProvider == DatabaseProviders.MySql) - { - string columnDefinitionSql = string.Format(@" -SELECT CONCAT( - CAST(COLUMN_TYPE AS CHAR), - IF(ISNULL(CHARACTER_SET_NAME), - '', - CONCAT(' CHARACTER SET ', CHARACTER_SET_NAME)), - IF(ISNULL(COLLATION_NAME), - '', - CONCAT(' COLLATE ', COLLATION_NAME)), - ' ', - IF(IS_NULLABLE = 'NO', 'NOT NULL ', ''), - IF(IS_NULLABLE = 'NO' AND COLUMN_DEFAULT IS NULL, - '', - CONCAT('DEFAULT ', QUOTE(COLUMN_DEFAULT), ' ')), - UPPER(extra)) - FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_NAME = '{0}' AND COLUMN_NAME = '{1}'", TableName, OldName); - - var columnDefinition = database.ExecuteScalar(columnDefinitionSql); - return this.ToString() + " " + columnDefinition; - } - - return this.ToString(); - } - - public override string ToString() - { - if (IsExpressionSupported() == false) - return string.Empty; - - return SqlSyntax.FormatColumnRename(TableName, OldName, NewName); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameTableExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameTableExpression.cs deleted file mode 100644 index ec60117d7e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameTableExpression.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Expressions -{ - public class RenameTableExpression : MigrationExpressionBase - { - public RenameTableExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - } - - public virtual string SchemaName { get; set; } - public virtual string OldName { get; set; } - public virtual string NewName { get; set; } - - public override string ToString() - { - if (IsExpressionSupported() == false) - return string.Empty; - - return SqlSyntax.FormatTableRename(OldName, NewName); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/IRenameBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/IRenameBuilder.cs deleted file mode 100644 index 7a52d2647e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/IRenameBuilder.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Rename.Column; -using Umbraco.Core.Persistence.Migrations.Syntax.Rename.Table; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename -{ - public interface IRenameBuilder : IFluentSyntax - { - IRenameTableSyntax Table(string oldName); - IRenameColumnTableSyntax Column(string oldName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/RenameBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/RenameBuilder.cs deleted file mode 100644 index ebfb08b862..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/RenameBuilder.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Rename.Column; -using Umbraco.Core.Persistence.Migrations.Syntax.Rename.Expressions; -using Umbraco.Core.Persistence.Migrations.Syntax.Rename.Table; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename -{ - public class RenameBuilder : IRenameBuilder - { - private readonly IMigrationContext _context; - private readonly ISqlSyntaxProvider _sqlSyntax; - private readonly DatabaseProviders[] _databaseProviders; - - public RenameBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) - { - _context = context; - _sqlSyntax = sqlSyntax; - _databaseProviders = databaseProviders; - } - - public IRenameTableSyntax Table(string oldName) - { - var expression = new RenameTableExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { OldName = oldName }; - _context.Expressions.Add(expression); - return new RenameTableBuilder(expression); - } - - public IRenameColumnTableSyntax Column(string oldName) - { - var expression = new RenameColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { OldName = oldName }; - _context.Expressions.Add(expression); - return new RenameColumnBuilder(expression); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Table/IRenameTableSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Table/IRenameTableSyntax.cs deleted file mode 100644 index f44fdea9da..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Table/IRenameTableSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Table -{ - public interface IRenameTableSyntax : IFluentSyntax - { - void To(string name); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Table/RenameTableBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Table/RenameTableBuilder.cs deleted file mode 100644 index c5e493fb4a..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Table/RenameTableBuilder.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Rename.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Table -{ - public class RenameTableBuilder : ExpressionBuilderBase, IRenameTableSyntax - { - public RenameTableBuilder(RenameTableExpression expression) : base(expression) - { - } - - public void To(string name) - { - Expression.NewName = name; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/Expressions/UpdateDataExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/Expressions/UpdateDataExpression.cs deleted file mode 100644 index 18bec8b938..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/Expressions/UpdateDataExpression.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Update.Expressions -{ - public class UpdateDataExpression : MigrationExpressionBase - { - public UpdateDataExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ISqlSyntaxProvider sqlSyntax) - : base(current, databaseProviders, sqlSyntax) - { - } - - public string SchemaName { get; set; } - public string TableName { get; set; } - - public List> Set { get; set; } - public List> Where { get; set; } - public bool IsAllRows { get; set; } - - public override string ToString() - { - if (IsExpressionSupported() == false) - return string.Empty; - - var updateItems = new List(); - var whereClauses = new List(); - - foreach (var item in Set) - { - updateItems.Add(string.Format("{0} = {1}", - SqlSyntax.GetQuotedColumnName(item.Key), - GetQuotedValue(item.Value))); - } - - if (IsAllRows) - { - whereClauses.Add("1 = 1"); - } - else - { - foreach (var item in Where) - { - whereClauses.Add(string.Format("{0} {1} {2}", - SqlSyntax.GetQuotedColumnName(item.Key), - item.Value == null ? "IS" : "=", - GetQuotedValue(item.Value))); - } - } - return string.Format(SqlSyntax.UpdateData, - SqlSyntax.GetQuotedTableName(TableName), - string.Join(", ", updateItems.ToArray()), - string.Join(" AND ", whereClauses.ToArray())); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/IUpdateBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/IUpdateBuilder.cs deleted file mode 100644 index 9b94af958e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/IUpdateBuilder.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Update -{ - public interface IUpdateBuilder : IFluentSyntax - { - IUpdateSetSyntax Table(string tableName); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/IUpdateSetSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/IUpdateSetSyntax.cs deleted file mode 100644 index 2c1d8a1c9f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/IUpdateSetSyntax.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Update -{ - public interface IUpdateSetSyntax - { - IUpdateWhereSyntax Set(object dataAsAnonymousType); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/IUpdateWhereSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/IUpdateWhereSyntax.cs deleted file mode 100644 index 2d318a02bf..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/IUpdateWhereSyntax.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Persistence.Migrations.Syntax.Update -{ - public interface IUpdateWhereSyntax - { - void Where(object dataAsAnonymousType); - void AllRows(); - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/UpdateBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/UpdateBuilder.cs deleted file mode 100644 index 3816e65132..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/UpdateBuilder.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Umbraco.Core.Persistence.Migrations.Syntax.Update.Expressions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Update -{ - public class UpdateBuilder : IUpdateBuilder - { - private readonly IMigrationContext _context; - private readonly ISqlSyntaxProvider _sqlSyntax; - private readonly DatabaseProviders[] _databaseProviders; - - public UpdateBuilder(IMigrationContext context, ISqlSyntaxProvider sqlSyntax, params DatabaseProviders[] databaseProviders) - { - _context = context; - _sqlSyntax = sqlSyntax; - _databaseProviders = databaseProviders; - } - - public IUpdateSetSyntax Table(string tableName) - { - var expression = new UpdateDataExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName }; - _context.Expressions.Add(expression); - return new UpdateDataBuilder(expression, _context); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/UpdateDataBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/UpdateDataBuilder.cs deleted file mode 100644 index 47eff5c2a3..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Update/UpdateDataBuilder.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using Umbraco.Core.Persistence.Migrations.Syntax.Update.Expressions; - -namespace Umbraco.Core.Persistence.Migrations.Syntax.Update -{ - public class UpdateDataBuilder : IUpdateSetSyntax, IUpdateWhereSyntax - { - private readonly UpdateDataExpression _expression; - private readonly IMigrationContext _context; - - public UpdateDataBuilder(UpdateDataExpression expression, IMigrationContext context) - { - _context = context; - _expression = expression; - } - - public IUpdateWhereSyntax Set(object dataAsAnonymousType) - { - _expression.Set = GetData(dataAsAnonymousType); - return this; - } - - public void Where(object dataAsAnonymousType) - { - _expression.Where = GetData(dataAsAnonymousType); - } - - public void AllRows() - { - _expression.IsAllRows = true; - } - - private static List> GetData(object dataAsAnonymousType) - { - var data = new List>(); - var properties = TypeDescriptor.GetProperties(dataAsAnonymousType); - - foreach (PropertyDescriptor property in properties) - { - data.Add(new KeyValuePair(property.Name, property.GetValue(dataAsAnonymousType))); - } - - return data; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourNineZero/RemoveUmbracoAppConstraints.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourNineZero/RemoveUmbracoAppConstraints.cs deleted file mode 100644 index 062ec4f22b..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourNineZero/RemoveUmbracoAppConstraints.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Data; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero -{ - [MigrationAttribute("4.9.0", 0, Constants.System.UmbracoMigrationName)] - public class RemoveUmbracoAppConstraints : MigrationBase - { - public RemoveUmbracoAppConstraints(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //This will work on mysql and should work on mssql however the old keys were not named consistently with how the keys are - // structured now. So we need to do a check and manually remove them based on their old aliases. - - if (this.Context.CurrentDatabaseProvider == DatabaseProviders.MySql) - { - Delete.ForeignKey().FromTable("umbracoUser2app").ForeignColumn("app").ToTable("umbracoApp").PrimaryColumn("appAlias"); - Delete.ForeignKey().FromTable("umbracoAppTree").ForeignColumn("appAlias").ToTable("umbracoApp").PrimaryColumn("appAlias"); - } - else - { - //These are the old aliases, before removing them, check they exist - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser2app") && x.Item3.InvariantEquals("FK_umbracoUser2app_umbracoApp"))) - { - Delete.ForeignKey("FK_umbracoUser2app_umbracoApp").OnTable("umbracoUser2app"); - //name this migration, this is a hack for DeleteAppTables to ensure it's not executed twice - ((MigrationExpressionBase) Context.Expressions.Last()).Name = "FK_umbracoUser2app_umbracoApp"; - } - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser2app") && x.Item3.InvariantEquals("FK_umbracoUser2app_umbracoUser"))) - { - Delete.ForeignKey("FK_umbracoUser2app_umbracoUser").OnTable("umbracoUser2app"); - } - - } - - } - - public override void Down() - { - Create.ForeignKey("FK_umbracoUser2app_umbracoApp").FromTable("umbracoUser2app").ForeignColumn("app") - .ToTable("umbracoApp").PrimaryColumn("appAlias").OnDeleteOrUpdate(Rule.None); - - Create.ForeignKey("FK_umbracoAppTree_umbracoApp").FromTable("umbracoAppTree").ForeignColumn("appAlias") - .ToTable("umbracoApp").PrimaryColumn("appAlias").OnDeleteOrUpdate(Rule.None); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourOneZero/AddPreviewXmlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourOneZero/AddPreviewXmlTable.cs deleted file mode 100644 index 4ac06d1531..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionFourOneZero/AddPreviewXmlTable.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourOneZero -{ - [Migration("4.1.0", 0, Constants.System.UmbracoMigrationName)] - public class AddPreviewXmlTable : MigrationBase - { - public AddPreviewXmlTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var tableName = "cmsPreviewXml"; - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains(tableName)) return; - - Create.Table(tableName) - .WithColumn("nodeId").AsInt32().NotNullable() - .WithColumn("versionId").AsGuid().NotNullable() - .WithColumn("timestamp").AsDateTime().NotNullable() - .WithColumn("xml").AsString(); - } - - public override void Down() - { } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroPropertyTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroPropertyTable.cs deleted file mode 100644 index 8dfe51f8dc..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroPropertyTable.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - /// - /// Creats a unique index across two columns so we cannot have duplicate property aliases for one macro - /// - [Migration("7.0.0", 5, Constants.System.UmbracoMigrationName)] - public class AddIndexToCmsMacroPropertyTable : MigrationBase - { - private readonly bool _skipIndexCheck; - - internal AddIndexToCmsMacroPropertyTable(bool skipIndexCheck, ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - _skipIndexCheck = skipIndexCheck; - } - - public AddIndexToCmsMacroPropertyTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var dbIndexes = _skipIndexCheck ? new DbIndexDefinition[]{} : SqlSyntax.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMacroProperty_Alias")) == false) - { - Create.Index("IX_cmsMacroProperty_Alias").OnTable("cmsMacroProperty") - .OnColumn("macro") - .Ascending() - .OnColumn("macroPropertyAlias") - .Unique(); - } - - - } - - public override void Down() - { - Delete.Index("IX_cmsMacroProperty_Alias").OnTable("cmsMacroProperty"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroTable.cs deleted file mode 100644 index e6c237d4d6..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddIndexToCmsMacroTable.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.CodeDom; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - /// - /// Creates a unique index on the macro alias so we cannot have duplicates by alias - /// - [Migration("7.0.0", 4, Constants.System.UmbracoMigrationName)] - public class AddIndexToCmsMacroTable : MigrationBase - { - private readonly bool _forTesting; - - internal AddIndexToCmsMacroTable(bool forTesting, ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - _forTesting = forTesting; - } - - public AddIndexToCmsMacroTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var dbIndexes = _forTesting ? new DbIndexDefinition[] { } : SqlSyntax.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMacro_Alias")) == false) - { - //in order to create this index, we need to ensure that there are no duplicates. This could have happened with very old/corrupt umbraco versions. - // So we'll remove any duplicates based on alias and only keep the one with the smallest id since I'm pretty sure we'd always choose the 'first' one - // when running a query. - - if (_forTesting == false) - { - //NOTE: Using full SQL statement here in case the DTO has changed between versions - var macros = Context.Database.Fetch("SELECT * FROM cmsMacro") - .GroupBy(x => x.Alias) - .Where(x => x.Count() > 1); - - foreach (var m in macros) - { - //get the min id (to keep) - var minId = m.Min(x => x.Id); - //delete all the others - foreach (var macroDto in m.Where(x => x.Id != minId)) - { - Delete.FromTable("cmsMacro").Row(new { id = macroDto.Id }); - } - } - } - - - Create.Index("IX_cmsMacro_Alias").OnTable("cmsMacro").OnColumn("macroAlias").Unique(); - } - - } - - public override void Down() - { - Delete.Index("IX_cmsMacro_Alias").OnTable("cmsMacro"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddPropertyEditorAliasColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddPropertyEditorAliasColumn.cs deleted file mode 100644 index 474da442be..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AddPropertyEditorAliasColumn.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - - [Migration("7.0.0", 0, Constants.System.UmbracoMigrationName)] - public class AddPropertyEditorAliasColumn : MigrationBase - { - public AddPropertyEditorAliasColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Alter.Table("cmsDataType").AddColumn("propertyEditorAlias").AsString(255).NotNullable().WithDefaultValue(""); - } - - public override void Down() - { - Delete.Column("propertyEditorAlias").FromTable("cmsDataType"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterCmsMacroPropertyTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterCmsMacroPropertyTable.cs deleted file mode 100644 index 474b8a3c13..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterCmsMacroPropertyTable.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.DefaultConstraint; -using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions; -using Umbraco.Core.Persistence.Migrations.Syntax.Execute; -using Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - /// - /// We are removing the cmsMacroPropertyType which the cmsMacroProperty references and the cmsMacroProperty.macroPropertyType column - /// needs to be changed to editorAlias, we'll do this by removing the constraint,changing the macroPropertyType to the new - /// editorAlias column (and maintaing data so we can reference it) - /// - [Migration("7.0.0", 6, Constants.System.UmbracoMigrationName)] - public class AlterCmsMacroPropertyTable : MigrationBase - { - public AlterCmsMacroPropertyTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //now that the controlId column is renamed and now a string we need to convert - if (Context == null || Context.Database == null) return; - - //var cpt = SqlSyntaxContext.SqlSyntaxProvider.GetConstraintsPerTable(Context.Database); - //var di = SqlSyntaxContext.SqlSyntaxProvider.GetDefinedIndexes(Context.Database); - - if (Context.CurrentDatabaseProvider != DatabaseProviders.SqlServer) - { - Delete.DefaultConstraint().OnTable("cmsMacroProperty").OnColumn("macroPropertyHidden"); - } - else - { - //If we are on SQLServer, we need to delete default constraints by name, older versions of umbraco did not name these default constraints - // consistently so we need to look up the constraint name to delete, this only pertains to SQL Server and this issue: - // http://issues.umbraco.org/issue/U4-4133 - var sqlServerSyntaxProvider = new SqlServerSyntaxProvider(); - var defaultConstraints = sqlServerSyntaxProvider.GetDefaultConstraintsPerColumn(Context.Database).Distinct(); - - //lookup the constraint we want to delete, normally would be called "DF_cmsMacroProperty_macroPropertyHidden" but - // we cannot be sure with really old versions - var constraint = defaultConstraints - .SingleOrDefault(x => x.Item1 == "cmsMacroProperty" && x.Item2 == "macroPropertyHidden"); - if (constraint != null) - { - Execute.Sql(string.Format("ALTER TABLE [{0}] DROP CONSTRAINT [{1}]", "cmsMacroProperty", constraint.Item3)); - } - } - - Delete.Column("macroPropertyHidden").FromTable("cmsMacroProperty"); - - if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) - { - Delete.ForeignKey().FromTable("cmsMacroProperty").ForeignColumn("macroPropertyType").ToTable("cmsMacroPropertyType").PrimaryColumn("id"); - } - else - { - //Before we try to delete this constraint, we'll see if it exists first, some older schemas never had it and some older schema's had this named - // differently than the default. - - var keyConstraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct(); - var constraint = keyConstraints - .SingleOrDefault(x => x.Item1 == "cmsMacroProperty" && x.Item2 == "macroPropertyType" && x.Item3.InvariantStartsWith("PK_") == false); - if (constraint != null) - { - Delete.ForeignKey(constraint.Item3).OnTable("cmsMacroProperty"); - } - } - - Alter.Table("cmsMacroProperty").AddColumn("editorAlias").AsString(255).NotNullable().WithDefaultValue(""); - - //we need to get the data and create the migration scripts before we change the actual schema bits below! - var list = Context.Database.Fetch("SELECT * FROM cmsMacroPropertyType"); - foreach (var item in list) - { - - var alias = item.macroPropertyTypeAlias; - //check if there's a map created - var newAlias = (string)LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(alias); - if (newAlias.IsNullOrWhiteSpace() == false) - { - alias = newAlias; - } - - //update the table with the alias, the current macroPropertyType will contain the original id - Update.Table("cmsMacroProperty").Set(new { editorAlias = alias }).Where(new { macroPropertyType = item.id }); - } - - //drop the column now - Delete.Column("macroPropertyType").FromTable("cmsMacroProperty"); - - //drop the default constraint - Delete.DefaultConstraint().OnTable("cmsMacroProperty").OnColumn("editorAlias"); - } - - public override void Down() - { - throw new DataLossException("Cannot downgrade from a version 7 database to a prior version, the database schema has already been modified"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs deleted file mode 100644 index 704fcffc84..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Data; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - [Migration("7.0.0", 8, Constants.System.UmbracoMigrationName)] - public class AlterTagRelationsTable : MigrationBase - { - public AlterTagRelationsTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - if (Context == null || Context.Database == null) return; - - Initial(); - - Upgrade(); - - Final(); - } - - private void Initial() - { - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - - //create a new col which we will make a foreign key, but first needs to be populated with data. - Alter.Table("cmsTagRelationship").AddColumn("propertyTypeId").AsInt32().Nullable(); - - //drop the foreign key on umbracoNode. Must drop foreign key first before primary key can be removed in MySql. - if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) - { - Delete.ForeignKey().FromTable("cmsTagRelationship").ForeignColumn("nodeId").ToTable("umbracoNode").PrimaryColumn("id"); - //check for another strange really old one that might have existed - if (constraints.Any(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "tagId")) - { - Delete.ForeignKey().FromTable("cmsTagRelationship").ForeignColumn("tagId").ToTable("cmsTags").PrimaryColumn("id"); - } - } - else - { - //Before we try to delete this constraint, we'll see if it exists first, some older schemas never had it and some older schema's had this named - // differently than the default. - - var constraintMatches = constraints.Where(x => x.Item1 == "cmsTagRelationship" && x.Item2 == "nodeId" && x.Item3.InvariantStartsWith("PK_") == false); - - foreach (var constraint in constraintMatches) - { - Delete.ForeignKey(constraint.Item3).OnTable("cmsTagRelationship"); - } - } - - //we need to drop the primary key, this is sql specific since MySQL has never had primary keys on this table - // at least since 6.0 and the new installation way but perhaps it had them way back in 4.x so we need to check - // it exists before trying to drop it. - if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) - { - //this will let us know if this pk exists on this table - if (constraints.Count(x => x.Item1.InvariantEquals("cmsTagRelationship") && x.Item3.InvariantEquals("PRIMARY")) > 0) - { - Delete.PrimaryKey("PK_cmsTagRelationship").FromTable("cmsTagRelationship"); - } - } - else - { - //lookup the PK by name - var pkName = constraints.FirstOrDefault(x => x.Item1.InvariantEquals("cmsTagRelationship") && x.Item3.InvariantStartsWith("PK_")); - if (pkName != null) - { - Delete.PrimaryKey(pkName.Item3).FromTable("cmsTagRelationship"); - } - } - - } - - private void Upgrade() - { - //get all data from the tag relationship table - var tagRelations = Context.Database.Fetch("SELECT nodeId, tagId FROM cmsTagRelationship"); - - //get all node id -> property type id references for nodes that are in a tag relations and for properties that are of those nodes that are of the tag data type - var propertyTypeIdRef = Context.Database.Fetch(@"SELECT DISTINCT cmsTagRelationship.nodeId as NodeId, cmsPropertyType.id as PropertyTypeId - FROM cmsTags - INNER JOIN cmsTagRelationship ON cmsTagRelationship.tagId = cmsTags.id - INNER JOIN umbracoNode ON umbracoNode.id = cmsTagRelationship.nodeId - INNER JOIN cmsContent ON cmsContent.nodeId = umbracoNode.id - INNER JOIN cmsContentType ON cmsContentType.nodeId = cmsContent.contentType - INNER JOIN cmsPropertyType ON cmsPropertyType.contentTypeId = cmsContentType.nodeId - INNER JOIN cmsDataType ON cmsDataType.nodeId = cmsPropertyType.dataTypeId - WHERE cmsDataType.controlId = '4023E540-92F5-11DD-AD8B-0800200C9A66'"); - - foreach (var tr in tagRelations) - { - //for each tag relation we need to assign it a property type id which must exist in our references, if it doesn't it means that - // someone has tag data that relates to node that is not in the cmsContent table - we'll have to delete it and log it if that is the case. - - var propertyTypes = propertyTypeIdRef.Where(x => x.NodeId == tr.NodeId).ToArray(); - if (propertyTypes.Length == 0) - { - Logger.Warn("There was no cmsContent reference for cmsTagRelationship for nodeId " - + tr.NodeId + - ". The new tag system only supports tags with references to content in the cmsContent and cmsPropertyType tables. This row will be deleted: " - + string.Format("nodeId: {0}, tagId: {1}", tr.NodeId, tr.TagId)); - Delete.FromTable("cmsTagRelationship").Row(new { nodeId = tr.NodeId, tagId = tr.TagId }); - } - else - { - //update the first one found to the existing row, there might be more if there are more than one tag property assigned to the node - //in that case we need to create a row. - - //update the table with the alias, the current editorAlias will contain the original id - var first = propertyTypes[0]; - Update.Table("cmsTagRelationship") - .Set(new { propertyTypeId = first.PropertyTypeId }) - .Where(new { nodeId = tr.NodeId, tagId = tr.TagId }); - - if (propertyTypes.Length > 1) - { - //now we need to create rows for the other ones - for (var i = 1; i < propertyTypes.Length; i++) - { - Insert.IntoTable("cmsTagRelationship").Row(new { nodeId = tr.NodeId, tagId = tr.TagId, propertyTypeId = propertyTypes[i].PropertyTypeId }); - } - } - } - } - } - - private void Final() - { - //we need to change this to not nullable - Alter.Table("cmsTagRelationship").AlterColumn("propertyTypeId").AsInt32().NotNullable(); - - //we need to re-add the new primary key on all 3 columns - Create.PrimaryKey("PK_cmsTagRelationship").OnTable("cmsTagRelationship").Columns(new[] { "nodeId", "propertyTypeId", "tagId" }); - - //now we need to add a foreign key to the propertyTypeId column and change it's constraints - Create.ForeignKey("FK_cmsTagRelationship_cmsPropertyType") - .FromTable("cmsTagRelationship") - .ForeignColumn("propertyTypeId") - .ToTable("cmsPropertyType") - .PrimaryColumn("id") - .OnDelete(Rule.None) - .OnUpdate(Rule.None); - - //now we need to add a foreign key to the nodeId column to cmsContent (intead of the original umbracoNode) - Create.ForeignKey("FK_cmsTagRelationship_cmsContent") - .FromTable("cmsTagRelationship") - .ForeignColumn("nodeId") - .ToTable("cmsContent") - .PrimaryColumn("nodeId") - .OnDelete(Rule.None) - .OnUpdate(Rule.None); - } - - public override void Down() - { - throw new DataLossException("Cannot downgrade from a version 7 database to a prior version, the database schema has already been modified"); - } - - /// - /// A custom class to map to so that we can linq to it easily without dynamics - /// - private class PropertyTypeReferenceDto - { - public int NodeId { get; set; } - public int PropertyTypeId { get; set; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagsTable.cs deleted file mode 100644 index 32bd9d1403..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagsTable.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Data; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - [Migration("7.0.0", 9, Constants.System.UmbracoMigrationName)] - public class AlterTagsTable : MigrationBase - { - public AlterTagsTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - - //add a foreign key to the parent id column too! - - //In some cases in very old corrupted db's this will fail, so it means we need to clean the data first - //set the parentID to NULL where it doesn't actually exist in the normal ids - Execute.Sql(@"UPDATE cmsTags SET parentId = NULL WHERE parentId IS NOT NULL AND parentId NOT IN (SELECT id FROM cmsTags)"); - - Create.ForeignKey("FK_cmsTags_cmsTags") - .FromTable("cmsTags") - .ForeignColumn("ParentId") - .ToTable("cmsTags") - .PrimaryColumn("id") - .OnDelete(Rule.None) - .OnUpdate(Rule.None); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsTags")) == false) - { - //add an index to tag/group since it's queried often - Create.Index("IX_cmsTags").OnTable("cmsTags").OnColumn("tag").Ascending().OnColumn("group").Ascending().WithOptions().NonClustered(); - } - - } - - public override void Down() - { - throw new DataLossException("Cannot downgrade from a version 7 database to a prior version, the database schema has already been modified"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterUserTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterUserTable.cs deleted file mode 100644 index d82d73f7c3..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterUserTable.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - [Migration("7.0.0", 3, Constants.System.UmbracoMigrationName)] - public class AlterUserTable : MigrationBase - { - public AlterUserTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Delete.Column("userDefaultPermissions").FromTable("umbracoUser"); - - //"[DF_umbracoUser_defaultToLiveEditing]"" - Delete.DefaultConstraint().OnTable("umbracoUser").OnColumn("defaultToLiveEditing"); - Delete.Column("defaultToLiveEditing").FromTable("umbracoUser"); - } - - public override void Down() - { - throw new DataLossException("Cannot downgrade from a version 7 database to a prior version, the database schema has already been modified"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AssignMissingKeysAndIndexes.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AssignMissingKeysAndIndexes.cs deleted file mode 100644 index c76031e4e4..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AssignMissingKeysAndIndexes.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Data; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - /// - /// I'm not actually sure how this is possible but I've come across one install that was missing these PKs - /// and it wasn't a MySQL install. - /// see: http://issues.umbraco.org/issue/U4-5707 - /// - [Migration("7.0.0", 0, Constants.System.UmbracoMigrationName)] - public class AssignMissingKeysAndIndexes : MigrationBase - { - public AssignMissingKeysAndIndexes(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - - //Some very old schemas don't have an index on the cmsContent.nodeId column, I'm not actually sure when it was added but - // it is absolutely required to exist in order to have it as a foreign key reference, so we'll need to check it's existence - // this came to light from this issue: http://issues.umbraco.org/issue/U4-4133 - var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsContent")) == false) - { - Create.Index("IX_cmsContent").OnTable("cmsContent").OnColumn("nodeId").Ascending().WithOptions().Unique(); - } - - if (Context.CurrentDatabaseProvider == DatabaseProviders.SqlServer - || Context.CurrentDatabaseProvider == DatabaseProviders.SqlServerCE) - { - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - - //This should be 2 because this table has 2 keys - if (constraints.Count(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantStartsWith("PK_")) == 0) - { - Create.PrimaryKey("PK_cmsContentPreviewXml") - .OnTable("cmsPreviewXml") - .Columns(new[] { "nodeId", "versionId" }); - } - - if (constraints.Count(x => x.Item1.InvariantEquals("cmsTags") && x.Item3.InvariantStartsWith("PK_")) == 0) - { - Create.PrimaryKey("PK_cmsTags") - .OnTable("cmsTags") - .Columns(new[] { "id" }); - } - - if (constraints.Count(x => x.Item1.InvariantEquals("cmsStylesheetProperty") && x.Item3.InvariantStartsWith("PK_")) == 0) - { - Create.PrimaryKey("PK_cmsStylesheetProperty") - .OnTable("cmsStylesheetProperty") - .Columns(new[] { "nodeId" }); - } - - if (constraints.Count(x => x.Item1.InvariantEquals("cmsStylesheet") && x.Item3.InvariantStartsWith("PK_")) == 0) - { - Create.PrimaryKey("PK_cmsStylesheet") - .OnTable("cmsStylesheet") - .Columns(new[] { "nodeId" }); - - Create.ForeignKey("FK_cmsStylesheet_umbracoNode_id").FromTable("cmsStylesheet").ForeignColumn("nodeId") - .ToTable("umbracoNode").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); - } - - if (constraints.Count(x => x.Item1.InvariantEquals("cmsMember") && x.Item3.InvariantStartsWith("PK_")) == 0) - { - Create.PrimaryKey("PK_cmsMember") - .OnTable("cmsMember") - .Columns(new[] { "nodeId" }); - - Create.ForeignKey("FK_cmsMember_umbracoNode_id").FromTable("cmsMember").ForeignColumn("nodeId") - .ToTable("umbracoNode").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); - - Create.ForeignKey("FK_cmsMember_cmsContent_nodeId").FromTable("cmsMember").ForeignColumn("nodeId") - .ToTable("cmsContent").PrimaryColumn("nodeId").OnDeleteOrUpdate(Rule.None); - } - } - } - - public override void Down() - { - //don't do anything, these keys should have always existed! - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/DropControlIdColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/DropControlIdColumn.cs deleted file mode 100644 index 7928255968..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/DropControlIdColumn.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - [Migration("7.0.0", 2, Constants.System.UmbracoMigrationName)] - public class DropControlIdColumn : MigrationBase - { - public DropControlIdColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Delete.Column("controlId").FromTable("cmsDataType"); - //drop the default contstraint on the new column too - Delete.DefaultConstraint().OnTable("cmsDataType").OnColumn("propertyEditorAlias"); - } - - public override void Down() - { - throw new DataLossException("Cannot downgrade from a version 7 database to a prior version, the database schema has already been modified"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/RemoveCmsMacroPropertyTypeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/RemoveCmsMacroPropertyTypeTable.cs deleted file mode 100644 index 1c83ef5972..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/RemoveCmsMacroPropertyTypeTable.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - [Migration("7.0.0", 7, Constants.System.UmbracoMigrationName)] - public class RemoveCmsMacroPropertyTypeTable : MigrationBase - { - public RemoveCmsMacroPropertyTypeTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Delete.Table("cmsMacroPropertyType"); - } - - public override void Down() - { - throw new DataLossException("Cannot downgrade from a version 7 database to a prior version, the database schema has already been modified"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateControlIdToPropertyEditorAlias.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateControlIdToPropertyEditorAlias.cs deleted file mode 100644 index f94fb7ce2c..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateControlIdToPropertyEditorAlias.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - /// - /// Updates the data in the changed propertyEditorAlias column after it has been changed by ChangeControlIdColumn - /// - [Migration("7.0.0", 1, Constants.System.UmbracoMigrationName)] - public class UpdateControlIdToPropertyEditorAlias : MigrationBase - { - public UpdateControlIdToPropertyEditorAlias(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //now that the controlId column is renamed and now a string we need to convert - if (Context == null || Context.Database == null) return; - - //we need to get the data and create the migration scripts before we change the actual schema bits below! - var list = Context.Database.Fetch("SELECT pk, controlId FROM cmsDataType"); - foreach (var item in list) - { - Guid legacyId = item.controlId; - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(legacyId); - if (alias != null) - { - //check that the new property editor exists with that alias - var editor = PropertyEditorResolver.Current.GetByAlias(alias); - if (editor == null) - { - //We cannot find a map for this property editor so we're going to make it a label. This is because: - // * we want the upgrade to continue - // * we don't want any data loss - // * developers can change the property editor for the data type at a later time when there's a compatible one - // * editors cannot edit the value with an invalid editor - - Update.Table("cmsDataType").Set(new { propertyEditorAlias = Constants.PropertyEditors.NoEditAlias }).Where(new { item.pk }); - } - else - { - Update.Table("cmsDataType").Set(new { propertyEditorAlias = alias }).Where(new { item.pk }); - } - } - else - { - //We cannot find a map for this property editor so we're going to make it a label. This is because: - // * we want the upgrade to continue - // * we don't want any data loss - // * developers can change the property editor for the data type at a later time when there's a compatible one - // * editors cannot edit the value with an invalid editor - - Update.Table("cmsDataType").Set(new { propertyEditorAlias = Constants.PropertyEditors.NoEditAlias }).Where(new { item.pk }); - } - } - } - - public override void Down() - { - throw new DataLossException("Cannot downgrade from a version 7 database to a prior version, the database schema has already been modified"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs deleted file mode 100644 index d29935acd2..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Deployment.Internal; -using System.Dynamic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Web.Script.Serialization; -using System.Xml; -using System.Xml.Linq; -using Newtonsoft.Json; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven -{ - [Migration("7.0.0", 10, Constants.System.UmbracoMigrationName)] - public class UpdateRelatedLinksData : MigrationBase - { - public UpdateRelatedLinksData(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Execute.Code(UpdateRelatedLinksDataDo); - } - - public string UpdateRelatedLinksDataDo(Database database) - { - if (database != null) - { - var dtSql = new Sql().Select("nodeId") - .From(SqlSyntax) - .Where(dto => dto.PropertyEditorAlias == Constants.PropertyEditors.RelatedLinksAlias); - - var dataTypeIds = database.Fetch(dtSql); - if (dataTypeIds.Any() == false) return string.Empty; - - // need to use dynamic, as PropertyDataDto has new properties (eg decimal...) in further versions that don't exist yet - var propertyData = database.Fetch("SELECT * FROM cmsPropertyData" - + " WHERE propertyTypeId in (SELECT id from cmsPropertyType where dataTypeID IN (@dataTypeIds))", new { /*dataTypeIds =*/ dataTypeIds }); - if (propertyData.Any() == false) return string.Empty; - - var nodesIdsWithProperty = propertyData.Select(x => (int) x.contentNodeId).Distinct().ToArray(); - - var cmsContentXmlEntries = new List(); - //We're doing an "IN" query here but SQL server only supports 2100 query parameters so we're going to split on that - // It would be better to do this as an INNER join + sub query but I don't have time for that at the moment and this is only run once - // so it's not a big deal. - var batches = nodesIdsWithProperty.InGroupsOf(2000); - foreach (var batch in batches) - { - cmsContentXmlEntries.AddRange(database.Fetch("WHERE nodeId in (@nodeIds)", new { nodeIds = batch })); - } - - var propertyTypeIds = propertyData.Select(x => (int) x.propertytypeid).Distinct(); - - //NOTE: We are writing the full query because we've added a column to the PropertyTypeDto in later versions so one of the columns - // won't exist yet - var propertyTypes = database.Fetch("SELECT * FROM cmsPropertyType WHERE id in (@propertyTypeIds)", new { propertyTypeIds = propertyTypeIds }); - - foreach (var data in propertyData) - { - if (string.IsNullOrEmpty(data.dataNtext) == false) - { - XmlDocument xml; - //fetch the current data (that's in xml format) - try - { - xml = new XmlDocument(); - xml.LoadXml(data.dataNtext); - } - catch (Exception ex) - { - int dataId = data.id; - int dataNodeId = data.contentNodeId; - string dataText = data.dataNtext; - Logger.Error("The data stored for property id " + dataId + " on document " + dataNodeId + - " is not valid XML, the data will be removed because it cannot be converted to the new format. The value was: " + dataText, ex); - - data.dataNtext = ""; - database.Update("cmsPropertyData", "id", data, new[] { "dataNText" }); - - UpdateXmlTable(propertyTypes, data, cmsContentXmlEntries, database); - - continue; - } - - var links = new List(); - - //loop all the stored links - foreach (XmlNode node in xml.DocumentElement.ChildNodes) - { - var title = node.Attributes["title"].Value; - var type = node.Attributes["type"].Value; - var newwindow = node.Attributes["newwindow"].Value.Equals("1"); - var lnk = node.Attributes["link"].Value; - - //create the links in the format the new prop editor expects it to be - var link = new ExpandoObject() as IDictionary; - link.Add("title", title); - link.Add("caption", title); - link.Add("link", lnk); - link.Add("newWindow", newwindow); - link.Add("type", type.Equals("internal") ? "internal" : "external"); - link.Add("internal", type.Equals("internal") ? lnk : null); - - link.Add("edit", false); - link.Add("isInternal", type.Equals("internal")); - - links.Add((ExpandoObject)link); - } - - //store the serialized data - data.dataNtext = JsonConvert.SerializeObject(links); - - database.Update("cmsPropertyData", "id", data, new[] { "dataNText" }); - - UpdateXmlTable(propertyTypes, data, cmsContentXmlEntries, database); - - } - } - } - return string.Empty; - } - - public override void Down() - { - throw new DataLossException("Cannot downgrade from a version 7 database to a prior version, the database schema has already been modified"); - } - - private static void UpdateXmlTable(List propertyTypes, dynamic data, List cmsContentXmlEntries, Database database) - { - //now we need to update the cmsContentXml table - var propertyType = propertyTypes.SingleOrDefault(x => x.Id == data.propertytypeid); - if (propertyType != null) - { - var xmlItem = cmsContentXmlEntries.SingleOrDefault(x => x.NodeId == data.contentNodeId); - if (xmlItem != null) - { - var x = XElement.Parse(xmlItem.Xml); - var prop = x.Element(propertyType.Alias); - if (prop != null) - { - prop.ReplaceAll(new XCData(data.dataNtext)); - database.Update(xmlItem); - } - } - } - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddCmsMediaTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddCmsMediaTable.cs deleted file mode 100644 index 8b8b44c084..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddCmsMediaTable.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero -{ - [Migration("7.8.0", 1, Constants.System.UmbracoMigrationName)] - public class AddCmsMediaTable : MigrationBase - { - public AddCmsMediaTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains("cmsMedia") == false) - { - Create.Table(); - - MigrateMediaPaths(); - } - } - - private void MigrateMediaPaths() - { - Execute.Code(database => - { - //Due to how much data there can be and that this query will result in executing an Index Scan on the cmsPropertyData PK - //which will literally iterate over every row in there (assume there's 1,000,000 and you can see why this will perform - //horribly) ultimately we need to fix this: http://issues.umbraco.org/issue/U4-10286 - //so in the meantime I've found an ugly query to work around this Index Scan issue which is to use a sub-query of Ids. - //Doing this results in the same Index Scan taking place but it only visits the explicit rows that are provided, not every row - //in the cmsPropertyData table. This results in a tremendously faster query when there is tons of data. - - //The will return any media property in nvarchar or ntext that is an umbracoFile that is not null. - //This will give us the least amount of data returned to work with. - var sql = @"SELECT cmsPropertyData.dataNvarchar, cmsPropertyData.dataNtext, umbracoNode.id, cmsContentVersion.VersionId - FROM cmsPropertyData - INNER JOIN cmsPropertyType ON cmsPropertyType.id = cmsPropertyData.propertytypeid - INNER JOIN umbracoNode ON umbracoNode.id = cmsPropertyData.contentNodeId - INNER JOIN cmsContentVersion ON cmsContentVersion.ContentId = umbracoNode.id - WHERE cmsPropertyType.Alias = (@alias) - AND umbracoNode.id IN (SELECT umbracoNode.id - FROM umbracoNode - INNER JOIN cmsContent ON cmsContent.nodeId = umbracoNode.id - INNER JOIN cmsContentType ON cmsContentType.nodeId = cmsContent.contentType - INNER JOIN cmsPropertyType ON cmsPropertyType.contentTypeId = cmsContentType.nodeId - WHERE cmsPropertyType.Alias = (@alias) AND umbracoNode.nodeObjectType = (@nodeObjectType)) - AND (cmsPropertyData.dataNvarchar IS NOT NULL OR cmsPropertyData.dataNtext IS NOT NULL)"; - - var paths = new List(); - - //using QUERY = a db cursor, we won't load this all into memory first, just row by row - foreach (var row in database.Query(sql, new {alias = "umbracoFile", nodeObjectType = Constants.ObjectTypes.Media})) - { - var id = (int) row.id; - var versionId = (Guid)row.VersionId; - - string mediaPath = null; - - //if there's values in dataNvarchar then ensure there's a media path match and extract it - if (row.dataNvarchar != null && MediaFactory.TryMatch((string) row.dataNvarchar, out mediaPath)) - { - paths.Add(new MediaDto - { - MediaPath = mediaPath, - NodeId = id, - VersionId = versionId - }); - } - //if there's values in dataNtext then ensure there's a media path match and extract it - else if (row.dataNtext != null && MediaFactory.TryMatch((string) row.dataNtext, out mediaPath)) - { - paths.Add(new MediaDto - { - MediaPath = mediaPath, - NodeId = id, - VersionId = versionId - }); - } - } - - //now we need to insert paths for each item - database.BulkInsertRecords(paths, SqlSyntax); - - return null; - }); - - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddIndexToPropertyTypeAliasColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddIndexToPropertyTypeAliasColumn.cs deleted file mode 100644 index d5d13acc91..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddIndexToPropertyTypeAliasColumn.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero -{ - [Migration("7.8.0", 0, Constants.System.UmbracoMigrationName)] - public class AddIndexToPropertyTypeAliasColumn : MigrationBase - { - public AddIndexToPropertyTypeAliasColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - Execute.Code(database => - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsPropertyTypeAlias")) == false) - { - var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - - //we can apply the index - localContext.Create.Index("IX_cmsPropertyTypeAlias").OnTable("cmsPropertyType") - .OnColumn("Alias") - .Ascending() - .WithOptions() - .NonClustered(); - - return localContext.GetSql(); - } - - return null; - - }); - - - } - - public override void Down() - { - Delete.Index("IX_cmsPropertyTypeAlias").OnTable("cmsPropertyType"); - } - - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddInstructionCountColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddInstructionCountColumn.cs deleted file mode 100644 index 52996c85d5..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddInstructionCountColumn.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero -{ - [Migration("7.8.0", 2, Constants.System.UmbracoMigrationName)] - public class AddInstructionCountColumn : MigrationBase - { - public AddInstructionCountColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Don't exeucte if the column is already there - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoCacheInstruction") && x.ColumnName.InvariantEquals("instructionCount")) == false) - Create.Column("instructionCount") - .OnTable("umbracoCacheInstruction") - .AsInt32() - .WithDefaultValue(1) - .NotNullable(); - } - - public override void Down() - { - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddTourDataUserColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddTourDataUserColumn.cs deleted file mode 100644 index ea39c20a2e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddTourDataUserColumn.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero -{ - [Migration("7.8.0", 1, Constants.System.UmbracoMigrationName)] - public class AddTourDataUserColumn : MigrationBase - { - public AddTourDataUserColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Don't exeucte if the column is already there - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("tourData")) == false) - { - var textType = SqlSyntax.GetSpecialDbType(SpecialDbTypes.NTEXT); - Create.Column("tourData").OnTable("umbracoUser").AsCustom(textType).Nullable(); - } - - } - - public override void Down() - { - } - } - -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddUserLoginTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddUserLoginTable.cs deleted file mode 100644 index 9dd7a06a5e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenEightZero/AddUserLoginTable.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero -{ - [Migration("7.8.0", 4, Constants.System.UmbracoMigrationName)] - public class AddUserLoginTable : MigrationBase - { - public AddUserLoginTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains("umbracoUserLogin") == false) - { - Create.Table(); - } - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs deleted file mode 100644 index 1acb979ff6..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveFive/UpdateAllowedMediaTypesAtRoot.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive -{ - /// - /// See: http://issues.umbraco.org/issue/U4-4196 - /// - [Migration("7.5.5", 1, Constants.System.UmbracoMigrationName)] - public class UpdateAllowedMediaTypesAtRoot : MigrationBase - { - public UpdateAllowedMediaTypesAtRoot(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - Execute.Sql("UPDATE cmsContentType SET allowAtRoot = 1 WHERE nodeId = 1032 OR nodeId = 1033"); - } - - public override void Down() - { } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs deleted file mode 100644 index 94bac477f2..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/AddRedirectUrlTable.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero -{ - [Migration("7.5.0", 100, Constants.System.UmbracoMigrationName)] - public class AddRedirectUrlTable : MigrationBase - { - public AddRedirectUrlTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - // defer, because we are making decisions based upon what's in the database - Execute.Code(MigrationCode); - } - - private string MigrationCode(Database database) - { - var umbracoRedirectUrlTableName = "umbracoRedirectUrl"; - - var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - - var tables = SqlSyntax.GetTablesInSchema(database).ToArray(); - - if (tables.InvariantContains(umbracoRedirectUrlTableName)) - { - var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); - if (columns.Any(x => x.TableName.InvariantEquals(umbracoRedirectUrlTableName) && x.ColumnName.InvariantEquals("id") && x.DataType == "uniqueidentifier")) - return null; - localContext.Delete.Table(umbracoRedirectUrlTableName); - } - - localContext.Create.Table(umbracoRedirectUrlTableName) - .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_" + umbracoRedirectUrlTableName) - .WithColumn("createDateUtc").AsDateTime().NotNullable() - .WithColumn("url").AsString(2048).NotNullable() - .WithColumn("contentKey").AsGuid().NotNullable() - .WithColumn("urlHash").AsString(40).NotNullable(); - - localContext.Create.Index("IX_" + umbracoRedirectUrlTableName).OnTable(umbracoRedirectUrlTableName) - .OnColumn("urlHash") - .Ascending() - .OnColumn("contentKey") - .Ascending() - .OnColumn("createDateUtc") - .Descending() - .WithOptions().NonClustered(); - - localContext.Create.ForeignKey("FK_" + umbracoRedirectUrlTableName) - .FromTable(umbracoRedirectUrlTableName).ForeignColumn("contentKey") - .ToTable("umbracoNode").PrimaryColumn("uniqueID"); - - return localContext.GetSql(); - } - - public override void Down() - { } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs deleted file mode 100644 index 4c83a70fe2..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/EnsureServersLockObject.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero -{ - // This migration exists for 7.3.0 but it seems like it was not always running properly - // if you're upgrading from 7.3.0 or higher than we add this migration, if you're upgrading - // from 7.3.0 or lower then you will already get this migration in the migration to get to 7.3.0 - [Migration("7.3.0", "7.5.0", 10, Constants.System.UmbracoMigrationName)] - public class EnsureServersLockObject : MigrationBase - { - public EnsureServersLockObject(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - // that lock object should have been part of BaseDataCreation since 7.3.0 but - // for some reason it was not, so it was created during migrations but not during - // new installs, so for ppl that upgrade, make sure they have it - - EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers"); - } - - public override void Down() - { - // not implemented - } - - private void EnsureLockObject(int id, string uniqueId, string text) - { - var exists = Context.Database.Exists(id); - if (exists) return; - - Insert - .IntoTable("umbracoNode") - .EnableIdentityInsert() - .Row(new - { - id = id, // NodeId - trashed = false, - parentId = -1, - nodeUser = 0, - level = 1, - path = "-1," + id, - sortOrder = 0, - uniqueId = new Guid(uniqueId), - text = text, - nodeObjectType = new Guid(Constants.ObjectTypes.LockObject), - createDate = DateTime.Now - }); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs deleted file mode 100644 index f8c4c14bbe..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/RemoveStylesheetDataAndTablesAgain.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero -{ - /// - /// This is here to re-remove these tables, we dropped them in 7.3 but new installs created them again so we're going to re-drop them - /// - [Migration("7.5.0", 1, Constants.System.UmbracoMigrationName)] - public class RemoveStylesheetDataAndTablesAgain : MigrationBase - { - public RemoveStylesheetDataAndTablesAgain(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - // defer, because we are making decisions based upon what's in the database - Execute.Code(MigrationCode); - } - - private string MigrationCode(Database database) - { - var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - - //Clear all stylesheet data if the tables exist - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("cmsStylesheetProperty")) - { - localContext.Delete.FromTable("cmsStylesheetProperty").AllRows(); - localContext.Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.StylesheetProperty) }); - - localContext.Delete.Table("cmsStylesheetProperty"); - } - if (tables.InvariantContains("cmsStylesheet")) - { - localContext.Delete.FromTable("cmsStylesheet").AllRows(); - localContext.Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.Stylesheet) }); - - localContext.Delete.Table("cmsStylesheet"); - } - - return localContext.GetSql(); - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs deleted file mode 100644 index 8ac4d86290..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero -{ - /// - /// See: http://issues.umbraco.org/issue/U4-8522 - /// - [Migration("7.5.0", 2, Constants.System.UmbracoMigrationName)] - public class UpdateUniqueIndexOnCmsPropertyData : MigrationBase - { - public UpdateUniqueIndexOnCmsPropertyData(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Clear all stylesheet data if the tables exist - //tuple = tablename, indexname, columnname, unique - var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); - var found = indexes.FirstOrDefault( - x => x.Item1.InvariantEquals("cmsPropertyData") - && x.Item2.InvariantEquals("IX_cmsPropertyData_1") - //we're searching for the old index which is not unique - && x.Item4 == false); - - if (found != null) - { - //Check for MySQL - if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) - { - //Use the special double nested sub query for MySQL since that is the only - //way delete sub queries works - var delPropQry = SqlSyntax.GetDeleteSubquery( - "cmsPropertyData", - "id", - new Sql("SELECT MIN(id) FROM cmsPropertyData GROUP BY contentNodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL"), - WhereInType.NotIn); - Execute.Sql(delPropQry.SQL); - } - else - { - //NOTE: Even though the above will work for MSSQL, we are not going to execute the - // nested delete sub query logic since it will be slower and there could be a ton of property - // data here so needs to be as fast as possible. - Execute.Sql("DELETE FROM cmsPropertyData WHERE id NOT IN (SELECT MIN(id) FROM cmsPropertyData GROUP BY contentNodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL)"); - } - - //we need to re create this index - Delete.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData"); - Create.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData") - .OnColumn("contentNodeId").Ascending() - .OnColumn("versionId").Ascending() - .OnColumn("propertytypeid").Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique(); - } - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddDataDecimalColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddDataDecimalColumn.cs deleted file mode 100644 index 26fb006e82..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddDataDecimalColumn.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero -{ - [Migration("7.4.0", 1, Constants.System.UmbracoMigrationName)] - public class AddDataDecimalColumn : MigrationBase - { - public AddDataDecimalColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Don't exeucte if the column is already there - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("cmsPropertyData") && x.ColumnName.InvariantEquals("dataDecimal")) == false) - Create.Column("dataDecimal").OnTable("cmsPropertyData").AsDecimal().Nullable(); - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUmbracoDeployTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUmbracoDeployTables.cs deleted file mode 100644 index 0a1e6058a9..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUmbracoDeployTables.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero -{ - [Migration("7.4.0", 5, Constants.System.UmbracoMigrationName)] - public class AddUmbracoDeployTables : MigrationBase - { - public AddUmbracoDeployTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - //Don't exeucte if the table is already there - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoDeployChecksum")) return; - - Create.Table("umbracoDeployChecksum") - .WithColumn("id").AsInt32().Identity().PrimaryKey("PK_umbracoDeployChecksum") - .WithColumn("entityType").AsString(32).NotNullable() - .WithColumn("entityGuid").AsGuid().Nullable() - .WithColumn("entityPath").AsString(256).Nullable() - .WithColumn("localChecksum").AsString(32).NotNullable() - .WithColumn("compositeChecksum").AsString(32).Nullable(); - - Create.Table("umbracoDeployDependency") - .WithColumn("sourceId").AsInt32().NotNullable().ForeignKey("FK_umbracoDeployDependency_umbracoDeployChecksum_id1", "umbracoDeployChecksum", "id") - .WithColumn("targetId").AsInt32().NotNullable().ForeignKey("FK_umbracoDeployDependency_umbracoDeployChecksum_id2", "umbracoDeployChecksum", "id") - .WithColumn("mode").AsInt32().NotNullable(); - - Create.PrimaryKey("PK_umbracoDeployDependency").OnTable("umbracoDeployDependency").Columns(new[] {"sourceId", "targetId"}); - - Create.Index("IX_umbracoDeployChecksum").OnTable("umbracoDeployChecksum") - .OnColumn("entityType") - .Ascending() - .OnColumn("entityGuid") - .Ascending() - .OnColumn("entityPath") - .Unique(); - } - - public override void Down() - { } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs deleted file mode 100644 index 5f987fc966..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero -{ - [Migration("7.4.0", 2, Constants.System.UmbracoMigrationName)] - public class AddUniqueIdPropertyTypeGroupColumn : MigrationBase - { - public AddUniqueIdPropertyTypeGroupColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - // don't execute if the column is already there - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("cmsPropertyTypeGroup") && x.ColumnName.InvariantEquals("uniqueID")) == false) - { - Create.Column("uniqueID").OnTable("cmsPropertyTypeGroup").AsGuid().NotNullable().WithDefault(SystemMethods.NewGuid); - - // unique constraint on name + version - Create.Index("IX_cmsPropertyTypeGroupUniqueID").OnTable("cmsPropertyTypeGroup") - .OnColumn("uniqueID").Ascending() - .WithOptions() - .NonClustered() - .WithOptions() - .Unique(); - - // fill in the data in a way that is consistent over all environments - // (ie cannot use random guids, http://issues.umbraco.org/issue/U4-6942) - Execute.Code(UpdateGuids); - } - } - - private static string UpdateGuids(Database database) - { - var updates = new List>(); - - foreach (var data in database.Query(@" -SELECT cmsPropertyTypeGroup.id grId, cmsPropertyTypeGroup.text grName, cmsContentType.alias ctAlias, umbracoNode.nodeObjectType nObjType -FROM cmsPropertyTypeGroup -INNER JOIN cmsContentType -ON cmsPropertyTypeGroup.contentTypeNodeId = cmsContentType.nodeId -INNER JOIN umbracoNode -ON cmsContentType.nodeId = umbracoNode.id")) - { - Guid guid; - // see BaseDataCreation... built-in groups have their own guids - if (data.grId == 3) - { - guid = new Guid(Constants.PropertyTypeGroups.Image); - } - else if (data.grId == 4) - { - guid = new Guid(Constants.PropertyTypeGroups.File); - } - else if (data.grId == 5) - { - guid = new Guid(Constants.PropertyTypeGroups.Contents); - } - else if (data.grId == 11) - { - guid = new Guid(Constants.PropertyTypeGroups.Membership); - } - else - { - // create a consistent guid from - // group name + content type alias + object type - string guidSource = data.grName + data.ctAlias + data.nObjType; - guid = guidSource.ToGuid(); - } - - // set the Unique Id to the one we've generated - // but not within the foreach loop (as we already have a data reader open) - updates.Add(Tuple.Create(guid, data.grId)); - } - - foreach (var update in updates) - database.Execute("UPDATE cmsPropertyTypeGroup SET uniqueID=@uid WHERE id=@id", new { uid = update.Item1, id = update.Item2 }); - - return string.Empty; - } - - public override void Down() - { } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs deleted file mode 100644 index c24048a6a7..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero -{ - /// - /// Courier on v. 7.4+ will handle ContentTypes using GUIDs instead of - /// alias, so we need to ensure that these are initially consistent on - /// all environments (based on the alias). - /// - [Migration("7.4.0", 3, Constants.System.UmbracoMigrationName)] - public class EnsureContentTypeUniqueIdsAreConsistent : MigrationBase - { - public EnsureContentTypeUniqueIdsAreConsistent(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - var objectTypes = new[] - { - Constants.ObjectTypes.DocumentTypeGuid, - Constants.ObjectTypes.MediaTypeGuid, - Constants.ObjectTypes.MemberTypeGuid, - }; - - var sql = new Sql() - .Select("umbracoNode.id,cmsContentType.alias,umbracoNode.nodeObjectType") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId) - .WhereIn(x => x.NodeObjectType, objectTypes); - - var rows = Context.Database.Fetch(sql); - - foreach (var row in rows) - { - // create a consistent guid from - // alias + object type - var guidSource = ((string) row.alias) + ((Guid) row.nodeObjectType); - var guid = guidSource.ToGuid(); - - // set the Unique Id to the one we've generated - Update.Table("umbracoNode").Set(new { uniqueID = guid }).Where(new { id = row.id }); - } - } - - public override void Down() - { } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs deleted file mode 100644 index 7d5714218a..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero -{ - [Migration("7.4.0", 4, Constants.System.UmbracoMigrationName)] - public class FixListViewMediaSortOrder : MigrationBase - { - public FixListViewMediaSortOrder(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var mediaListviewIncludeProperties = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax).Where(x => x.Id == -9)).FirstOrDefault(); - if (mediaListviewIncludeProperties != null) - { - if (mediaListviewIncludeProperties.Value.Contains("\"alias\":\"sort\"")) - { - mediaListviewIncludeProperties.Value = mediaListviewIncludeProperties.Value.Replace("\"alias\":\"sort\"", "\"alias\":\"sortOrder\""); - Context.Database.InsertOrUpdate(mediaListviewIncludeProperties); - } - } - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/RemoveParentIdPropertyTypeGroupColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/RemoveParentIdPropertyTypeGroupColumn.cs deleted file mode 100644 index 5f4120f243..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/RemoveParentIdPropertyTypeGroupColumn.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZero -{ - [Migration("7.4.0", 4, Constants.System.UmbracoMigrationName)] - public class RemoveParentIdPropertyTypeGroupColumn : MigrationBase - { - public RemoveParentIdPropertyTypeGroupColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - // don't execute if the column is not there anymore - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("cmsPropertyTypeGroup") && x.ColumnName.InvariantEquals("parentGroupId")) == false) - return; - - //This constraing can be based on old aliases, before removing them, check they exist - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - if (constraints.Any(x => x.Item1.InvariantEquals("cmsPropertyTypeGroup") && x.Item3.InvariantEquals("FK_cmsPropertyTypeGroup_cmsPropertyTypeGroup_id"))) - { - Delete.ForeignKey("FK_cmsPropertyTypeGroup_cmsPropertyTypeGroup_id").OnTable("cmsPropertyTypeGroup"); - } - if (constraints.Any(x => x.Item1.InvariantEquals("cmsPropertyTypeGroup") && x.Item3.InvariantEquals("FK_cmsPropertyTypeGroup_cmsPropertyTypeGroup"))) - { - Delete.ForeignKey("FK_cmsPropertyTypeGroup_cmsPropertyTypeGroup").OnTable("cmsPropertyTypeGroup"); - } - - Delete.Column("parentGroupId").FromTable("cmsPropertyTypeGroup"); - } - - public override void Down() - { } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenOneZero/AssignMissingPrimaryForMySqlKeys.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenOneZero/AssignMissingPrimaryForMySqlKeys.cs deleted file mode 100644 index 904a4a9349..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenOneZero/AssignMissingPrimaryForMySqlKeys.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenOneZero -{ - //see: http://issues.umbraco.org/issue/U4-4430 - Except this one is specific to 7 because - // we've modified the tagRelationship PK already. - //We have to target this specifically however to ensure this DOES NOT execute if upgrading from a version previous to 7.0, - // this is because when the 7.0.0 migrations are executed, this primary key get's created so if this migration is also executed - // we will get exceptions because it is trying to create the PK two times. - - [Migration("7.0.0", "7.1.0", 0, Constants.System.UmbracoMigrationName)] - public class AssignMissingPrimaryForMySqlKeys : MigrationBase - { - public AssignMissingPrimaryForMySqlKeys(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) - { - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - - //This should be 3 because this table has 3 keys - if (constraints.Count(x => x.Item1.InvariantEquals("cmsTagRelationship") && x.Item3.InvariantEquals("PRIMARY")) == 0) - { - Create.PrimaryKey("PK_cmsTagRelationship") - .OnTable("cmsTagRelationship") - .Columns(new[] { "nodeId", "propertyTypeId", "tagId" }); - } - - } - } - - public override void Down() - { - //don't do anything, these keys should have always existed! - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddIndexToDictionaryKeyColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddIndexToDictionaryKeyColumn.cs deleted file mode 100644 index 8d708a18ea..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddIndexToDictionaryKeyColumn.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Remoting.Contexts; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero -{ - [Migration("7.7.0", 5, Constants.System.UmbracoMigrationName)] - public class AddIndexToDictionaryKeyColumn : MigrationBase - { - public AddIndexToDictionaryKeyColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - Execute.Code(database => - { - //Now we need to check if we can actually do this because we won't be able to if there's data in there that is too long - var colLen = (SqlSyntax is MySqlSyntaxProvider) - ? database.ExecuteScalar(string.Format("select max(LENGTH({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))) - : database.ExecuteScalar(string.Format("select max(datalength({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))); - - if (colLen < 900 == false && colLen != null) - { - return null; - } - - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDictionary_key")) == false) - { - var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - - //we can apply the index - localContext.Create.Index("IX_cmsDictionary_key").OnTable("cmsDictionary") - .OnColumn("key") - .Ascending() - .WithOptions() - .NonClustered(); - - return localContext.GetSql(); - } - - return null; - - }); - - - } - - public override void Down() - { - Delete.Index("IX_cmsDictionary_key").OnTable("cmsDictionary"); - } - - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs deleted file mode 100644 index 5e67dcfedd..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs +++ /dev/null @@ -1,364 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero -{ - [Migration("7.7.0", 1, Constants.System.UmbracoMigrationName)] - public class AddUserGroupTables : MigrationBase - { - private readonly string _collateSyntax; - - public AddUserGroupTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - //For some of the migration data inserts we require to use a special MSSQL collate expression since - //some databases may have a custom collation specified and if that is the case, when we compare strings - //in dynamic SQL it will try to compare strings in different collations and this will yield errors. - _collateSyntax = (sqlSyntax is MySqlSyntaxProvider || sqlSyntax is SqlCeSyntaxProvider) - ? string.Empty - : "COLLATE DATABASE_DEFAULT"; - } - - public override void Up() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToList(); - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - //In some very rare cases, there might alraedy be user group tables that we'll need to remove first - //but of course we don't want to remove the tables we will be creating below if they already exist so - //need to do some checks first since these old rare tables have a different schema - RemoveOldTablesIfExist(tables, columns); - - if (AddNewTables(tables)) - { - MigrateUserPermissions(); - MigrateUserTypesToGroups(); - DeleteOldTables(tables, constraints); - SetDefaultIcons(); - } - else - { - //if we aren't adding the tables, make sure that the umbracoUserGroup table has the correct FKs - these - //were added after the beta release so we need to do some cleanup - //if the FK doesn't exist - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUserGroup") - && x.Item2.InvariantEquals("startContentId") - && x.Item3.InvariantEquals("FK_startContentId_umbracoNode_id")) == false) - { - //before we add any foreign key we need to make sure there's no stale data in there which would have happened in the beta - //release if a start node was assigned and then that start node was deleted. - Execute.Sql(@"UPDATE umbracoUserGroup SET startContentId = NULL WHERE startContentId NOT IN (SELECT id FROM umbracoNode)"); - - Create.ForeignKey("FK_startContentId_umbracoNode_id") - .FromTable("umbracoUserGroup") - .ForeignColumn("startContentId") - .ToTable("umbracoNode") - .PrimaryColumn("id") - .OnDelete(Rule.None) - .OnUpdate(Rule.None); - } - - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUserGroup") - && x.Item2.InvariantEquals("startMediaId") - && x.Item3.InvariantEquals("FK_startMediaId_umbracoNode_id")) == false) - { - //before we add any foreign key we need to make sure there's no stale data in there which would have happened in the beta - //release if a start node was assigned and then that start node was deleted. - Execute.Sql(@"UPDATE umbracoUserGroup SET startMediaId = NULL WHERE startMediaId NOT IN (SELECT id FROM umbracoNode)"); - - Create.ForeignKey("FK_startMediaId_umbracoNode_id") - .FromTable("umbracoUserGroup") - .ForeignColumn("startMediaId") - .ToTable("umbracoNode") - .PrimaryColumn("id") - .OnDelete(Rule.None) - .OnUpdate(Rule.None); - } - } - } - - /// - /// In some very rare cases, there might alraedy be user group tables that we'll need to remove first - /// but of course we don't want to remove the tables we will be creating below if they already exist so - /// need to do some checks first since these old rare tables have a different schema - /// - /// - /// - private void RemoveOldTablesIfExist(List tables, ColumnInfo[] columns) - { - if (tables.Contains("umbracoUser2userGroup", StringComparer.InvariantCultureIgnoreCase)) - { - //this column doesn't exist in the 7.7 schema, so if it's there, then this is a super old table - var foundOldColumn = columns - .FirstOrDefault(x => - x.ColumnName.Equals("user", StringComparison.InvariantCultureIgnoreCase) - && x.TableName.Equals("umbracoUser2userGroup", StringComparison.InvariantCultureIgnoreCase)); - if (foundOldColumn != null) - { - Delete.Table("umbracoUser2userGroup"); - //remove from the tables list since this will be re-checked in further logic - tables.Remove("umbracoUser2userGroup"); - } - } - - if (tables.Contains("umbracoUserGroup", StringComparer.InvariantCultureIgnoreCase)) - { - //The new schema has several columns, the super old one for this table only had 2 so if it's 2 get rid of it - var countOfCols = columns - .Count(x => x.TableName.Equals("umbracoUserGroup", StringComparison.InvariantCultureIgnoreCase)); - if (countOfCols == 2) - { - Delete.Table("umbracoUserGroup"); - //remove from the tables list since this will be re-checked in further logic - tables.Remove("umbracoUserGroup"); - } - } - } - - private void SetDefaultIcons() - { - Execute.Sql(string.Format("UPDATE umbracoUserGroup SET icon = '{0}' WHERE userGroupAlias = '{1}'", "", Constants.Security.AdminGroupAlias)); - Execute.Sql(string.Format("UPDATE umbracoUserGroup SET icon = '{0}' WHERE userGroupAlias = '{1}'", "icon-edit", "writer")); - Execute.Sql(string.Format("UPDATE umbracoUserGroup SET icon = '{0}' WHERE userGroupAlias = '{1}'", "icon-tools", "editor")); - Execute.Sql(string.Format("UPDATE umbracoUserGroup SET icon = '{0}' WHERE userGroupAlias = '{1}'", "icon-globe", "translator")); - } - - private bool AddNewTables(List tables) - { - var updated = false; - if (tables.InvariantContains("umbracoUserGroup") == false) - { - Create.Table(); - updated = true; - } - - if (tables.InvariantContains("umbracoUser2UserGroup") == false) - { - Create.Table(); - updated = true; - } - - if (tables.InvariantContains("umbracoUserGroup2App") == false) - { - Create.Table(); - updated = true; - } - - if (tables.InvariantContains("umbracoUserGroup2NodePermission") == false) - { - Create.Table(); - updated = true; - } - - - - return updated; - } - - private void MigrateUserTypesToGroups() - { - // Create a user group for each user type - Execute.Sql(@"INSERT INTO umbracoUserGroup (userGroupAlias, userGroupName, userGroupDefaultPermissions) - SELECT userTypeAlias, userTypeName, userTypeDefaultPermissions - FROM umbracoUserType"); - - // Add each user to the group created from their type - Execute.Sql(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId) - SELECT u.id, ug.id - FROM umbracoUser u - INNER JOIN umbracoUserType ut ON ut.id = u.userType - INNER JOIN umbracoUserGroup ug ON ug.userGroupAlias {0} = ut.userTypeAlias {0}", _collateSyntax)); - - // Add the built-in administrator account to all apps - // this will lookup all of the apps that the admin currently has access to in order to assign the sections - // instead of use statically assigning since there could be extra sections we don't know about. - Execute.Sql(@"INSERT INTO umbracoUserGroup2app (userGroupId,app) - SELECT ug.id, app - FROM umbracoUserGroup ug - INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id - INNER JOIN umbracoUser u ON u.id = u2ug.userId - INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id - WHERE u.id = 0"); - - // Add the default section access to the other built-in accounts - // writer: - Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, 'content' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = 'writer' {0}", _collateSyntax)); - // editor - Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, 'content' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = 'editor' {0}", _collateSyntax)); - Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, 'media' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = 'editor' {0}", _collateSyntax)); - // translator - Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, 'translation' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = 'translator' {0}", _collateSyntax)); - - //We need to lookup all distinct combinations of section access and create a group for each distinct collection - //and assign groups accordingly. We'll perform the lookup 'now' to then create the queued SQL migrations. - var userAppsData = Context.Database.Query(@"SELECT u.id, u2a.app FROM umbracoUser u - INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id - ORDER BY u.id, u2a.app"); - var usersWithApps = new Dictionary>(); - foreach (var userApps in userAppsData) - { - List apps; - if (usersWithApps.TryGetValue(userApps.id, out apps) == false) - { - apps = new List {userApps.app}; - usersWithApps.Add(userApps.id, apps); - } - else - { - apps.Add(userApps.app); - } - } - //At this stage we have a dictionary of users with a collection of their apps which are sorted - //and we need to determine the unique/distinct app collections for each user to create groups with. - //We can do this by creating a hash value of all of the app values and since they are already sorted we can get a distinct - //collection by this hash. - var distinctApps = usersWithApps - .Select(x => new {appCollection = x.Value, appsHash = string.Join("", x.Value).GenerateHash()}) - .DistinctBy(x => x.appsHash) - .ToArray(); - //Now we need to create user groups for each of these distinct app collections, and then assign the corresponding users to those groups - for (var i = 0; i < distinctApps.Length; i++) - { - //create the group - var alias = "MigratedSectionAccessGroup_" + (i + 1); - Insert.IntoTable("umbracoUserGroup").Row(new - { - userGroupAlias = "MigratedSectionAccessGroup_" + (i + 1), - userGroupName = "Migrated Section Access Group " + (i + 1) - }); - //now assign the apps - var distinctApp = distinctApps[i]; - foreach (var app in distinctApp.appCollection) - { - Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, '" + app + @"' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = '" + alias + "' {0}", _collateSyntax)); - } - //now assign the corresponding users to this group - foreach (var userWithApps in usersWithApps) - { - //check if this user's groups hash matches the current groups hash - var hash = string.Join("", userWithApps.Value).GenerateHash(); - if (hash == distinctApp.appsHash) - { - //it matches so assign the user to this group - Execute.Sql(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId) - SELECT " + userWithApps.Key + @", ug.id - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = '" + alias + "' {0}", _collateSyntax)); - } - } - } - - // Rename some groups for consistency (plural form) - Execute.Sql("UPDATE umbracoUserGroup SET userGroupName = 'Writers' WHERE userGroupAlias = 'writer'"); - Execute.Sql("UPDATE umbracoUserGroup SET userGroupName = 'Translators' WHERE userGroupAlias = 'translator'"); - - //Ensure all built in groups have a start node of -1 - Execute.Sql("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'editor'"); - Execute.Sql("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'editor'"); - Execute.Sql("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'writer'"); - Execute.Sql("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'writer'"); - Execute.Sql("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'translator'"); - Execute.Sql("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'translator'"); - Execute.Sql("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'admin'"); - Execute.Sql("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'admin'"); - } - - private void MigrateUserPermissions() - { - // Create user group records for all non-admin users that have specific permissions set - Execute.Sql(@"INSERT INTO umbracoUserGroup(userGroupAlias, userGroupName) - SELECT 'permissionGroupFor' + userLogin, 'Migrated Permission Group for ' + userLogin - FROM umbracoUser - WHERE (id IN ( - SELECT userid - FROM umbracoUser2NodePermission - )) - AND id > 0"); - - // Associate those groups with the users - Execute.Sql(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId) - SELECT u.id, ug.id - FROM umbracoUser u - INNER JOIN umbracoUserGroup ug ON ug.userGroupAlias {0} = 'permissionGroupFor' + userLogin {0}", _collateSyntax)); - - // Create node permissions on the groups - Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2NodePermission (userGroupId,nodeId,permission) - SELECT ug.id, nodeId, permission - FROM umbracoUserGroup ug - INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id - INNER JOIN umbracoUser u ON u.id = u2ug.userId - INNER JOIN umbracoUser2NodePermission u2np ON u2np.userId = u.id - WHERE ug.userGroupAlias {0} NOT IN ( - SELECT userTypeAlias {0} - FROM umbracoUserType - )", _collateSyntax)); - - // Create app permissions on the groups - Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId,app) - SELECT ug.id, app - FROM umbracoUserGroup ug - INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id - INNER JOIN umbracoUser u ON u.id = u2ug.userId - INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id - WHERE ug.userGroupAlias {0} NOT IN ( - SELECT userTypeAlias {0} - FROM umbracoUserType - )", _collateSyntax)); - } - - private void DeleteOldTables(List tables, Tuple[] constraints) - { - if (tables.InvariantContains("umbracoUser2App")) - { - Delete.Table("umbracoUser2App"); - } - - if (tables.InvariantContains("umbracoUser2NodePermission")) - { - Delete.Table("umbracoUser2NodePermission"); - } - - if (tables.InvariantContains("umbracoUserType") && tables.InvariantContains("umbracoUser")) - { - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_umbracoUser_umbracoUserType_id"))) - { - Delete.ForeignKey("FK_umbracoUser_umbracoUserType_id").OnTable("umbracoUser"); - } - //This is the super old constraint name of the FK for user type so check this one too - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_user_userType"))) - { - Delete.ForeignKey("FK_user_userType").OnTable("umbracoUser"); - } - - Delete.Column("userType").FromTable("umbracoUser"); - Delete.Table("umbracoUserType"); - } - } - - public override void Down() - { } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs deleted file mode 100644 index 086949663b..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero -{ - [Migration("7.7.0", 2, Constants.System.UmbracoMigrationName)] - public class AddUserStartNodeTable : MigrationBase - { - public AddUserStartNodeTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains("umbracoUserStartNode") == false) - { - Create.Table(); - - MigrateUserStartNodes(); - - //now remove the old columns - - Delete.Column("startStructureID").FromTable("umbracoUser"); - Delete.Column("startMediaID").FromTable("umbracoUser"); - } - } - - private void MigrateUserStartNodes() - { - Execute.Sql(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) - SELECT id, startStructureID, 1 - FROM umbracoUser - WHERE startStructureID IS NOT NULL AND startStructureID > 0 AND startStructureID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Document + "')"); - - Execute.Sql(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) - SELECT id, startMediaID, 2 - FROM umbracoUser - WHERE startMediaID IS NOT NULL AND startMediaID > 0 AND startMediaID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Media + "')"); - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/EnsureContentTemplatePermissions.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/EnsureContentTemplatePermissions.cs deleted file mode 100644 index f880709834..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/EnsureContentTemplatePermissions.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero -{ - /// - /// Ensures the built-in user groups have the blueprint permission by default on upgrade - /// - [Migration("7.7.0", 6, Constants.System.UmbracoMigrationName)] - public class EnsureContentTemplatePermissions : MigrationBase - { - public EnsureContentTemplatePermissions(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Execute.Code(database => - { - var userGroups = database.Fetch( - new Sql().Select("*") - .From(SqlSyntax) - .Where(x => x.Alias == "admin" || x.Alias == "editor", SqlSyntax)); - - var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - - foreach (var userGroup in userGroups) - { - if (userGroup.DefaultPermissions.Contains('') == false) - { - userGroup.DefaultPermissions += ""; - localContext.Update.Table("umbracoUserGroup") - .Set(new { userGroupDefaultPermissions = userGroup.DefaultPermissions }) - .Where(new { id = userGroup.Id }); - } - } - - return localContext.GetSql(); - }); - - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/ReduceDictionaryKeyColumnsSize.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/ReduceDictionaryKeyColumnsSize.cs deleted file mode 100644 index a707e4687a..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/ReduceDictionaryKeyColumnsSize.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero -{ - [Migration("7.7.0", 4, Constants.System.UmbracoMigrationName)] - public class ReduceDictionaryKeyColumnsSize : MigrationBase - { - public ReduceDictionaryKeyColumnsSize(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - //Now we need to check if we can actually do this because we won't be able to if there's data in there that is too long - - Execute.Code(database => - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(database); - - var colLen = (SqlSyntax is MySqlSyntaxProvider) - ? database.ExecuteScalar(string.Format("select max(LENGTH({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))) - : database.ExecuteScalar(string.Format("select max(datalength({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))); - - if (colLen < 900 == false) return null; - - var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - - //if it exists we need to drop it first - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDictionary_key"))) - { - localContext.Delete.Index("IX_cmsDictionary_key").OnTable("cmsDictionary"); - } - - //we can apply the col length change - localContext.Alter.Table("cmsDictionary") - .AlterColumn("key") - .AsString(450) - .NotNullable(); - - return localContext.GetSql(); - }); - } - - public override void Down() - { - - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs deleted file mode 100644 index c7fae7b2aa..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToCmsMemberLoginName.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - [Migration("7.6.0", 3, Constants.System.UmbracoMigrationName)] - public class AddIndexToCmsMemberLoginName : MigrationBase - { - public AddIndexToCmsMemberLoginName(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - Execute.Code(database => - { - //Now we need to check if we can actually d6 this because we won't be able to if there's data in there that is too long - //http://issues.umbraco.org/issue/U4-9758 - - var colLen = (SqlSyntax is MySqlSyntaxProvider) - ? database.ExecuteScalar("select max(LENGTH(LoginName)) from cmsMember") - : database.ExecuteScalar("select max(datalength(LoginName)) from cmsMember"); - - if (colLen < 900 == false && colLen != null) - { - return null; - } - - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")) == false) - { - var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - - //we can apply the index - localContext.Create.Index("IX_cmsMember_LoginName").OnTable("cmsMember") - .OnColumn("LoginName") - .Ascending() - .WithOptions() - .NonClustered(); - - return localContext.GetSql(); - } - - return null; - - }); - - - } - - public override void Down() - { - Delete.Index("IX_cmsMember_LoginName").OnTable("cmsMember"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUmbracoNodePath.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUmbracoNodePath.cs deleted file mode 100644 index c7fb573761..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUmbracoNodePath.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] - public class AddIndexToUmbracoNodePath : MigrationBase - { - public AddIndexToUmbracoNodePath(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodePath")) == false) - { - Create.Index("IX_umbracoNodePath").OnTable("umbracoNode") - .OnColumn("path") - .Ascending() - .WithOptions() - .NonClustered(); - } - } - - public override void Down() - { - Delete.Index("IX_umbracoNodePath").OnTable("umbracoNode"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUser2NodePermission.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUser2NodePermission.cs deleted file mode 100644 index b510ef428c..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexToUser2NodePermission.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] - public class AddIndexToUser2NodePermission : MigrationBase - { - public AddIndexToUser2NodePermission(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoUser2NodePermission_nodeId")) == false) - { - Create.Index("IX_umbracoUser2NodePermission_nodeId").OnTable("umbracoUser2NodePermission") - .OnColumn("nodeId") - .Ascending() - .WithOptions() - .NonClustered(); - } - } - - public override void Down() - { - Delete.Index("IX_umbracoUser2NodePermission_nodeId").OnTable("cmsMember"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelationTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelationTables.cs deleted file mode 100644 index b8c0d78ef1..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddIndexesToUmbracoRelationTables.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] - public class AddIndexesToUmbracoRelationTables : MigrationBase - { - public AddIndexesToUmbracoRelationTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database).ToArray(); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelation_parentChildType")) == false) - { - //This will remove any corrupt/duplicate data in the relation table before the index is applied - //Ensure this executes in a defered block which will be done inside of the migration transaction - this.Execute.Code(database => - { - //We need to check if this index has corrupted data and then clear that data - var duplicates = database.Fetch("SELECT parentId,childId,relType FROM umbracoRelation GROUP BY parentId,childId,relType HAVING COUNT(*) > 1"); - if (duplicates.Count > 0) - { - //need to fix this there cannot be duplicates so we'll take the latest entries, it's really not going to matter though - foreach (var duplicate in duplicates) - { - var ids = database.Fetch("SELECT id FROM umbracoRelation WHERE parentId=@parentId AND childId=@childId AND relType=@relType ORDER BY datetime DESC", - new { parentId = duplicate.parentId, childId = duplicate.childId, relType = duplicate.relType }); - - if (ids.Count == 1) - { - //this is just a safety check, this should absolutely never happen - throw new InvalidOperationException("Duplicates were detected but could not be discovered"); - } - - //delete the others - ids = ids.Skip(0).ToList(); - - //iterate in groups of 2000 to avoid the max sql parameter limit - foreach (var idGroup in ids.InGroupsOf(2000)) - { - database.Execute("DELETE FROM umbracoRelation WHERE id IN (@ids)", new { ids = idGroup }); - } - } - } - return ""; - }); - - //unique index to prevent duplicates - and for better perf - Create.Index("IX_umbracoRelation_parentChildType").OnTable("umbracoRelation") - .OnColumn("parentId").Ascending() - .OnColumn("childId").Ascending() - .OnColumn("relType").Ascending() - .WithOptions() - .Unique(); - } - - //need indexes on alias and name for relation type since these are queried against - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_alias")) == false) - { - Create.Index("IX_umbracoRelationType_alias").OnTable("umbracoRelationType") - .OnColumn("alias") - .Ascending() - .WithOptions() - .NonClustered(); - } - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_name")) == false) - { - Create.Index("IX_umbracoRelationType_name").OnTable("umbracoRelationType") - .OnColumn("name") - .Ascending() - .WithOptions() - .NonClustered(); - } - - } - - public override void Down() - { - Delete.Index("IX_umbracoNodePath").OnTable("umbracoNode"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddLockObjects.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddLockObjects.cs deleted file mode 100644 index ed6fcaae23..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddLockObjects.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - [Migration("7.6.0", 101, Constants.System.UmbracoMigrationName)] - public class AddLockObjects : MigrationBase - { - public AddLockObjects(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - EnsureLockObject(Constants.Locks.Servers, "Servers"); - } - - public override void Down() - { - // not implemented - } - - private void EnsureLockObject(int id, string name) - { - Execute.Code(db => - { - var exists = db.Exists(id); - if (exists) return string.Empty; - // be safe: delete old umbracoNode lock objects if any - db.Execute("DELETE FROM umbracoNode WHERE id=@id;", new { id }); - // then create umbracoLock object - db.Execute("INSERT umbracoLock (id, name, value) VALUES (@id, @name, 1);", new { id, name }); - return string.Empty; - }); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddLockTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddLockTable.cs deleted file mode 100644 index 809f18e77e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddLockTable.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - [Migration("7.6.0", 100, Constants.System.UmbracoMigrationName)] - public class AddLockTable : MigrationBase - { - public AddLockTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoLock") == false) - { - Create.Table("umbracoLock") - .WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock") - .WithColumn("value").AsInt32().NotNullable() - .WithColumn("name").AsString(64).NotNullable(); - } - } - - public override void Down() - { - // not implemented - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddMacroUniqueIdColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddMacroUniqueIdColumn.cs deleted file mode 100644 index a4a99391db..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddMacroUniqueIdColumn.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] - public class AddMacroUniqueIdColumn : MigrationBase - { - public AddMacroUniqueIdColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("cmsMacro") && x.ColumnName.InvariantEquals("uniqueId")) == false) - { - Create.Column("uniqueId").OnTable("cmsMacro").AsGuid().Nullable(); - Execute.Code(UpdateMacroGuids); - Alter.Table("cmsMacro").AlterColumn("uniqueId").AsGuid().NotNullable(); - Create.Index("IX_cmsMacro_UniqueId").OnTable("cmsMacro").OnColumn("uniqueId") - .Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique(); - - } - - if (columns.Any(x => x.TableName.InvariantEquals("cmsMacroProperty") && x.ColumnName.InvariantEquals("uniquePropertyId")) == false) - { - Create.Column("uniquePropertyId").OnTable("cmsMacroProperty").AsGuid().Nullable(); - Execute.Code(UpdateMacroPropertyGuids); - Alter.Table("cmsMacroProperty").AlterColumn("uniquePropertyId").AsGuid().NotNullable(); - Create.Index("IX_cmsMacroProperty_UniquePropertyId").OnTable("cmsMacroProperty").OnColumn("uniquePropertyId") - .Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique(); - } - } - - private static string UpdateMacroGuids(Database database) - { - var updates = database.Query("SELECT id, macroAlias FROM cmsMacro") - .Select(macro => Tuple.Create((int) macro.id, ("macro____" + (string) macro.macroAlias).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE cmsMacro set uniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - - return string.Empty; - } - - private static string UpdateMacroPropertyGuids(Database database) - { - var updates = database.Query(@"SELECT cmsMacroProperty.id id, macroPropertyAlias propertyAlias, cmsMacro.macroAlias macroAlias -FROM cmsMacroProperty -JOIN cmsMacro ON cmsMacroProperty.macro=cmsMacro.id") - .Select(prop => Tuple.Create((int) prop.id, ("macro____" + (string) prop.macroAlias + "____" + (string) prop.propertyAlias).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE cmsMacroProperty set uniquePropertyId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - - return string.Empty; - } - - public override void Down() - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddRelationTypeUniqueIdColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddRelationTypeUniqueIdColumn.cs deleted file mode 100644 index c18f795c3a..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/AddRelationTypeUniqueIdColumn.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] - public class AddRelationTypeUniqueIdColumn : MigrationBase - { - public AddRelationTypeUniqueIdColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoRelationType") && x.ColumnName.InvariantEquals("typeUniqueId")) == false) - { - Create.Column("typeUniqueId").OnTable("umbracoRelationType").AsGuid().Nullable(); - Execute.Code(UpdateRelationTypeGuids); - Alter.Table("umbracoRelationType").AlterColumn("typeUniqueId").AsGuid().NotNullable(); - Create.Index("IX_umbracoRelationType_UniqueId").OnTable("umbracoRelationType").OnColumn("typeUniqueId") - .Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique(); - } - } - - private static string UpdateRelationTypeGuids(Database database) - { - var updates = database.Query("SELECT id, alias, name FROM umbracoRelationType") - .Select(relationType => Tuple.Create((int) relationType.id, ("relationType____" + (string) relationType.alias + "____" + (string) relationType.name).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE umbracoRelationType set typeUniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - - return string.Empty; - } - - public override void Down() - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/NormalizeTemplateGuids.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/NormalizeTemplateGuids.cs deleted file mode 100644 index f9f0d0efbb..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/NormalizeTemplateGuids.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] - public class NormalizeTemplateGuids : MigrationBase - { - public NormalizeTemplateGuids(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - Execute.Code(UpdateTemplateGuids); - } - - private static string UpdateTemplateGuids(Database database) - { - // we need this migration because ppl running pre-7.6 on Cloud and Courier have templates in different - // environments having different GUIDs (Courier does not sync template GUIDs) and we need to normalize - // these GUIDs so templates with the same alias on different environments have the same GUID. - // however, if already running a prerelease version of 7.6, we do NOT want to normalize the GUIDs as quite - // probably, we are already running Deploy and the GUIDs are OK. assuming noone is running a prerelease - // of 7.6 on Courier. - // so... testing if we already have a 7.6.0 version installed. not pretty but...? - // - var version = database.FirstOrDefault("SELECT version FROM umbracoMigration WHERE name=@name ORDER BY version DESC", new { name = Constants.System.UmbracoMigrationName }); - if (version != null && version.StartsWith("7.6.0")) return string.Empty; - - var updates = database.Query(@"SELECT umbracoNode.id, cmsTemplate.alias FROM umbracoNode -JOIN cmsTemplate ON umbracoNode.id=cmsTemplate.nodeId -WHERE nodeObjectType = @guid", new { guid = Constants.ObjectTypes.TemplateTypeGuid}) - .Select(template => Tuple.Create((int) template.id, ("template____" + (string) template.alias).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE umbracoNode set uniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - - return string.Empty; - } - - public override void Down() - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/ReduceLoginNameColumnsSize.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/ReduceLoginNameColumnsSize.cs deleted file mode 100644 index b3a3bd00c5..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/ReduceLoginNameColumnsSize.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - [Migration("7.6.0", 2, Constants.System.UmbracoMigrationName)] - public class ReduceLoginNameColumnsSize : MigrationBase - { - public ReduceLoginNameColumnsSize(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - //Now we need to check if we can actually d6 this because we won't be able to if there's data in there that is too long - //http://issues.umbraco.org/issue/U4-9758 - - Execute.Code(database => - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(database); - - var colLen = (SqlSyntax is MySqlSyntaxProvider) - ? database.ExecuteScalar("select max(LENGTH(LoginName)) from cmsMember") - : database.ExecuteScalar("select max(datalength(LoginName)) from cmsMember"); - - if (colLen < 900 == false) return null; - - var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger); - - //if an index exists on this table we need to drop it. Normally we'd check via index name but in some odd cases (i.e. Our) - //the index name is something odd (starts with "mi_"). In any case, the index cannot exist if we want to alter the column - //so we'll drop whatever index is there and add one with the correct name after. - var loginNameIndex = dbIndexes.FirstOrDefault(x => x.TableName.InvariantEquals("cmsMember") && x.ColumnName.InvariantEquals("LoginName")); - if (loginNameIndex != null) - { - localContext.Delete.Index(loginNameIndex.IndexName).OnTable("cmsMember"); - } - - //we can apply the col length change - localContext.Alter.Table("cmsMember") - .AlterColumn("LoginName") - .AsString(225) - .NotNullable(); - - return localContext.GetSql(); - }); - } - - public override void Down() - { - - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs deleted file mode 100644 index e041ad1425..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - /// - /// See: http://issues.umbraco.org/issue/U4-9188 - /// - [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] - public class UpdateUniqueIndexOnCmsPropertyData : MigrationBase - { - public UpdateUniqueIndexOnCmsPropertyData(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //tuple = tablename, indexname, columnname, unique - var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); - var found = indexes.FirstOrDefault( - x => x.Item1.InvariantEquals("cmsPropertyData") - && x.Item2.InvariantEquals("IX_cmsPropertyData")); - - if (found != null) - { - //drop the index - Delete.Index("IX_cmsPropertyData").OnTable("cmsPropertyData"); - } - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemoveUmbracoDeployTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemoveUmbracoDeployTables.cs deleted file mode 100644 index 5cf129700f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemoveUmbracoDeployTables.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero -{ - [Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)] - public class RemoveUmbracoDeployTables : MigrationBase - { - public RemoveUmbracoDeployTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - // there are two versions of umbracoDeployDependency, - // 1. one created by 7.4 and never used, we need to remove it (has a sourceId column) - // 2. one created by Deploy itself, we need to keep it (has a sourceUdi column) - if (tables.InvariantContains("umbracoDeployDependency")) - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - if (columns.Any(x => x.TableName.InvariantEquals("umbracoDeployDependency") && x.ColumnName.InvariantEquals("sourceId"))) - Delete.Table("umbracoDeployDependency"); - } - - // always remove umbracoDeployChecksum - if (tables.InvariantContains("umbracoDeployChecksum")) - Delete.Table("umbracoDeployChecksum"); - } - - public override void Down() - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs deleted file mode 100644 index c3d7857d75..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeOne -{ - /// - /// This fixes the storage of user languages from the old format like en_us to en-US - /// - [Migration("7.3.1", 0, Constants.System.UmbracoMigrationName)] - public class UpdateUserLanguagesToIsoCode : MigrationBase - { - public UpdateUserLanguagesToIsoCode(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var userData = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); - foreach (var user in userData.Where(x => x.UserLanguage.Contains("_"))) - { - var languageParts = user.UserLanguage.Split('_'); - if (languageParts.Length == 2) - { - Update.Table("umbracoUser") - .Set(new {userLanguage = languageParts[0] + "-" + languageParts[1].ToUpperInvariant()}) - .Where(new {id = user.Id}); - } - } - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs deleted file mode 100644 index a52abf1331..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeTwo -{ - /// - /// This reinserts all migrations in the migrations table to account for initial rows inserted - /// on creation without identity enabled. - /// - [Migration("7.3.2", 0, Constants.System.UmbracoMigrationName)] - public class EnsureMigrationsTableIdentityIsCorrect : MigrationBase - { - public EnsureMigrationsTableIdentityIsCorrect(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - // Due to the delayed execution of migrations, we have to wrap this code in Execute.Code to ensure the previous - // migration steps (esp. creating the migrations table) have completed before trying to fetch migrations from - // this table. - List migrations = null; - Execute.Code(db => - { - migrations = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); - return string.Empty; - }); - Delete.FromTable("umbracoMigration").AllRows(); - Execute.Code(database => - { - if (migrations != null) - { - foreach (var migration in migrations) - { - database.Insert("umbracoMigration", "id", true, - new {name = migration.Name, createDate = migration.CreateDate, version = migration.Version}); - } - } - return string.Empty; - }); - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddExternalLoginsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddExternalLoginsTable.cs deleted file mode 100644 index b5bb24e40e..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddExternalLoginsTable.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 9, Constants.System.UmbracoMigrationName)] - public class AddExternalLoginsTable : MigrationBase - { - public AddExternalLoginsTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Don't exeucte if the table is already there - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoExternalLogin")) return; - - Create.Table("umbracoExternalLogin") - .WithColumn("id").AsInt32().Identity().PrimaryKey("PK_umbracoExternalLogin") - .WithColumn("userId").AsInt32().NotNullable().ForeignKey("FK_umbracoExternalLogin_umbracoUser_id", "umbracoUser", "id") - .WithColumn("loginProvider").AsString(4000).NotNullable() - .WithColumn("providerKey").AsString(4000).NotNullable() - .WithColumn("createDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime); - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs deleted file mode 100644 index 0baf5bff1a..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Data; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 14, Constants.System.UmbracoMigrationName)] - public class AddForeignKeysForLanguageAndDictionaryTables : MigrationBase - { - public AddForeignKeysForLanguageAndDictionaryTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - - //if the FK doesn't exist - if (constraints.Any(x => x.Item1.InvariantEquals("cmsLanguageText") && x.Item2.InvariantEquals("languageId") && x.Item3.InvariantEquals("FK_cmsLanguageText_umbracoLanguage_id")) == false) - { - //Somehow, a language text item might end up with a language Id of zero or one that no longer exists - //before we add the foreign key - foreach (var pk in Context.Database.Query( - "SELECT cmsLanguageText.pk FROM cmsLanguageText WHERE cmsLanguageText.languageId NOT IN (SELECT umbracoLanguage.id FROM umbracoLanguage)")) - { - Delete.FromTable("cmsLanguageText").Row(new { pk = pk }); - } - - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); - - if (columns.Any(x => x.ColumnName.InvariantEquals("id") - && x.TableName.InvariantEquals("umbracoLanguage") - && x.DataType.InvariantEquals("smallint"))) - { - //Ensure that the umbracoLanguage PK is INT and not SmallInt (which it might be in older db versions) - // In order to 'change' this to an INT, we have to run a full migration script which is super annoying - Create.Table("umbracoLanguage_TEMP") - .WithColumn("id").AsInt32().NotNullable().Identity() - .WithColumn("languageISOCode").AsString(10).Nullable() - .WithColumn("languageCultureName").AsString(50).Nullable(); - - var currentData = this.Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); - foreach (var languageDto in currentData) - { - Insert.IntoTable("umbracoLanguage_TEMP") - .EnableIdentityInsert() - .Row(new {id = languageDto.Id, languageISOCode = languageDto.IsoCode, languageCultureName = languageDto.CultureName}); - } - - //ok, all data has been copied over, drop the old table, rename the temp table and re-add constraints. - Delete.Table("umbracoLanguage"); - Rename.Table("umbracoLanguage_TEMP").To("umbracoLanguage"); - - //add the pk - Create.PrimaryKey("PK_language").OnTable("umbracoLanguage").Column("id"); - } - - var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDictionary_id")) == false) - { - Create.Index("IX_cmsDictionary_id").OnTable("cmsDictionary") - .OnColumn("id").Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique(); - } - - //now we need to create a foreign key - Create.ForeignKey("FK_cmsLanguageText_umbracoLanguage_id").FromTable("cmsLanguageText").ForeignColumn("languageId") - .ToTable("umbracoLanguage").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); - - Alter.Table("cmsDictionary").AlterColumn("parent").AsGuid().Nullable(); - - //set the parent to null if it equals the default dictionary item root id - foreach (var pk in Context.Database.Query("SELECT pk FROM cmsDictionary WHERE parent NOT IN (SELECT id FROM cmsDictionary)")) - { - Update.Table("cmsDictionary").Set(new { parent = (Guid?)null }).Where(new { pk = pk }); - } - - Create.ForeignKey("FK_cmsDictionary_cmsDictionary_id").FromTable("cmsDictionary").ForeignColumn("parent") - .ToTable("cmsDictionary").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); - } - - - - } - - public override void Down() - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddMigrationTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddMigrationTable.cs deleted file mode 100644 index 856a1d8ff6..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddMigrationTable.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 11, Constants.System.UmbracoMigrationName)] - public class AddMigrationTable : MigrationBase - { - public AddMigrationTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Don't exeucte if the table is already there - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoMigration")) return; - - Create.Table("umbracoMigration") - .WithColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoMigrations").Identity() - .WithColumn("name").AsString(255).NotNullable() - .WithColumn("version").AsString(50).NotNullable() - .WithColumn("createDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime); - - //unique constraint on name + version - Create.Index("IX_umbracoMigration").OnTable("umbracoMigration") - .OnColumn("name").Ascending() - .OnColumn("version").Ascending() - .WithOptions() - .NonClustered() - .WithOptions() - .Unique(); - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs deleted file mode 100644 index b4ec1c20a6..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddPublicAccessTables.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 6, Constants.System.UmbracoMigrationName)] - public class AddPublicAccessTables : MigrationBase - { - public AddPublicAccessTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Don't exeucte if the table is already there - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoAccess")) return; - - Create.Table("umbracoAccess") - .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_umbracoAccess") - .WithColumn("nodeId").AsInt32().NotNullable().ForeignKey("FK_umbracoAccess_umbracoNode_id", "umbracoNode", "id") - .WithColumn("loginNodeId").AsInt32().NotNullable().ForeignKey("FK_umbracoAccess_umbracoNode_id1", "umbracoNode", "id") - .WithColumn("noAccessNodeId").AsInt32().NotNullable().ForeignKey("FK_umbracoAccess_umbracoNode_id2", "umbracoNode", "id") - .WithColumn("createDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime) - .WithColumn("updateDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime); - - //unique constraint on node id = 1:1 - Create.Index("IX_umbracoAccess_nodeId").OnTable("umbracoAccess").OnColumn("nodeId").Unique(); - - Create.Table("umbracoAccessRule") - .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_umbracoAccessRule") - .WithColumn("accessId").AsGuid().NotNullable().ForeignKey("FK_umbracoAccessRule_umbracoAccess_id", "umbracoAccess", "id") - .WithColumn("ruleValue").AsString().NotNullable() - .WithColumn("ruleType").AsString().NotNullable() - .WithColumn("createDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime) - .WithColumn("updateDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime); - - //unique constraint on node + ruleValue + ruleType - Create.Index("IX_umbracoAccessRule").OnTable("umbracoAccessRule") - .OnColumn("accessId").Ascending() - .OnColumn("ruleValue").Ascending() - .OnColumn("ruleType").Ascending() - .WithOptions() - .Unique(); - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs deleted file mode 100644 index b8141711d7..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddRelationTypeForDocumentOnDelete.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 0, Constants.System.UmbracoMigrationName)] - public class AddRelationTypeForDocumentOnDelete : MigrationBase - { - public AddRelationTypeForDocumentOnDelete(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var exists = Context.Database.FirstOrDefault("WHERE alias=@alias", new {alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias}); - if (exists == null) - { - Insert.IntoTable("umbracoRelationType").Row(new - { - dual = false, - parentObjectType = Guid.Parse(Constants.ObjectTypes.Document), - childObjectType = Guid.Parse(Constants.ObjectTypes.Document), - name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName, - alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias - }); - } - } - - public override void Down() - { } - - // need to capture the DTO as it is modified in later migrations - - [TableName("umbracoRelationType")] - [PrimaryKey("id")] - [ExplicitColumns] - internal class RelationTypeDtoCapture - { - public const int NodeIdSeed = 3; - - [Column("id")] - [PrimaryKeyColumn(IdentitySeed = NodeIdSeed)] - public int Id { get; set; } - - [Column("dual")] - public bool Dual { get; set; } - - [Column("parentObjectType")] - public Guid ParentObjectType { get; set; } - - [Column("childObjectType")] - public Guid ChildObjectType { get; set; } - - [Column("name")] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")] - public string Name { get; set; } - - [Column("alias")] - [NullSetting(NullSetting = NullSettings.Null)] - [Length(100)] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")] - public string Alias { get; set; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs deleted file mode 100644 index 00ab602343..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 17, Constants.System.UmbracoMigrationName)] - public class AddServerRegistrationColumnsAndLock : MigrationBase - { - public AddServerRegistrationColumnsAndLock(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - // don't execute if the column is already there - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - if (columns.Any(x => x.TableName.InvariantEquals("umbracoServer") && x.ColumnName.InvariantEquals("isMaster")) == false) - { - Create.Column("isMaster").OnTable("umbracoServer").AsBoolean().NotNullable().WithDefaultValue(0); - } - - EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers"); - } - - public override void Down() - { - // not implemented - } - - private void EnsureLockObject(int id, string uniqueId, string text) - { - var exists = Context.Database.Exists(id); - if (exists) return; - - Insert - .IntoTable("umbracoNode") - .EnableIdentityInsert() - .Row(new - { - id = id, // NodeId - trashed = false, - parentId = -1, - nodeUser = 0, - level = 1, - path = "-1," + id, - sortOrder = 0, - uniqueId = new Guid(uniqueId), - text = text, - nodeObjectType = new Guid(Constants.ObjectTypes.LockObject), - createDate = DateTime.Now - }); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs deleted file mode 100644 index 070ef5d512..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUniqueIdPropertyTypeColumn.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 13, Constants.System.UmbracoMigrationName)] - public class AddUniqueIdPropertyTypeColumn : MigrationBase - { - public AddUniqueIdPropertyTypeColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - //Don't exeucte if the column is already there - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("cmsPropertyType") && x.ColumnName.InvariantEquals("uniqueID")) == false) - { - Create.Column("uniqueID").OnTable("cmsPropertyType").AsGuid().NotNullable().WithDefault(SystemMethods.NewGuid); - - // unique constraint on name + version - Create.Index("IX_cmsPropertyTypeUniqueID").OnTable("cmsPropertyType") - .OnColumn("uniqueID").Ascending() - .WithOptions() - .NonClustered() - .WithOptions() - .Unique(); - - // fill in the data in a way that is consistent over all environments - // (ie cannot use random guids, http://issues.umbraco.org/issue/U4-6942) - - foreach (var data in Context.Database.Query(@" -SELECT cmsPropertyType.id ptId, cmsPropertyType.Alias ptAlias, cmsContentType.alias ctAlias, umbracoNode.nodeObjectType nObjType -FROM cmsPropertyType -INNER JOIN cmsContentType -ON cmsPropertyType.contentTypeId = cmsContentType.nodeId -INNER JOIN umbracoNode -ON cmsContentType.nodeId = umbracoNode.id")) - { - // create a consistent guid from - // property alias + content type alias + object type - string guidSource = data.ptAlias + data.ctAlias + data.nObjType; - var guid = guidSource.ToGuid(); - - // set the Unique Id to the one we've generated - Update.Table("cmsPropertyType").Set(new { uniqueID = guid }).Where(new { id = data.ptId }); - } - } - } - - public override void Down() - { } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs deleted file mode 100644 index 01a3c61791..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddUserColumns.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 10, Constants.System.UmbracoMigrationName)] - public class AddUserColumns : MigrationBase - { - public AddUserColumns(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Don't exeucte if the column is already there - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("securityStampToken")) == false) - Create.Column("securityStampToken").OnTable("umbracoUser").AsString(255).Nullable(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("failedLoginAttempts")) == false) - Create.Column("failedLoginAttempts").OnTable("umbracoUser").AsInt32().Nullable(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("lastLockoutDate")) == false) - Create.Column("lastLockoutDate").OnTable("umbracoUser").AsDateTime().Nullable(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("lastPasswordChangeDate")) == false) - Create.Column("lastPasswordChangeDate").OnTable("umbracoUser").AsDateTime().Nullable(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("lastLoginDate")) == false) - Create.Column("lastLoginDate").OnTable("umbracoUser").AsDateTime().Nullable(); - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CleanUpCorruptedPublishedFlags.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CleanUpCorruptedPublishedFlags.cs deleted file mode 100644 index f8dfc0e8bb..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CleanUpCorruptedPublishedFlags.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - /// - /// Older corrupted dbs might have multiple published flags for a content item, this shouldn't be possible - /// so we need to clear the content flag on the older version - /// - [Migration("7.3.0", 18, Constants.System.UmbracoMigrationName)] - public class CleanUpCorruptedPublishedFlags : MigrationBase - { - public CleanUpCorruptedPublishedFlags(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Get all cmsDocument items that have more than one published date set - var sql = @"SELECT * FROM cmsDocument WHERE nodeId IN -(SELECT nodeId -FROM cmsDocument -GROUP BY nodeId -HAVING SUM(CASE WHEN published = 0 THEN 0 ELSE 1 END) > 1) -AND published = 1 -ORDER BY nodeId, updateDate"; - - var docs = Context.Database.Fetch(sql).GroupBy(x => x.NodeId); - - foreach (var doc in docs) - { - var latest = doc.OrderByDescending(x => x.UpdateDate).First(); - foreach (var old in doc.Where(x => x.VersionId != latest.VersionId)) - { - Update.Table("cmsDocument").Set(new {published = 0}).Where(new {nodeId = old.NodeId}); - } - } - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs deleted file mode 100644 index d51aceb679..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 1, Constants.System.UmbracoMigrationName)] - public class CreateCacheInstructionTable : MigrationBase - { - public CreateCacheInstructionTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - - //Don't exeucte if the table is already there - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoCacheInstruction")) return; - - var textType = SqlSyntax.GetSpecialDbType(SpecialDbTypes.NTEXT); - - Create.Table("umbracoCacheInstruction") - .WithColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoCacheInstruction").Identity() - .WithColumn("utcStamp").AsDateTime().NotNullable() - .WithColumn("jsonInstruction").AsCustom(textType).NotNullable() - .WithColumn("originated").AsString(500).NotNullable(); - - } - - public override void Down() - { - Delete.PrimaryKey("PK_umbracoCacheInstruction").FromTable("cmsContentType2ContentType"); - Delete.Table("cmsContentType2ContentType"); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs deleted file mode 100644 index 59236106ab..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - /// - /// Remove the master column after we've migrated all of the values into the 'ParentId' and Path column of Umbraco node - /// - [Migration("7.3.0", 1, Constants.System.UmbracoMigrationName)] - public class MigrateAndRemoveTemplateMasterColumn : MigrationBase - { - - public MigrateAndRemoveTemplateMasterColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - - //Don't execute anything if there is no 'master' column - this might occur if the db is already upgraded - var cols = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - if (cols.Any(x => x.ColumnName.InvariantEquals("master") && x.TableName.InvariantEquals("cmsTemplate")) == false) - { - return; - } - - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - - //update the parentId column for all templates to be correct so it matches the current 'master' template - - //In some old corrupted databases, the information will not be correct in the master column so we need to fix that - //first by nulling out the master column where the id doesn't actually exist - Execute.Sql(@"UPDATE cmsTemplate SET master = NULL WHERE " + - SqlSyntax.GetQuotedColumnName("master") + @" IS NOT NULL AND " + - SqlSyntax.GetQuotedColumnName("master") + @" NOT IN (" + - //Stupid MySQL... needs this stupid syntax because it can do an update with a sub query of itself, - // yet it can do one with a sub sub query - // ... this will work in all dbs too - @"SELECT nodeId FROM (SELECT * FROM cmsTemplate a) b)"); - - //Now we can bulk update the parentId column - - //NOTE: This single statement should be used but stupid SQLCE doesn't support Update with a FROM !! - // so now we have to do this by individual rows :( - //Execute.Sql(@"UPDATE umbracoNode - //SET parentID = COALESCE(t2." + SqlSyntax.GetQuotedColumnName("master") + @", -1) - //FROM umbracoNode t1 - //INNER JOIN cmsTemplate t2 - //ON t1.id = t2.nodeId"); - Execute.Code(database => - { - var templateData = database.Fetch("SELECT * FROM cmsTemplate"); - - foreach (var template in templateData) - { - var sql = "SET parentID=@parentId WHERE id=@nodeId"; - - LogHelper.Info("Executing sql statement: UPDATE umbracoNode " + sql); - - database.Update(sql, - new {parentId = template.master ?? -1, nodeId = template.nodeId}); - } - - return string.Empty; - }); - - //Now we can update the path, but this needs to be done in a delegate callback so that the query runs after the updates just completed - Execute.Code(database => - { - //NOTE: we are using dynamic because we need to get the data in a column that no longer exists in the schema - var templates = database.Fetch(new Sql().Select("*").From()); - foreach (var template in templates) - { - var sql = string.Format(SqlSyntax.UpdateData, - SqlSyntax.GetQuotedTableName("umbracoNode"), - "path=@buildPath", - "id=@nodeId"); - - LogHelper.Info("Executing sql statement: " + sql); - - //now build the correct path for the template - database.Execute(sql, new - { - buildPath = BuildPath(template, templates), - nodeId = template.nodeId - }); - } - - return string.Empty; - }); - - - - //now remove the master column and key - if (this.Context.CurrentDatabaseProvider == DatabaseProviders.MySql) - { - //Because MySQL doesn't name keys with what you want, we need to query for the one that is associated - // this is required for this specific case because there are 2 foreign keys on the cmsTemplate table - var fkName = constraints.FirstOrDefault(x => x.Item1.InvariantEquals("cmsTemplate") && x.Item2.InvariantEquals("master")); - if (fkName != null) - { - Delete.ForeignKey(fkName.Item3).OnTable("cmsTemplate"); - } - } - else - { - if (constraints.Any(x => x.Item1.InvariantEquals("cmsTemplate") && x.Item3.InvariantEquals("FK_cmsTemplate_cmsTemplate"))) - { - Delete.ForeignKey("FK_cmsTemplate_cmsTemplate").OnTable("cmsTemplate"); - } - - //TODO: Hopefully it's not named something else silly in some crazy old versions - } - - - var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - - //in some databases there's an index (IX_Master) on the master column which needs to be dropped first - var foundIndex = dbIndexes.FirstOrDefault(x => x.TableName.InvariantEquals("cmsTemplate") && x.ColumnName.InvariantEquals("master")); - if (foundIndex != null) - { - Delete.Index(foundIndex.IndexName).OnTable("cmsTemplate"); - } - - if (cols.Any(x => x.ColumnName.InvariantEquals("master") && x.TableName.InvariantEquals("cmsTemplate"))) - { - Delete.Column("master").FromTable("cmsTemplate"); - } - } - - public override void Down() - { - } - - private string BuildPath(dynamic template, IEnumerable allTemplates) - { - if (template.master == null) - { - return string.Format("-1,{0}", template.nodeId); - } - - var parent = allTemplates.FirstOrDefault(x => x.nodeId == template.master); - - if (parent == null) - { - //this shouldn't happen but i suppose it could if people have bad data - return string.Format("-1,{0}", template.nodeId); - } - - //recurse - var parentPath = BuildPath(parent, allTemplates); - - var path = parentPath + string.Format(",{0}", template.nodeId); - return path; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateStylesheetDataToFile.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateStylesheetDataToFile.cs deleted file mode 100644 index 40d2a6f183..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateStylesheetDataToFile.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence.SqlSyntax; -using File = System.IO.File; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - /// - /// Ensures that any stylesheets that have properties defined at the db level have the correct new syntax - /// in their files before we remove the stylesheet db tables - /// - /// - /// Instead of modifying the files directly (since we cannot roll them back) we will create temporary migrated files. - /// These files will then be copied over once the entire migration is complete so that if any migration fails and the db changes are - /// rolled back, the original files won't be affected. - /// - [Migration("7.3.0", 2, Constants.System.UmbracoMigrationName)] - public class MigrateStylesheetDataToFile : MigrationBase - { - public MigrateStylesheetDataToFile(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Don't exeucte if the stylesheet table is not there - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("cmsStylesheet") == false) return; - - //This is all rather nasty but it's how stylesheets used to work in the 2 various ugly ways so we just have to - // deal with that to get this migration done - - - var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/CssMigration/"); - if (Directory.Exists(tempFolder)) - { - //clear any existing css files (we back the real ones up so if this migration is run again for whatever reason anything that - // was previously backed up is still there, backup happens in a post migration: OverwriteStylesheetFilesFromTempFiles class) - var files = Directory.GetFiles(tempFolder, "*.css", SearchOption.AllDirectories); - foreach (var file in files) - { - File.Delete(file); - } - } - //create the temp folder - var tempDir = Directory.CreateDirectory(IOHelper.MapPath("~/App_Data/TEMP/CssMigration/")); - - - var sheets = Context.Database.Fetch("SELECT * FROM cmsStylesheet INNER JOIN umbracoNode on cmsStylesheet.nodeId = umbracoNode.id"); - foreach (var sheet in sheets) - { - var fileName = sheet.text; - - string dbContent; - //we will always use the file content over the db content if there is any - using (var memStream = new MemoryStream(Encoding.UTF8.GetBytes(sheet.content))) - { - dbContent = GetContentAboveUmbracoProps(memStream); - } - - var fileContent = string.Empty; - - //check for file and read in it's data - umbraco properties will only be kept that are in the db, - // anything below the infamous: /* EDITOR PROPERTIES - PLEASE DON'T DELETE THIS LINE TO AVOID DUPLICATE PROPERTIES */ - // line is an Umbraco property and therefore anything that is there that is not in the db will be cleared. - - var filePath = IOHelper.MapPath(string.Format("{0}/{1}.css", SystemDirectories.Css, fileName)); - if (File.Exists(filePath)) - { - using (var stream = File.OpenRead(filePath)) - { - fileContent = GetContentAboveUmbracoProps(stream); - } - } - - var props = Context.Database.Fetch("SELECT * FROM cmsStylesheetProperty INNER JOIN umbracoNode ON cmsStylesheetProperty.nodeId = umbracoNode.id WHere umbracoNode.parentID = @id", new { id = sheet.nodeId }); - - var cssFolderPath = IOHelper.MapPath(SystemDirectories.Css); - var relativeFsPath = StringExtensions.TrimStart(StringExtensions.TrimStart(filePath, cssFolderPath), "\\"); - var stylesheetInstance = new Stylesheet(relativeFsPath) - { - Content = fileContent.IsNullOrWhiteSpace() ? dbContent : fileContent - }; - - foreach (var prop in props) - { - if (stylesheetInstance.Properties.Any(x => x.Name == prop.text) == false) - { - stylesheetInstance.AddProperty(new StylesheetProperty(prop.text, prop.stylesheetPropertyAlias, prop.stylesheetPropertyValue)); - } - } - - //Save to temp folder - - //ensure the folder for the file exists since it could be in a sub folder - var tempFilePath = Path.Combine(tempDir.FullName, relativeFsPath); - Directory.CreateDirectory(Path.GetDirectoryName(tempFilePath)); - - File.WriteAllText(tempFilePath, stylesheetInstance.Content, Encoding.UTF8); - - } - } - - public override void Down() - { - throw new NotSupportedException("Cannot downgrade from 7.3 as there are database table deletions"); - } - - private string GetContentAboveUmbracoProps(Stream stream) - { - var content = string.Empty; - using (var re = new StreamReader(stream)) - { - string input; - var readingProperties = false; - - while ((input = re.ReadLine()) != null) - { - //This is that line that was in there before that was delimiting umb props from normal css: - // /* EDITOR PROPERTIES - PLEASE DON'T DELETE THIS LINE TO AVOID DUPLICATE PROPERTIES */ - if (input.Contains("EDITOR PROPERTIES")) - { - readingProperties = true; - continue; - } - - if (readingProperties == false) - { - content += input; - } - } - } - return content; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs deleted file mode 100644 index 16d1c6fbf5..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 7, Constants.System.UmbracoMigrationName)] - public class MovePublicAccessXmlDataToDb : MigrationBase - { - public MovePublicAccessXmlDataToDb(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Don't lookup data if the table doesn't exist - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoAccess")) - { - //don't run if data is already there. - var dataExists = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); - if (dataExists.Any()) return; - } - - var xmlFile = IOHelper.MapPath(SystemFiles.AccessXml); - - if (File.Exists(xmlFile) == false) return; - - using (var fileStream = File.OpenRead(xmlFile)) - { - var xml = XDocument.Load(fileStream); - - foreach (var page in xml.Root.Elements("page")) - { - var pageId = (int?)page.Attribute("id"); - var loginPageId = (int?)page.Attribute("loginPage"); - var noRightsPageId = (int?)page.Attribute("noRightsPage"); - if (pageId.HasValue == false || loginPageId.HasValue == false || noRightsPageId.HasValue == false) - continue; - - //ensure this page exists! - var umbracoNode = Context.Database.FirstOrDefault("WHERE id = @Id", new { Id = pageId.Value }); - if (umbracoNode != null) - { - var loginNode = Context.Database.FirstOrDefault("WHERE id = @Id", new { Id = loginPageId.Value }); - if (loginNode != null) - { - var noRightsPage = Context.Database.FirstOrDefault("WHERE id = @Id", new { Id = noRightsPageId.Value }); - if (noRightsPage != null) - { - var accessId = Guid.NewGuid(); - Insert.IntoTable("umbracoAccess").Row(new - { - id = accessId, - nodeId = umbracoNode.NodeId, - loginNodeId = loginNode.NodeId, - noAccessNodeId = noRightsPage.NodeId, - createDate = DateTime.Now, - updateDate = DateTime.Now - }); - - //if a memberId has been specified, then add that as a rule - var memberId = (string)page.Attribute("memberId"); - if (memberId.IsNullOrWhiteSpace() == false) - { - Insert.IntoTable("umbracoAccessRule").Row(new - { - id = Guid.NewGuid(), - accessId = accessId, - ruleValue = memberId, - ruleType = Constants.Conventions.PublicAccess.MemberUsernameRuleType, - createDate = DateTime.Now, - updateDate = DateTime.Now - }); - } - - //now there should be a member group(s) defined here - var memberGroupElements = page.Elements("group"); - foreach (var memberGroupElement in memberGroupElements) - { - var memberGroup = (string)memberGroupElement.Attribute("id"); - if (memberGroup.IsNullOrWhiteSpace() == false) - { - //create a member group rule - Insert.IntoTable("umbracoAccessRule").Row(new - { - id = Guid.NewGuid(), - accessId = accessId, - ruleValue = memberGroup, - ruleType = Constants.Conventions.PublicAccess.MemberRoleRuleType, - createDate = DateTime.Now, - updateDate = DateTime.Now - }); - } - } - - } - else - { - Logger.Warn("No umbracoNode could be found with id " + noRightsPageId.Value); - } - } - else - { - Logger.Warn("No umbracoNode could be found with id " + loginPageId.Value); - } - - } - else - { - Logger.Warn("No umbracoNode could be found with id " + pageId.Value); - } - } - - } - - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveHelpTextColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveHelpTextColumn.cs deleted file mode 100644 index 5370ec0de3..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveHelpTextColumn.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 8, Constants.System.UmbracoMigrationName)] - public class RemoveHelpTextColumn : MigrationBase - { - public RemoveHelpTextColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); - - if (columns.Any(x => x.ColumnName.InvariantEquals("helpText") && x.TableName.InvariantEquals("cmsPropertyType"))) - { - Delete.Column("helpText").FromTable("cmsPropertyType"); - } - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveLanguageLocaleColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveLanguageLocaleColumn.cs deleted file mode 100644 index bf57364615..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveLanguageLocaleColumn.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 4, Constants.System.UmbracoMigrationName)] - public class RemoveLanguageLocaleColumn : MigrationBase - { - public RemoveLanguageLocaleColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); - - if (columns.Any(x => x.ColumnName.InvariantEquals("LanguageLocale") && x.TableName.InvariantEquals("cmsContentVersion"))) - { - Delete.Column("LanguageLocale").FromTable("cmsContentVersion"); - } - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveStylesheetDataAndTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveStylesheetDataAndTables.cs deleted file mode 100644 index e6b195cbf4..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveStylesheetDataAndTables.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 3, Constants.System.UmbracoMigrationName)] - public class RemoveStylesheetDataAndTables : MigrationBase - { - public RemoveStylesheetDataAndTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Clear all stylesheet data if the tables exist - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("cmsStylesheetProperty")) - { - Delete.FromTable("cmsStylesheetProperty").AllRows(); - Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.StylesheetProperty) }); - - Delete.Table("cmsStylesheetProperty"); - } - if (tables.InvariantContains("cmsStylesheet")) - { - Delete.FromTable("cmsStylesheet").AllRows(); - Delete.FromTable("umbracoNode").Row(new { nodeObjectType = new Guid(Constants.ObjectTypes.Stylesheet) }); - - Delete.Table("cmsStylesheet"); - } - } - - public override void Down() - { - throw new NotSupportedException("Cannot downgrade from 7.3 as there are database table deletions"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs deleted file mode 100644 index 5fef478cc6..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/RemoveUmbracoLoginsTable.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 15, Constants.System.UmbracoMigrationName)] - public class RemoveUmbracoLoginsTable : MigrationBase - { - public RemoveUmbracoLoginsTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { - } - - public override void Up() - { - if (SqlSyntax.GetColumnsInSchema(Context.Database).Any(x => x.TableName.InvariantEquals("umbracoUserLogins"))) - { - Delete.Table("umbracoUserLogins"); - } - } - - public override void Down() - { - throw new System.NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs deleted file mode 100644 index dc8415a9f8..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/UpdateUniqueIdToHaveCorrectIndexType.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero -{ - [Migration("7.3.0", 5, Constants.System.UmbracoMigrationName)] - public class UpdateUniqueIdToHaveCorrectIndexType : MigrationBase - { - public UpdateUniqueIdToHaveCorrectIndexType(ISqlSyntaxProvider sqlSyntax, ILogger logger) - : base(sqlSyntax, logger) - { } - - public override void Up() - { - var indexes = SqlSyntax.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - - // drop the index if it exists - if (indexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodeUniqueID"))) - Delete.Index("IX_umbracoNodeUniqueID").OnTable("umbracoNode"); - - // set uniqueID to be non-nullable - // the index *must* be dropped else 'one or more objects access this column' exception - Alter.Table("umbracoNode").AlterColumn("uniqueID").AsGuid().NotNullable(); - - // create the index - Create.Index("IX_umbracoNode_uniqueID").OnTable("umbracoNode").OnColumn("uniqueID").Unique(); - } - - public override void Down() - { } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddMissingForeignKeyForContentType.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddMissingForeignKeyForContentType.cs deleted file mode 100644 index 47b1f334d3..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AddMissingForeignKeyForContentType.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Data; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwoZero -{ - [Migration("7.2.0", 1, Constants.System.UmbracoMigrationName)] - public class AddMissingForeignKeyForContentType : MigrationBase - { - public AddMissingForeignKeyForContentType(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - - //if the FK doesn't exist - if (constraints.Any(x => x.Item1.InvariantEquals("cmsContent") && x.Item2.InvariantEquals("contentType") && x.Item3.InvariantEquals("FK_cmsContent_cmsContentType_nodeId")) == false) - { - //Before we can add the foreign key there cannot be any orphaned content, media, members - var orphanedIds = Context.Database.Fetch("SELECT contentType FROM cmsContent WHERE NOT EXISTS (SELECT cmsContentType.nodeId FROM cmsContentType WHERE cmsContentType.nodeId = cmsContent.contentType)"); - foreach (var orphanedId in orphanedIds) - { - Delete.FromTable("cmsTask").Row(new { nodeId = orphanedId }); - Delete.FromTable("umbracoUser2NodeNotify").Row(new { nodeId = orphanedId }); - Delete.FromTable("umbracoUser2NodePermission").Row(new { nodeId = orphanedId }); - Delete.FromTable("umbracoRelation").Row(new { parentId = orphanedId }); - Delete.FromTable("umbracoRelation").Row(new { childId = orphanedId }); - Delete.FromTable("cmsTagRelationship").Row(new { nodeId = orphanedId }); - Delete.FromTable("umbracoDomains").Row(new { domainRootStructureID = orphanedId }); - Delete.FromTable("cmsDocument").Row(new { nodeId = orphanedId }); - Delete.FromTable("cmsPropertyData").Row(new { contentNodeId = orphanedId }); - Delete.FromTable("cmsMember2MemberGroup").Row(new { Member = orphanedId }); - Delete.FromTable("cmsMember").Row(new { nodeId = orphanedId }); - Delete.FromTable("cmsPreviewXml").Row(new { nodeId = orphanedId }); - Delete.FromTable("cmsContentVersion").Row(new { ContentId = orphanedId }); - Delete.FromTable("cmsContentXml").Row(new { nodeId = orphanedId }); - Delete.FromTable("cmsContent").Row(new { nodeId = orphanedId }); - Delete.FromTable("umbracoNode").Row(new { id = orphanedId }); - } - - //Some very old schemas don't have an index on the cmsContentType.nodeId column, I'm not actually sure when it was added but - // it is absolutely required to exist in order to add other foreign keys and much better for perf, so we'll need to check it's existence - // this came to light from this issue: http://issues.umbraco.org/issue/U4-4133 - var dbIndexes = SqlSyntaxContext.SqlSyntaxProvider.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsContentType")) == false) - { - Create.Index("IX_cmsContentType").OnTable("cmsContentType").OnColumn("nodeId").Ascending().WithOptions().Unique(); - } - if (dbIndexes.Any(x => x.TableName.InvariantEquals("cmsContentType") && x.ColumnName.InvariantEquals("icon")) == false) - { - Create.Index("IX_cmsContentType_icon").OnTable("cmsContentType").OnColumn("icon").Ascending().WithOptions().NonClustered(); - } - - Create.ForeignKey("FK_cmsContent_cmsContentType_nodeId") - .FromTable("cmsContent") - .ForeignColumn("contentType") - .ToTable("cmsContentType") - .PrimaryColumn("nodeId") - .OnDelete(Rule.None) - .OnUpdate(Rule.None); - } - - - } - - public override void Down() - { - throw new DataLossException("Cannot downgrade from a version 7.2 database to a prior version, the database schema has already been modified"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AlterDataTypePreValueTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AlterDataTypePreValueTable.cs deleted file mode 100644 index ba3938dd18..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/AlterDataTypePreValueTable.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Linq; -using AutoMapper; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwoZero -{ - - - [Migration("7.2.0", 0, Constants.System.UmbracoMigrationName)] - public class AlterDataTypePreValueTable : MigrationBase - { - public AlterDataTypePreValueTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); - - //Check if it's already text - if (columns.Any(x => x.ColumnName.InvariantEquals("value") && x.TableName.InvariantEquals("cmsDataTypePreValues") - //mysql check - && (x.DataType.InvariantEquals("longtext") == false - //sql server check - && x.DataType.InvariantEquals("ntext") == false))) - { - //To text - var textType = SqlSyntax.GetSpecialDbType(SpecialDbTypes.NTEXT); - Alter.Table("cmsDataTypePreValues").AlterColumn("value").AsCustom(textType).Nullable(); - } - - } - - public override void Down() - { - //back to 2500 chars - Alter.Table("cmsDataTypePreValues").AlterColumn("value").AsString(2500).Nullable(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/RemoveCmsDocumentAliasColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/RemoveCmsDocumentAliasColumn.cs deleted file mode 100644 index e67a8e7756..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenTwoZero/RemoveCmsDocumentAliasColumn.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwoZero -{ - [Migration("7.2.0", 2, Constants.System.UmbracoMigrationName)] - public class RemoveCmsDocumentAliasColumn : MigrationBase - { - public RemoveCmsDocumentAliasColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray(); - - if (columns.Any(x => x.ColumnName.InvariantEquals("alias") && x.TableName.InvariantEquals("cmsDocument"))) - { - Delete.Column("alias").FromTable("cmsDocument"); - } - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/DeleteAppTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/DeleteAppTables.cs deleted file mode 100644 index b4d394bec2..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/DeleteAppTables.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix -{ - - [Migration("6.0.0", 10, Constants.System.UmbracoMigrationName)] - public class DeleteAppTables : MigrationBase - { - public DeleteAppTables(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Delete.Table("umbracoAppTree"); - - //NOTE: this is a hack since old umbraco versions might not have had their db's upgraded correctly so they are all quite inconsistent. - // This is actually done in migration: RemoveUmbracoAppConstraints to target 4.9.0 but we've found with some db's that are currently at 4.9.1, - // these upgrades did not run. So, now we not only have to check if these constraints exist, but we also have to check if the RemoveUmbracoAppConstraints - // has executed since we cannot drop the same foreign key twice or we'll get errors. - - //Here we'll do a dirty check to see if RemoveUmbracoAppConstraints has executed - if (Context.Expressions.Any(x => - { - var b = x as MigrationExpressionBase; - if (b == null) return false; - return b.Name == "FK_umbracoUser2app_umbracoApp"; - }) == false) - { - //These are the old aliases, before removing them, check they exist - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser2app") && x.Item3.InvariantEquals("FK_umbracoUser2app_umbracoApp"))) - { - Delete.ForeignKey("FK_umbracoUser2app_umbracoApp").OnTable("umbracoUser2app"); - } - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser2app") && x.Item3.InvariantEquals("FK_umbracoUser2app_umbracoUser"))) - { - Delete.ForeignKey("FK_umbracoUser2app_umbracoUser").OnTable("umbracoUser2app"); - } - } - - Delete.Table("umbracoApp"); - } - - public override void Down() - { - //This cannot be rolled back!! - throw new DataLossException("Cannot rollback migration " + typeof(DeleteAppTables) + " the db tables umbracoAppTree and umbracoApp have been droppped"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/EnsureAppsTreesUpdated.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/EnsureAppsTreesUpdated.cs deleted file mode 100644 index 49cedc6dc7..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/EnsureAppsTreesUpdated.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix -{ - [Migration("6.0.0", 9, Constants.System.UmbracoMigrationName)] - public class EnsureAppsTreesUpdated : MigrationBase - { - public EnsureAppsTreesUpdated(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var e = new UpgradingEventArgs(); - - if (Upgrading != null) - Upgrading(this, e); - } - - public override void Down() - { - } - - public static event EventHandler Upgrading; - - public class UpgradingEventArgs : EventArgs{} - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/MoveMasterContentTypeData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/MoveMasterContentTypeData.cs deleted file mode 100644 index 6e77050fd4..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/MoveMasterContentTypeData.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix -{ - [Migration("6.0.0", 5, Constants.System.UmbracoMigrationName)] - public class MoveMasterContentTypeData : MigrationBase - { - public MoveMasterContentTypeData(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Reading entries from the cmsContentType table in order to update the parentID on the umbracoNode table. - //NOTE This is primarily done because of a shortcoming in sql ce, which has really bad support for updates (can't use FROM or subqueries with multiple results). - if (base.Context != null && base.Context.Database != null) - { - var list = base.Context.Database.Fetch("SELECT nodeId, masterContentType FROM cmsContentType WHERE not masterContentType is null AND masterContentType != 0"); - foreach (var item in list) - { - Update.Table("umbracoNode").Set(new { parentID = item.masterContentType }).Where(new { id = item.nodeId }); - } - } - - Execute.Sql( - "INSERT INTO cmsContentType2ContentType (parentContentTypeId, childContentTypeId) SELECT masterContentType, nodeId FROM cmsContentType WHERE not masterContentType is null and masterContentType != 0"); - } - - public override void Down() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/NewCmsContentType2ContentTypeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/NewCmsContentType2ContentTypeTable.cs deleted file mode 100644 index ee427bb517..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/NewCmsContentType2ContentTypeTable.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix -{ - [Migration("6.0.0", 4, Constants.System.UmbracoMigrationName)] - public class NewCmsContentType2ContentTypeTable : MigrationBase - { - public NewCmsContentType2ContentTypeTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Create.Table("cmsContentType2ContentType") - .WithColumn("parentContentTypeId").AsInt16().NotNullable() - .WithColumn("childContentTypeId").AsInt16().NotNullable(); - - Create.PrimaryKey("PK_cmsContentType2ContentType") - .OnTable("cmsContentType2ContentType") - .Columns(new[] {"parentContentTypeId", "childContentTypeId"}); - } - - public override void Down() - { - Delete.PrimaryKey("PK_cmsContentType2ContentType").FromTable("cmsContentType2ContentType"); - - Delete.Table("cmsContentType2ContentType"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RemoveMasterContentTypeColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RemoveMasterContentTypeColumn.cs deleted file mode 100644 index e8964ced28..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RemoveMasterContentTypeColumn.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix -{ - [Migration("6.0.0", 6, Constants.System.UmbracoMigrationName)] - public class RemoveMasterContentTypeColumn : MigrationBase - { - public RemoveMasterContentTypeColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //NOTE Don't think we can remove this column yet as it seems to be used by some starterkits - IfDatabase(DatabaseProviders.SqlAzure, DatabaseProviders.SqlServer, DatabaseProviders.SqlServerCE) - .Delete.DefaultConstraint().OnTable("cmsContentType").OnColumn("masterContentType"); - - Delete.Column("masterContentType").FromTable("cmsContentType"); - } - - public override void Down() - { - Create.UniqueConstraint("DF_cmsContentType_masterContentType").OnTable("cmsContentType").Column("masterContentType"); - - Create.Column("masterContentType").OnTable("cmsContentType").AsInt16().Nullable().WithDefaultValue(0); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameCmsTabTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameCmsTabTable.cs deleted file mode 100644 index c0738383d2..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameCmsTabTable.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix -{ - [Migration("6.0.0", 0, Constants.System.UmbracoMigrationName)] - public class RenameCmsTabTable : MigrationBase - { - public RenameCmsTabTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Rename.Table("cmsTab").To("cmsPropertyTypeGroup"); - } - - public override void Down() - { - Rename.Table("cmsPropertyTypeGroup").To("cmsTab"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameTabIdColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameTabIdColumn.cs deleted file mode 100644 index fff439c03d..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/RenameTabIdColumn.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Data; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix -{ - [Migration("6.0.0", 7, Constants.System.UmbracoMigrationName)] - public class RenameTabIdColumn : MigrationBase - { - public RenameTabIdColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //Conditional Create-column for Sql Ce databases - IfDatabase(DatabaseProviders.SqlServerCE) - .Create.Column("propertyTypeGroupId").OnTable("cmsPropertyType").AsInt16().Nullable(); - - //Conditional Create-foreign for Sql Ce databases - IfDatabase(DatabaseProviders.SqlServerCE) - .Create.ForeignKey("FK_cmsPropertyType_cmsPropertyTypeGroup") - .FromTable("cmsPropertyType").ForeignColumn("propertyTypeGroupId") - .ToTable("cmsPropertyTypeGroup").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); - - //Conditional Delete-foreignkey for MySql databases - IfDatabase(DatabaseProviders.MySql) - .Delete.ForeignKey().FromTable("cmsPropertyType").ForeignColumn("tabId").ToTable("cmsPropertyTypeGroup").PrimaryColumn("id"); - - Rename.Column("tabId").OnTable("cmsPropertyType").To("propertyTypeGroupId"); - - //Conditional Create-foreign for MySql databases - IfDatabase(DatabaseProviders.MySql) - .Create.ForeignKey("FK_cmsPropertyType_cmsPropertyTypeGroup") - .FromTable("cmsPropertyType").ForeignColumn("propertyTypeGroupId") - .ToTable("cmsPropertyTypeGroup").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); - - //Conditional Delete-foreignkey for Sql Ce databases - IfDatabase(DatabaseProviders.SqlServerCE) - .Delete.ForeignKey("FK_cmsPropertyType_cmsTab").OnTable("cmsPropertyType"); - - //Conditional Delete-column for Sql Ce databases - IfDatabase(DatabaseProviders.SqlServerCE) - .Delete.Column("tabId").FromTable("cmsPropertyType"); - } - - public override void Down() - { - //Conditional Create-column for Sql Ce databases - IfDatabase(DatabaseProviders.SqlServerCE) - .Create.Column("tabId").OnTable("cmsPropertyType").AsInt16().Nullable(); - - //Conditional Create-foreign for Sql Ce databases - IfDatabase(DatabaseProviders.SqlServerCE) - .Create.ForeignKey("FK_cmsPropertyType_cmsTab") - .FromTable("cmsPropertyType").ForeignColumn("tabId") - .ToTable("cmsTab").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); - - //Conditional Delete-foreignkey for MySql databases - IfDatabase(DatabaseProviders.MySql) - .Delete.ForeignKey().FromTable("cmsPropertyType").ForeignColumn("propertyTypeGroupId").ToTable("cmsPropertyTypeGroup").PrimaryColumn("id"); - - Rename.Column("propertyTypeGroupId").OnTable("cmsPropertyType").To("tabId"); - - //Conditional Create-foreign for MySql databases - IfDatabase(DatabaseProviders.MySql) - .Create.ForeignKey("FK_cmsPropertyType_cmsPropertyTypeGroup") - .FromTable("cmsPropertyType").ForeignColumn("tabId") - .ToTable("cmsPropertyTypeGroup").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); - - //Conditional Delete-foreignkey for Sql Ce databases - IfDatabase(DatabaseProviders.SqlServerCE) - .Delete.ForeignKey("FK_cmsPropertyType_cmsPropertyTypeGroup").OnTable("cmsPropertyType"); - - //Conditional Delete-column for Sql Ce databases - IfDatabase(DatabaseProviders.SqlServerCE) - .Delete.Column("propertyTypeGroupId").FromTable("propertyTypeGroupId"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeAllowedContentTypeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeAllowedContentTypeTable.cs deleted file mode 100644 index 11168a69cb..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeAllowedContentTypeTable.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix -{ - [Migration("6.0.0", 3, Constants.System.UmbracoMigrationName)] - public class UpdateCmsContentTypeAllowedContentTypeTable : MigrationBase - { - public UpdateCmsContentTypeAllowedContentTypeTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Alter.Table("cmsContentTypeAllowedContentType").AddColumn("sortOrder").AsInt16().NotNullable().WithDefaultValue(1); - } - - public override void Down() - { - Delete.Column("sortOrder").FromTable("cmsContentTypeAllowedContentType"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeTable.cs deleted file mode 100644 index eb57f87c95..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentTypeTable.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix -{ - [Migration("6.0.0", 2, Constants.System.UmbracoMigrationName)] - public class UpdateCmsContentTypeTable : MigrationBase - { - public UpdateCmsContentTypeTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Alter.Table("cmsContentType").AddColumn("isContainer").AsBoolean().NotNullable().WithDefaultValue(0); - - Alter.Table("cmsContentType").AddColumn("allowAtRoot").AsBoolean().NotNullable().WithDefaultValue(0); - } - - public override void Down() - { - Delete.Column("allowAtRoot").FromTable("cmsContentType"); - - Delete.Column("isContainer").FromTable("cmsContentType"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentVersionTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentVersionTable.cs deleted file mode 100644 index cee55fee41..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsContentVersionTable.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix -{ - [Migration("6.0.0", 8, Constants.System.UmbracoMigrationName)] - public class UpdateCmsContentVersionTable : MigrationBase - { - public UpdateCmsContentVersionTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Alter.Table("cmsContentVersion").AddColumn("LanguageLocale").AsString(10).Nullable(); - } - - public override void Down() - { - Delete.Column("LanguageLocale").FromTable("cmsContentVersion"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsPropertyTypeGroupTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsPropertyTypeGroupTable.cs deleted file mode 100644 index 7e0244bc57..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSix/UpdateCmsPropertyTypeGroupTable.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Data; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix -{ - [Migration("6.0.0", 1, Constants.System.UmbracoMigrationName)] - public class UpdateCmsPropertyTypeGroupTable : MigrationBase - { - public UpdateCmsPropertyTypeGroupTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Alter.Table("cmsPropertyTypeGroup").AddColumn("parentGroupId").AsInt16().Nullable(); - - Create.ForeignKey("FK_cmsPropertyTypeGroup_cmsPropertyTypeGroup_id") - .FromTable("cmsPropertyTypeGroup").ForeignColumn("parentGroupId") - .ToTable("cmsPropertyTypeGroup").PrimaryColumn("id").OnDeleteOrUpdate(Rule.None); - } - - public override void Down() - { - Delete.ForeignKey().FromTable("cmsPropertyTypeGroup").ForeignColumn("parentGroupId").ToTable("cmsPropertyTypeGroup").PrimaryColumn("id"); - - Delete.Column("parentGroupId").FromTable("cmsPropertyTypeGroup"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixOneZero/CreateServerRegistryTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixOneZero/CreateServerRegistryTable.cs deleted file mode 100644 index bcd7ac5d6f..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixOneZero/CreateServerRegistryTable.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixOneZero -{ - - [Migration("6.1.0", 0, Constants.System.UmbracoMigrationName)] - public class CreateServerRegistryTable : MigrationBase - { - public CreateServerRegistryTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - var schemaHelper = new DatabaseSchemaHelper(Context.Database, Logger, SqlSyntax); - - //NOTE: This isn't the correct way to do this but to manually create this table with the Create syntax is a pain in the arse - schemaHelper.CreateTable(); - } - - public override void Down() - { - Delete.Table("umbracoServer"); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AddChangeDocumentTypePermission.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AddChangeDocumentTypePermission.cs deleted file mode 100644 index 42a652b166..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AddChangeDocumentTypePermission.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero -{ - [Migration("7.1.0", 3, Constants.System.UmbracoMigrationName)] - [Migration("6.2.0", 3, Constants.System.UmbracoMigrationName)] - public class AddChangeDocumentTypePermission : MigrationBase - { - public AddChangeDocumentTypePermission(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Execute.Code(AddChangeDocumentTypePermissionDo); - } - - public override void Down() - { - Execute.Code(UndoChangeDocumentTypePermissionDo); - } - - private static string AddChangeDocumentTypePermissionDo(Database database) - { - var adminUserType = database.Fetch("WHERE Id = 1").FirstOrDefault(); - - if (adminUserType != null) - { - if (adminUserType.DefaultPermissions.Contains("7") == false) - { - adminUserType.DefaultPermissions = adminUserType.DefaultPermissions + "7"; - database.Save(adminUserType); - } - } - - return string.Empty; - } - - private static string UndoChangeDocumentTypePermissionDo(Database database) - { - var adminUserType = database.Fetch("WHERE Id = 1").FirstOrDefault(); - - if (adminUserType != null) - { - if (adminUserType.DefaultPermissions.Contains("7")) - { - adminUserType.DefaultPermissions = adminUserType.DefaultPermissions.Replace("7", ""); - database.Save(adminUserType); - } - } - - return string.Empty; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs deleted file mode 100644 index 64f2970bdf..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AdditionalIndexesAndKeys.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Migrations.Initial; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero -{ - [Migration("7.1.0", 1, Constants.System.UmbracoMigrationName)] - [Migration("6.2.0", 1, Constants.System.UmbracoMigrationName)] - public class AdditionalIndexesAndKeys : MigrationBase - { - public AdditionalIndexesAndKeys(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - - var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database) - .Select(x => new DbIndexDefinition() - { - TableName = x.Item1, - IndexName = x.Item2, - ColumnName = x.Item3, - IsUnique = x.Item4 - }).ToArray(); - - //do not create any indexes if they already exist in the database - - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodeTrashed")) == false) - { - Create.Index("IX_umbracoNodeTrashed").OnTable("umbracoNode").OnColumn("trashed").Ascending().WithOptions().NonClustered(); - } - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsContentVersion_ContentId")) == false) - { - Create.Index("IX_cmsContentVersion_ContentId").OnTable("cmsContentVersion").OnColumn("ContentId").Ascending().WithOptions().NonClustered(); - } - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDocument_published")) == false) - { - Create.Index("IX_cmsDocument_published").OnTable("cmsDocument").OnColumn("published").Ascending().WithOptions().NonClustered(); - } - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDocument_newest")) == false) - { - Create.Index("IX_cmsDocument_newest").OnTable("cmsDocument").OnColumn("newest").Ascending().WithOptions().NonClustered(); - } - - //We need to do this for SQL Azure V2 since it does not let you drop any clustered indexes - // Issue: http://issues.umbraco.org/issue/U4-5673 - if (Context.CurrentDatabaseProvider == DatabaseProviders.SqlServer || Context.CurrentDatabaseProvider == DatabaseProviders.SqlAzure) - { - var version = Context.Database.ExecuteScalar("SELECT @@@@VERSION"); - if (version.Contains("Microsoft SQL Azure")) - { - var parts = version.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); - if (parts.Length > 1) - { - if (parts[1].StartsWith("11.")) - { - - //we want to drop the umbracoUserLogins_Index index since it is named incorrectly and then re-create it so - // it follows the standard naming convention - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("umbracoUserLogins_Index"))) - { - //It's the old version that doesn't support dropping a clustered index on a table, so we need to do some manual work. - ExecuteSqlAzureSqlForChangingIndex(); - } - - return; - } - } - } - } - - - //we want to drop the umbracoUserLogins_Index index since it is named incorrectly and then re-create it so - // it follows the standard naming convention - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("umbracoUserLogins_Index"))) - { - Delete.Index("umbracoUserLogins_Index").OnTable("umbracoUserLogins"); - } - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoUserLogins_Index")) == false) - { - Create.Index("IX_umbracoUserLogins_Index").OnTable("umbracoUserLogins").OnColumn("contextID").Ascending().WithOptions().Clustered(); - } - } - - public override void Down() - { - Delete.Index("IX_umbracoNodeTrashed").OnTable("umbracoNode"); - Delete.Index("IX_cmsContentVersion_ContentId").OnTable("cmsContentVersion"); - Delete.Index("IX_cmsDocument_published").OnTable("cmsDocument"); - Delete.Index("IX_cmsDocument_newest").OnTable("cmsDocument"); - } - - private void ExecuteSqlAzureSqlForChangingIndex() - { - Context.Database.Execute(@"CREATE TABLE ""umbracoUserLogins_temp"" -( - contextID uniqueidentifier NOT NULL, - userID int NOT NULL, - [timeout] bigint NOT NULL -); -CREATE CLUSTERED INDEX ""IX_umbracoUserLogins_Index"" ON ""umbracoUserLogins_temp"" (""contextID""); -INSERT INTO ""umbracoUserLogins_temp"" SELECT * FROM ""umbracoUserLogins"" -DROP TABLE ""umbracoUserLogins"" -CREATE TABLE ""umbracoUserLogins"" -( - contextID uniqueidentifier NOT NULL, - userID int NOT NULL, - [timeout] bigint NOT NULL -); -CREATE CLUSTERED INDEX ""IX_umbracoUserLogins_Index"" ON ""umbracoUserLogins"" (""contextID""); -INSERT INTO ""umbracoUserLogins"" SELECT * FROM ""umbracoUserLogins_temp"" -DROP TABLE ""umbracoUserLogins_temp"""); - - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs deleted file mode 100644 index 5f2d6292c1..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Linq; -using System.Web.UI; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero -{ - - //see: http://issues.umbraco.org/issue/U4-4430 - [Migration("7.1.0", 0, Constants.System.UmbracoMigrationName)] - [Migration("6.2.0", 0, Constants.System.UmbracoMigrationName)] - public class AssignMissingPrimaryForMySqlKeys : MigrationBase - { - public AssignMissingPrimaryForMySqlKeys(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) - { - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - - //This should be 2 because this table has 2 keys - if (constraints.Count(x => x.Item1.InvariantEquals("cmsContentTypeAllowedContentType") && x.Item3.InvariantEquals("PRIMARY")) == 0) - { - Create.PrimaryKey("PK_cmsContentTypeAllowedContentType") - .OnTable("cmsContentTypeAllowedContentType") - .Columns(new[] { "Id", "AllowedId" }); - } - - //This should be 2 because this table has 2 keys - if (constraints.Count(x => x.Item1.InvariantEquals("cmsDocumentType") && x.Item3.InvariantEquals("PRIMARY")) == 0) - { - Create.PrimaryKey("PK_cmsDocumentType") - .OnTable("cmsDocumentType") - .Columns(new[] { "contentTypeNodeId", "templateNodeId" }); - } - - //This should be 2 because this table has 2 keys - if (constraints.Count(x => x.Item1.InvariantEquals("cmsMember2MemberGroup") && x.Item3.InvariantEquals("PRIMARY")) == 0) - { - Create.PrimaryKey("PK_cmsMember2MemberGroup") - .OnTable("cmsMember2MemberGroup") - .Columns(new[] { "Member", "MemberGroup" }); - } - - //This should be 2 because this table has 2 keys - if (constraints.Count(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantEquals("PRIMARY")) == 0) - { - Create.PrimaryKey("PK_cmsContentPreviewXml") - .OnTable("cmsPreviewXml") - .Columns(new[] { "nodeId", "versionId" }); - } - - //This should be 2 because this table has 2 keys - if (constraints.Count(x => x.Item1.InvariantEquals("umbracoUser2app") && x.Item3.InvariantEquals("PRIMARY")) == 0) - { - Create.PrimaryKey("PK_user2app") - .OnTable("umbracoUser2app") - .Columns(new[] { "user", "app" }); - } - - //This should be 2 because this table has 3 keys - if (constraints.Count(x => x.Item1.InvariantEquals("umbracoUser2NodeNotify") && x.Item3.InvariantEquals("PRIMARY")) == 0) - { - Create.PrimaryKey("PK_umbracoUser2NodeNotify") - .OnTable("umbracoUser2NodeNotify") - .Columns(new[] { "userId", "nodeId", "action" }); - } - - //This should be 2 because this table has 3 keys - if (constraints.Count(x => x.Item1.InvariantEquals("umbracoUser2NodePermission") && x.Item3.InvariantEquals("PRIMARY")) == 0) - { - Create.PrimaryKey("PK_umbracoUser2NodePermission") - .OnTable("umbracoUser2NodePermission") - .Columns(new[] { "userId", "nodeId", "permission" }); - } - } - } - - public override void Down() - { - //don't do anything, these keys should have always existed! - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys2.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys2.cs deleted file mode 100644 index da7a5ce609..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys2.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero -{ - //We have to target this specifically to ensure this DOES NOT execute if upgrading from a version previous to 6.0, - // this is because when the 6.0.0 migrations are executed, this primary key get's created so if this migration is also executed - // we will get exceptions because it is trying to create the PK two times. - [Migration("6.0.0", "6.2.0", 0, Constants.System.UmbracoMigrationName)] - public class AssignMissingPrimaryForMySqlKeys2 : MigrationBase - { - public AssignMissingPrimaryForMySqlKeys2(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) - { - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - - //This should be 2 because this table has 2 keys - if (constraints.Count(x => x.Item1.InvariantEquals("cmsContentType2ContentType") && x.Item3.InvariantEquals("PRIMARY")) == 0) - { - Create.PrimaryKey("PK_cmsContentType2ContentType") - .OnTable("cmsContentType2ContentType") - .Columns(new[] {"parentContentTypeId", "childContentTypeId"}); - } - } - } - - public override void Down() - { - //don't do anything, these keys should have always existed! - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/ChangePasswordColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/ChangePasswordColumn.cs deleted file mode 100644 index 327291f34b..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/ChangePasswordColumn.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero -{ - [Migration("7.1.0", 2, Constants.System.UmbracoMigrationName)] - [Migration("6.2.0", 2, Constants.System.UmbracoMigrationName)] - public class ChangePasswordColumn : MigrationBase - { - public ChangePasswordColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - //up to 500 chars - Alter.Table("umbracoUser").AlterColumn("userPassword").AsString(500).NotNullable(); - } - - public override void Down() - { - //back to 125 chars - Alter.Table("umbracoUser").AlterColumn("userPassword").AsString(125).NotNullable(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs deleted file mode 100644 index 4038bdd1e9..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero -{ - [Migration("7.1.0", 4, Constants.System.UmbracoMigrationName)] - [Migration("6.2.0", 4, Constants.System.UmbracoMigrationName)] - public class UpdateToNewMemberPropertyAliases : MigrationBase - { - public UpdateToNewMemberPropertyAliases(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Execute.Code(Update); - } - - internal string Update(Database database) - { - if (database != null) - { - var aliasMap = new Dictionary - { - {"umbracoPasswordRetrievalQuestionPropertyTypeAlias", Constants.Conventions.Member.PasswordQuestion}, - {"umbracoPasswordRetrievalAnswerPropertyTypeAlias", Constants.Conventions.Member.PasswordAnswer}, - {"umbracoCommentPropertyTypeAlias", Constants.Conventions.Member.Comments}, - {"umbracoApprovePropertyTypeAlias", Constants.Conventions.Member.IsApproved}, - {"umbracoLockPropertyTypeAlias", Constants.Conventions.Member.IsLockedOut}, - {"umbracoLastLoginPropertyTypeAlias", Constants.Conventions.Member.LastLoginDate}, - {"umbracoMemberLastPasswordChange", Constants.Conventions.Member.LastPasswordChangeDate}, - {"umbracoMemberLastLockout", Constants.Conventions.Member.LastLockoutDate}, - {"umbracoFailedPasswordAttemptsPropertyTypeAlias", Constants.Conventions.Member.FailedPasswordAttempts} - }; - - - //This query is structured to work with MySql, SQLCE and SqlServer: - // http://issues.umbraco.org/issue/U4-3876 - - const string propertyTypeUpdateSql = @"UPDATE cmsPropertyType -SET Alias = @newAlias -WHERE Alias = @oldAlias AND contentTypeId IN ( -SELECT nodeId FROM (SELECT DISTINCT cmsContentType.nodeId FROM cmsPropertyType -INNER JOIN cmsContentType ON cmsPropertyType.contentTypeId = cmsContentType.nodeId -INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id -WHERE umbracoNode.nodeObjectType = @objectType) x)"; - - const string xmlSelectSql = @"SELECT cmsContentXml.* FROM cmsContentXml -INNER JOIN umbracoNode ON cmsContentXml.nodeId = umbracoNode.id -WHERE umbracoNode.nodeObjectType = @objectType"; - - using (var trans = database.GetTransaction()) - { - try - { - - //Upate all of the property type aliases - foreach (var map in aliasMap) - { - database.Execute(propertyTypeUpdateSql, new { newAlias = map.Value, oldAlias = map.Key, objectType = Constants.ObjectTypes.MemberType }); - } - - //Update all of the XML - var items = database.Fetch(xmlSelectSql, new { objectType = Constants.ObjectTypes.Member }); - foreach (var item in items) - { - foreach (var map in aliasMap) - { - item.Xml = item.Xml.Replace("<" + map.Key + ">", "<" + map.Value + ">"); - item.Xml = item.Xml.Replace("", ""); - } - database.Update(item); - } - - trans.Complete(); - } - catch (Exception ex) - { - Logger.Error("Exception was thrown when trying to upgrade old member aliases to the new ones", ex); - throw; - } - } - - - } - return string.Empty; - } - - public override void Down() - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs deleted file mode 100644 index 15817d313d..0000000000 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixZeroOne -{ - [Migration("6.0.2", 0, Constants.System.UmbracoMigrationName)] - public class UpdatePropertyTypesAndGroups : MigrationBase - { - public UpdatePropertyTypesAndGroups(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger) - { - } - - public override void Up() - { - Execute.Code(UpdatePropertyTypesAndGroupsDo); - } - - public override void Down() - { - - } - - public static string UpdatePropertyTypesAndGroupsDo(Database database) - { - if (database != null) - { - //Fetch all PropertyTypes that belongs to a PropertyTypeGroup - //NOTE: We are writing the full query because we've added a column to the PropertyTypeDto in later versions so one of the columns - // won't exist yet - var propertyTypes = database.Fetch("SELECT * FROM cmsPropertyType WHERE propertyTypeGroupId > 0"); - - // need to use dynamic, as PropertyTypeGroupDto has new properties - var propertyGroups = database.Fetch("SELECT * FROM cmsPropertyTypeGroup WHERE id > 0"); - - foreach (var propertyType in propertyTypes) - { - // get the PropertyTypeGroup of the current PropertyType, skip if not found - var propertyTypeGroup = propertyGroups.FirstOrDefault(x => x.id == propertyType.propertyTypeGroupId); - if (propertyTypeGroup == null) continue; - - // if the PropretyTypeGroup belongs to the same content type as the PropertyType, then fine - if (propertyTypeGroup.contenttypeNodeId == propertyType.contentTypeId) continue; - - // else we want to assign the PropertyType to a proper PropertyTypeGroup - // ie one that does belong to the same content - look for it - var okPropertyTypeGroup = propertyGroups.FirstOrDefault(x => - x.text == propertyTypeGroup.text && // same name - x.contenttypeNodeId == propertyType.contentTypeId); // but for proper content type - - if (okPropertyTypeGroup == null) - { - // does not exist, create a new PropertyTypeGroup - // cannot use a PropertyTypeGroupDto because of the new (not-yet-existing) uniqueID property - // cannot use a dynamic because database.Insert fails to set the value of property - var propertyGroup = new PropertyTypeGroupDtoTemp - { - id = 0, - contenttypeNodeId = propertyType.contentTypeId, - text = propertyTypeGroup.text, - sortorder = propertyTypeGroup.sortorder - }; - - // save + add to list of groups - int id = Convert.ToInt16(database.Insert("cmsPropertyTypeGroup", "id", propertyGroup)); - propertyGroup.id = id; - propertyGroups.Add(propertyGroup); - - // update the PropertyType to use the new PropertyTypeGroup - propertyType.propertyTypeGroupId = id; - } - else - { - // exists, update PropertyType to use the PropertyTypeGroup - propertyType.propertyTypeGroupId = okPropertyTypeGroup.id; - } - database.Update("cmsPropertyType", "id", propertyType); - } - } - - return string.Empty; - } - - private class PropertyTypeGroupDtoTemp - { - public int id { get; set; } - public int contenttypeNodeId { get; set; } - public string text { get; set; } - public int sortorder { get; set; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs new file mode 100644 index 0000000000..77b412ef2b --- /dev/null +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlClient; +using System.Data.SqlServerCe; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NPoco; +using StackExchange.Profiling.Data; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence +{ + /// + /// Provides extension methods to NPoco Database class. + /// + public static partial class NPocoDatabaseExtensions + { + // todo: review NPoco native InsertBulk to replace the code below + + /// + /// Bulk-inserts records within a transaction. + /// + /// The type of the records. + /// The database. + /// The records. + /// Whether to use native bulk insert when available. + public static void BulkInsertRecordsWithTransaction(this IUmbracoDatabase database, IEnumerable records, bool useNativeBulkInsert = true) + { + var recordsA = records.ToArray(); + if (recordsA.Length == 0) + return; + + // no need to "try...catch", if the transaction is not completed it will rollback! + using (var tr = database.GetTransaction()) + { + database.BulkInsertRecords(recordsA, useNativeBulkInsert); + tr.Complete(); + } + } + + /// + /// Bulk-inserts records. + /// + /// The type of the records. + /// The database. + /// The records. + /// Whether to use native bulk insert when available. + /// The number of records that were inserted. + public static int BulkInsertRecords(this IUmbracoDatabase database, IEnumerable records, bool useNativeBulkInsert = true) + { + var recordsA = records.ToArray(); + if (recordsA.Length == 0) return 0; + + var pocoData = database.PocoDataFactory.ForType(typeof(T)); + if (pocoData == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T)); + + if (database.DatabaseType.IsSqlCe()) + { + if (useNativeBulkInsert) return BulkInsertRecordsSqlCe(database, pocoData, recordsA); + // else, no other choice + foreach (var record in recordsA) + database.Insert(record); + return recordsA.Length; + } + + if (database.DatabaseType.IsSqlServer()) + { + return useNativeBulkInsert && database.DatabaseType.IsSqlServer2008OrLater() + ? BulkInsertRecordsSqlServer(database, pocoData, recordsA) + : BulkInsertRecordsWithCommands(database, recordsA); + } + + if (database.DatabaseType.IsMySql()) + return BulkInsertRecordsWithCommands(database, recordsA); + + throw new NotSupportedException(); + } + + /// + /// Bulk-insert records using commands. + /// + /// The type of the records. + /// The database. + /// The records. + /// The number of records that were inserted. + private static int BulkInsertRecordsWithCommands(IUmbracoDatabase database, T[] records) + { + foreach (var command in database.GenerateBulkInsertCommands(records)) + command.ExecuteNonQuery(); + + return records.Length; // what else? + } + + /// + /// Creates bulk-insert commands. + /// + /// The type of the records. + /// The database. + /// The records. + /// The sql commands to execute. + internal static IDbCommand[] GenerateBulkInsertCommands(this IUmbracoDatabase database, T[] records) + { + if (database?.Connection == null) throw new ArgumentException("Null database?.connection.", nameof(database)); + + var pocoData = database.PocoDataFactory.ForType(typeof(T)); + + // get columns to include, = number of parameters per row + var columns = pocoData.Columns.Where(c => IncludeColumn(pocoData, c)).ToArray(); + var paramsPerRecord = columns.Length; + + // format columns to sql + var tableName = database.DatabaseType.EscapeTableName(pocoData.TableInfo.TableName); + var columnNames = string.Join(", ", columns.Select(c => tableName + "." + database.DatabaseType.EscapeSqlIdentifier(c.Key))); + + // example: + // assume 4168 records, each record containing 8 fields, ie 8 command parameters + // max 2100 parameter per command + // Math.Floor(2100 / 8) = 262 record per command + // 4168 / 262 = 15.908... = there will be 16 command in total + // (if we have disabled db parameters, then all records will be included, in only one command) + var recordsPerCommand = paramsPerRecord == 0 ? int.MaxValue : Convert.ToInt32(Math.Floor(2000.00 / paramsPerRecord)); + var commandsCount = Convert.ToInt32(Math.Ceiling((double)records.Length / recordsPerCommand)); + + var commands = new IDbCommand[commandsCount]; + var recordsIndex = 0; + var recordsLeftToInsert = records.Length; + var prefix = database.DatabaseType.GetParameterPrefix(database.ConnectionString); + for (var commandIndex = 0; commandIndex < commandsCount; commandIndex++) + { + var command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty); + var parameterIndex = 0; + var commandRecords = Math.Min(recordsPerCommand, recordsLeftToInsert); + var recordsValues = new string[commandRecords]; + for (var commandRecordIndex = 0; commandRecordIndex < commandRecords; commandRecordIndex++, recordsIndex++, recordsLeftToInsert--) + { + var record = records[recordsIndex]; + var recordValues = new string[columns.Length]; + for (var columnIndex = 0; columnIndex < columns.Length; columnIndex++) + { + database.AddParameter(command, columns[columnIndex].Value.GetValue(record)); + recordValues[columnIndex] = prefix + parameterIndex++; + } + recordsValues[commandRecordIndex] = "(" + string.Join(",", recordValues) + ")"; + } + + command.CommandText = $"INSERT INTO {tableName} ({columnNames}) VALUES {string.Join(", ", recordsValues)}"; + commands[commandIndex] = command; + } + + return commands; + } + + /// + /// Determines whether a column should be part of a bulk-insert. + /// + /// The PocoData object corresponding to the record's type. + /// The column. + /// A value indicating whether the column should be part of the bulk-insert. + /// Columns that are primary keys and auto-incremental, or result columns, are excluded from bulk-inserts. + private static bool IncludeColumn(PocoData pocoData, KeyValuePair column) + { + return column.Value.ResultColumn == false + && (pocoData.TableInfo.AutoIncrement == false || column.Key != pocoData.TableInfo.PrimaryKey); + } + + /// + /// Bulk-insert records using SqlCE TableDirect method. + /// + /// The type of the records. + /// The database. + /// The PocoData object corresponding to the record's type. + /// The records. + /// The number of records that were inserted. + internal static int BulkInsertRecordsSqlCe(IUmbracoDatabase database, PocoData pocoData, IEnumerable records) + { + var columns = pocoData.Columns.ToArray(); + + // create command against the original database.Connection + using (var command = database.CreateCommand(database.Connection, CommandType.TableDirect, string.Empty)) + { + command.CommandText = pocoData.TableInfo.TableName; + command.CommandType = CommandType.TableDirect; // fixme - why repeat? + // fixme - not supporting transactions? + //cmd.Transaction = GetTypedTransaction(db.Connection.); + + var count = 0; + var tCommand = GetTypedCommand(command); // execute on the real command + + // seems to cause problems, I think this is primarily used for retrieval, not inserting. + // see: https://msdn.microsoft.com/en-us/library/system.data.sqlserverce.sqlcecommand.indexname%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396 + //tCommand.IndexName = pd.TableInfo.PrimaryKey; + + using (var resultSet = tCommand.ExecuteResultSet(ResultSetOptions.Updatable)) + { + var updatableRecord = resultSet.CreateRecord(); + foreach (var record in records) + { + for (var i = 0; i < columns.Length; i++) + { + // skip the index if this shouldn't be included (i.e. PK) + if (IncludeColumn(pocoData, columns[i])) + { + var val = columns[i].Value.GetValue(record); + updatableRecord.SetValue(i, val); + } + } + resultSet.Insert(updatableRecord); + count++; + } + } + + return count; + } + } + + /// + /// Bulk-insert records using SqlServer BulkCopy method. + /// + /// The type of the records. + /// The database. + /// The PocoData object corresponding to the record's type. + /// The records. + /// The number of records that were inserted. + internal static int BulkInsertRecordsSqlServer(IUmbracoDatabase database, PocoData pocoData, IEnumerable records) + { + // create command against the original database.Connection + using (var command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty)) + { + // use typed connection and transactionf or SqlBulkCopy + var tConnection = GetTypedConnection(database.Connection); + var tTransaction = GetTypedTransaction(command.Transaction); + var tableName = pocoData.TableInfo.TableName; + + var syntax = database.SqlContext.SqlSyntax as SqlServerSyntaxProvider; + if (syntax == null) throw new NotSupportedException("SqlSyntax must be SqlServerSyntaxProvider."); + + using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction) { BulkCopyTimeout = 10000, DestinationTableName = tableName }) + using (var bulkReader = new PocoDataDataReader(records, pocoData, syntax)) + { + copy.WriteToServer(bulkReader); + return bulkReader.RecordsAffected; + } + } + } + + } +} diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs new file mode 100644 index 0000000000..b81e68e567 --- /dev/null +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Data.SqlClient; +using System.Data.SqlServerCe; +using System.Linq; +using System.Text.RegularExpressions; +using NPoco; +using StackExchange.Profiling.Data; +using Umbraco.Core.Persistence.FaultHandling; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence +{ + /// + /// Provides extension methods to NPoco Database class. + /// + public static partial class NPocoDatabaseExtensions + { + // NOTE + // + // proper way to do it with TSQL and SQLCE + // IF EXISTS (SELECT ... FROM table WITH (UPDLOCK,HOLDLOCK)) WHERE ...) + // BEGIN + // UPDATE table SET ... WHERE ... + // END + // ELSE + // BEGIN + // INSERT INTO table (...) VALUES (...) + // END + // + // works in READ COMMITED, TSQL & SQLCE lock the constraint even if it does not exist, so INSERT is OK + // + // proper way to do it with MySQL + // IF EXISTS (SELECT ... FROM table WHERE ... FOR UPDATE) + // BEGIN + // UPDATE table SET ... WHERE ... + // END + // ELSE + // BEGIN + // INSERT INTO table (...) VALUES (...) + // END + // + // MySQL locks the constraint ONLY if it exists, so INSERT may fail... + // in theory, happens in READ COMMITTED but not REPEATABLE READ + // http://www.percona.com/blog/2012/08/28/differences-between-read-committed-and-repeatable-read-transaction-isolation-levels/ + // but according to + // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html + // it won't work for exact index value (only ranges) so really... + // + // MySQL should do + // INSERT INTO table (...) VALUES (...) ON DUPLICATE KEY UPDATE ... + // + // also the lock is released when the transaction is committed + // not sure if that can have unexpected consequences on our code? + // + // so... for the time being, let's do with that somewhat crazy solution below... + // todo: use the proper database syntax, not this kludge + + /// + /// Safely inserts a record, or updates if it exists, based on a unique constraint. + /// + /// + /// + /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object + /// passed in will contain the updated value. + /// + /// We cannot rely on database-specific options such as MySql ON DUPLICATE KEY UPDATE or MSSQL MERGE WHEN MATCHED because SQLCE + /// does not support any of them. Ideally this should be achieved with proper transaction isolation levels but that would mean revisiting + /// isolation levels globally. We want to keep it simple for the time being and manage it manually. + /// We handle it by trying to update, then insert, etc. until something works, or we get bored. + /// Note that with proper transactions, if T2 begins after T1 then we are sure that the database will contain T2's value + /// once T1 and T2 have completed. Whereas here, it could contain T1's value. + /// + internal static RecordPersistenceType InsertOrUpdate(this IUmbracoDatabase db, T poco) + where T : class + { + return db.InsertOrUpdate(poco, null, null); + } + + /// + /// Safely inserts a record, or updates if it exists, based on a unique constraint. + /// + /// + /// + /// + /// If the entity has a composite key they you need to specify the update command explicitly + /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object + /// passed in will contain the updated value. + /// + /// We cannot rely on database-specific options such as MySql ON DUPLICATE KEY UPDATE or MSSQL MERGE WHEN MATCHED because SQLCE + /// does not support any of them. Ideally this should be achieved with proper transaction isolation levels but that would mean revisiting + /// isolation levels globally. We want to keep it simple for the time being and manage it manually. + /// We handle it by trying to update, then insert, etc. until something works, or we get bored. + /// Note that with proper transactions, if T2 begins after T1 then we are sure that the database will contain T2's value + /// once T1 and T2 have completed. Whereas here, it could contain T1's value. + /// + internal static RecordPersistenceType InsertOrUpdate(this IUmbracoDatabase db, + T poco, + string updateCommand, + object updateArgs) + where T : class + { + if (poco == null) + throw new ArgumentNullException(nameof(poco)); + + // fixme - NPoco has a Save method that works with the primary key + // in any case, no point trying to update if there's no primary key! + + // try to update + var rowCount = updateCommand.IsNullOrWhiteSpace() + ? db.Update(poco) + : db.Update(updateCommand, updateArgs); + if (rowCount > 0) + return RecordPersistenceType.Update; + + // failed: does not exist, need to insert + // RC1 race cond here: another thread may insert a record with the same constraint + + var i = 0; + while (i++ < 4) + { + try + { + // try to insert + db.Insert(poco); + return RecordPersistenceType.Insert; + } + catch (SqlException) // assuming all db engines will throw that exception + { + // failed: exists (due to race cond RC1) + // RC2 race cond here: another thread may remove the record + + // try to update + rowCount = updateCommand.IsNullOrWhiteSpace() + ? db.Update(poco) + : db.Update(updateCommand, updateArgs); + if (rowCount > 0) + return RecordPersistenceType.Update; + + // failed: does not exist (due to race cond RC2), need to insert + // loop + } + } + + // this can go on forever... have to break at some point and report an error. + throw new DataException("Record could not be inserted or updated."); + } + + /// + /// This will escape single @ symbols for npoco values so it doesn't think it's a parameter + /// + /// + /// + public static string EscapeAtSymbols(string value) + { + if (value.Contains("@") == false) return value; + + //this fancy regex will only match a single @ not a double, etc... + var regex = new Regex("(? + /// Returns the underlying connection as a typed connection - this is used to unwrap the profiled mini profiler stuff + /// + /// + /// + /// + private static TConnection GetTypedConnection(IDbConnection connection) + where TConnection : class, IDbConnection + { + var profiled = connection as ProfiledDbConnection; + return profiled == null ? connection as TConnection : profiled.InnerConnection as TConnection; + } + + /// + /// Returns the underlying transaction as a typed transaction - this is used to unwrap the profiled mini profiler stuff + /// + /// + /// + /// + private static TTransaction GetTypedTransaction(IDbTransaction transaction) + where TTransaction : class, IDbTransaction + { + var profiled = transaction as ProfiledDbTransaction; + return profiled == null ? transaction as TTransaction : profiled.WrappedTransaction as TTransaction; + } + + /// + /// Returns the underlying command as a typed command - this is used to unwrap the profiled mini profiler stuff + /// + /// + /// + /// + private static TCommand GetTypedCommand(IDbCommand command) + where TCommand : class, IDbCommand + { + var faultHandling = command as FaultHandlingDbCommand; + if (faultHandling != null) command = faultHandling.Inner; + var profiled = command as ProfiledDbCommand; + if (profiled != null) command = profiled.InternalCommand; + return command as TCommand; + } + + public static void TruncateTable(this IDatabase db, ISqlSyntaxProvider sqlSyntax, string tableName) + { + var sql = new Sql(string.Format( + sqlSyntax.TruncateTable, + sqlSyntax.GetQuotedTableName(tableName))); + db.Execute(sql); + } + + public static IsolationLevel GetCurrentTransactionIsolationLevel(this IDatabase database) + { + var transaction = database.Transaction; + return transaction?.IsolationLevel ?? IsolationLevel.Unspecified; + } + + public static IEnumerable FetchByGroups(this IDatabase db, IEnumerable source, int groupSize, Func, Sql> sqlFactory) + { + return source.SelectByGroups(x => db.Fetch(sqlFactory(x)), groupSize); + } + } +} diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs new file mode 100644 index 0000000000..e40e6dedd3 --- /dev/null +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs @@ -0,0 +1,35 @@ +using NPoco; + +namespace Umbraco.Core.Persistence +{ + internal static class NPocoDatabaseTypeExtensions + { + public static bool IsMySql(this DatabaseType databaseType) + { + return databaseType is NPoco.DatabaseTypes.MySqlDatabaseType; + } + + public static bool IsSqlServer(this DatabaseType databaseType) + { + // note that because SqlServerDatabaseType is the base class for + // all Sql Server types eg SqlServer2012DatabaseType, this will + // test *any* version of Sql Server. + return databaseType is NPoco.DatabaseTypes.SqlServerDatabaseType; + } + + public static bool IsSqlServer2008OrLater(this DatabaseType databaseType) + { + return databaseType is NPoco.DatabaseTypes.SqlServer2008DatabaseType; + } + + public static bool IsSqlCe(this DatabaseType databaseType) + { + return databaseType is NPoco.DatabaseTypes.SqlServerCEDatabaseType; + } + + public static bool IsSqlServerOrCe(this DatabaseType databaseType) + { + return databaseType.IsSqlServer() || databaseType.IsSqlCe(); + } + } +} diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs new file mode 100644 index 0000000000..7b12e2e69d --- /dev/null +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -0,0 +1,980 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using NPoco; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence +{ + public static class NPocoSqlExtensions + { + // note: here we take benefit from the fact that NPoco methods that return a Sql, such as + // when doing "sql = sql.Where(...)" actually append to, and return, the original Sql, not + // a new one. + + #region Special extensions + + /// + /// Provides a mean to express aliases in SELECT Sql statements. + /// + /// + /// First register with using static Umbraco.Core.Persistence.NPocoSqlExtensions.Aliaser, + /// then use eg Sql{Foo}(x => Alias(x.Id, "id")). + /// + public static class Statics + { + /// + /// Aliases a field. + /// + /// The field to alias. + /// The alias. + public static object Alias(object field, string alias) => field; + + /// + /// Produces Sql text. + /// + /// The name of the field. + /// A function producing Sql text. + public static T SqlText(string field, Func expr) => default; + + /// + /// Produces Sql text. + /// + /// The name of the first field. + /// The name of the second field. + /// A function producing Sql text. + public static T SqlText(string field1, string field2, Func expr) => default; + + /// + /// Produces Sql text. + /// + /// The name of the first field. + /// The name of the second field. + /// The name of the third field. + /// A function producing Sql text. + public static T SqlText(string field1, string field2, string field3, Func expr) => default; + } + + #endregion + + #region Where + + /// + /// Appends a WHERE clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// A predicate to transform and append to the Sql statement. + /// An optional alias for the table. + /// The Sql statement. + public static Sql Where(this Sql sql, Expression> predicate, string alias = null) + { + var expresionist = new PocoToSqlExpressionVisitor(sql.SqlContext, alias); + var whereExpression = expresionist.Visit(predicate); + sql.Where(whereExpression, expresionist.GetSqlParameters()); + return sql; + } + + /// + /// Appends a WHERE clause to the Sql statement. + /// + /// The type of Dto 1. + /// The type of Dto 2. + /// The Sql statement. + /// A predicate to transform and append to the Sql statement. + /// An optional alias for Dto 1 table. + /// An optional alias for Dto 2 table. + /// The Sql statement. + public static Sql Where(this Sql sql, Expression> predicate, string alias1 = null, string alias2 = null) + { + var expresionist = new PocoToSqlExpressionVisitor(sql.SqlContext, alias1, alias2); + var whereExpression = expresionist.Visit(predicate); + sql.Where(whereExpression, expresionist.GetSqlParameters()); + return sql; + } + + /// + /// Appends a WHERE IN clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An expression specifying the field. + /// The values. + /// The Sql statement. + public static Sql WhereIn(this Sql sql, Expression> field, IEnumerable values) + { + var fieldName = GetFieldName(field, sql.SqlContext.SqlSyntax); + sql.Where(fieldName + " IN (@values)", new { values }); + return sql; + } + + /// + /// Appends a WHERE IN clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An expression specifying the field. + /// A subquery returning the value. + /// The Sql statement. + public static Sql WhereIn(this Sql sql, Expression> field, Sql values) + { + return sql.WhereIn(field, values, false); + } + + /// + /// Appends a WHERE NOT IN clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An expression specifying the field. + /// The values. + /// The Sql statement. + public static Sql WhereNotIn(this Sql sql, Expression> field, IEnumerable values) + { + var fieldName = GetFieldName(field, sql.SqlContext.SqlSyntax); + sql.Where(fieldName + " NOT IN (@values)", new { values }); + return sql; + } + + /// + /// Appends a WHERE NOT IN clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An expression specifying the field. + /// A subquery returning the value. + /// The Sql statement. + public static Sql WhereNotIn(this Sql sql, Expression> field, Sql values) + { + return sql.WhereIn(field, values, true); + } + + /// + /// Appends multiple OR WHERE IN clauses to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// Expressions specifying the fields. + /// The values. + /// The Sql statement. + public static Sql WhereAnyIn(this Sql sql, Expression>[] fields, IEnumerable values) + { + var fieldNames = fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + var sb = new StringBuilder(); + sb.Append("("); + for (var i = 0; i < fieldNames.Length; i++) + { + if (i > 0) sb.Append(" OR "); + sb.Append(fieldNames[i]); + sql.Append(" IN (@values)"); + } + sb.Append(")"); + sql.Where(sb.ToString(), new { values }); + return sql; + } + + private static Sql WhereIn(this Sql sql, Expression> fieldSelector, Sql valuesSql, bool not) + { + var fieldName = GetFieldName(fieldSelector, sql.SqlContext.SqlSyntax); + sql.Where(fieldName + (not ? " NOT" : "") +" IN (" + valuesSql.SQL + ")", valuesSql.Arguments); + return sql; + } + + /// + /// Appends multiple OR WHERE clauses to the Sql statement. + /// + /// The Sql statement. + /// The WHERE predicates. + /// The Sql statement. + public static Sql WhereAny(this Sql sql, params Func, Sql>[] predicates) + { + var wsql = new Sql(sql.SqlContext); + + wsql.Append("("); + for (var i = 0; i < predicates.Length; i++) + { + if (i > 0) + wsql.Append(") OR ("); + var temp = new Sql(sql.SqlContext); + temp = predicates[i](temp); + wsql.Append(temp.SQL.TrimStart("WHERE "), temp.Arguments); + } + wsql.Append(")"); + + return sql.Where(wsql.SQL, wsql.Arguments); + } + + /// + /// Appends a WHERE NOT NULL clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// Expression specifying the field. + /// An optional alias for the table. + /// The Sql statement. + public static Sql WhereNotNull(this Sql sql, Expression> field, string tableAlias = null) + { + return sql.WhereNull(field, tableAlias, true); + } + + /// + /// Appends a WHERE [NOT] NULL clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// Expression specifying the field. + /// An optional alias for the table. + /// A value indicating whether to NOT NULL. + /// The Sql statement. + public static Sql WhereNull(this Sql sql, Expression> field, string tableAlias = null, bool not = false) + { + var column = sql.GetColumns(columnExpressions: new[] { field }, tableAlias: tableAlias, withAlias: false).First(); + return sql.Where("(" + column + " IS " + (not ? "NOT " : "") + "NULL)"); + } + + #endregion + + #region From + + /// + /// Appends a FROM clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An optional table alias + /// The Sql statement. + public static Sql From(this Sql sql, string alias = null) + { + var type = typeof (TDto); + var tableName = type.GetTableName(); + + var from = sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName); + if (!string.IsNullOrWhiteSpace(alias)) + from += " " + sql.SqlContext.SqlSyntax.GetQuotedTableName(alias); + sql.From(from); + + return sql; + } + + #endregion + + #region OrderBy, GroupBy + + /// + /// Appends an ORDER BY clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An expression specifying the field. + /// The Sql statement. + public static Sql OrderBy(this Sql sql, Expression> field) + { + return sql.OrderBy("(" + GetFieldName(field, sql.SqlContext.SqlSyntax) + ")"); + } + + /// + /// Appends an ORDER BY clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// Expression specifying the fields. + /// The Sql statement. + public static Sql OrderBy(this Sql sql, params Expression>[] fields) + { + var columns = fields.Length == 0 + ? sql.GetColumns(withAlias: false) + : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + return sql.OrderBy(columns); + } + + /// + /// Appends an ORDER BY DESC clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An expression specifying the field. + /// The Sql statement. + public static Sql OrderByDescending(this Sql sql, Expression> field) + { + return sql.OrderBy("(" + GetFieldName(field, sql.SqlContext.SqlSyntax) + ") DESC"); + } + + /// + /// Appends an ORDER BY DESC clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// Expression specifying the fields. + /// The Sql statement. + public static Sql OrderByDescending(this Sql sql, params Expression>[] fields) + { + var columns = fields.Length == 0 + ? sql.GetColumns(withAlias: false) + : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + return sql.OrderBy(columns.Select(x => x + " DESC")); + } + + /// + /// Appends an ORDER BY DESC clause to the Sql statement. + /// + /// The Sql statement. + /// Expression specifying the fields. + /// The Sql statement. + public static Sql OrderByDescending(this Sql sql, params object[] fields) + { + return sql.Append("ORDER BY " + string.Join(", ", fields.Select(x => x + " DESC"))); + } + + /// + /// Appends a GROUP BY clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An expression specifying the field. + /// The Sql statement. + public static Sql GroupBy(this Sql sql, Expression> field) + { + return sql.GroupBy(GetFieldName(field, sql.SqlContext.SqlSyntax)); + } + + /// + /// Appends a GROUP BY clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// Expression specifying the fields. + /// The Sql statement. + public static Sql GroupBy(this Sql sql, params Expression>[] fields) + { + var columns = fields.Length == 0 + ? sql.GetColumns(withAlias: false) + : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + return sql.GroupBy(columns); + } + + /// + /// Appends more ORDER BY or GROUP BY fields to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// Expressions specifying the fields. + /// The Sql statement. + public static Sql AndBy(this Sql sql, params Expression>[] fields) + { + var columns = fields.Length == 0 + ? sql.GetColumns(withAlias: false) + : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + return sql.Append(", " + string.Join(", ", columns)); + } + + /// + /// Appends more ORDER BY DESC fields to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// Expressions specifying the fields. + /// The Sql statement. + public static Sql AndByDescending(this Sql sql, params Expression>[] fields) + { + var columns = fields.Length == 0 + ? sql.GetColumns(withAlias: false) + : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + return sql.Append(", " + string.Join(", ", columns.Select(x => x + " DESC"))); + } + + #endregion + + #region Joins + + /// + /// Appends an INNER JOIN clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An optional alias for the joined table. + /// A SqlJoin statement. + public static Sql.SqlJoinClause InnerJoin(this Sql sql, string alias = null) + { + var type = typeof(TDto); + var tableName = type.GetTableName(); + var join = sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName); + if (alias != null) join += " " + sql.SqlContext.SqlSyntax.GetQuotedTableName(alias); + + return sql.InnerJoin(join); + } + + /// + /// Appends a LEFT JOIN clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An optional alias for the joined table. + /// A SqlJoin statement. + public static Sql.SqlJoinClause LeftJoin(this Sql sql, string alias = null) + { + var type = typeof(TDto); + var tableName = type.GetTableName(); + var join = sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName); + if (alias != null) join += " " + sql.SqlContext.SqlSyntax.GetQuotedTableName(alias); + + return sql.LeftJoin(join); + } + + /// + /// Appends a LEFT JOIN clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// A nested join statement. + /// An optional alias for the joined table. + /// A SqlJoin statement. + /// Nested statement produces LEFT JOIN xxx JOIN yyy ON ... ON ... + public static Sql.SqlJoinClause LeftJoin(this Sql sql, Func, Sql> nestedJoin, string alias = null) + { + var type = typeof(TDto); + var tableName = type.GetTableName(); + var join = sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName); + if (alias != null) join += " " + sql.SqlContext.SqlSyntax.GetQuotedTableName(alias); + + var nestedSql = new Sql(sql.SqlContext); + nestedSql = nestedJoin(nestedSql); + + var sqlJoin = sql.LeftJoin(join); + sql.Append(nestedSql); + return sqlJoin; + } + + /// + /// Appends a RIGHT JOIN clause to the Sql statement. + /// + /// The type of the Dto. + /// The Sql statement. + /// An optional alias for the joined table. + /// A SqlJoin statement. + public static Sql.SqlJoinClause RightJoin(this Sql sql, string alias = null) + { + var type = typeof(TDto); + var tableName = type.GetTableName(); + var join = sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName); + if (alias != null) join += " " + sql.SqlContext.SqlSyntax.GetQuotedTableName(alias); + + return sql.RightJoin(join); + } + + /// + /// Appends an ON clause to a SqlJoin statement. + /// + /// The type of the left Dto. + /// The type of the right Dto. + /// The Sql join statement. + /// An expression specifying the left field. + /// An expression specifying the right field. + /// The Sql statement. + public static Sql On(this Sql.SqlJoinClause sqlJoin, + Expression> leftField, Expression> rightField) + { + // fixme - ugly - should define on SqlContext! + + var xLeft = new Sql(sqlJoin.SqlContext).Columns(leftField); + var xRight = new Sql(sqlJoin.SqlContext).Columns(rightField); + return sqlJoin.On(xLeft + " = " + xRight); + + //var sqlSyntax = clause.SqlContext.SqlSyntax; + + //var leftType = typeof (TLeft); + //var rightType = typeof (TRight); + //var leftTableName = leftType.GetTableName(); + //var rightTableName = rightType.GetTableName(); + + //var leftColumn = ExpressionHelper.FindProperty(leftMember) as PropertyInfo; + //var rightColumn = ExpressionHelper.FindProperty(rightMember) as PropertyInfo; + + //var leftColumnName = leftColumn.GetColumnName(); + //var rightColumnName = rightColumn.GetColumnName(); + + //string onClause = $"{sqlSyntax.GetQuotedTableName(leftTableName)}.{sqlSyntax.GetQuotedColumnName(leftColumnName)} = {sqlSyntax.GetQuotedTableName(rightTableName)}.{sqlSyntax.GetQuotedColumnName(rightColumnName)}"; + //return clause.On(onClause); + } + + /// + /// Appends an ON clause to a SqlJoin statement. + /// + /// The Sql join statement. + /// A Sql fragment to use as the ON clause body. + /// The Sql statement. + public static Sql On(this Sql.SqlJoinClause sqlJoin, Func, Sql> on) + { + var sql = new Sql(sqlJoin.SqlContext); + sql = on(sql); + var text = sql.SQL.Trim().TrimStart("WHERE").Trim(); + return sqlJoin.On(text, sql.Arguments); + } + + /// + /// Appends an ON clause to a SqlJoin statement. + /// + /// The type of Dto 1. + /// The type of Dto 2. + /// The SqlJoin statement. + /// A predicate to transform and use as the ON clause body. + /// An optional alias for Dto 1 table. + /// An optional alias for Dto 2 table. + /// The Sql statement. + public static Sql On(this Sql.SqlJoinClause sqlJoin, Expression> predicate, string aliasLeft = null, string aliasRight = null) + { + var expresionist = new PocoToSqlExpressionVisitor(sqlJoin.SqlContext, aliasLeft, aliasRight); + var onExpression = expresionist.Visit(predicate); + return sqlJoin.On(onExpression, expresionist.GetSqlParameters()); + } + + #endregion + + #region Select + + /// + /// Alters a Sql statement to return a maximum amount of rows. + /// + /// The Sql statement. + /// The maximum number of rows to return. + /// The Sql statement. + public static Sql SelectTop(this Sql sql, int count) + { + return sql.SqlContext.SqlSyntax.SelectTop(sql, count); + } + + /// + /// Creates a SELECT COUNT(*) Sql statement. + /// + /// The origin sql. + /// The Sql statement. + public static Sql SelectCount(this Sql sql) + { + return sql.Select("COUNT(*)"); + } + + /// + /// Creates a SELECT COUNT Sql statement. + /// + /// The type of the DTO to count. + /// The origin sql. + /// Expressions indicating the columns to count. + /// The Sql statement. + /// + /// If is empty, all columns are counted. + /// + public static Sql SelectCount(this Sql sql, params Expression>[] fields) + { + var columns = fields.Length == 0 + ? sql.GetColumns(withAlias: false) + : fields.Select(x => GetFieldName(x, sql.SqlContext.SqlSyntax)).ToArray(); + return sql.Select("COUNT (" + string.Join(", ", columns) + ")"); + } + + /// + /// Creates a SELECT * Sql statement. + /// + /// The origin sql. + /// The Sql statement. + public static Sql SelectAll(this Sql sql) + { + return sql.Select("*"); + } + + /// + /// Creates a SELECT Sql statement. + /// + /// The type of the DTO to select. + /// The origin sql. + /// Expressions indicating the columns to select. + /// The Sql statement. + /// + /// If is empty, all columns are selected. + /// + public static Sql Select(this Sql sql, params Expression>[] fields) + { + return sql.Select(sql.GetColumns(columnExpressions: fields)); + } + + /// + /// Creates a SELECT Sql statement. + /// + /// The type of the DTO to select. + /// The origin sql. + /// A table alias. + /// Expressions indicating the columns to select. + /// The Sql statement. + /// + /// If is empty, all columns are selected. + /// + public static Sql Select(this Sql sql, string tableAlias, params Expression>[] fields) + { + return sql.Select(sql.GetColumns(tableAlias: tableAlias, columnExpressions: fields)); + } + + /// + /// Adds columns to a SELECT Sql statement. + /// + /// The type of the DTO to select. + /// The origin sql. + /// Expressions indicating the columns to select. + /// The Sql statement. + /// + /// If is empty, all columns are selected. + /// + public static Sql AndSelect(this Sql sql, params Expression>[] fields) + { + return sql.Append(", " + string.Join(", ", sql.GetColumns(columnExpressions: fields))); + } + + + /// + /// Adds columns to a SELECT Sql statement. + /// + /// The type of the DTO to select. + /// The origin sql. + /// A table alias. + /// Expressions indicating the columns to select. + /// The Sql statement. + /// + /// If is empty, all columns are selected. + /// + public static Sql AndSelect(this Sql sql, string tableAlias, params Expression>[] fields) + { + return sql.Append(", " + string.Join(", ", sql.GetColumns(tableAlias: tableAlias, columnExpressions: fields))); + } + + /// + /// Creates a SELECT Sql statement with a referenced Dto. + /// + /// The type of the Dto to select. + /// The origin Sql. + /// An expression specifying the reference. + /// The Sql statement. + public static Sql Select(this Sql sql, Func, SqlRef> reference) + { + sql.Select(sql.GetColumns()); + + reference?.Invoke(new SqlRef(sql, null)); + return sql; + } + + /// + /// Creates a SELECT Sql statement with a referenced Dto. + /// + /// The type of the Dto to select. + /// The origin Sql. + /// An expression speficying the reference. + /// An expression to apply to the Sql statement before adding the reference selection. + /// The Sql statement. + /// The expression applies to the Sql statement before the reference selection + /// is added, so that it is possible to add (e.g. calculated) columns to the referencing Dto. + public static Sql Select(this Sql sql, Func, SqlRef> reference, Func, Sql> sqlexpr) + { + sql.Select(sql.GetColumns()); + + sql = sqlexpr(sql); + + reference(new SqlRef(sql, null)); + return sql; + } + + /// + /// Represents a Dto reference expression. + /// + /// The type of the referencing Dto. + public class SqlRef + { + /// + /// Initializes a new Dto reference expression. + /// + /// The original Sql expression. + /// The current Dtos prefix. + public SqlRef(Sql sql, string prefix) + { + Sql = sql; + Prefix = prefix; + } + + /// + /// Gets the original Sql expression. + /// + public Sql Sql { get; } + + /// + /// Gets the current Dtos prefix. + /// + public string Prefix { get; } + + /// + /// Appends fields for a referenced Dto. + /// + /// The type of the referenced Dto. + /// An expression specifying the referencing field. + /// An optional expression representing a nested reference selection. + /// A SqlRef statement. + public SqlRef Select(Expression> field, Func, SqlRef> reference = null) + => Select(field, null, reference); + + /// + /// Appends fields for a referenced Dto. + /// + /// The type of the referenced Dto. + /// An expression specifying the referencing field. + /// The referenced Dto table alias. + /// An optional expression representing a nested reference selection. + /// A SqlRef statement. + public SqlRef Select(Expression> field, string tableAlias, Func, SqlRef> reference = null) + { + var property = field == null ? null : ExpressionHelper.FindProperty(field).Item1 as PropertyInfo; + return Select(property, tableAlias, reference); + } + + /// + /// Selects referenced DTOs. + /// + /// The type of the referenced DTOs. + /// An expression specifying the referencing field. + /// An optional expression representing a nested reference selection. + /// A referenced DTO expression. + /// + /// The referencing property has to be a List{}. + /// + public SqlRef Select(Expression>> field, Func, SqlRef> reference = null) + => Select(field, null, reference); + + /// + /// Selects referenced DTOs. + /// + /// The type of the referenced DTOs. + /// An expression specifying the referencing field. + /// The DTO table alias. + /// An optional expression representing a nested reference selection. + /// A referenced DTO expression. + /// + /// The referencing property has to be a List{}. + /// + public SqlRef Select(Expression>> field, string tableAlias, Func, SqlRef> reference = null) + { + var property = field == null ? null : ExpressionHelper.FindProperty(field).Item1 as PropertyInfo; + return Select(property, tableAlias, reference); + } + + private SqlRef Select(PropertyInfo propertyInfo, string tableAlias, Func, SqlRef> nested = null) + { + var referenceName = propertyInfo?.Name ?? typeof (TDto).Name; + if (Prefix != null) referenceName = Prefix + PocoData.Separator + referenceName; + + var columns = Sql.GetColumns(tableAlias, referenceName); + Sql.Append(", " + string.Join(", ", columns)); + + nested?.Invoke(new SqlRef(Sql, referenceName)); + return this; + } + } + + /// + /// Gets fields for a Dto. + /// + /// The type of the Dto. + /// The origin sql. + /// Expressions specifying the fields. + /// The comma-separated list of fields. + /// + /// If is empty, all fields are selected. + /// + public static string Columns(this Sql sql, params Expression>[] fields) + { + return string.Join(", ", sql.GetColumns(columnExpressions: fields, withAlias: false)); + } + + /// + /// Gets fields for a Dto. + /// + /// The type of the Dto. + /// The origin sql. + /// The Dto table alias. + /// Expressions specifying the fields. + /// The comma-separated list of fields. + /// + /// If is empty, all fields are selected. + /// + public static string Columns(this Sql sql, string alias, params Expression>[] fields) + { + return string.Join(", ", sql.GetColumns(columnExpressions: fields, withAlias: false, tableAlias: alias)); + } + + #endregion + + #region Delete + + public static Sql Delete(this Sql sql) + { + sql.Append("DELETE"); + return sql; + } + + public static Sql Delete(this Sql sql) + { + var type = typeof(TDto); + var tableName = type.GetTableName(); + + sql.Append($"DELETE {sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName)}"); + return sql; + } + + #endregion + + #region Update + + public static Sql Update(this Sql sql) + { + sql.Append("UPDATE"); + return sql; + } + + public static Sql Update(this Sql sql) + { + var type = typeof(TDto); + var tableName = type.GetTableName(); + + sql.Append($"UPDATE {sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName)}"); + return sql; + } + + public static Sql Update(this Sql sql, Func, SqlUpd> updates) + { + var type = typeof(TDto); + var tableName = type.GetTableName(); + + sql.Append($"UPDATE {sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName)} SET"); + + var u = new SqlUpd(sql.SqlContext); + u = updates(u); + for (var i = 0; i < u.SetExpressions.Count; i++) + { + var setExpression = u.SetExpressions[i]; + sql.Append(setExpression.Item1 + "=@0" + (i < u.SetExpressions.Count - 1 ? "," : ""), setExpression.Item2); + } + + return sql; + } + + public class SqlUpd + { + private readonly ISqlContext _sqlContext; + private readonly List> _setExpressions = new List>(); + + public SqlUpd(ISqlContext sqlContext) + { + _sqlContext = sqlContext; + } + + public SqlUpd Set(Expression> fieldSelector, object value) + { + var fieldName = GetFieldName(fieldSelector, _sqlContext.SqlSyntax); + _setExpressions.Add(new Tuple(fieldName, value)); + return this; + } + + public List> SetExpressions => _setExpressions; + } + + #endregion + + #region Utilities + + private static string[] GetColumns(this Sql sql, string tableAlias = null, string referenceName = null, Expression>[] columnExpressions = null, bool withAlias = true) + { + var pd = sql.SqlContext.PocoDataFactory.ForType(typeof (TDto)); + var tableName = tableAlias ?? pd.TableInfo.TableName; + var queryColumns = pd.QueryColumns; + + Dictionary aliases = null; + + if (columnExpressions != null && columnExpressions.Length > 0) + { + var names = columnExpressions.Select(x => + { + (var member, var alias) = ExpressionHelper.FindProperty(x); + var field = member as PropertyInfo; + var fieldName = field.GetColumnName(); + if (alias != null) + { + if (aliases == null) + aliases = new Dictionary(); + aliases[fieldName] = alias; + } + return fieldName; + }).ToArray(); + + queryColumns = queryColumns.Where(x => names.Contains(x.Key)).ToArray(); + } + + string GetAlias(PocoColumn column) + { + if (aliases != null && aliases.TryGetValue(column.ColumnName, out var alias)) + return alias; + + return withAlias ? (string.IsNullOrEmpty(column.ColumnAlias) ? column.MemberInfoKey : column.ColumnAlias) : null; + } + + return queryColumns + .Select(x => GetColumn(sql.SqlContext.DatabaseType, tableName, x.Value.ColumnName, GetAlias(x.Value), referenceName)) + .ToArray(); + } + + private static string GetColumn(DatabaseType dbType, string tableName, string columnName, string columnAlias, string referenceName = null) + { + tableName = dbType.EscapeTableName(tableName); + columnName = dbType.EscapeSqlIdentifier(columnName); + var column = tableName + "." + columnName; + if (columnAlias == null) return column; + + referenceName = referenceName == null ? string.Empty : referenceName + "__"; + columnAlias = dbType.EscapeSqlIdentifier(referenceName + columnAlias); + column += " AS " + columnAlias; + return column; + } + + private static string GetTableName(this Type type) + { + // todo: returning string.Empty for now + // BUT the code bits that calls this method cannot deal with string.Empty so we + // should either throw, or fix these code bits... + var attr = type.FirstAttribute(); + return string.IsNullOrWhiteSpace(attr?.Value) ? string.Empty : attr.Value; + } + + private static string GetColumnName(this PropertyInfo column) + { + var attr = column.FirstAttribute(); + return string.IsNullOrWhiteSpace(attr?.Name) ? column.Name : attr.Name; + } + + private static string GetFieldName(Expression> fieldSelector, ISqlSyntaxProvider sqlSyntax) + { + var field = ExpressionHelper.FindProperty(fieldSelector).Item1 as PropertyInfo; + var fieldName = field.GetColumnName(); + + var type = typeof (TDto); + var tableName = type.GetTableName(); + + return sqlSyntax.GetQuotedTableName(tableName) + "." + sqlSyntax.GetQuotedColumnName(fieldName); + } + + internal static void WriteToConsole(this Sql sql) + { + Console.WriteLine(sql.SQL); + var i = 0; + foreach (var arg in sql.Arguments) + Console.WriteLine($" @{i++}: {arg}"); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs deleted file mode 100644 index 79b3ce3871..0000000000 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ /dev/null @@ -1,2550 +0,0 @@ -/* PetaPoco v4.0.3 - A Tiny ORMish thing for your POCO's. - * Copyright © 2011 Topten Software. All Rights Reserved. - * - * Apache License 2.0 - http://www.toptensoftware.com/petapoco/license - * - * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for - * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice - * and Adam Schroder (@schotime) for lots of suggestions, improvements and Oracle support - */ - -// Define PETAPOCO_NO_DYNAMIC in your project settings on .NET 3.5 - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Caching; -using System.Security; -using System.Security.Permissions; -using System.Text; -using System.Configuration; -using System.Data.Common; -using System.Data; -using System.Text.RegularExpressions; -using System.Reflection; -using System.Reflection.Emit; -using System.Linq.Expressions; - -namespace Umbraco.Core.Persistence -{ - // Poco's marked [Explicit] require all column properties to be marked - [AttributeUsage(AttributeTargets.Class)] - public class ExplicitColumnsAttribute : Attribute - { - } - // For non-explicit pocos, causes a property to be ignored - [AttributeUsage(AttributeTargets.Property)] - public class IgnoreAttribute : Attribute - { - } - - // For explicit pocos, marks property as a column and optionally supplies column name - [AttributeUsage(AttributeTargets.Property)] - public class ColumnAttribute : Attribute - { - public ColumnAttribute() { } - public ColumnAttribute(string name) { Name = name; } - public string Name { get; set; } - } - - // For explicit pocos, marks property as a result column and optionally supplies column name - [AttributeUsage(AttributeTargets.Property)] - public class ResultColumnAttribute : ColumnAttribute - { - public ResultColumnAttribute() { } - public ResultColumnAttribute(string name) : base(name) { } - } - - // Specify the table name of a poco - [AttributeUsage(AttributeTargets.Class)] - public class TableNameAttribute : Attribute - { - public TableNameAttribute(string tableName) - { - Value = tableName; - } - public string Value { get; private set; } - } - - // Specific the primary key of a poco class (and optional sequence name for Oracle) - [AttributeUsage(AttributeTargets.Class)] - public class PrimaryKeyAttribute : Attribute - { - public PrimaryKeyAttribute(string primaryKey) - { - Value = primaryKey; - autoIncrement = true; - } - - public string Value { get; private set; } - public string sequenceName { get; set; } - public bool autoIncrement { get; set; } - } - - [AttributeUsage(AttributeTargets.Property)] - public class AutoJoinAttribute : Attribute - { - public AutoJoinAttribute() { } - } - - // Results from paged request - public class Page - { - public long CurrentPage { get; set; } - public long TotalPages { get; set; } - public long TotalItems { get; set; } - public long ItemsPerPage { get; set; } - public List Items { get; set; } - public object Context { get; set; } - } - - // Pass as parameter value to force to DBType.AnsiString - public class AnsiString - { - public AnsiString(string str) - { - Value = str; - } - public string Value { get; private set; } - } - - // Used by IMapper to override table bindings for an object - public class TableInfo - { - public string TableName { get; set; } - public string PrimaryKey { get; set; } - public bool AutoIncrement { get; set; } - public string SequenceName { get; set; } - } - - // Optionally provide an implementation of this to Database.Mapper - public interface IMapper - { - void GetTableInfo(Type t, TableInfo ti); - bool MapPropertyToColumn(Type t, PropertyInfo pi, ref string columnName, ref bool resultColumn); - Func GetFromDbConverter(PropertyInfo pi, Type SourceType); - Func GetToDbConverter(Type SourceType); - } - - // This will be merged with IMapper in the next major version - public interface IMapper2 : IMapper - { - Func GetFromDbConverter(Type DestType, Type SourceType); - } - - // Database class ... this is where most of the action happens - public class Database : IDisposable - { - public Database(IDbConnection connection) - { - _sharedConnection = connection; - _connectionString = connection.ConnectionString; - _sharedConnectionDepth = 2; // Prevent closing external connection - CommonConstruct(); - } - - public Database(string connectionString, string providerName) - { - _connectionString = connectionString; - _providerName = providerName; - CommonConstruct(); - } - - public Database(string connectionString, DbProviderFactory provider) - { - _connectionString = connectionString; - _factory = provider; - CommonConstruct(); - } - - public Database(string connectionStringName) - { - // Use first? - if (connectionStringName == "") - connectionStringName = ConfigurationManager.ConnectionStrings[0].Name; - - // Work out connection string and provider name - var providerName = Constants.DatabaseProviders.SqlServer; - if (ConfigurationManager.ConnectionStrings[connectionStringName] != null) - { - if (string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName) == false) - providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName; - } - else - { - throw new NullReferenceException("Can't find a connection string with the name '" + connectionStringName + "'"); - } - - // Store factory and connection string - _connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - _providerName = providerName; - CommonConstruct(); - } - - public enum DBType - { - SqlServer, - SqlServerCE, - MySql, - PostgreSQL, - Oracle, - SQLite - } - private DBType _dbType = DBType.SqlServer; - - public DBType DatabaseType { get { return _dbType; } } - - // Common initialization - private void CommonConstruct() - { - _transactionDepth = 0; - EnableAutoSelect = true; - EnableNamedParams = true; - ForceDateTimesToUtc = true; - - if (_providerName != null) - _factory = DbProviderFactories.GetFactory(_providerName); - - string dbtype = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; - - if (dbtype.StartsWith("MySql")) _dbType = DBType.MySql; - else if (dbtype.StartsWith("SqlCe")) _dbType = DBType.SqlServerCE; - else if (dbtype.StartsWith("Npgsql")) _dbType = DBType.PostgreSQL; - else if (dbtype.StartsWith("Oracle")) _dbType = DBType.Oracle; - else if (dbtype.StartsWith("SQLite")) _dbType = DBType.SQLite; - - if (_dbType == DBType.MySql && _connectionString != null && _connectionString.IndexOf("Allow User Variables=true") >= 0) - _paramPrefix = "?"; - if (_dbType == DBType.Oracle) - _paramPrefix = ":"; - - // by default use MSSQL default ReadCommitted level - //TODO change to RepeatableRead - but that is breaking - _isolationLevel = IsolationLevel.ReadCommitted; - } - - // Automatically close one open shared connection - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - // Automatically close one open connection reference - // (Works with KeepConnectionAlive and manually opening a shared connection) - CloseSharedConnection(); - } - - // Set to true to keep the first opened connection alive until this object is disposed - public bool KeepConnectionAlive { get; set; } - - // Open a connection (can be nested) - public void OpenSharedConnection() - { - if (_sharedConnectionDepth == 0) - { - _sharedConnection = _factory.CreateConnection(); - _sharedConnection.ConnectionString = _connectionString; - _sharedConnection.OpenWithRetry();//Changed .Open() => .OpenWithRetry() extension method - - // ensure we have the proper isolation level, as levels can leak in pools - // read http://stackoverflow.com/questions/9851415/sql-server-isolation-level-leaks-across-pooled-connections - // and http://stackoverflow.com/questions/641120/what-exec-sp-reset-connection-shown-in-sql-profiler-means - // - // NPoco has that code in OpenSharedConnectionImp (commented out?) - //using (var cmd = _sharedConnection.CreateCommand()) - //{ - // cmd.CommandText = GetSqlForTransactionLevel(_isolationLevel); - // cmd.CommandTimeout = CommandTimeout; - // cmd.ExecuteNonQuery(); - //} - // - // although MSDN documentation for SQL CE clearly states that the above method - // should work, it fails & reports an error parsing the query on 'TRANSACTION', - // and Google is no help (others have faced the same issue... no solution). So, - // switching to another method that does work on all databases. - var tr = _sharedConnection.BeginTransaction(_isolationLevel); - tr.Commit(); - tr.Dispose(); - - _sharedConnection = OnConnectionOpened(_sharedConnection); - - if (KeepConnectionAlive) - _sharedConnectionDepth++; // Make sure you call Dispose - } - _sharedConnectionDepth++; - } - - // Close a previously opened connection - public void CloseSharedConnection() - { - if (_sharedConnectionDepth > 0) - { - _sharedConnectionDepth--; - if (_sharedConnectionDepth == 0) - { - OnConnectionClosing(_sharedConnection); - _sharedConnection.Dispose(); - _sharedConnection = null; - } - } - } - - // Access to our shared connection - public IDbConnection Connection - { - get { return _sharedConnection; } - } - - // Helper to create a transaction scope - public Transaction GetTransaction() - { - return GetTransaction(_isolationLevel); - } - - public Transaction GetTransaction(IsolationLevel isolationLevel) - { - return new Transaction(this, isolationLevel); - } - - public IsolationLevel CurrentTransactionIsolationLevel - { - get { return _transaction == null ? IsolationLevel.Unspecified : _transaction.IsolationLevel; } - } - - // Use by derived repo generated by T4 templates - public virtual void OnBeginTransaction() { } - public virtual void OnEndTransaction() { } - - // Start a new transaction, can be nested, every call must be - // matched by a call to AbortTransaction or CompleteTransaction - // Use `using (var scope=db.Transaction) { scope.Complete(); }` to ensure correct semantics - public void BeginTransaction() - { - BeginTransaction(_isolationLevel); - } - - public void BeginTransaction(IsolationLevel isolationLevel) - { - _transactionDepth++; - - if (_transactionDepth == 1) - { - OpenSharedConnection(); - try - { - _transaction = _sharedConnection.BeginTransaction(isolationLevel); - } - - catch (Exception e) - { - throw; - } - _transactionCancelled = false; - OnBeginTransaction(); - } - else if (isolationLevel > _transaction.IsolationLevel) - throw new Exception("Already in a transaction with a lower isolation level."); - } - - // Internal helper to cleanup transaction stuff - void CleanupTransaction() - { - OnEndTransaction(); - - if (_transactionCancelled) - _transaction.Rollback(); - else - _transaction.Commit(); - - _transaction.Dispose(); - _transaction = null; - - CloseSharedConnection(); - } - - // Abort the entire outer most transaction scope - public void AbortTransaction() - { - _transactionCancelled = true; - //TODO what shall we do if transactionDepth is already zero? - if ((--_transactionDepth) == 0) - CleanupTransaction(); - } - - // Complete the transaction - public void CompleteTransaction() - { - //TODO what shall we do if transactionDepth is already zero? - if ((--_transactionDepth) == 0) - CleanupTransaction(); - } - - // in NPoco this is in DatabaseType - private static string GetSqlForTransactionLevel(IsolationLevel isolationLevel) - { - switch (isolationLevel) - { - case IsolationLevel.ReadCommitted: - return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; - case IsolationLevel.ReadUncommitted: - return "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"; - case IsolationLevel.RepeatableRead: - return "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"; - case IsolationLevel.Serializable: - return "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"; - case IsolationLevel.Snapshot: - return "SET TRANSACTION ISOLATION LEVEL SNAPSHOT"; - default: - return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; - } - } - - // Helper to handle named parameters from object properties - static Regex rxParams = new Regex(@"(? args_dest) - { - return rxParams.Replace(_sql, m => - { - string param = m.Value.Substring(1); - - object arg_val; - - int paramIndex; - if (int.TryParse(param, out paramIndex)) - { - // Numbered parameter - if (paramIndex < 0 || paramIndex >= args_src.Length) - throw new ArgumentOutOfRangeException(string.Format("Parameter '@{0}' specified but only {1} parameters supplied (in `{2}`)", paramIndex, args_src.Length, _sql)); - arg_val = args_src[paramIndex]; - } - else - { - // Look for a property on one of the arguments with this name - bool found = false; - arg_val = null; - foreach (var o in args_src) - { - var pi = o.GetType().GetProperty(param); - if (pi != null) - { - arg_val = pi.GetValue(o, null); - found = true; - break; - } - } - - if (!found) - throw new ArgumentException(string.Format("Parameter '@{0}' specified but none of the passed arguments have a property with this name (in '{1}')", param, _sql)); - } - - // Expand collections to parameter lists - if ((arg_val as System.Collections.IEnumerable) != null && - (arg_val as string) == null && - (arg_val as byte[]) == null) - { - var sb = new StringBuilder(); - foreach (var i in arg_val as System.Collections.IEnumerable) - { - sb.Append((sb.Length == 0 ? "@" : ",@") + args_dest.Count.ToString()); - args_dest.Add(i); - } - return sb.ToString(); - } - else - { - args_dest.Add(arg_val); - return "@" + (args_dest.Count - 1).ToString(); - } - } - ); - } - - // Add a parameter to a DB command - internal void AddParam(IDbCommand cmd, object item, string ParameterPrefix) - { - // Convert value to from poco type to db type - if (Database.Mapper != null && item!=null) - { - var fn = Database.Mapper.GetToDbConverter(item.GetType()); - if (fn!=null) - item = fn(item); - } - - // Support passed in parameters - var idbParam = item as IDbDataParameter; - if (idbParam != null) - { - idbParam.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count); - cmd.Parameters.Add(idbParam); - return; - } - - var p = cmd.CreateParameter(); - p.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count); - if (item == null) - { - p.Value = DBNull.Value; - } - else - { - var t = item.GetType(); - if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int - { - p.Value = (int)item; - } - else if (t == typeof(Guid)) - { - p.Value = item.ToString(); - p.DbType = DbType.String; - p.Size = 40; - } - else if (t == typeof(string)) - { - // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. - //Set before attempting to set Size, or Size will always max out at 4000 - if ((item as string).Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter") - p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); - - p.Size = (item as string).Length + 1; - if(p.Size < 4000) - p.Size = Math.Max((item as string).Length + 1, 4000); // Help query plan caching by using common size - - p.Value = item; - } - else if (t == typeof(AnsiString)) - { - // Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar - p.Size = Math.Max((item as AnsiString).Value.Length + 1, 4000); - p.Value = (item as AnsiString).Value; - p.DbType = DbType.AnsiString; - } - else if (t == typeof(bool) && _dbType != DBType.PostgreSQL) - { - p.Value = ((bool)item) ? 1 : 0; - } - else if (item.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type - { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type - p.Value = item; - } - - else if (item.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type - { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type - p.Value = item; - } - else - { - p.Value = item; - } - } - - cmd.Parameters.Add(p); - } - - // Create a command - static Regex rxParamsPrefix = new Regex(@"(?(); - sql = ProcessParams(sql, args, new_args); - args = new_args.ToArray(); - } - - // Perform parameter prefix replacements - if (_paramPrefix != "@") - sql = rxParamsPrefix.Replace(sql, m => _paramPrefix + m.Value.Substring(1)); - sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @ - - // Create the command and add parameters - IDbCommand cmd = connection.CreateCommand(); - cmd.Connection = connection; - cmd.CommandText = sql; - cmd.Transaction = _transaction; - foreach (var item in args) - { - AddParam(cmd, item, _paramPrefix); - } - - if (_dbType == DBType.Oracle) - { - cmd.GetType().GetProperty("BindByName").SetValue(cmd, true, null); - } - - if (!String.IsNullOrEmpty(sql)) - DoPreExecute(cmd); - - return cmd; - } - - // Override this to log/capture exceptions - public virtual void OnException(Exception x) - { - System.Diagnostics.Debug.WriteLine(x.ToString()); - System.Diagnostics.Debug.WriteLine(LastCommand); - } - - // Override this to log commands, or modify command before execution - public virtual IDbConnection OnConnectionOpened(IDbConnection conn) { return conn; } - public virtual void OnConnectionClosing(IDbConnection conn) { } - public virtual void OnExecutingCommand(IDbCommand cmd) { } - public virtual void OnExecutedCommand(IDbCommand cmd) { } - - // Execute a non-query command - public int Execute(string sql, params object[] args) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - var retv=cmd.ExecuteNonQueryWithRetry(); - OnExecutedCommand(cmd); - return retv; - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - OnException(x); - throw; - } - } - - public int Execute(Sql sql) - { - return Execute(sql.SQL, sql.Arguments); - } - - // Execute and cast a scalar property - public T ExecuteScalar(string sql, params object[] args) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - object val = cmd.ExecuteScalarWithRetry(); - OnExecutedCommand(cmd); - - if (val == null || val == DBNull.Value) - return default(T); - - Type t = typeof(T); - Type u = Nullable.GetUnderlyingType(t); - - return (T)Convert.ChangeType(val, u ?? t); - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - OnException(x); - throw; - } - } - - public T ExecuteScalar(Sql sql) - { - return ExecuteScalar(sql.SQL, sql.Arguments); - } - - Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); - Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); - string AddSelectClause(string sql) - { - if (sql.StartsWith(";")) - return sql.Substring(1); - - if (!rxSelect.IsMatch(sql)) - { - var pd = PocoData.ForType(typeof(T)); - var tableName = EscapeTableName(pd.TableInfo.TableName); - string cols = string.Join(", ", (from c in pd.QueryColumns select tableName + "." + EscapeSqlIdentifier(c)).ToArray()); - if (!rxFrom.IsMatch(sql)) - sql = string.Format("SELECT {0} FROM {1} {2}", cols, tableName, sql); - else - sql = string.Format("SELECT {0} {1}", cols, sql); - } - return sql; - } - - public bool EnableAutoSelect { get; set; } - public bool EnableNamedParams { get; set; } - public bool ForceDateTimesToUtc { get; set; } - - // Return a typed list of pocos - public List Fetch(string sql, params object[] args) - { - return Query(sql, args).ToList(); - } - - public List Fetch(Sql sql) - { - return Fetch(sql.SQL, sql.Arguments); - } - - static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); - static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); - public static bool SplitSqlForPaging(string sql, out string sqlCount, out string sqlSelectRemoved, out string sqlOrderBy) - { - sqlSelectRemoved = null; - sqlCount = null; - sqlOrderBy = null; - - // Extract the columns from "SELECT FROM" - var m = rxColumns.Match(sql); - if (!m.Success) - return false; - - // Save column list and replace with COUNT(*) - Group g = m.Groups[1]; - sqlSelectRemoved = sql.Substring(g.Index); - - if (rxDistinct.IsMatch(sqlSelectRemoved)) - sqlCount = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length); - else - sqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length); - - // Look for an "ORDER BY " clause - m = rxOrderBy.Match(sqlCount); - if (!m.Success) - { - sqlOrderBy = null; - } - else - { - g = m.Groups[0]; - sqlOrderBy = g.ToString(); - sqlCount = sqlCount.Substring(0, g.Index) + sqlCount.Substring(g.Index + g.Length); - } - return true; - } - - /// - /// NOTE: This is a custom mod of PetaPoco!! This builds the paging sql for different db providers - /// - /// - /// - /// - /// - /// - /// - /// - /// - internal virtual void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage) - { - // this is overriden in UmbracoDatabase, and if running SqlServer >=2012, the database type - // is switched from SqlServer to SqlServerCE in order to use the better paging syntax that - // SqlCE supports, and SqlServer >=2012 too. - // so the first case is actually for SqlServer <2012, and second case is CE *and* SqlServer >=2012 - - if (databaseType == DBType.SqlServer || databaseType == DBType.Oracle) - { - sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); - if (rxDistinct.IsMatch(sqlSelectRemoved)) - { - sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner"; - } - - // split to ensure that peta_rn is the last field to be selected, else Page would fail - // the resulting sql is not perfect, NPoco has a much nicer way to do it, but it would require - // importing large parts of NPoco - var pos = sqlSelectRemoved.IndexOf("FROM"); - var sqlColumns = sqlSelectRemoved.Substring(0, pos); - var sqlFrom = sqlSelectRemoved.Substring(pos); - - sqlPage = string.Format("SELECT * FROM (SELECT {0}, ROW_NUMBER() OVER ({1}) peta_rn {2}) peta_paged WHERE peta_rn>@{3} AND peta_rn<=@{4}", - sqlColumns, sqlOrderBy ?? "ORDER BY (SELECT NULL)", sqlFrom, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, skip + take }).ToArray(); - } - else if (databaseType == DBType.SqlServerCE) - { - sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, take }).ToArray(); - } - else - { - sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { take, skip }).ToArray(); - } - } - - public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) - { - // Add auto select clause - if (EnableAutoSelect) - sql = AddSelectClause(sql); - - // Split the SQL into the bits we need - string sqlSelectRemoved, sqlOrderBy; - if (SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy) == false) - throw new Exception("Unable to parse SQL statement for paged query"); - if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*")) - throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); - - BuildSqlDbSpecificPagingQuery(_dbType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); - } - - // Fetch a page - public Page Page(long page, long itemsPerPage, string sql, params object[] args) - { - string sqlCount, sqlPage; - BuildPageQueries((page-1)*itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage); - - // Save the one-time command time out and use it for both queries - int saveTimeout = OneTimeCommandTimeout; - - // Setup the paged result - var result = new Page(); - result.CurrentPage = page; - result.ItemsPerPage = itemsPerPage; - result.TotalItems = ExecuteScalar(sqlCount, args); - result.TotalPages = result.TotalItems / itemsPerPage; - if ((result.TotalItems % itemsPerPage) != 0) - result.TotalPages++; - - OneTimeCommandTimeout = saveTimeout; - - // Get the records - result.Items = Fetch(sqlPage, args); - - // Done - return result; - } - - public Page Page(long page, long itemsPerPage, Sql sql) - { - return Page(page, itemsPerPage, sql.SQL, sql.Arguments); - } - - - public List Fetch(long page, long itemsPerPage, string sql, params object[] args) - { - return SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql, args); - } - - public List Fetch(long page, long itemsPerPage, Sql sql) - { - return SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql.SQL, sql.Arguments); - } - - public List SkipTake(long skip, long take, string sql, params object[] args) - { - string sqlCount, sqlPage; - BuildPageQueries(skip, take, sql, ref args, out sqlCount, out sqlPage); - return Fetch(sqlPage, args); - } - - public List SkipTake(long skip, long take, Sql sql) - { - return SkipTake(skip, take, sql.SQL, sql.Arguments); - } - - // Return an enumerable collection of pocos - public IEnumerable Query(string sql, params object[] args) - { - if (EnableAutoSelect) - sql = AddSelectClause(sql); - - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - IDataReader r; - var pd = PocoData.ForType(typeof(T)); - try - { - r = cmd.ExecuteReaderWithRetry(); - OnExecutedCommand(cmd); - } - catch (Exception x) - { - OnException(x); - throw; - } - var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, ForceDateTimesToUtc, 0, r.FieldCount, r) as Func; - using (r) - { - while (true) - { - T poco; - try - { - if (!r.Read()) - yield break; - poco = factory(r); - } - catch (Exception x) - { - OnException(x); - throw; - } - - yield return poco; - } - } - } - } - finally - { - CloseSharedConnection(); - } - } - - // Multi Fetch - public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } - public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } - public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } - public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } - - // Multi Query - public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql, args); } - public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3)}, cb, sql, args); } - public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4)}, cb, sql, args); } - public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, cb, sql, args); } - - // Multi Fetch (SQL builder) - public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } - - // Multi Query (SQL builder) - public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments); } - public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments); } - public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments); } - public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, cb, sql.SQL, sql.Arguments); } - - // Multi Fetch (Simple) - public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - - // Multi Query (Simple) - public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql, args); } - public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); } - public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); } - public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, null, sql, args); } - - // Multi Fetch (Simple) (SQL builder) - public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } - - // Multi Query (Simple) (SQL builder) - public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments); } - public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments); } - public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments); } - public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, null, sql.SQL, sql.Arguments); } - - // Automagically guess the property relationships between various POCOs and create a delegate that will set them up - Delegate GetAutoMapper(Type[] types) - { - // Build a key - var kb = new StringBuilder(); - foreach (var t in types) - { - kb.Append(t.ToString()); - kb.Append(":"); - } - var key = kb.ToString(); - - // Check cache - RWLock.EnterReadLock(); - try - { - Delegate mapper; - if (AutoMappers.TryGetValue(key, out mapper)) - return mapper; - } - finally - { - RWLock.ExitReadLock(); - } - - // Create it - RWLock.EnterWriteLock(); - try - { - // Try again - Delegate mapper; - if (AutoMappers.TryGetValue(key, out mapper)) - return mapper; - - // Create a method - var m = new DynamicMethod("petapoco_automapper", types[0], types, true); - var il = m.GetILGenerator(); - - for (int i = 1; i < types.Length; i++) - { - bool handled = false; - for (int j = i - 1; j >= 0; j--) - { - // Find the property - var candidates = from p in types[j].GetProperties() where p.PropertyType == types[i] select p; - if (candidates.Count() == 0) - continue; - if (candidates.Count() > 1) - throw new InvalidOperationException(string.Format("Can't auto join {0} as {1} has more than one property of type {0}", types[i], types[j])); - - // Generate code - il.Emit(OpCodes.Ldarg_S, j); - il.Emit(OpCodes.Ldarg_S, i); - il.Emit(OpCodes.Callvirt, candidates.First().GetSetMethod(true)); - handled = true; - } - - if (!handled) - throw new InvalidOperationException(string.Format("Can't auto join {0}", types[i])); - } - - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ret); - - // Cache it - var del = m.CreateDelegate(Expression.GetFuncType(types.Concat(types.Take(1)).ToArray())); - AutoMappers.Add(key, del); - return del; - } - finally - { - RWLock.ExitWriteLock(); - } - } - - // Find the split point in a result set for two different pocos and return the poco factory for the first - Delegate FindSplitPoint(Type typeThis, Type typeNext, string sql, IDataReader r, ref int pos) - { - // Last? - if (typeNext == null) - return PocoData.ForType(typeThis).GetFactory(sql, _sharedConnection.ConnectionString, ForceDateTimesToUtc, pos, r.FieldCount - pos, r); - - // Get PocoData for the two types - PocoData pdThis = PocoData.ForType(typeThis); - PocoData pdNext = PocoData.ForType(typeNext); - - // Find split point - int firstColumn = pos; - var usedColumns = new Dictionary(); - for (; pos < r.FieldCount; pos++) - { - // Split if field name has already been used, or if the field doesn't exist in current poco but does in the next - string fieldName = r.GetName(pos); - if (usedColumns.ContainsKey(fieldName) || (!pdThis.Columns.ContainsKey(fieldName) && pdNext.Columns.ContainsKey(fieldName))) - { - return pdThis.GetFactory(sql, _sharedConnection.ConnectionString, ForceDateTimesToUtc, firstColumn, pos - firstColumn, r); - } - usedColumns.Add(fieldName, true); - } - - throw new InvalidOperationException(string.Format("Couldn't find split point between {0} and {1}", typeThis, typeNext)); - } - - - // Instance data used by the Multipoco factory delegate - essentially a list of the nested poco factories to call - public class MultiPocoFactory - { - - public MultiPocoFactory(IEnumerable dels) - { - Delegates = new List(dels); - } - private List Delegates { get; set; } - private Delegate GetItem(int index) { return Delegates[index]; } - - /// - /// Calls the delegate at the specified index and returns its values - /// - /// - /// - /// - private object CallDelegate(int index, IDataReader reader) - { - var d = GetItem(index); - var output = d.DynamicInvoke(reader); - return output; - } - - /// - /// Calls the callback delegate and passes in the output of all delegates as the parameters - /// - /// - /// - /// - /// - /// - public TRet CallCallback(Delegate callback, IDataReader dr, int count) - { - var args = new List(); - for(var i = 0;i CreateMultiPocoFactory(Type[] types, string sql, IDataReader r) - { - // Call each delegate - var dels = new List(); - int pos = 0; - for (int i=0; i mpFactory.CallCallback(arg3, reader, types.Length); - } - - // Various cached stuff - static Dictionary MultiPocoFactories = new Dictionary(); - static Dictionary AutoMappers = new Dictionary(); - static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim(); - - // Get (or create) the multi-poco factory for a query - Func GetMultiPocoFactory(Type[] types, string sql, IDataReader r) - { - // Build a key string (this is crap, should address this at some point) - var kb = new StringBuilder(); - kb.Append(typeof(TRet).ToString()); - kb.Append(":"); - foreach (var t in types) - { - kb.Append(":"); - kb.Append(t.ToString()); - } - kb.Append(":"); kb.Append(_sharedConnection.ConnectionString); - kb.Append(":"); kb.Append(ForceDateTimesToUtc); - kb.Append(":"); kb.Append(sql); - string key = kb.ToString(); - - // Check cache - RWLock.EnterReadLock(); - try - { - object oFactory; - if (MultiPocoFactories.TryGetValue(key, out oFactory)) - { - //mpFactory = oFactory; - return (Func)oFactory; - } - } - finally - { - RWLock.ExitReadLock(); - } - - // Cache it - RWLock.EnterWriteLock(); - try - { - // Check again - object oFactory; ; - if (MultiPocoFactories.TryGetValue(key, out oFactory)) - { - return (Func)oFactory; - } - - // Create the factory - var factory = CreateMultiPocoFactory(types, sql, r); - - MultiPocoFactories.Add(key, factory); - return factory; - } - finally - { - RWLock.ExitWriteLock(); - } - - } - - // Actual implementation of the multi-poco query - public IEnumerable Query(Type[] types, Delegate cb, string sql, params object[] args) - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - IDataReader r; - try - { - r = cmd.ExecuteReaderWithRetry(); - OnExecutedCommand(cmd); - } - catch (Exception x) - { - OnException(x); - throw; - } - var factory = GetMultiPocoFactory(types, sql, r); - if (cb == null) - cb = GetAutoMapper(types.ToArray()); - bool bNeedTerminator=false; - using (r) - { - while (true) - { - TRet poco; - try - { - if (!r.Read()) - break; - poco = factory(r, cb); - } - catch (Exception x) - { - OnException(x); - throw; - } - - if (poco != null) - yield return poco; - else - bNeedTerminator = true; - } - if (bNeedTerminator) - { - var poco = (TRet)(cb as Delegate).DynamicInvoke(new object[types.Length]); - if (poco != null) - yield return poco; - else - yield break; - } - } - } - } - finally - { - CloseSharedConnection(); - } - } - - - public IEnumerable Query(Sql sql) - { - return Query(sql.SQL, sql.Arguments); - } - - public bool Exists(object primaryKey) - { - return FirstOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey) != null; - } - public T Single(object primaryKey) - { - return Single(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); - } - public T SingleOrDefault(object primaryKey) - { - return SingleOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); - } - public T Single(string sql, params object[] args) - { - return Query(sql, args).Single(); - } - public T SingleOrDefault(string sql, params object[] args) - { - return Query(sql, args).SingleOrDefault(); - } - public T First(string sql, params object[] args) - { - return Query(sql, args).First(); - } - public T FirstOrDefault(string sql, params object[] args) - { - return Query(sql, args).FirstOrDefault(); - } - - public T Single(Sql sql) - { - return Query(sql).Single(); - } - public T SingleOrDefault(Sql sql) - { - return Query(sql).SingleOrDefault(); - } - public T First(Sql sql) - { - return Query(sql).First(); - } - public T FirstOrDefault(Sql sql) - { - return Query(sql).FirstOrDefault(); - } - - public string EscapeTableName(string str) - { - // Assume table names with "dot", or opening sq is already escaped - return str.IndexOf('.') >= 0 ? str : EscapeSqlIdentifier(str); - } - public string EscapeSqlIdentifier(string str) - { - switch (_dbType) - { - case DBType.MySql: - return string.Format("`{0}`", str); - - case DBType.PostgreSQL: - case DBType.Oracle: - return string.Format("\"{0}\"", str); - - default: - return string.Format("[{0}]", str); - } - } - - public object Insert(string tableName, string primaryKeyName, object poco) - { - return Insert(tableName, primaryKeyName, true, poco); - } - - // Insert a poco into a table. If the poco has a property with the same name - // as the primary key the id of the new record is assigned to it. Either way, - // the new id is returned. - public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, "")) - { - var pd = PocoData.ForObject(poco, primaryKeyName); - var names = new List(); - var values = new List(); - var index = 0; - foreach (var i in pd.Columns) - { - // Don't insert result columns - if (i.Value.ResultColumn) - continue; - - // Don't insert the primary key (except under oracle where we need bring in the next sequence value) - if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, true)==0) - { - if (_dbType == DBType.Oracle && !string.IsNullOrEmpty(pd.TableInfo.SequenceName)) - { - names.Add(i.Key); - values.Add(string.Format("{0}.nextval", pd.TableInfo.SequenceName)); - } - continue; - } - - names.Add(EscapeSqlIdentifier(i.Key)); - values.Add(string.Format("{0}{1}", _paramPrefix, index++)); - AddParam(cmd, i.Value.GetValue(poco), _paramPrefix); - } - - cmd.CommandText = string.Format("INSERT INTO {0} ({1}) VALUES ({2})", - EscapeTableName(tableName), - string.Join(",", names.ToArray()), - string.Join(",", values.ToArray()) - ); - - if (!autoIncrement) - { - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - OnExecutedCommand(cmd); - return true; - } - - - object id; - switch (_dbType) - { - case DBType.SqlServerCE: - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - OnExecutedCommand(cmd); - id = ExecuteScalar("SELECT @@@IDENTITY AS NewID;"); - break; - case DBType.SqlServer: - cmd.CommandText += ";\nSELECT SCOPE_IDENTITY() AS NewID;"; - DoPreExecute(cmd); - id = cmd.ExecuteScalarWithRetry(); - OnExecutedCommand(cmd); - break; - case DBType.PostgreSQL: - if (primaryKeyName != null) - { - cmd.CommandText += string.Format("returning {0} as NewID", EscapeSqlIdentifier(primaryKeyName)); - DoPreExecute(cmd); - id = cmd.ExecuteScalarWithRetry(); - } - else - { - id = -1; - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - } - OnExecutedCommand(cmd); - break; - case DBType.Oracle: - if (primaryKeyName != null) - { - cmd.CommandText += string.Format(" returning {0} into :newid", EscapeSqlIdentifier(primaryKeyName)); - var param = cmd.CreateParameter(); - param.ParameterName = ":newid"; - param.Value = DBNull.Value; - param.Direction = ParameterDirection.ReturnValue; - param.DbType = DbType.Int64; - cmd.Parameters.Add(param); - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - id = param.Value; - } - else - { - id = -1; - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - } - OnExecutedCommand(cmd); - break; - case DBType.SQLite: - if (primaryKeyName != null) - { - cmd.CommandText += ";\nSELECT last_insert_rowid();"; - DoPreExecute(cmd); - id = cmd.ExecuteScalarWithRetry(); - } - else - { - id = -1; - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - } - OnExecutedCommand(cmd); - break; - default: - cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;"; - DoPreExecute(cmd); - id = cmd.ExecuteScalarWithRetry(); - OnExecutedCommand(cmd); - break; - } - - - // Assign the ID back to the primary key property - if (primaryKeyName != null) - { - PocoColumn pc; - if (pd.Columns.TryGetValue(primaryKeyName, out pc)) - { - pc.SetValue(poco, pc.ChangeType(id)); - } - } - - return id; - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - OnException(x); - throw; - } - } - - // Insert an annotated poco object - public object Insert(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - return Insert(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, poco); - } - - public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue) - { - return Update(tableName, primaryKeyName, poco, primaryKeyValue, null); - } - - - // Update a record with values from a poco. primary key value can be either supplied or read from the poco - public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, "")) - { - var sb = new StringBuilder(); - var index = 0; - var pd = PocoData.ForObject(poco,primaryKeyName); - if (columns == null) - { - foreach (var i in pd.Columns) - { - // Don't update the primary key, but grab the value if we don't have it - if (string.Compare(i.Key, primaryKeyName, true) == 0) - { - if (primaryKeyValue == null) - primaryKeyValue = i.Value.GetValue(poco); - continue; - } - - // Dont update result only columns - if (i.Value.ResultColumn) - continue; - - // Build the sql - if (index > 0) - sb.Append(", "); - sb.AppendFormat("{0} = {1}{2}", EscapeSqlIdentifier(i.Key), _paramPrefix, index++); - - // Store the parameter in the command - AddParam(cmd, i.Value.GetValue(poco), _paramPrefix); - } - } - else - { - foreach (var colname in columns) - { - var pc = pd.Columns[colname]; - - // Build the sql - if (index > 0) - sb.Append(", "); - sb.AppendFormat("{0} = {1}{2}", EscapeSqlIdentifier(colname), _paramPrefix, index++); - - // Store the parameter in the command - AddParam(cmd, pc.GetValue(poco), _paramPrefix); - } - - // Grab primary key value - if (primaryKeyValue == null) - { - var pc = pd.Columns[primaryKeyName]; - primaryKeyValue = pc.GetValue(poco); - } - - } - - cmd.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2} = {3}{4}", - EscapeTableName(tableName), sb.ToString(), EscapeSqlIdentifier(primaryKeyName), _paramPrefix, index++); - AddParam(cmd, primaryKeyValue, _paramPrefix); - - DoPreExecute(cmd); - - // Do it - var retv=cmd.ExecuteNonQueryWithRetry(); - OnExecutedCommand(cmd); - return retv; - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - OnException(x); - throw; - } - } - - public int Update(string tableName, string primaryKeyName, object poco) - { - return Update(tableName, primaryKeyName, poco, null); - } - - public int Update(string tableName, string primaryKeyName, object poco, IEnumerable columns) - { - return Update(tableName, primaryKeyName, poco, null, columns); - } - - public int Update(object poco, IEnumerable columns) - { - return Update(poco, null, columns); - } - - public int Update(object poco) - { - return Update(poco, null, null); - } - - public int Update(object poco, object primaryKeyValue) - { - return Update(poco, primaryKeyValue, null); - } - public int Update(object poco, object primaryKeyValue, IEnumerable columns) - { - var pd = PocoData.ForType(poco.GetType()); - return Update(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco, primaryKeyValue, columns); - } - - public int Update(string sql, params object[] args) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(string.Format("UPDATE {0} {1}", EscapeTableName(pd.TableInfo.TableName), sql), args); - } - - public int Update(Sql sql) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(new Sql(string.Format("UPDATE {0}", EscapeTableName(pd.TableInfo.TableName))).Append(sql)); - } - - public int Delete(string tableName, string primaryKeyName, object poco) - { - return Delete(tableName, primaryKeyName, poco, null); - } - - public int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue) - { - // If primary key value not specified, pick it up from the object - if (primaryKeyValue == null) - { - var pd = PocoData.ForObject(poco,primaryKeyName); - PocoColumn pc; - if (pd.Columns.TryGetValue(primaryKeyName, out pc)) - { - primaryKeyValue = pc.GetValue(poco); - } - } - - // Do it - var sql = string.Format("DELETE FROM {0} WHERE {1}=@0", EscapeTableName(tableName), EscapeSqlIdentifier(primaryKeyName)); - return Execute(sql, primaryKeyValue); - } - - public int Delete(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); - } - - public int Delete(object pocoOrPrimaryKey) - { - if (pocoOrPrimaryKey.GetType() == typeof(T)) - return Delete(pocoOrPrimaryKey); - var pd = PocoData.ForType(typeof(T)); - return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, null, pocoOrPrimaryKey); - } - - public int Delete(string sql, params object[] args) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(string.Format("DELETE FROM {0} {1}", EscapeTableName(pd.TableInfo.TableName), sql), args); - } - - public int Delete(Sql sql) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(new Sql(string.Format("DELETE FROM {0}", EscapeTableName(pd.TableInfo.TableName))).Append(sql)); - } - - // Check if a poco represents a new record - public bool IsNew(string primaryKeyName, object poco) - { - var pd = PocoData.ForObject(poco, primaryKeyName); - object pk; - PocoColumn pc; - if (pd.Columns.TryGetValue(primaryKeyName, out pc)) - { - pk = pc.GetValue(poco); - } -#if !PETAPOCO_NO_DYNAMIC - else if (poco.GetType() == typeof(System.Dynamic.ExpandoObject)) - { - return true; - } -#endif - else - { - var pi = poco.GetType().GetProperty(primaryKeyName); - if (pi == null) - throw new ArgumentException(string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName)); - pk = pi.GetValue(poco, null); - } - - if (pk == null) - return true; - - var type = pk.GetType(); - - if (type.IsValueType) - { - // Common primary key types - if (type == typeof(long)) - return (long)pk == 0; - else if (type == typeof(ulong)) - return (ulong)pk == 0; - else if (type == typeof(int)) - return (int)pk == 0; - else if (type == typeof(uint)) - return (uint)pk == 0; - - // Create a default instance and compare - return pk == Activator.CreateInstance(pk.GetType()); - } - else - { - return pk == null; - } - } - - public bool IsNew(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - if (!pd.TableInfo.AutoIncrement) - throw new InvalidOperationException("IsNew() and Save() are only supported on tables with auto-increment/identity primary key columns"); - return IsNew(pd.TableInfo.PrimaryKey, poco); - } - - // Insert new record or Update existing record - public void Save(string tableName, string primaryKeyName, object poco) - { - if (IsNew(primaryKeyName, poco)) - { - Insert(tableName, primaryKeyName, true, poco); - } - else - { - Update(tableName, primaryKeyName, poco); - } - } - - public void Save(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - Save(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); - } - - public int CommandTimeout { get; set; } - public int OneTimeCommandTimeout { get; set; } - - void DoPreExecute(IDbCommand cmd) - { - // Setup command timeout - if (OneTimeCommandTimeout != 0) - { - cmd.CommandTimeout = OneTimeCommandTimeout; - OneTimeCommandTimeout = 0; - } - else if (CommandTimeout!=0) - { - cmd.CommandTimeout = CommandTimeout; - } - - // Call hook - OnExecutingCommand(cmd); - - // Save it - _lastSql = cmd.CommandText; - _lastArgs = (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray(); - } - - public string LastSQL { get { return _lastSql; } } - public object[] LastArgs { get { return _lastArgs; } } - public string LastCommand - { - get { return FormatCommand(_lastSql, _lastArgs); } - } - - public string FormatCommand(IDbCommand cmd) - { - return FormatCommand(cmd.CommandText, (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray()); - } - - public string FormatCommand(string sql, object[] args) - { - var sb = new StringBuilder(); - if (sql == null) - return ""; - sb.Append(sql); - if (args != null && args.Length > 0) - { - sb.Append("\n"); - for (int i = 0; i < args.Length; i++) - { - sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, args[i].GetType().Name, args[i]); - } - sb.Remove(sb.Length - 1, 1); - } - return sb.ToString(); - } - - - public static IMapper Mapper - { - get; - set; - } - - public class PocoColumn - { - public string ColumnName; - public PropertyInfo PropertyInfo; - public bool ResultColumn; - public virtual void SetValue(object target, object val) { PropertyInfo.SetValue(target, val, null); } - public virtual object GetValue(object target) { return PropertyInfo.GetValue(target, null); } - public virtual object ChangeType(object val) { return Convert.ChangeType(val, PropertyInfo.PropertyType); } - } - public class ExpandoColumn : PocoColumn - { - public override void SetValue(object target, object val) { (target as IDictionary)[ColumnName]=val; } - public override object GetValue(object target) - { - object val=null; - (target as IDictionary).TryGetValue(ColumnName, out val); - return val; - } - public override object ChangeType(object val) { return val; } - } - - /// - /// Container for a Memory cache object - /// - /// - /// Better to have one memory cache instance than many so it's memory management can be handled more effectively - /// http://stackoverflow.com/questions/8463962/using-multiple-instances-of-memorycache - /// - internal class ManagedCache - { - public ObjectCache GetCache() - { - return ObjectCache; - } - - static readonly ObjectCache ObjectCache = new MemoryCache("NPoco"); - - } - - public class PocoData - { - //USE ONLY FOR TESTING - internal static bool UseLongKeys = false; - //USE ONLY FOR TESTING - default is one hr - internal static int SlidingExpirationSeconds = 3600; - - public static PocoData ForObject(object o, string primaryKeyName) - { - var t = o.GetType(); -#if !PETAPOCO_NO_DYNAMIC - if (t == typeof(System.Dynamic.ExpandoObject)) - { - var pd = new PocoData(); - pd.TableInfo = new TableInfo(); - pd.Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); - pd.Columns.Add(primaryKeyName, new ExpandoColumn() { ColumnName = primaryKeyName }); - pd.TableInfo.PrimaryKey = primaryKeyName; - pd.TableInfo.AutoIncrement = true; - foreach (var col in (o as IDictionary).Keys) - { - if (col!=primaryKeyName) - pd.Columns.Add(col, new ExpandoColumn() { ColumnName = col }); - } - return pd; - } - else -#endif - return ForType(t); - } - - public static PocoData ForType(Type t) - { -#if !PETAPOCO_NO_DYNAMIC - if (t == typeof(System.Dynamic.ExpandoObject)) - throw new InvalidOperationException("Can't use dynamic types with this method"); -#endif - // Check cache - InnerLock.EnterReadLock(); - PocoData pd; - try - { - if (m_PocoDatas.TryGetValue(t, out pd)) - return pd; - } - finally - { - InnerLock.ExitReadLock(); - } - - - // Cache it - InnerLock.EnterWriteLock(); - try - { - // Check again - if (m_PocoDatas.TryGetValue(t, out pd)) - return pd; - - // Create it - pd = new PocoData(t); - - m_PocoDatas.Add(t, pd); - } - finally - { - InnerLock.ExitWriteLock(); - } - - return pd; - } - - public PocoData() - { - } - - public PocoData(Type t) - { - type = t; - TableInfo=new TableInfo(); - - // Get the table name - var a = t.GetCustomAttributes(typeof(TableNameAttribute), true); - TableInfo.TableName = a.Length == 0 ? t.Name : (a[0] as TableNameAttribute).Value; - - // Get the primary key - a = t.GetCustomAttributes(typeof(PrimaryKeyAttribute), true); - TableInfo.PrimaryKey = a.Length == 0 ? "ID" : (a[0] as PrimaryKeyAttribute).Value; - TableInfo.SequenceName = a.Length == 0 ? null : (a[0] as PrimaryKeyAttribute).sequenceName; - TableInfo.AutoIncrement = a.Length == 0 ? false : (a[0] as PrimaryKeyAttribute).autoIncrement; - - // Call column mapper - if (Database.Mapper != null) - Database.Mapper.GetTableInfo(t, TableInfo); - - // Work out bound properties - bool ExplicitColumns = t.GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Length > 0; - Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var pi in t.GetProperties()) - { - // Work out if properties is to be included - var ColAttrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true); - if (ExplicitColumns) - { - if (ColAttrs.Length == 0) - continue; - } - else - { - if (pi.GetCustomAttributes(typeof(IgnoreAttribute), true).Length != 0) - continue; - } - - var pc = new PocoColumn(); - pc.PropertyInfo = pi; - - // Work out the DB column name - if (ColAttrs.Length > 0) - { - var colattr = (ColumnAttribute)ColAttrs[0]; - pc.ColumnName = colattr.Name; - if ((colattr as ResultColumnAttribute) != null) - pc.ResultColumn = true; - } - if (pc.ColumnName == null) - { - pc.ColumnName = pi.Name; - if (Database.Mapper != null && !Database.Mapper.MapPropertyToColumn(t, pi, ref pc.ColumnName, ref pc.ResultColumn)) - continue; - } - - // Store it - Columns.Add(pc.ColumnName, pc); - } - - // Build column list for automatic select - QueryColumns = (from c in Columns where !c.Value.ResultColumn select c.Key).ToArray(); - - } - - static bool IsIntegralType(Type t) - { - var tc = Type.GetTypeCode(t); - return tc >= TypeCode.SByte && tc <= TypeCode.UInt64; - } - - - - // Create factory function that can convert a IDataReader record into a POCO - public Delegate GetFactory(string sql, string connString, bool ForceDateTimesToUtc, int firstColumn, int countColumns, IDataReader r) - { - - //TODO: It would be nice to remove the irrelevant SQL parts - for a mapping operation anything after the SELECT clause isn't required. - // This would ensure less duplicate entries that get cached, currently both of these queries would be cached even though they are - // returning the same structured data: - // SELECT * FROM MyTable ORDER BY MyColumn - // SELECT * FROM MyTable ORDER BY MyColumn DESC - - string key; - if (UseLongKeys) - { - key = string.Format("{0}:{1}:{2}:{3}:{4}", sql, connString, ForceDateTimesToUtc, firstColumn, countColumns); - } - else - { - //Create a hashed key, we don't want to store so much string data in memory - var combiner = new HashCodeCombiner(); - combiner.AddCaseInsensitiveString(sql); - combiner.AddCaseInsensitiveString(connString); - combiner.AddObject(ForceDateTimesToUtc); - combiner.AddInt(firstColumn); - combiner.AddInt(countColumns); - key = combiner.GetCombinedHashCode(); - } - - - var objectCache = _managedCache.GetCache(); - - Func factory = () => - { - // Create the method - var m = new DynamicMethod("petapoco_factory_" + objectCache.GetCount(), type, new Type[] { typeof(IDataReader) }, true); - var il = m.GetILGenerator(); - -#if !PETAPOCO_NO_DYNAMIC - if (type == typeof(object)) - { - // var poco=new T() - il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj - - MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add"); - - // Enumerate all fields generating a set assignment for the column - for (int i = firstColumn; i < firstColumn + countColumns; i++) - { - var srcType = r.GetFieldType(i); - - il.Emit(OpCodes.Dup); // obj, obj - il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname - - // Get the converter - Func converter = null; - if (Database.Mapper != null) - converter = Database.Mapper.GetFromDbConverter(null, srcType); - if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime)) - converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; - - // Setup stack for call to converter - AddConverterToStack(il, converter); - - // r[i] - il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr - il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i - il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value - - // Convert DBNull to null - il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value - il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null) - var lblNotNull = il.DefineLabel(); - il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value - il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter? - if (converter != null) - il.Emit(OpCodes.Pop); // obj, obj, fieldname, - il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null - if (converter != null) - { - var lblReady = il.DefineLabel(); - il.Emit(OpCodes.Br_S, lblReady); - il.MarkLabel(lblNotNull); - il.Emit(OpCodes.Callvirt, fnInvoke); - il.MarkLabel(lblReady); - } - else - { - il.MarkLabel(lblNotNull); - } - - il.Emit(OpCodes.Callvirt, fnAdd); - } - } - else -#endif - if (type.IsValueType || type == typeof(string) || type == typeof(byte[])) - { - // Do we need to install a converter? - var srcType = r.GetFieldType(0); - var converter = GetConverter(ForceDateTimesToUtc, null, srcType, type); - - // "if (!rdr.IsDBNull(i))" - il.Emit(OpCodes.Ldarg_0); // rdr - il.Emit(OpCodes.Ldc_I4_0); // rdr,0 - il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool - var lblCont = il.DefineLabel(); - il.Emit(OpCodes.Brfalse_S, lblCont); - il.Emit(OpCodes.Ldnull); // null - var lblFin = il.DefineLabel(); - il.Emit(OpCodes.Br_S, lblFin); - - il.MarkLabel(lblCont); - - // Setup stack for call to converter - AddConverterToStack(il, converter); - - il.Emit(OpCodes.Ldarg_0); // rdr - il.Emit(OpCodes.Ldc_I4_0); // rdr,0 - il.Emit(OpCodes.Callvirt, fnGetValue); // value - - // Call the converter - if (converter != null) - il.Emit(OpCodes.Callvirt, fnInvoke); - - il.MarkLabel(lblFin); - il.Emit(OpCodes.Unbox_Any, type); // value converted - } - else - { - // var poco=new T() - il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); - - // Enumerate all fields generating a set assignment for the column - for (int i = firstColumn; i < firstColumn + countColumns; i++) - { - // Get the PocoColumn for this db column, ignore if not known - PocoColumn pc; - if (!Columns.TryGetValue(r.GetName(i), out pc)) - continue; - - // Get the source type for this column - var srcType = r.GetFieldType(i); - var dstType = pc.PropertyInfo.PropertyType; - - // "if (!rdr.IsDBNull(i))" - il.Emit(OpCodes.Ldarg_0); // poco,rdr - il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i - il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool - var lblNext = il.DefineLabel(); - il.Emit(OpCodes.Brtrue_S, lblNext); // poco - - il.Emit(OpCodes.Dup); // poco,poco - - // Do we need to install a converter? - var converter = GetConverter(ForceDateTimesToUtc, pc, srcType, dstType); - - // Fast - bool Handled = false; - if (converter == null) - { - var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) }); - if (valuegetter != null - && valuegetter.ReturnType == srcType - && (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType))) - { - il.Emit(OpCodes.Ldarg_0); // *,rdr - il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i - il.Emit(OpCodes.Callvirt, valuegetter); // *,value - - // Convert to Nullable - if (Nullable.GetUnderlyingType(dstType) != null) - { - il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) })); - } - - il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco - Handled = true; - } - } - - // Not so fast - if (!Handled) - { - // Setup stack for call to converter - AddConverterToStack(il, converter); - - // "value = rdr.GetValue(i)" - il.Emit(OpCodes.Ldarg_0); // *,rdr - il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i - il.Emit(OpCodes.Callvirt, fnGetValue); // *,value - - // Call the converter - if (converter != null) - il.Emit(OpCodes.Callvirt, fnInvoke); - - // Assign it - il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value - il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco - } - - il.MarkLabel(lblNext); - } - - var fnOnLoaded = RecurseInheritedTypes(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); - if (fnOnLoaded != null) - { - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Callvirt, fnOnLoaded); - } - } - - il.Emit(OpCodes.Ret); - - // return it - var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type)); - - return del; - }; - - //lazy usage of AddOrGetExisting ref: http://stackoverflow.com/questions/10559279/how-to-deal-with-costly-building-operations-using-memorycache/15894928#15894928 - var newValue = new Lazy(factory); - // the line belows returns existing item or adds the new value if it doesn't exist - var value = (Lazy)objectCache.AddOrGetExisting(key, newValue, new CacheItemPolicy - { - //sliding expiration of 1 hr, if the same key isn't used in this - // timeframe it will be removed from the cache - SlidingExpiration = new TimeSpan(0, 0, SlidingExpirationSeconds) - }); - return (value ?? newValue).Value; // Lazy handles the locking itself - - } - - private static void AddConverterToStack(ILGenerator il, Func converter) - { - if (converter != null) - { - // Add the converter - int converterIndex = m_Converters.Count; - m_Converters.Add(converter); - - // Generate IL to push the converter onto the stack - il.Emit(OpCodes.Ldsfld, fldConverters); - il.Emit(OpCodes.Ldc_I4, converterIndex); - il.Emit(OpCodes.Callvirt, fnListGetItem); // Converter - } - } - - private static Func GetConverter(bool forceDateTimesToUtc, PocoColumn pc, Type srcType, Type dstType) - { - Func converter = null; - - // Get converter from the mapper - if (Database.Mapper != null) - { - if (pc != null) - { - converter = Database.Mapper.GetFromDbConverter(pc.PropertyInfo, srcType); - } - else - { - var m2 = Database.Mapper as IMapper2; - if (m2 != null) - { - converter = m2.GetFromDbConverter(dstType, srcType); - } - } - } - - // Standard DateTime->Utc mapper - if (forceDateTimesToUtc && converter == null && srcType == typeof(DateTime) && (dstType == typeof(DateTime) || dstType == typeof(DateTime?))) - { - converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; - } - - // Forced type conversion including integral types -> enum - if (converter == null) - { - if (dstType.IsEnum && IsIntegralType(srcType)) - { - if (srcType != typeof(int)) - { - converter = delegate(object src) { return Convert.ChangeType(src, typeof(int), null); }; - } - } - else if (!dstType.IsAssignableFrom(srcType)) - { - converter = delegate(object src) { return Convert.ChangeType(src, dstType, null); }; - } - } - return converter; - } - - - static T RecurseInheritedTypes(Type t, Func cb) - { - while (t != null) - { - T info = cb(t); - if (info != null) - return info; - t = t.BaseType; - } - return default(T); - } - - ManagedCache _managedCache = new ManagedCache(); - static Dictionary m_PocoDatas = new Dictionary(); - static List> m_Converters = new List>(); - static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) }); - static MethodInfo fnIsDBNull = typeof(IDataRecord).GetMethod("IsDBNull"); - static FieldInfo fldConverters = typeof(PocoData).GetField("m_Converters", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic); - static MethodInfo fnListGetItem = typeof(List>).GetProperty("Item").GetGetMethod(); - static MethodInfo fnInvoke = typeof(Func).GetMethod("Invoke"); - public Type type; - public string[] QueryColumns { get; private set; } - public TableInfo TableInfo { get; private set; } - public Dictionary Columns { get; private set; } - static System.Threading.ReaderWriterLockSlim InnerLock = new System.Threading.ReaderWriterLockSlim(); - - /// - /// Returns a report of the current cache being utilized by PetaPoco - /// - /// - public static string PrintDebugCacheReport(out double totalBytes, out IEnumerable allKeys) - { - var managedCache = new ManagedCache(); - - var sb = new StringBuilder(); - sb.AppendLine("m_PocoDatas:"); - foreach (var pocoData in m_PocoDatas) - { - sb.AppendFormat("\t{0}\n", pocoData.Key); - sb.AppendFormat("\t\tTable:{0} - Col count:{1}\n", pocoData.Value.TableInfo.TableName, pocoData.Value.QueryColumns.Length); - } - - var cache = managedCache.GetCache(); - allKeys = cache.Select(x => x.Key).ToArray(); - - sb.AppendFormat("\tTotal Poco data count:{0}\n", allKeys.Count()); - - var keys = string.Join("", cache.Select(x => x.Key)); - //Bytes in .Net are stored as utf-16 = unicode little endian - totalBytes = Encoding.Unicode.GetByteCount(keys); - - sb.AppendFormat("\tTotal byte for keys:{0}\n", totalBytes); - - sb.AppendLine("\tAll Poco cache items:"); - - foreach (var item in cache) - { - sb.AppendFormat("\t\t Key -> {0}\n", item.Key); - sb.AppendFormat("\t\t Value -> {0}\n", item.Value); - } - - sb.AppendLine("-------------------END REPORT------------------------"); - return sb.ToString(); - } - } - - - // Member variables - readonly string _connectionString; - readonly string _providerName; - DbProviderFactory _factory; - IDbConnection _sharedConnection; - IDbTransaction _transaction; - int _sharedConnectionDepth; - int _transactionDepth; - bool _transactionCancelled; - string _lastSql; - object[] _lastArgs; - string _paramPrefix = "@"; - IsolationLevel _isolationLevel; - } - - // Transaction object helps maintain transaction depth counts - public class Transaction : IDisposable - { - public Transaction(Database db, IsolationLevel isolationLevel) - { - _db = db; - _db.BeginTransaction(isolationLevel); - } - - public virtual void Complete() - { - _db.CompleteTransaction(); - _db = null; - } - - public void Dispose() - { - //TODO prevent multiple calls to Dispose - if (_db != null) - _db.AbortTransaction(); - } - - Database _db; - } - - // Simple helper class for building SQL statments - public class Sql - { - public Sql() - { - } - - public Sql(string sql, params object[] args) - { - _sql = sql; - _args = args; - } - - public static Sql Builder - { - get { return new Sql(); } - } - - string _sql; - object[] _args; - Sql _rhs; - string _sqlFinal; - object[] _argsFinal; - - private void Build() - { - // already built? - if (_sqlFinal != null) - return; - - // Build it - var sb = new StringBuilder(); - var args = new List(); - Build(sb, args, null); - _sqlFinal = sb.ToString(); - _argsFinal = args.ToArray(); - } - - public string SQL - { - get - { - Build(); - return _sqlFinal; - } - } - - public object[] Arguments - { - get - { - Build(); - return _argsFinal; - } - } - - public Sql Append(Sql sql) - { - if (_rhs != null) - _rhs.Append(sql); - else - _rhs = sql; - - _sqlFinal = null; - return this; - } - - public Sql Append(string sql, params object[] args) - { - return Append(new Sql(sql, args)); - } - - static bool Is(Sql sql, string sqltype) - { - return sql != null && sql._sql != null && sql._sql.StartsWith(sqltype, StringComparison.InvariantCultureIgnoreCase); - } - - private void Build(StringBuilder sb, List args, Sql lhs) - { - if (!String.IsNullOrEmpty(_sql)) - { - // Add SQL to the string - if (sb.Length > 0) - { - sb.Append("\n"); - } - - var sql = Database.ProcessParams(_sql, _args, args); - - if (Is(lhs, "WHERE ") && Is(this, "WHERE ")) - sql = "AND " + sql.Substring(6); - if (Is(lhs, "ORDER BY ") && Is(this, "ORDER BY ")) - sql = ", " + sql.Substring(9); - - sb.Append(sql); - } - - // Now do rhs - if (_rhs != null) - _rhs.Build(sb, args, this); - } - - public Sql Where(string sql, params object[] args) - { - return Append(new Sql("WHERE (" + sql + ")", args)); - } - - public Sql OrderBy(params object[] columns) - { - return Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - public Sql Select(params object[] columns) - { - return Append(new Sql("SELECT " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - public Sql From(params object[] tables) - { - return Append(new Sql("FROM " + String.Join(", ", (from x in tables select x.ToString()).ToArray()))); - } - - public Sql GroupBy(params object[] columns) - { - return Append(new Sql("GROUP BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - private SqlJoinClause Join(string JoinType, string table) - { - return new SqlJoinClause(Append(new Sql(JoinType + table))); - } - - public SqlJoinClause InnerJoin(string table) { return Join("INNER JOIN ", table); } - public SqlJoinClause LeftJoin(string table) { return Join("LEFT JOIN ", table); } - public SqlJoinClause LeftOuterJoin(string table) { return Join("LEFT OUTER JOIN ", table); } - public SqlJoinClause RightJoin(string table) { return Join("RIGHT JOIN ", table); } - - public class SqlJoinClause - { - private readonly Sql _sql; - - public SqlJoinClause(Sql sql) - { - _sql = sql; - } - - public Sql On(string onClause, params object[] args) - { - return _sql.Append("ON " + onClause, args); - } - } - } - -} diff --git a/src/Umbraco.Core/Persistence/PetaPocoCommandExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoCommandExtensions.cs deleted file mode 100644 index 72829d8df4..0000000000 --- a/src/Umbraco.Core/Persistence/PetaPocoCommandExtensions.cs +++ /dev/null @@ -1,276 +0,0 @@ -using System; -using System.Data; -using Umbraco.Core.Persistence.FaultHandling; - -namespace Umbraco.Core.Persistence -{ - /// - /// Provides a set of extension methods adding retry capabilities into the standard implementation, which is used in PetaPoco. - /// - public static class PetaPocoCommandExtensions - { - #region ExecuteNonQueryWithRetry method implementations - /// - /// Executes a Transact-SQL statement against the connection and returns the number of rows affected. Uses the default retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// The number of rows affected. - public static int ExecuteNonQueryWithRetry(this IDbCommand command) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteNonQueryWithRetry(command, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Executes a Transact-SQL statement against the connection and returns the number of rows affected. Uses the specified retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// The retry policy defining whether to retry a command if a connection fails while executing the command. - /// The number of rows affected. - public static int ExecuteNonQueryWithRetry(this IDbCommand command, RetryPolicy retryPolicy) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteNonQueryWithRetry(command, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Executes a Transact-SQL statement against the connection and returns the number of rows affected. Uses the specified retry policy when executing the command. - /// Uses a separate specified retry policy when establishing a connection. - /// - /// The command object that is required as per extension method declaration. - /// The command retry policy defining whether to retry a command if it fails while executing. - /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. - /// The number of rows affected. - public static int ExecuteNonQueryWithRetry(this IDbCommand command, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) - { - //GuardConnectionIsNotNull(command); - - // Check if retry policy was specified, if not, use the default retry policy. - return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => - { - var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); - - try - { - return command.ExecuteNonQuery(); - } - finally - { - if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) - { - //Connection is closed in PetaPoco, so no need to do it here (?) - //command.Connection.Close(); - } - } - }); - } - #endregion - - #region ExecuteReaderWithRetry method implementations - /// - /// Sends the specified command to the connection and builds a SqlDataReader object containing the results. - /// Uses the default retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// A System.Data.IDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteReaderWithRetry(command, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Sends the specified command to the connection and builds a SqlDataReader object containing the results. - /// Uses the specified retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// The retry policy defining whether to retry a command if a connection fails while executing the command. - /// A System.Data.IDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, RetryPolicy retryPolicy) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteReaderWithRetry(command, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Sends the specified command to the connection and builds a SqlDataReader object containing the results. - /// Uses the specified retry policy when executing the command. Uses a separate specified retry policy when - /// establishing a connection. - /// - /// The command object that is required as per extension method declaration. - /// The command retry policy defining whether to retry a command if it fails while executing. - /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. - /// A System.Data.IDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) - { - //GuardConnectionIsNotNull(command); - - // Check if retry policy was specified, if not, use the default retry policy. - return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => - { - var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); - - try - { - return command.ExecuteReader(); - } - catch (Exception) - { - if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) - { - //command.Connection.Close(); - } - - throw; - } - }); - } - - /// - /// Sends the specified command to the connection and builds a SqlDataReader object using one of the - /// CommandBehavior values. Uses the default retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// One of the System.Data.CommandBehavior values. - /// A System.Data.IDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, CommandBehavior behavior) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteReaderWithRetry(command, behavior, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Sends the specified command to the connection and builds a SqlDataReader object using one of the - /// CommandBehavior values. Uses the specified retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// One of the System.Data.CommandBehavior values. - /// The retry policy defining whether to retry a command if a connection fails while executing the command. - /// A System.Data.SqlClient.SqlDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, CommandBehavior behavior, RetryPolicy retryPolicy) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteReaderWithRetry(command, behavior, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Sends the specified command to the connection and builds a SqlDataReader object using one of the - /// CommandBehavior values. Uses the specified retry policy when executing the command. - /// Uses a separate specified retry policy when establishing a connection. - /// - /// The command object that is required as per extension method declaration. - /// One of the System.Data.CommandBehavior values. - /// The command retry policy defining whether to retry a command if it fails while executing. - /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. - /// A System.Data.IDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, CommandBehavior behavior, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) - { - //GuardConnectionIsNotNull(command); - - // Check if retry policy was specified, if not, use the default retry policy. - return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => - { - var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); - - try - { - return command.ExecuteReader(behavior); - } - catch (Exception) - { - if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) - { - //command.Connection.Close(); - } - - throw; - } - }); - } - #endregion - - #region ExecuteScalarWithRetry method implementations - /// - /// Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored. - /// Uses the default retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// The first column of the first row in the result set, or a null reference if the result set is empty. Returns a maximum of 2033 characters. - public static object ExecuteScalarWithRetry(this IDbCommand command) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteScalarWithRetry(command, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored. - /// Uses the specified retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// The retry policy defining whether to retry a command if a connection fails while executing the command. - /// The first column of the first row in the result set, or a null reference if the result set is empty. Returns a maximum of 2033 characters. - public static object ExecuteScalarWithRetry(this IDbCommand command, RetryPolicy retryPolicy) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteScalarWithRetry(command, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); - } - /// - /// Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored. - /// Uses the specified retry policy when executing the command. Uses a separate specified retry policy when establishing a connection. - /// - /// The command object that is required as per extension method declaration. - /// The command retry policy defining whether to retry a command if it fails while executing. - /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. - /// The first column of the first row in the result set, or a null reference if the result set is empty. Returns a maximum of 2033 characters. - public static object ExecuteScalarWithRetry(this IDbCommand command, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) - { - //GuardConnectionIsNotNull(command); - - // Check if retry policy was specified, if not, use the default retry policy. - return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => - { - var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); - - try - { - return command.ExecuteScalar(); - } - finally - { - if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) - { - //Connection is closed in PetaPoco, so no need to do it here (?) - //command.Connection.Close(); - } - } - }); - } - #endregion - - /// - /// Ensure a valid connection in case a connection hasn't been opened by PetaPoco (which shouldn't be possible by the way). - /// - /// - /// - /// - private static bool EnsureValidConnection(IDbCommand command, RetryPolicy retryPolicy) - { - if (command != null) - { - //GuardConnectionIsNotNull(command); - - // Verify whether or not the connection is valid and is open. This code may be retried therefore - // it is important to ensure that a connection is re-established should it have previously failed. - if (command.Connection.State != ConnectionState.Open) - { - // Attempt to open the connection using the retry policy that matches the policy for SQL commands. - command.Connection.OpenWithRetry(retryPolicy); - - return true; - } - } - - return false; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/PetaPocoConnectionExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoConnectionExtensions.cs deleted file mode 100644 index e13608bc31..0000000000 --- a/src/Umbraco.Core/Persistence/PetaPocoConnectionExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Data; -using Umbraco.Core.Persistence.FaultHandling; - -namespace Umbraco.Core.Persistence -{ - /// - /// Provides a set of extension methods adding retry capabilities into the standard interface, which is used in PetaPoco. - /// - public static class PetaPocoConnectionExtensions - { - /// - /// Opens a database connection with the connection settings specified in the ConnectionString property of the connection object. - /// Uses the default retry policy when opening the connection. - /// - /// The connection object that is required as per extension method declaration. - public static void OpenWithRetry(this IDbConnection connection) - { - var connectionString = connection.ConnectionString ?? string.Empty; - var retryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString); - OpenWithRetry(connection, retryPolicy); - } - - /// - /// Opens a database connection with the connection settings specified in the ConnectionString property of the connection object. - /// Uses the specified retry policy when opening the connection. - /// - /// The connection object that is required as per extension method declaration. - /// The retry policy defining whether to retry a request if the connection fails to be opened. - public static void OpenWithRetry(this IDbConnection connection, RetryPolicy retryPolicy) - { - // Check if retry policy was specified, if not, use the default retry policy. - (retryPolicy != null ? retryPolicy : RetryPolicy.NoRetry).ExecuteAction(connection.Open); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs deleted file mode 100644 index 8a75805727..0000000000 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ /dev/null @@ -1,682 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.SqlClient; -using System.Data.SqlServerCe; -using System.Linq; -using System.Text.RegularExpressions; -using StackExchange.Profiling.Data; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence -{ - public static class PetaPocoExtensions - { - // NOTE - // - // proper way to do it with TSQL and SQLCE - // IF EXISTS (SELECT ... FROM table WITH (UPDLOCK,HOLDLOCK)) WHERE ...) - // BEGIN - // UPDATE table SET ... WHERE ... - // END - // ELSE - // BEGIN - // INSERT INTO table (...) VALUES (...) - // END - // - // works in READ COMMITED, TSQL & SQLCE lock the constraint even if it does not exist, so INSERT is OK - // - // proper way to do it with MySQL - // IF EXISTS (SELECT ... FROM table WHERE ... FOR UPDATE) - // BEGIN - // UPDATE table SET ... WHERE ... - // END - // ELSE - // BEGIN - // INSERT INTO table (...) VALUES (...) - // END - // - // MySQL locks the constraint ONLY if it exists, so INSERT may fail... - // in theory, happens in READ COMMITTED but not REPEATABLE READ - // http://www.percona.com/blog/2012/08/28/differences-between-read-committed-and-repeatable-read-transaction-isolation-levels/ - // but according to - // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html - // it won't work for exact index value (only ranges) so really... - // - // MySQL should do - // INSERT INTO table (...) VALUES (...) ON DUPLICATE KEY UPDATE ... - // - // also the lock is released when the transaction is committed - // not sure if that can have unexpected consequences on our code? - // - // so... for the time being, let's do with that somewhat crazy solution below... - - /// - /// Safely inserts a record, or updates if it exists, based on a unique constraint. - /// - /// - /// - /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object - /// passed in will contain the updated value. - /// - /// We cannot rely on database-specific options such as MySql ON DUPLICATE KEY UPDATE or MSSQL MERGE WHEN MATCHED because SQLCE - /// does not support any of them. Ideally this should be achieved with proper transaction isolation levels but that would mean revisiting - /// isolation levels globally. We want to keep it simple for the time being and manage it manually. - /// We handle it by trying to update, then insert, etc. until something works, or we get bored. - /// Note that with proper transactions, if T2 begins after T1 then we are sure that the database will contain T2's value - /// once T1 and T2 have completed. Whereas here, it could contain T1's value. - /// - internal static RecordPersistenceType InsertOrUpdate(this Database db, T poco) - where T : class - { - return db.InsertOrUpdate(poco, null, null); - } - - /// - /// Safely inserts a record, or updates if it exists, based on a unique constraint. - /// - /// - /// - /// - /// If the entity has a composite key they you need to specify the update command explicitly - /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object - /// passed in will contain the updated value. - /// - /// We cannot rely on database-specific options such as MySql ON DUPLICATE KEY UPDATE or MSSQL MERGE WHEN MATCHED because SQLCE - /// does not support any of them. Ideally this should be achieved with proper transaction isolation levels but that would mean revisiting - /// isolation levels globally. We want to keep it simple for the time being and manage it manually. - /// We handle it by trying to update, then insert, etc. until something works, or we get bored. - /// Note that with proper transactions, if T2 begins after T1 then we are sure that the database will contain T2's value - /// once T1 and T2 have completed. Whereas here, it could contain T1's value. - /// - internal static RecordPersistenceType InsertOrUpdate(this Database db, - T poco, - string updateCommand, - object updateArgs) - where T : class - { - if (poco == null) - throw new ArgumentNullException("poco"); - - // try to update - var rowCount = updateCommand.IsNullOrWhiteSpace() - ? db.Update(poco) - : db.Update(updateCommand, updateArgs); - if (rowCount > 0) - return RecordPersistenceType.Update; - - // failed: does not exist, need to insert - // RC1 race cond here: another thread may insert a record with the same constraint - - var i = 0; - while (i++ < 4) - { - try - { - // try to insert - db.Insert(poco); - return RecordPersistenceType.Insert; - } - catch (SqlException) // TODO: need to find out if all db will throw that exception - probably OK - { - // failed: exists (due to race cond RC1) - // RC2 race cond here: another thread may remove the record - - // try to update - rowCount = updateCommand.IsNullOrWhiteSpace() - ? db.Update(poco) - : db.Update(updateCommand, updateArgs); - if (rowCount > 0) - return RecordPersistenceType.Update; - - // failed: does not exist (due to race cond RC2), need to insert - // loop - } - } - - // this can go on forever... have to break at some point and report an error. - throw new DataException("Record could not be inserted or updated."); - } - - /// - /// This will escape single @ symbols for peta poco values so it doesn't think it's a parameter - /// - /// - /// - public static string EscapeAtSymbols(string value) - { - if (value.Contains("@")) - { - //this fancy regex will only match a single @ not a double, etc... - var regex = new Regex("(?(this Database db) - where T : new() - { - var creator = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider); - creator.CreateTable(); - } - - [Obsolete("Use the DatabaseSchemaHelper instead")] - public static void CreateTable(this Database db, bool overwrite) - where T : new() - { - var creator = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider); - creator.CreateTable(overwrite); - } - - /// - /// Performs the bulk insertion in the context of a current transaction with an optional parameter to complete the transaction - /// when finished - /// - /// - /// - /// - [Obsolete("Use the method that specifies an SqlSyntaxContext instance instead")] - public static void BulkInsertRecords(this Database db, IEnumerable collection) - { - db.BulkInsertRecords(collection, null, SqlSyntaxContext.SqlSyntaxProvider, true, false); - } - - - /// - /// Performs the bulk insertion - /// - /// - /// - /// - /// - /// - /// If this is false this will try to just generate bulk insert statements instead of using the current SQL platform's bulk - /// insert logic. For SQLCE, bulk insert statements do not work so if this is false it will insert one at a time. - /// - /// The number of items inserted - public static int BulkInsertRecords(this Database db, - IEnumerable collection, - ISqlSyntaxProvider syntaxProvider, - bool useNativeSqlPlatformBulkInsert = true) - { - return BulkInsertRecords(db, collection, null, syntaxProvider, useNativeSqlPlatformBulkInsert, false); - } - - /// - /// Performs the bulk insertion in the context of a current transaction with an optional parameter to complete the transaction - /// when finished - /// - /// - /// - /// - /// - /// - /// - /// If this is false this will try to just generate bulk insert statements instead of using the current SQL platform's bulk - /// insert logic. For SQLCE, bulk insert statements do not work so if this is false it will insert one at a time. - /// - /// - /// The number of items inserted - public static int BulkInsertRecords(this Database db, - IEnumerable collection, - Transaction tr, - ISqlSyntaxProvider syntaxProvider, - bool useNativeSqlPlatformBulkInsert = true, - bool commitTrans = false) - { - db.OpenSharedConnection(); - try - { - return BulkInsertRecordsTry(db, collection, tr, syntaxProvider, useNativeSqlPlatformBulkInsert, commitTrans); - } - finally - { - db.CloseSharedConnection(); - } - } - - public static int BulkInsertRecordsTry(this Database db, - IEnumerable collection, - Transaction tr, - ISqlSyntaxProvider syntaxProvider, - bool useNativeSqlPlatformBulkInsert = true, - bool commitTrans = false) - { - if (commitTrans && tr == null) - throw new ArgumentNullException("tr", "The transaction cannot be null if commitTrans is true."); - - //don't do anything if there are no records. - if (collection.Any() == false) - { - return 0; - } - - var pd = Database.PocoData.ForType(typeof (T)); - if (pd == null) throw new InvalidOperationException("Could not find PocoData for " + typeof (T)); - - try - { - int processed = 0; - - var usedNativeSqlPlatformInserts = useNativeSqlPlatformBulkInsert - && NativeSqlPlatformBulkInsertRecords(db, syntaxProvider, pd, collection, out processed); - - if (usedNativeSqlPlatformInserts == false) - { - //if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts. - var sqlServerSyntax = syntaxProvider as SqlServerSyntaxProvider; - if ((sqlServerSyntax != null && (int) sqlServerSyntax.GetVersionName(db) < (int) SqlServerVersionName.V2008) - || syntaxProvider is SqlCeSyntaxProvider) - { - //SqlCe doesn't support bulk insert statements! - foreach (var poco in collection) - { - db.Insert(poco); - } - } - else - { - //we'll need to generate insert statements instead - - string[] sqlStatements; - var cmds = db.GenerateBulkInsertCommand(pd, collection, out sqlStatements); - for (var i = 0; i < sqlStatements.Length; i++) - { - using (var cmd = cmds[i]) - { - cmd.CommandText = sqlStatements[i]; - cmd.ExecuteNonQuery(); - processed++; - } - } - } - } - - if (commitTrans) - tr.Complete(); - return processed; - } - catch - { - if (commitTrans) - tr.Dispose(); - throw; - } - - } - - /// - /// Performs the bulk insertion in the context of a current transaction with an optional parameter to complete the transaction - /// when finished - /// - /// - /// - /// - /// - /// - [Obsolete("Use the method that specifies an SqlSyntaxContext instance instead")] - public static void BulkInsertRecords(this Database db, IEnumerable collection, Transaction tr, bool commitTrans = false) - { - db.BulkInsertRecords(collection, tr, SqlSyntaxContext.SqlSyntaxProvider, commitTrans); - } - - /// - /// Creates a bulk insert command - /// - /// - /// - /// - /// - /// - /// Sql commands with populated command parameters required to execute the sql statement - /// - /// The limits for number of parameters are 2100 (in sql server, I think there's many more allowed in mysql). So - /// we need to detect that many params and split somehow. - /// For some reason the 2100 limit is not actually allowed even though the exception from sql server mentions 2100 as a max, perhaps it is 2099 - /// that is max. I've reduced it to 2000 anyways. - /// - internal static IDbCommand[] GenerateBulkInsertCommand( - this Database db, - Database.PocoData pd, - IEnumerable collection, - out string[] sql) - { - if (db == null) throw new ArgumentNullException("db"); - if (db.Connection == null) throw new ArgumentException("db.Connection is null."); - - var tableName = db.EscapeTableName(pd.TableInfo.TableName); - - //get all columns to include and format for sql - var cols = string.Join(", ", - pd.Columns - .Where(c => IncludeColumn(pd, c)) - .Select(c => tableName + "." + db.EscapeSqlIdentifier(c.Key)).ToArray()); - - var itemArray = collection.ToArray(); - - //calculate number of parameters per item - var paramsPerItem = pd.Columns.Count(i => IncludeColumn(pd, i)); - - //Example calc: - // Given: we have 4168 items in the itemArray, each item contains 8 command parameters (values to be inserterted) - // 2100 / 8 = 262.5 - // Math.Floor(2100 / 8) = 262 items per trans - // 4168 / 262 = 15.908... = there will be 16 trans in total - - //all items will be included if we have disabled db parameters - var itemsPerTrans = Math.Floor(2000.00/paramsPerItem); - //there will only be one transaction if we have disabled db parameters - var numTrans = Math.Ceiling(itemArray.Length/itemsPerTrans); - - var sqlQueries = new List(); - var commands = new List(); - - for (var tIndex = 0; tIndex < numTrans; tIndex++) - { - var itemsForTrans = itemArray - .Skip(tIndex*(int) itemsPerTrans) - .Take((int) itemsPerTrans); - - var cmd = db.CreateCommand(db.Connection, string.Empty); - var pocoValues = new List(); - var index = 0; - foreach (var poco in itemsForTrans) - { - var values = new List(); - //get all columns except result cols and not the primary key if it is auto-incremental - foreach (var i in pd.Columns.Where(x => IncludeColumn(pd, x))) - { - db.AddParam(cmd, i.Value.GetValue(poco), "@"); - values.Add(string.Format("{0}{1}", "@", index++)); - } - pocoValues.Add("(" + string.Join(",", values.ToArray()) + ")"); - } - - var sqlResult = string.Format("INSERT INTO {0} ({1}) VALUES {2}", tableName, cols, string.Join(", ", pocoValues)); - sqlQueries.Add(sqlResult); - commands.Add(cmd); - } - - sql = sqlQueries.ToArray(); - - return commands.ToArray(); - } - - /// - /// A filter used below a few times to get all columns except result cols and not the primary key if it is auto-incremental - /// - /// - /// - /// - private static bool IncludeColumn(Database.PocoData data, KeyValuePair column) - { - if (column.Value.ResultColumn) return false; - if (data.TableInfo.AutoIncrement && column.Key == data.TableInfo.PrimaryKey) return false; - return true; - } - - /// - /// Bulk insert records with Sql BulkCopy or TableDirect or whatever sql platform specific bulk insert records should be used - /// - /// - /// - /// - /// - /// The number of records inserted - private static bool NativeSqlPlatformBulkInsertRecords(Database db, ISqlSyntaxProvider syntaxProvider, Database.PocoData pd, IEnumerable collection, out int processed) - { - var dbConnection = db.Connection; - - //unwrap the profiled connection if there is one - var profiledConnection = dbConnection as ProfiledDbConnection; - if (profiledConnection != null) - { - dbConnection = profiledConnection.InnerConnection; - } - - //check if it's SQL or SqlCe - - var sqlConnection = dbConnection as SqlConnection; - if (sqlConnection != null) - { - processed = BulkInsertRecordsSqlServer(db, (SqlServerSyntaxProvider)syntaxProvider, pd, collection); - return true; - } - - var sqlCeConnection = dbConnection as SqlCeConnection; - if (sqlCeConnection != null) - { - processed = BulkInsertRecordsSqlCe(db, pd, collection); - return true; - } - - //could not use the SQL server's specific bulk insert operations - processed = 0; - return false; - } - - /// - /// Logic used to perform bulk inserts with SqlCe's TableDirect - /// - /// - /// - /// - /// - /// - internal static int BulkInsertRecordsSqlCe(Database db, - Database.PocoData pd, - IEnumerable collection) - { - var cols = pd.Columns.ToArray(); - - using (var cmd = db.CreateCommand(db.Connection, string.Empty)) - { - cmd.CommandText = pd.TableInfo.TableName; - cmd.CommandType = CommandType.TableDirect; - //cmd.Transaction = GetTypedTransaction(db.Connection.); - - //get the real command - using (var sqlCeCommand = GetTypedCommand(cmd)) - { - // This seems to cause problems, I think this is primarily used for retrieval, not - // inserting. see: https://msdn.microsoft.com/en-us/library/system.data.sqlserverce.sqlcecommand.indexname%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396 - //sqlCeCommand.IndexName = pd.TableInfo.PrimaryKey; - - var count = 0; - using (var rs = sqlCeCommand.ExecuteResultSet(ResultSetOptions.Updatable)) - { - var rec = rs.CreateRecord(); - - foreach (var item in collection) - { - for (var i = 0; i < cols.Length; i++) - { - //skip the index if this shouldn't be included (i.e. PK) - if (IncludeColumn(pd, cols[i])) - { - var val = cols[i].Value.GetValue(item); - rec.SetValue(i, val); - } - } - rs.Insert(rec); - count++; - } - } - return count; - } - - } - } - - /// - /// Logic used to perform bulk inserts with SqlServer's BulkCopy - /// - /// - /// - /// - /// - /// - /// - internal static int BulkInsertRecordsSqlServer(Database db, SqlServerSyntaxProvider sqlSyntaxProvider, - Database.PocoData pd, IEnumerable collection) - { - //NOTE: We need to use the original db.Connection here to create the command, but we need to pass in the typed - // connection below to the SqlBulkCopy - using (var cmd = db.CreateCommand(db.Connection, string.Empty)) - { - using (var copy = new SqlBulkCopy( - GetTypedConnection(db.Connection), - SqlBulkCopyOptions.Default, - GetTypedTransaction(cmd.Transaction)) - { - BulkCopyTimeout = 10000, - DestinationTableName = pd.TableInfo.TableName - }) - { - //var cols = pd.Columns.Where(x => IncludeColumn(pd, x)).Select(x => x.Value).ToArray(); - - using (var bulkReader = new PocoDataDataReader(collection, pd, sqlSyntaxProvider)) - { - copy.WriteToServer(bulkReader); - - return bulkReader.RecordsAffected; - } - } - } - } - - - /// - /// Returns the underlying connection as a typed connection - this is used to unwrap the profiled mini profiler stuff - /// - /// - /// - /// - private static TConnection GetTypedConnection(IDbConnection connection) - where TConnection : class, IDbConnection - { - var profiled = connection as ProfiledDbConnection; - if (profiled != null) - { - return profiled.InnerConnection as TConnection; - } - return connection as TConnection; - } - - /// - /// Returns the underlying connection as a typed connection - this is used to unwrap the profiled mini profiler stuff - /// - /// - /// - /// - private static TTransaction GetTypedTransaction(IDbTransaction connection) - where TTransaction : class, IDbTransaction - { - var profiled = connection as ProfiledDbTransaction; - if (profiled != null) - { - return profiled.WrappedTransaction as TTransaction; - } - return connection as TTransaction; - } - - /// - /// Returns the underlying connection as a typed connection - this is used to unwrap the profiled mini profiler stuff - /// - /// - /// - /// - private static TCommand GetTypedCommand(IDbCommand command) - where TCommand : class, IDbCommand - { - var profiled = command as ProfiledDbCommand; - if (profiled != null) - { - return profiled.InternalCommand as TCommand; - } - return command as TCommand; - } - - [Obsolete("Use the DatabaseSchemaHelper instead")] - public static void CreateTable(this Database db, bool overwrite, Type modelType) - { - var creator = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider); - creator.CreateTable(overwrite, modelType); - } - - [Obsolete("Use the DatabaseSchemaHelper instead")] - public static void DropTable(this Database db) - where T : new() - { - var helper = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider); - helper.DropTable(); - } - - [Obsolete("Use the DatabaseSchemaHelper instead")] - public static void DropTable(this Database db, string tableName) - { - var helper = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider); - helper.DropTable(tableName); - } - - public static void TruncateTable(this Database db, string tableName) - { - var sql = new Sql(string.Format( - SqlSyntaxContext.SqlSyntaxProvider.TruncateTable, - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(tableName))); - db.Execute(sql); - } - - [Obsolete("Use the DatabaseSchemaHelper instead")] - public static bool TableExist(this Database db, string tableName) - { - return SqlSyntaxContext.SqlSyntaxProvider.DoesTableExist(db, tableName); - } - - [Obsolete("Use the DatabaseSchemaHelper instead")] - public static bool TableExist(this UmbracoDatabase db, string tableName) - { - return SqlSyntaxContext.SqlSyntaxProvider.DoesTableExist(db, tableName); - } - - /// - /// Creates the Umbraco db schema in the Database of the current Database. - /// Safe method that is only able to create the schema in non-configured - /// umbraco instances. - /// - /// Current PetaPoco object - [Obsolete("Use the DatabaseSchemaHelper instead")] - public static void CreateDatabaseSchema(this Database db) - { - CreateDatabaseSchema(db, true); - } - - /// - /// Creates the Umbraco db schema in the Database of the current Database - /// with the option to guard the db from having the schema created - /// multiple times. - /// - /// - /// - [Obsolete("Use the DatabaseSchemaHelper instead")] - public static void CreateDatabaseSchema(this Database db, bool guardConfiguration) - { - var helper = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider); - helper.CreateDatabaseSchema(guardConfiguration, ApplicationContext.Current); - } - - //TODO: What the heck? This makes no sense at all - public static DatabaseProviders GetDatabaseProvider(this Database db) - { - return ApplicationContext.Current.DatabaseContext.DatabaseProvider; - } - - - } - - -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs deleted file mode 100644 index 090410782a..0000000000 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ /dev/null @@ -1,243 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Text; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence -{ - /// - /// Extension methods adding strong types to PetaPoco's Sql Builder - /// - public static class PetaPocoSqlExtensions - { - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql From(this Sql sql) - { - return From(sql, SqlSyntaxContext.SqlSyntaxProvider); - } - - public static Sql From(this Sql sql, ISqlSyntaxProvider sqlSyntax) - { - var type = typeof(T); - var tableName = type.GetTableName(); - - return sql.From(sqlSyntax.GetQuotedTableName(tableName)); - } - - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql Where(this Sql sql, Expression> predicate) - { - var expresionist = new PocoToSqlExpressionVisitor(); - var whereExpression = expresionist.Visit(predicate); - return sql.Where(whereExpression, expresionist.GetSqlParameters()); - } - - public static Sql Where(this Sql sql, Expression> predicate, ISqlSyntaxProvider sqlSyntax) - { - var expresionist = new PocoToSqlExpressionVisitor(sqlSyntax); - var whereExpression = expresionist.Visit(predicate); - return sql.Where(whereExpression, expresionist.GetSqlParameters()); - } - - private static string GetFieldName(Expression> fieldSelector, ISqlSyntaxProvider sqlSyntax) - { - var field = ExpressionHelper.FindProperty(fieldSelector) as PropertyInfo; - var fieldName = field.GetColumnName(); - - var type = typeof(T); - var tableName = type.GetTableName(); - - return sqlSyntax.GetQuotedTableName(tableName) + "." + sqlSyntax.GetQuotedColumnName(fieldName); - } - - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql WhereIn(this Sql sql, Expression> fieldSelector, IEnumerable values) - { - return sql.WhereIn(fieldSelector, values, SqlSyntaxContext.SqlSyntaxProvider); - } - - public static Sql WhereIn(this Sql sql, Expression> fieldSelector, IEnumerable values, ISqlSyntaxProvider sqlSyntax) - { - var fieldName = GetFieldName(fieldSelector, sqlSyntax); - return sql.Where(fieldName + " IN (@values)", new { values }); - } - - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql WhereAnyIn(this Sql sql, Expression>[] fieldSelectors, IEnumerable values) - { - return sql.WhereAnyIn(fieldSelectors, values, SqlSyntaxContext.SqlSyntaxProvider); - } - - public static Sql WhereAnyIn(this Sql sql, Expression>[] fieldSelectors, IEnumerable values, ISqlSyntaxProvider sqlSyntax) - { - var fieldNames = fieldSelectors.Select(x => GetFieldName(x, sqlSyntax)).ToArray(); - var sb = new StringBuilder(); - sb.Append("("); - for (var i = 0; i < fieldNames.Length; i++) - { - if (i > 0) sb.Append(" OR "); - sb.Append(fieldNames[i] + " IN (@values)"); - } - sb.Append(")"); - return sql.Where(sb.ToString(), new { values }); - } - - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql OrderBy(this Sql sql, Expression> columnMember) - { - return OrderBy(sql, columnMember, SqlSyntaxContext.SqlSyntaxProvider); - } - - public static Sql OrderBy(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlSyntax) - { - var syntax = "(" + GetFieldName(columnMember, sqlSyntax) + ")"; - return sql.OrderBy(syntax); - } - - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql OrderByDescending(this Sql sql, Expression> columnMember) - { - return OrderByDescending(sql, columnMember, SqlSyntaxContext.SqlSyntaxProvider); - } - - public static Sql OrderByDescending(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlSyntax) - { - var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.GetColumnName(); - - var type = typeof(TColumn); - var tableName = type.GetTableName(); - - //need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 - var syntax = string.Format("({0}.{1}) DESC", - sqlSyntax.GetQuotedTableName(tableName), - sqlSyntax.GetQuotedColumnName(columnName)); - - return sql.OrderBy(syntax); - } - - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql GroupBy(this Sql sql, Expression> columnMember) - { - return GroupBy(sql, columnMember, SqlSyntaxContext.SqlSyntaxProvider); - } - - public static Sql GroupBy(this Sql sql, Expression> columnMember, ISqlSyntaxProvider sqlProvider) - { - var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.GetColumnName(); - - return sql.GroupBy(sqlProvider.GetQuotedColumnName(columnName)); - } - - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql.SqlJoinClause InnerJoin(this Sql sql) - { - return InnerJoin(sql, SqlSyntaxContext.SqlSyntaxProvider); - } - - public static Sql.SqlJoinClause InnerJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) - { - var type = typeof(T); - var tableName = type.GetTableName(); - - return sql.InnerJoin(sqlSyntax.GetQuotedTableName(tableName)); - } - - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql.SqlJoinClause LeftJoin(this Sql sql) - { - return LeftJoin(sql, SqlSyntaxContext.SqlSyntaxProvider); - } - - public static Sql.SqlJoinClause LeftJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) - { - var type = typeof(T); - var tableName = type.GetTableName(); - - return sql.LeftJoin(sqlSyntax.GetQuotedTableName(tableName)); - } - - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql.SqlJoinClause LeftOuterJoin(this Sql sql) - { - return LeftOuterJoin(sql, SqlSyntaxContext.SqlSyntaxProvider); - } - - public static Sql.SqlJoinClause LeftOuterJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) - { - var type = typeof(T); - var tableName = type.GetTableName(); - - return sql.LeftOuterJoin(sqlSyntax.GetQuotedTableName(tableName)); - } - - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql.SqlJoinClause RightJoin(this Sql sql) - { - return RightJoin(sql, SqlSyntaxContext.SqlSyntaxProvider); - } - - public static Sql.SqlJoinClause RightJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) - { - var type = typeof(T); - var tableName = type.GetTableName(); - - return sql.RightJoin(sqlSyntax.GetQuotedTableName(tableName)); - } - - [Obsolete("Use the overload specifying ISqlSyntaxProvider instead")] - public static Sql On(this Sql.SqlJoinClause sql, Expression> leftMember, - Expression> rightMember, params object[] args) - { - return On(sql, SqlSyntaxContext.SqlSyntaxProvider, leftMember, rightMember, args); - } - - public static Sql On(this Sql.SqlJoinClause sql, ISqlSyntaxProvider sqlSyntax, Expression> leftMember, - Expression> rightMember, params object[] args) - { - var leftType = typeof(TLeft); - var rightType = typeof(TRight); - var leftTableName = leftType.GetTableName(); - var rightTableName = rightType.GetTableName(); - - var leftColumn = ExpressionHelper.FindProperty(leftMember) as PropertyInfo; - var rightColumn = ExpressionHelper.FindProperty(rightMember) as PropertyInfo; - - var leftColumnName = leftColumn.GetColumnName(); - var rightColumnName = rightColumn.GetColumnName(); - - string onClause = string.Format("{0}.{1} = {2}.{3}", - sqlSyntax.GetQuotedTableName(leftTableName), - sqlSyntax.GetQuotedColumnName(leftColumnName), - sqlSyntax.GetQuotedTableName(rightTableName), - sqlSyntax.GetQuotedColumnName(rightColumnName)); - return sql.On(onClause); - } - - public static Sql OrderByDescending(this Sql sql, params object[] columns) - { - return sql.Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x + " DESC").ToArray()))); - } - - private static string GetTableName(this Type type) - { - // todo: returning string.Empty for now - // BUT the code bits that calls this method cannot deal with string.Empty so we - // should either throw, or fix these code bits... - var attr = type.FirstAttribute(); - return attr == null || string.IsNullOrWhiteSpace(attr.Value) ? string.Empty : attr.Value; - } - - private static string GetColumnName(this PropertyInfo column) - { - var attr = column.FirstAttribute(); - return attr == null || string.IsNullOrWhiteSpace(attr.Name) ? column.Name : attr.Name; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/PocoDataDataReader.cs b/src/Umbraco.Core/Persistence/PocoDataDataReader.cs index 4dac6f5004..7bfc31f66c 100644 --- a/src/Umbraco.Core/Persistence/PocoDataDataReader.cs +++ b/src/Umbraco.Core/Persistence/PocoDataDataReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data; using System.Linq; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; @@ -14,56 +15,48 @@ namespace Umbraco.Core.Persistence /// /// We are using a custom data reader so that tons of memory is not consumed when rebuilding this table, previously /// we'd generate SQL insert statements, but we'd have to put all of the XML structures into memory first. Alternatively - /// we can use .net's DataTable, but this also requires putting everything into memory. By using a DataReader we don't have to - /// store every content item and it's XML structure in memory to get it into the DB, we can stream it into the db with this + /// we can use .net's DataTable, but this also requires putting everything into memory. By using a DataReader we don't have to + /// store every content item and it's XML structure in memory to get it into the DB, we can stream it into the db with this /// reader. /// - internal class PocoDataDataReader : BulkDataReader + internal class PocoDataDataReader : BulkDataReader where TSyntax : ISqlSyntaxProvider - { + { private readonly MicrosoftSqlSyntaxProviderBase _sqlSyntaxProvider; private readonly TableDefinition _tableDefinition; - private readonly Database.PocoColumn[] _readerColumns; + private readonly PocoColumn[] _readerColumns; private readonly IEnumerator _enumerator; private readonly ColumnDefinition[] _columnDefinitions; private int _recordsAffected = -1; public PocoDataDataReader( - IEnumerable dataSource, - Database.PocoData pd, + IEnumerable dataSource, + PocoData pd, MicrosoftSqlSyntaxProviderBase sqlSyntaxProvider) { - if (dataSource == null) throw new ArgumentNullException("dataSource"); - if (sqlSyntaxProvider == null) throw new ArgumentNullException("sqlSyntaxProvider"); + if (dataSource == null) throw new ArgumentNullException(nameof(dataSource)); + if (sqlSyntaxProvider == null) throw new ArgumentNullException(nameof(sqlSyntaxProvider)); - _tableDefinition = DefinitionFactory.GetTableDefinition(sqlSyntaxProvider, pd.type); - if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.type); + _tableDefinition = DefinitionFactory.GetTableDefinition(pd.Type, sqlSyntaxProvider); + if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.Type); - //Only return real columns, do not include columns that are result columns + // only real columns, exclude result columns _readerColumns = pd.Columns .Where(x => x.Value.ResultColumn == false) .Select(x => x.Value) .ToArray(); + _sqlSyntaxProvider = sqlSyntaxProvider; _enumerator = dataSource.GetEnumerator(); _columnDefinitions = _tableDefinition.Columns.ToArray(); - + } - protected override string SchemaName - { - get { return _tableDefinition.SchemaName; } - } + protected override string SchemaName => _tableDefinition.SchemaName; - protected override string TableName - { - get { return _tableDefinition.Name; } - } + protected override string TableName => _tableDefinition.Name; - public override int RecordsAffected - { - get { return _recordsAffected <= 0 ? -1 : _recordsAffected; } - } + public override int RecordsAffected => _recordsAffected <= 0 ? -1 : _recordsAffected; /// /// This will automatically add the schema rows based on the Poco table definition and the columns passed in @@ -74,7 +67,7 @@ namespace Umbraco.Core.Persistence //foreach (var col in _columnDefinitions.Where(x => colNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase))) foreach (var col in _columnDefinitions) { - var sqlDbType = SqlDbType.NVarChar; + SqlDbType sqlDbType; if (col.HasSpecialDbType) { //get the SqlDbType from the 'special type' @@ -100,15 +93,14 @@ namespace Umbraco.Core.Persistence //get the SqlDbType from the clr type sqlDbType = _sqlSyntaxProvider.GetSqlDbType(col.PropertyType); } - - AddSchemaTableRow( - col.Name, - col.Size > 0 ? (int?)col.Size : null, - col.Precision > 0 ? (short?)col.Precision : null, - null, col.IsUnique, col.IsIdentity, col.IsNullable, sqlDbType, - null, null, null, null, null); - } + AddSchemaTableRow( + col.Name, + col.Size > 0 ? (int?)col.Size : null, + col.Precision > 0 ? (short?)col.Precision : null, + null, col.IsUnique, col.IsIdentity, col.IsNullable, sqlDbType, + null, null, null, null, null); + } } /// @@ -118,14 +110,7 @@ namespace Umbraco.Core.Persistence /// public override object GetValue(int i) { - if (_enumerator.Current != null) - { - return _readerColumns[i].GetValue(_enumerator.Current); - //return _columnDefinitions[i]. .GetValue(_enumerator.Current); - } - - return null; - //TODO: Or throw ? + return _enumerator.Current == null ? null : _readerColumns[i].GetValue(_enumerator.Current); } /// @@ -138,9 +123,7 @@ namespace Umbraco.Core.Persistence if (result) { if (_recordsAffected == -1) - { _recordsAffected = 0; - } _recordsAffected++; } return result; @@ -155,9 +138,7 @@ namespace Umbraco.Core.Persistence base.Dispose(disposing); if (disposing) - { _enumerator.Dispose(); - } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Querying/CachedExpression.cs b/src/Umbraco.Core/Persistence/Querying/CachedExpression.cs index 5c502ec170..76547265b9 100644 --- a/src/Umbraco.Core/Persistence/Querying/CachedExpression.cs +++ b/src/Umbraco.Core/Persistence/Querying/CachedExpression.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq.Expressions; namespace Umbraco.Core.Persistence.Querying @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Querying /// public string VisitResult { - get { return _visitResult; } + get => _visitResult; set { if (Visited) @@ -45,4 +45,4 @@ namespace Umbraco.Core.Persistence.Querying InnerExpression = expression; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Querying/ExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ExpressionHelper.cs deleted file mode 100644 index 4a97dc571c..0000000000 --- a/src/Umbraco.Core/Persistence/Querying/ExpressionHelper.cs +++ /dev/null @@ -1,572 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using Umbraco.Core.Persistence.Mappers; - -namespace Umbraco.Core.Persistence.Querying -{ - internal class ExpressionHelper - { - private string selectExpression = string.Empty; - private string whereExpression; - private string groupBy = string.Empty; - private string havingExpression; - private string orderBy = string.Empty; - - IList updateFields = new List(); - IList insertFields = new List(); - - private string sep = string.Empty; - private bool useFieldName = false; - private Database.PocoData pd; - - public ExpressionHelper() - { - Database.Mapper = new ModelDtoMapper(); - pd = new Database.PocoData(typeof(T)); - } - - protected internal virtual string Visit(Expression exp) - { - - if (exp == null) return string.Empty; - switch (exp.NodeType) - { - case ExpressionType.Lambda: - return VisitLambda(exp as LambdaExpression); - case ExpressionType.MemberAccess: - return VisitMemberAccess(exp as MemberExpression); - case ExpressionType.Constant: - return VisitConstant(exp as ConstantExpression); - case ExpressionType.Add: - case ExpressionType.AddChecked: - case ExpressionType.Subtract: - case ExpressionType.SubtractChecked: - case ExpressionType.Multiply: - case ExpressionType.MultiplyChecked: - case ExpressionType.Divide: - case ExpressionType.Modulo: - case ExpressionType.And: - case ExpressionType.AndAlso: - case ExpressionType.Or: - case ExpressionType.OrElse: - case ExpressionType.LessThan: - case ExpressionType.LessThanOrEqual: - case ExpressionType.GreaterThan: - case ExpressionType.GreaterThanOrEqual: - case ExpressionType.Equal: - case ExpressionType.NotEqual: - case ExpressionType.Coalesce: - case ExpressionType.ArrayIndex: - case ExpressionType.RightShift: - case ExpressionType.LeftShift: - case ExpressionType.ExclusiveOr: - return "(" + VisitBinary(exp as BinaryExpression) + ")"; - case ExpressionType.Negate: - case ExpressionType.NegateChecked: - case ExpressionType.Not: - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - case ExpressionType.ArrayLength: - case ExpressionType.Quote: - case ExpressionType.TypeAs: - return VisitUnary(exp as UnaryExpression); - case ExpressionType.Parameter: - return VisitParameter(exp as ParameterExpression); - case ExpressionType.Call: - return VisitMethodCall(exp as MethodCallExpression); - case ExpressionType.New: - return VisitNew(exp as NewExpression); - case ExpressionType.NewArrayInit: - case ExpressionType.NewArrayBounds: - return VisitNewArray(exp as NewArrayExpression); - default: - return exp.ToString(); - } - } - - protected virtual string VisitLambda(LambdaExpression lambda) - { - if (lambda.Body.NodeType == ExpressionType.MemberAccess && sep == " ") - { - MemberExpression m = lambda.Body as MemberExpression; - - if (m.Expression != null) - { - string r = VisitMemberAccess(m); - return string.Format("{0}={1}", r, GetQuotedTrueValue()); - } - - } - return Visit(lambda.Body); - } - - protected virtual string VisitBinary(BinaryExpression b) - { - string left, right; - var operand = BindOperant(b.NodeType); //sep= " " ?? - if (operand == "AND" || operand == "OR") - { - MemberExpression m = b.Left as MemberExpression; - if (m != null && m.Expression != null) - { - string r = VisitMemberAccess(m); - left = string.Format("{0}={1}", r, GetQuotedTrueValue()); - } - else - { - left = Visit(b.Left); - } - m = b.Right as MemberExpression; - if (m != null && m.Expression != null) - { - string r = VisitMemberAccess(m); - right = string.Format("{0}={1}", r, GetQuotedTrueValue()); - } - else - { - right = Visit(b.Right); - } - } - else - { - left = Visit(b.Left); - right = Visit(b.Right); - } - - if (operand == "=" && right == "null") operand = "is"; - else if (operand == "<>" && right == "null") operand = "is not"; - else if (operand == "=" || operand == "<>") - { - if (IsTrueExpression(right)) right = GetQuotedTrueValue(); - else if (IsFalseExpression(right)) right = GetQuotedFalseValue(); - - if (IsTrueExpression(left)) left = GetQuotedTrueValue(); - else if (IsFalseExpression(left)) left = GetQuotedFalseValue(); - - } - - switch (operand) - { - case "MOD": - case "COALESCE": - return string.Format("{0}({1},{2})", operand, left, right); - default: - return left + sep + operand + sep + right; - } - } - - protected virtual string VisitMemberAccess(MemberExpression m) - { - if (m.Expression != null && - m.Expression.NodeType == ExpressionType.Parameter - && m.Expression.Type == typeof(T)) - { - string field = GetFieldName(pd, m.Member.Name); - return field; - } - - if (m.Expression != null && m.Expression.NodeType != ExpressionType.Constant) - { - Database.Mapper = new ModelDtoMapper(); - var def = new Database.PocoData(m.Expression.Type); - string field = GetFieldName(def, m.Member.Name); - return field; - } - - - var member = Expression.Convert(m, typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - object o = getter(); - return GetQuotedValue(o, o != null ? o.GetType() : null); - - } - - protected virtual string VisitNew(NewExpression nex) - { - // TODO : check ! - var member = Expression.Convert(nex, typeof(object)); - var lambda = Expression.Lambda>(member); - try - { - var getter = lambda.Compile(); - object o = getter(); - return GetQuotedValue(o, o.GetType()); - } - catch (System.InvalidOperationException) - { // FieldName ? - List exprs = VisitExpressionList(nex.Arguments); - var r = new StringBuilder(); - foreach (Object e in exprs) - { - r.AppendFormat("{0}{1}", - r.Length > 0 ? "," : "", - e); - } - return r.ToString(); - } - - } - - protected virtual string VisitParameter(ParameterExpression p) - { - return p.Name; - } - - protected virtual string VisitConstant(ConstantExpression c) - { - if (c.Value == null) - return "null"; - else if (c.Value.GetType() == typeof(bool)) - { - object o = GetQuotedValue(c.Value, c.Value.GetType()); - return string.Format("({0}={1})", GetQuotedTrueValue(), o); - } - else - return GetQuotedValue(c.Value, c.Value.GetType()); - } - - protected virtual string VisitUnary(UnaryExpression u) - { - switch (u.NodeType) - { - case ExpressionType.Not: - string o = Visit(u.Operand); - if (IsFieldName(o)) o = o + "=" + GetQuotedValue(true, typeof(bool)); - return "NOT (" + o + ")"; - default: - return Visit(u.Operand); - } - - } - - protected virtual string VisitMethodCall(MethodCallExpression m) - { - List args = this.VisitExpressionList(m.Arguments); - - Object r; - if (m.Object != null) - r = Visit(m.Object); - else - { - r = args[0]; - args.RemoveAt(0); - } - - switch (m.Method.Name) - { - case "ToUpper": - return string.Format("upper({0})", r); - case "ToLower": - return string.Format("lower({0})", r); - case "StartsWith": - return string.Format("upper({0}) starting with {1} ", r, args[0].ToString().ToUpper()); - case "EndsWith": - return string.Format("upper({0}) like '%{1}'", r, RemoveQuote(args[0].ToString()).ToUpper()); - case "Contains": - return string.Format("upper({0}) like '%{1}%'", r, RemoveQuote(args[0].ToString()).ToUpper()); - case "Substring": - var startIndex = Int32.Parse(args[0].ToString()) + 1; - if (args.Count == 2) - { - var length = Int32.Parse(args[1].ToString()); - return string.Format("substring({0} from {1} for {2})", - r, - startIndex, - length); - } - else - return string.Format("substring({0} from {1})", - r, - startIndex); - case "Round": - case "Floor": - case "Ceiling": - case "Coalesce": - case "Abs": - case "Sum": - return string.Format("{0}({1}{2})", - m.Method.Name, - r, - args.Count == 1 ? string.Format(",{0}", args[0]) : ""); - case "Concat": - var s = new StringBuilder(); - foreach (Object e in args) - { - s.AppendFormat(" || {0}", e); - } - return string.Format("{0}{1}", r, s.ToString()); - - case "In": - - var member = Expression.Convert(m.Arguments[1], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - - var inArgs = getter() as object[]; - - var sIn = new StringBuilder(); - foreach (Object e in inArgs) - { - if (e.GetType().ToString() != "System.Collections.Generic.List`1[System.Object]") - { - sIn.AppendFormat("{0}{1}", - sIn.Length > 0 ? "," : "", - GetQuotedValue(e, e.GetType())); - } - else - { - var listArgs = e as IList; - foreach (Object el in listArgs) - { - sIn.AppendFormat("{0}{1}", - sIn.Length > 0 ? "," : "", - GetQuotedValue(el, el.GetType())); - } - } - } - - return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString()); - case "Desc": - return string.Format("{0} DESC", r); - case "Alias": - case "As": - return string.Format("{0} As {1}", r, - GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); - case "ToString": - return r.ToString(); - default: - var s2 = new StringBuilder(); - foreach (Object e in args) - { - s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); - } - return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); - } - } - - protected virtual List VisitExpressionList(ReadOnlyCollection original) - { - var list = new List(); - for (int i = 0, n = original.Count; i < n; i++) - { - if (original[i].NodeType == ExpressionType.NewArrayInit || - original[i].NodeType == ExpressionType.NewArrayBounds) - { - - list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression)); - } - else - list.Add(Visit(original[i])); - - } - return list; - } - - protected virtual string VisitNewArray(NewArrayExpression na) - { - - List exprs = VisitExpressionList(na.Expressions); - var r = new StringBuilder(); - foreach (Object e in exprs) - { - r.Append(r.Length > 0 ? "," + e : e); - } - - return r.ToString(); - } - - protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) - { - - List exprs = VisitExpressionList(na.Expressions); - return exprs; - } - - - protected virtual string BindOperant(ExpressionType e) - { - - switch (e) - { - case ExpressionType.Equal: - return "="; - case ExpressionType.NotEqual: - return "<>"; - case ExpressionType.GreaterThan: - return ">"; - case ExpressionType.GreaterThanOrEqual: - return ">="; - case ExpressionType.LessThan: - return "<"; - case ExpressionType.LessThanOrEqual: - return "<="; - case ExpressionType.AndAlso: - return "AND"; - case ExpressionType.OrElse: - return "OR"; - case ExpressionType.Add: - return "+"; - case ExpressionType.Subtract: - return "-"; - case ExpressionType.Multiply: - return "*"; - case ExpressionType.Divide: - return "/"; - case ExpressionType.Modulo: - return "MOD"; - case ExpressionType.Coalesce: - return "COALESCE"; - default: - return e.ToString(); - } - } - - public virtual string GetQuotedTableName(string tableName) - { - return string.Format("\"{0}\"", tableName); - } - - public virtual string GetQuotedColumnName(string columnName) - { - return string.Format("\"{0}\"", columnName); - } - - public virtual string GetQuotedName(string name) - { - return string.Format("\"{0}\"", name); - } - - private string GetQuotedTrueValue() - { - return GetQuotedValue(true, typeof(bool)); - } - - private string GetQuotedFalseValue() - { - return GetQuotedValue(false, typeof(bool)); - } - - public virtual string GetQuotedValue(object value, Type fieldType) - { - if (value == null) return "NULL"; - - if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string)) - { - //if (TypeSerializer.CanCreateFromString(fieldType)) - //{ - // return "'" + EscapeParam(TypeSerializer.SerializeToString(value)) + "'"; - //} - - throw new NotSupportedException( - string.Format("Property of type: {0} is not supported", fieldType.FullName)); - } - - if (fieldType == typeof(int)) - return ((int)value).ToString(CultureInfo.InvariantCulture); - - if (fieldType == typeof(float)) - return ((float)value).ToString(CultureInfo.InvariantCulture); - - if (fieldType == typeof(double)) - return ((double)value).ToString(CultureInfo.InvariantCulture); - - if (fieldType == typeof(decimal)) - return ((decimal)value).ToString(CultureInfo.InvariantCulture); - - return ShouldQuoteValue(fieldType) - ? "'" + EscapeParam(value) + "'" - : value.ToString(); - } - - public virtual string EscapeParam(object paramValue) - { - return paramValue.ToString().Replace("'", "''"); - } - - public virtual bool ShouldQuoteValue(Type fieldType) - { - return true; - } - - protected virtual string GetFieldName(Database.PocoData pocoData, string name) - { - var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name); - return column.Value.ColumnName; - } - - protected virtual string GetFieldName(string name) - { - - if (useFieldName) - { - //FieldDefinition fd = modelDef.FieldDefinitions.FirstOrDefault(x => x.Name == name); - //string fn = fd != default(FieldDefinition) ? fd.FieldName : name; - //return OrmLiteConfig.DialectProvider.GetQuotedColumnName(fn); - return "[" + name + "]"; - } - else - { - return name; - } - } - - protected string RemoveQuote(string exp) - { - - if (exp.StartsWith("'") && exp.EndsWith("'")) - { - exp = exp.Remove(0, 1); - exp = exp.Remove(exp.Length - 1, 1); - } - return exp; - } - - protected string RemoveQuoteFromAlias(string exp) - { - - if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) - && - (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) - { - exp = exp.Remove(0, 1); - exp = exp.Remove(exp.Length - 1, 1); - } - return exp; - } - - private string GetTrueExpression() - { - object o = GetQuotedTrueValue(); - return string.Format("({0}={1})", o, o); - } - - private string GetFalseExpression() - { - - return string.Format("({0}={1})", - GetQuotedTrueValue(), - GetQuotedFalseValue()); - } - - private bool IsTrueExpression(string exp) - { - return (exp == GetTrueExpression()); - } - - private bool IsFalseExpression(string exp) - { - return (exp == GetFalseExpression()); - } - - protected bool IsFieldName(string quotedExp) - { - return true; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs index fe73638b4b..a43e99177e 100644 --- a/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs +++ b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs @@ -1,882 +1,875 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence.Querying -{ - /// - /// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression. - /// - /// This object is stateful and cannot be re-used to parse an expression. - internal abstract class ExpressionVisitorBase - { - protected ExpressionVisitorBase(ISqlSyntaxProvider sqlSyntax) - { - SqlSyntax = sqlSyntax; - } - - /// - /// Gets or sets a value indicating whether the visited expression has been visited already, - /// in which case visiting will just populate the SQL parameters. - /// - protected bool Visited { get; set; } - - /// - /// Gets or sets the SQL syntax provider for the current database. - /// - protected ISqlSyntaxProvider SqlSyntax { get; private set; } - - /// - /// Gets the list of SQL parameters. - /// - protected readonly List SqlParameters = new List(); - - /// - /// Gets the SQL parameters. - /// - /// - public object[] GetSqlParameters() - { - return SqlParameters.ToArray(); - } - - /// - /// Visits the expression and produces the corresponding SQL statement. - /// - /// The expression - /// The SQL statement corresponding to the expression. - /// Also populates the SQL parameters. - public virtual string Visit(Expression expression) - { - // if the expression is a CachedExpression, - // visit the inner expression if not already visited - var cachedExpression = expression as CachedExpression; - if (cachedExpression != null) - { - Visited = cachedExpression.Visited; - expression = cachedExpression.InnerExpression; - } - - if (expression == null) return string.Empty; - - string result; - - switch (expression.NodeType) - { - case ExpressionType.Lambda: - result = VisitLambda(expression as LambdaExpression); - break; - case ExpressionType.MemberAccess: - result = VisitMemberAccess(expression as MemberExpression); - break; - case ExpressionType.Constant: - result = VisitConstant(expression as ConstantExpression); - break; - case ExpressionType.Add: - case ExpressionType.AddChecked: - case ExpressionType.Subtract: - case ExpressionType.SubtractChecked: - case ExpressionType.Multiply: - case ExpressionType.MultiplyChecked: - case ExpressionType.Divide: - case ExpressionType.Modulo: - case ExpressionType.And: - case ExpressionType.AndAlso: - case ExpressionType.Or: - case ExpressionType.OrElse: - case ExpressionType.LessThan: - case ExpressionType.LessThanOrEqual: - case ExpressionType.GreaterThan: - case ExpressionType.GreaterThanOrEqual: - case ExpressionType.Equal: - case ExpressionType.NotEqual: - case ExpressionType.Coalesce: - case ExpressionType.ArrayIndex: - case ExpressionType.RightShift: - case ExpressionType.LeftShift: - case ExpressionType.ExclusiveOr: - result = VisitBinary(expression as BinaryExpression); - break; - case ExpressionType.Negate: - case ExpressionType.NegateChecked: - case ExpressionType.Not: - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - case ExpressionType.ArrayLength: - case ExpressionType.Quote: - case ExpressionType.TypeAs: - result = VisitUnary(expression as UnaryExpression); - break; - case ExpressionType.Parameter: - result = VisitParameter(expression as ParameterExpression); - break; - case ExpressionType.Call: - result = VisitMethodCall(expression as MethodCallExpression); - break; - case ExpressionType.New: - result = VisitNew(expression as NewExpression); - break; - case ExpressionType.NewArrayInit: - case ExpressionType.NewArrayBounds: - result = VisitNewArray(expression as NewArrayExpression); - break; - default: - result = expression.ToString(); - break; - } - - // if the expression is a CachedExpression, - // and is not already compiled, assign the result - if (cachedExpression != null) - { - if (cachedExpression.Visited == false) - cachedExpression.VisitResult = result; - result = cachedExpression.VisitResult; - } - - return result; - } - - protected abstract string VisitMemberAccess(MemberExpression m); - - protected virtual string VisitLambda(LambdaExpression lambda) - { - if (lambda.Body.NodeType == ExpressionType.MemberAccess) - { - var m = lambda.Body as MemberExpression; - - if (m != null && m.Expression != null) - { - //This deals with members that are boolean (i.e. x => IsTrashed ) - var r = VisitMemberAccess(m); - - SqlParameters.Add(true); - - return Visited ? string.Empty : string.Format("{0} = @{1}", r, SqlParameters.Count - 1); - } - - } - return Visit(lambda.Body); - } - - protected virtual string VisitBinary(BinaryExpression b) - { - var left = string.Empty; - var right = string.Empty; - - var operand = BindOperant(b.NodeType); - if (operand == "AND" || operand == "OR") - { - var m = b.Left as MemberExpression; - if (m != null && m.Expression != null) - { - string r = VisitMemberAccess(m); - - SqlParameters.Add(1); - - //don't execute if compiled - if (Visited == false) - { - left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); - } - } - else - { - left = Visit(b.Left); - } - m = b.Right as MemberExpression; - if (m != null && m.Expression != null) - { - var r = VisitMemberAccess(m); - - SqlParameters.Add(1); - - //don't execute if compiled - if (Visited == false) - { - right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); - } - } - else - { - right = Visit(b.Right); - } - } - else if (operand == "=") - { - // deal with (x == true|false) - most common - var constRight = b.Right as ConstantExpression; - if (constRight != null && constRight.Type == typeof(bool)) - return (bool)constRight.Value ? VisitNotNot(b.Left) : VisitNot(b.Left); - right = Visit(b.Right); - - // deal with (true|false == x) - why not - var constLeft = b.Left as ConstantExpression; - if (constLeft != null && constLeft.Type == typeof(bool)) - return (bool)constLeft.Value ? VisitNotNot(b.Right) : VisitNot(b.Right); - left = Visit(b.Left); - } - else if (operand == "<>") - { - // deal with (x != true|false) - most common - var constRight = b.Right as ConstantExpression; - if (constRight != null && constRight.Type == typeof(bool)) - return (bool)constRight.Value ? VisitNot(b.Left) : VisitNotNot(b.Left); - right = Visit(b.Right); - - // deal with (true|false != x) - why not - var constLeft = b.Left as ConstantExpression; - if (constLeft != null && constLeft.Type == typeof(bool)) - return (bool)constLeft.Value ? VisitNot(b.Right) : VisitNotNot(b.Right); - left = Visit(b.Left); - } - else - { - left = Visit(b.Left); - right = Visit(b.Right); - } - - if (operand == "=" && right == "null") operand = "is"; - else if (operand == "<>" && right == "null") operand = "is not"; - else if (operand == "=" || operand == "<>") - { - //if (IsTrueExpression(right)) right = GetQuotedTrueValue(); - //else if (IsFalseExpression(right)) right = GetQuotedFalseValue(); - - //if (IsTrueExpression(left)) left = GetQuotedTrueValue(); - //else if (IsFalseExpression(left)) left = GetQuotedFalseValue(); - - } - - switch (operand) - { - case "MOD": - case "COALESCE": - //don't execute if compiled - if (Visited == false) - { - return string.Format("{0}({1},{2})", operand, left, right); - } - //already compiled, return - return string.Empty; - default: - //don't execute if compiled - if (Visited == false) - { - return string.Concat("(", left, " ", operand, " ", right, ")"); - } - //already compiled, return - return string.Empty; - } - } - - protected virtual List VisitExpressionList(ReadOnlyCollection original) - { - var list = new List(); - for (int i = 0, n = original.Count; i < n; i++) - { - if (original[i].NodeType == ExpressionType.NewArrayInit || - original[i].NodeType == ExpressionType.NewArrayBounds) - { - list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression)); - } - else - { - list.Add(Visit(original[i])); - } - } - return list; - } - - protected virtual string VisitNew(NewExpression nex) - { - // TODO : check ! - var member = Expression.Convert(nex, typeof(object)); - var lambda = Expression.Lambda>(member); - try - { - var getter = lambda.Compile(); - var o = getter(); - - SqlParameters.Add(o); - - //don't execute if compiled - if (Visited == false) - { - return string.Format("@{0}", SqlParameters.Count - 1); - } - //already compiled, return - return string.Empty; - } - catch (InvalidOperationException) - { - //don't execute if compiled - if (Visited == false) - { - // FieldName ? - List exprs = VisitExpressionList(nex.Arguments); - var r = new StringBuilder(); - foreach (var e in exprs) - { - r.AppendFormat("{0}{1}", - r.Length > 0 ? "," : "", - e); - } - return r.ToString(); - } - //already compiled, return - return string.Empty; - } - } - - protected virtual string VisitParameter(ParameterExpression p) - { - return p.Name; - } - - protected virtual string VisitConstant(ConstantExpression c) - { - if (c.Value == null) - return "null"; - - SqlParameters.Add(c.Value); - - //don't execute if compiled - if (Visited == false) - { - return string.Format("@{0}", SqlParameters.Count - 1); - } - //already compiled, return - return string.Empty; - } - - protected virtual string VisitUnary(UnaryExpression u) - { - switch (u.NodeType) - { - case ExpressionType.Not: - return VisitNot(u.Operand); - default: - return Visit(u.Operand); - } - } - - private string VisitNot(Expression exp) - { - var o = Visit(exp); - - // use a "NOT (...)" syntax instead of "<>" since we don't know whether "<>" works in all sql servers - // also, x.StartsWith(...) translates to "x LIKE '...%'" which we cannot "<>" and have to "NOT (...") - - switch (exp.NodeType) - { - case ExpressionType.MemberAccess: - // false property , i.e. x => !Trashed - SqlParameters.Add(true); - //don't execute if compiled - if (Visited == false) - { - return string.Format("NOT ({0} = @{1})", o, SqlParameters.Count - 1); - } - //already compiled, return - return string.Empty; - default: - //don't execute if compiled - if (Visited == false) - { - // could be anything else, such as: x => !x.Path.StartsWith("-20") - return string.Concat("NOT (", o, ")"); - } - //already compiled, return - return string.Empty; - } - } - - private string VisitNotNot(Expression exp) - { - var o = Visit(exp); - - switch (exp.NodeType) - { - case ExpressionType.MemberAccess: - // true property, i.e. x => Trashed - SqlParameters.Add(true); - - //don't execute if compiled - if (Visited == false) - { - return string.Format("({0} = @{1})", o, SqlParameters.Count - 1); - } - //already compiled, return - return string.Empty; - default: - // could be anything else, such as: x => x.Path.StartsWith("-20") - return o; - } - } - - protected virtual string VisitNewArray(NewArrayExpression na) - { - var exprs = VisitExpressionList(na.Expressions); - - //don't execute if compiled - if (Visited == false) - { - var r = new StringBuilder(); - foreach (var e in exprs) - { - r.Append(r.Length > 0 ? "," + e : e); - } - - return r.ToString(); - } - //already compiled, return - return string.Empty; - } - - protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) - { - var exprs = VisitExpressionList(na.Expressions); - return exprs; - } - - protected virtual string BindOperant(ExpressionType e) - { - switch (e) - { - case ExpressionType.Equal: - return "="; - case ExpressionType.NotEqual: - return "<>"; - case ExpressionType.GreaterThan: - return ">"; - case ExpressionType.GreaterThanOrEqual: - return ">="; - case ExpressionType.LessThan: - return "<"; - case ExpressionType.LessThanOrEqual: - return "<="; - case ExpressionType.AndAlso: - return "AND"; - case ExpressionType.OrElse: - return "OR"; - case ExpressionType.Add: - return "+"; - case ExpressionType.Subtract: - return "-"; - case ExpressionType.Multiply: - return "*"; - case ExpressionType.Divide: - return "/"; - case ExpressionType.Modulo: - return "MOD"; - case ExpressionType.Coalesce: - return "COALESCE"; - default: - return e.ToString(); - } - } - - protected virtual string VisitMethodCall(MethodCallExpression m) - { - //Here's what happens with a MethodCallExpression: - // If a method is called that contains a single argument, - // then m.Object is the object on the left hand side of the method call, example: - // x.Path.StartsWith(content.Path) - // m.Object = x.Path - // and m.Arguments.Length == 1, therefor m.Arguments[0] == content.Path - // If a method is called that contains multiple arguments, then m.Object == null and the - // m.Arguments collection contains the left hand side of the method call, example: - // x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar) - // m.Object == null - // m.Arguments.Length == 3, therefor, m.Arguments[0] == x.Path, m.Arguments[1] == content.Path, m.Arguments[2] == TextColumnType.NVarchar - // So, we need to cater for these scenarios. - - var objectForMethod = m.Object ?? m.Arguments[0]; - var visitedObjectForMethod = Visit(objectForMethod); - var methodArgs = m.Object == null - ? m.Arguments.Skip(1).ToArray() - : m.Arguments.ToArray(); - - switch (m.Method.Name) - { - case "ToString": - SqlParameters.Add(objectForMethod.ToString()); - //don't execute if compiled - if (Visited == false) - return string.Format("@{0}", SqlParameters.Count - 1); - //already compiled, return - return string.Empty; - case "ToUpper": - //don't execute if compiled - if (Visited == false) - return string.Format("upper({0})", visitedObjectForMethod); - //already compiled, return - return string.Empty; - case "ToLower": - //don't execute if compiled - if (Visited == false) - return string.Format("lower({0})", visitedObjectForMethod); - //already compiled, return - return string.Empty; - case "SqlWildcard": - case "StartsWith": - case "EndsWith": - case "Contains": - case "Equals": - case "SqlStartsWith": - case "SqlEndsWith": - case "SqlContains": - case "SqlEquals": - case "InvariantStartsWith": - case "InvariantEndsWith": - case "InvariantContains": - case "InvariantEquals": - - //special case, if it is 'Contains' and the argumet that Contains is being called on is - //Enumerable and the methodArgs is the actual member access, then it's an SQL IN claus - if (m.Object == null - && m.Arguments[0].Type != typeof(string) - && m.Arguments.Count == 2 - && methodArgs.Length == 1 - && methodArgs[0].NodeType == ExpressionType.MemberAccess - && TypeHelper.IsTypeAssignableFrom(m.Arguments[0].Type)) - { - goto case "SqlIn"; - } - - string compareValue; - - if (methodArgs[0].NodeType != ExpressionType.Constant) - { - //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) - // So we'll go get the value: - var member = Expression.Convert(methodArgs[0], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - compareValue = getter().ToString(); - } - else - { - compareValue = methodArgs[0].ToString(); - } - - //default column type - var colType = TextColumnType.NVarchar; - - //then check if the col type argument has been passed to the current method (this will be the case for methods like - // SqlContains and other Sql methods) - if (methodArgs.Length > 1) - { - var colTypeArg = methodArgs.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); - if (colTypeArg != null) - { - colType = (TextColumnType)((ConstantExpression)colTypeArg).Value; - } - } - - return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType); - - case "Replace": - string searchValue; - - if (methodArgs[0].NodeType != ExpressionType.Constant) - { - //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) - // So we'll go get the value: - var member = Expression.Convert(methodArgs[0], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - searchValue = getter().ToString(); - } - else - { - searchValue = methodArgs[0].ToString(); - } - - if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) - { - throw new NotSupportedException("An array Contains method is not supported"); - } - - string replaceValue; - - if (methodArgs[1].NodeType != ExpressionType.Constant) - { - //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) - // So we'll go get the value: - var member = Expression.Convert(methodArgs[1], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - replaceValue = getter().ToString(); - } - else - { - replaceValue = methodArgs[1].ToString(); - } - - if (methodArgs[1].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[1].Type)) - { - throw new NotSupportedException("An array Contains method is not supported"); - } - - SqlParameters.Add(RemoveQuote(searchValue)); - - SqlParameters.Add(RemoveQuote(replaceValue)); - - //don't execute if compiled - if (Visited == false) - return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1); - //already compiled, return - return string.Empty; - - //case "Substring": - // var startIndex = Int32.Parse(args[0].ToString()) + 1; - // if (args.Count == 2) - // { - // var length = Int32.Parse(args[1].ToString()); - // return string.Format("substring({0} from {1} for {2})", - // r, - // startIndex, - // length); - // } - // else - // return string.Format("substring({0} from {1})", - // r, - // startIndex); - //case "Round": - //case "Floor": - //case "Ceiling": - //case "Coalesce": - //case "Abs": - //case "Sum": - // return string.Format("{0}({1}{2})", - // m.Method.Name, - // r, - // args.Count == 1 ? string.Format(",{0}", args[0]) : ""); - //case "Concat": - // var s = new StringBuilder(); - // foreach (Object e in args) - // { - // s.AppendFormat(" || {0}", e); - // } - // return string.Format("{0}{1}", r, s); - - case "SqlIn": - - if (m.Object == null && methodArgs.Length == 1 && methodArgs[0].NodeType == ExpressionType.MemberAccess) - { - var memberAccess = VisitMemberAccess((MemberExpression) methodArgs[0]); - - var member = Expression.Convert(m.Arguments[0], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - - var inArgs = (IEnumerable)getter(); - - var sIn = new StringBuilder(); - foreach (var e in inArgs) - { - SqlParameters.Add(e); - - sIn.AppendFormat("{0}{1}", - sIn.Length > 0 ? "," : "", - string.Format("@{0}", SqlParameters.Count - 1)); - } - - return string.Format("{0} IN ({1})", memberAccess, sIn); - } - - throw new NotSupportedException("SqlIn must contain the member being accessed"); - - //case "Desc": - // return string.Format("{0} DESC", r); - //case "Alias": - //case "As": - // return string.Format("{0} As {1}", r, - // GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); - - default: - - throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name); - - //var s2 = new StringBuilder(); - //foreach (Object e in args) - //{ - // s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); - //} - //return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); - } - } - - public virtual string GetQuotedTableName(string tableName) - { - return Visited ? tableName : string.Format("\"{0}\"", tableName); - } - - public virtual string GetQuotedColumnName(string columnName) - { - return Visited ? columnName : string.Format("\"{0}\"", columnName); - } - - public virtual string GetQuotedName(string name) - { - return Visited ? name : string.Format("\"{0}\"", name); - } - - protected string HandleStringComparison(string col, string val, string verb, TextColumnType columnType) - { - switch (verb) - { - case "SqlWildcard": - SqlParameters.Add(RemoveQuote(val)); - //don't execute if compiled - if (Visited == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "Equals": - SqlParameters.Add(RemoveQuote(val)); - //don't execute if compiled - if (Visited == false) - return SqlSyntax.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "StartsWith": - SqlParameters.Add(string.Format("{0}{1}", - RemoveQuote(val), - SqlSyntax.GetWildcardPlaceholder())); - //don't execute if compiled - if (Visited == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "EndsWith": - SqlParameters.Add(string.Format("{0}{1}", - SqlSyntax.GetWildcardPlaceholder(), - RemoveQuote(val))); - //don't execute if compiled - if (Visited == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "Contains": - SqlParameters.Add(string.Format("{0}{1}{0}", - SqlSyntax.GetWildcardPlaceholder(), - RemoveQuote(val))); - //don't execute if compiled - if (Visited == false) - return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); - //already compiled, return - return string.Empty; - case "InvariantEquals": - case "SqlEquals": - //recurse - return HandleStringComparison(col, val, "Equals", columnType); - case "InvariantStartsWith": - case "SqlStartsWith": - //recurse - return HandleStringComparison(col, val, "StartsWith", columnType); - case "InvariantEndsWith": - case "SqlEndsWith": - //recurse - return HandleStringComparison(col, val, "EndsWith", columnType); - case "InvariantContains": - case "SqlContains": - //recurse - return HandleStringComparison(col, val, "Contains", columnType); - default: - throw new ArgumentOutOfRangeException("verb"); - } - } - - //public virtual string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null) - //{ - // if (value == null) return "NULL"; - - // if (escapeCallback == null) - // { - // escapeCallback = EscapeParam; - // } - // if (shouldQuoteCallback == null) - // { - // shouldQuoteCallback = ShouldQuoteValue; - // } - - // if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string)) - // { - // //if (TypeSerializer.CanCreateFromString(fieldType)) - // //{ - // // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'"; - // //} - - // throw new NotSupportedException( - // string.Format("Property of type: {0} is not supported", fieldType.FullName)); - // } - - // if (fieldType == typeof(int)) - // return ((int)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(float)) - // return ((float)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(double)) - // return ((double)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(decimal)) - // return ((decimal)value).ToString(CultureInfo.InvariantCulture); - - // if (fieldType == typeof(DateTime)) - // { - // return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'"; - // } - - // if (fieldType == typeof(bool)) - // return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture); - - // return shouldQuoteCallback(fieldType) - // ? "'" + escapeCallback(value) + "'" - // : value.ToString(); - //} - - public virtual string EscapeParam(object paramValue) - { - return paramValue == null ? string.Empty : SqlSyntax.EscapeString(paramValue.ToString()); - } - - public virtual bool ShouldQuoteValue(Type fieldType) - { - return true; - } - - protected virtual string RemoveQuote(string exp) - { - if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) - && - (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) - { - exp = exp.Remove(0, 1); - exp = exp.Remove(exp.Length - 1, 1); - } - return exp; - } - - //protected virtual string RemoveQuoteFromAlias(string expression) - //{ - - // if ((expression.StartsWith("\"") || expression.StartsWith("`") || expression.StartsWith("'")) - // && - // (expression.EndsWith("\"") || expression.EndsWith("`") || expression.EndsWith("'"))) - // { - // expression = expression.Remove(0, 1); - // expression = expression.Remove(expression.Length - 1, 1); - // } - // return expression; - //} - } -} \ No newline at end of file +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Persistence.Querying +{ + // fixme.npoco - are we basically duplicating entire parts of NPoco just because of SqlSyntax ?! + + /// + /// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression. + /// + /// This object is stateful and cannot be re-used to parse an expression. + internal abstract class ExpressionVisitorBase + { + protected ExpressionVisitorBase(ISqlSyntaxProvider sqlSyntax) + { + SqlSyntax = sqlSyntax; + } + + /// + /// Gets or sets a value indicating whether the visited expression has been visited already, + /// in which case visiting will just populate the SQL parameters. + /// + protected bool Visited { get; set; } + + /// + /// Gets or sets the SQL syntax provider for the current database. + /// + protected ISqlSyntaxProvider SqlSyntax { get; private set; } + + /// + /// Gets the list of SQL parameters. + /// + protected readonly List SqlParameters = new List(); + + /// + /// Gets the SQL parameters. + /// + /// + public object[] GetSqlParameters() + { + return SqlParameters.ToArray(); + } + + /// + /// Visits the expression and produces the corresponding SQL statement. + /// + /// The expression + /// The SQL statement corresponding to the expression. + /// Also populates the SQL parameters. + public virtual string Visit(Expression expression) + { + // if the expression is a CachedExpression, + // visit the inner expression if not already visited + var cachedExpression = expression as CachedExpression; + if (cachedExpression != null) + { + Visited = cachedExpression.Visited; + expression = cachedExpression.InnerExpression; + } + + if (expression == null) return string.Empty; + + string result; + + switch (expression.NodeType) + { + case ExpressionType.Lambda: + result = VisitLambda(expression as LambdaExpression); + break; + case ExpressionType.MemberAccess: + result = VisitMemberAccess(expression as MemberExpression); + break; + case ExpressionType.Constant: + result = VisitConstant(expression as ConstantExpression); + break; + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.And: + case ExpressionType.AndAlso: + case ExpressionType.Or: + case ExpressionType.OrElse: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.Coalesce: + case ExpressionType.ArrayIndex: + case ExpressionType.RightShift: + case ExpressionType.LeftShift: + case ExpressionType.ExclusiveOr: + result = VisitBinary(expression as BinaryExpression); + break; + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.ArrayLength: + case ExpressionType.Quote: + case ExpressionType.TypeAs: + result = VisitUnary(expression as UnaryExpression); + break; + case ExpressionType.Parameter: + result = VisitParameter(expression as ParameterExpression); + break; + case ExpressionType.Call: + result = VisitMethodCall(expression as MethodCallExpression); + break; + case ExpressionType.New: + result = VisitNew(expression as NewExpression); + break; + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + result = VisitNewArray(expression as NewArrayExpression); + break; + default: + result = expression.ToString(); + break; + } + + // if the expression is a CachedExpression, + // and is not already compiled, assign the result + if (cachedExpression != null) + { + if (cachedExpression.Visited == false) + cachedExpression.VisitResult = result; + result = cachedExpression.VisitResult; + } + + return result; + } + + protected abstract string VisitMemberAccess(MemberExpression m); + + protected virtual string VisitLambda(LambdaExpression lambda) + { + if (lambda.Body.NodeType == ExpressionType.MemberAccess) + { + var m = lambda.Body as MemberExpression; + + if (m != null && m.Expression != null) + { + //This deals with members that are boolean (i.e. x => IsTrashed ) + var r = VisitMemberAccess(m); + + SqlParameters.Add(true); + + return Visited ? string.Empty : string.Format("{0} = @{1}", r, SqlParameters.Count - 1); + } + + } + return Visit(lambda.Body); + } + + protected virtual string VisitBinary(BinaryExpression b) + { + var left = string.Empty; + var right = string.Empty; + + var operand = BindOperant(b.NodeType); + if (operand == "AND" || operand == "OR") + { + if (b.Left is MemberExpression mLeft && mLeft.Expression != null) + { + var r = VisitMemberAccess(mLeft); + + SqlParameters.Add(true); + + if (Visited == false) + left = $"{r} = @{SqlParameters.Count - 1}"; + } + else + { + left = Visit(b.Left); + } + if (b.Right is MemberExpression mRight && mRight.Expression != null) + { + var r = VisitMemberAccess(mRight); + + SqlParameters.Add(true); + + if (Visited == false) + right = $"{r} = @{SqlParameters.Count - 1}"; + } + else + { + right = Visit(b.Right); + } + } + else if (operand == "=") + { + // deal with (x == true|false) - most common + if (b.Right is ConstantExpression constRight && constRight.Type == typeof(bool)) + return (bool) constRight.Value ? VisitNotNot(b.Left) : VisitNot(b.Left); + right = Visit(b.Right); + + // deal with (true|false == x) - why not + if (b.Left is ConstantExpression constLeft && constLeft.Type == typeof(bool)) + return (bool) constLeft.Value ? VisitNotNot(b.Right) : VisitNot(b.Right); + left = Visit(b.Left); + } + else if (operand == "<>") + { + // deal with (x != true|false) - most common + if (b.Right is ConstantExpression constRight && constRight.Type == typeof (bool)) + return (bool) constRight.Value ? VisitNot(b.Left) : VisitNotNot(b.Left); + right = Visit(b.Right); + + // deal with (true|false != x) - why not + if (b.Left is ConstantExpression constLeft && constLeft.Type == typeof (bool)) + return (bool) constLeft.Value ? VisitNot(b.Right) : VisitNotNot(b.Right); + left = Visit(b.Left); + } + else + { + left = Visit(b.Left); + right = Visit(b.Right); + } + + if (operand == "=" && right == "null") operand = "is"; + else if (operand == "<>" && right == "null") operand = "is not"; + else if (operand == "=" || operand == "<>") + { + //if (IsTrueExpression(right)) right = GetQuotedTrueValue(); + //else if (IsFalseExpression(right)) right = GetQuotedFalseValue(); + + //if (IsTrueExpression(left)) left = GetQuotedTrueValue(); + //else if (IsFalseExpression(left)) left = GetQuotedFalseValue(); + + } + + switch (operand) + { + case "MOD": + case "COALESCE": + //don't execute if compiled + if (Visited == false) + { + return string.Format("{0}({1},{2})", operand, left, right); + } + //already compiled, return + return string.Empty; + default: + //don't execute if compiled + if (Visited == false) + { + return string.Concat("(", left, " ", operand, " ", right, ")"); + } + //already compiled, return + return string.Empty; + } + } + + protected virtual List VisitExpressionList(ReadOnlyCollection original) + { + var list = new List(); + for (int i = 0, n = original.Count; i < n; i++) + { + if (original[i].NodeType == ExpressionType.NewArrayInit || + original[i].NodeType == ExpressionType.NewArrayBounds) + { + list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression)); + } + else + { + list.Add(Visit(original[i])); + } + } + return list; + } + + protected virtual string VisitNew(NewExpression nex) + { + // TODO : check ! + var member = Expression.Convert(nex, typeof(object)); + var lambda = Expression.Lambda>(member); + try + { + var getter = lambda.Compile(); + var o = getter(); + + SqlParameters.Add(o); + return Visited ? string.Empty : $"@{SqlParameters.Count - 1}"; + } + catch (InvalidOperationException) + { + if (Visited) return string.Empty; + + var exprs = VisitExpressionList(nex.Arguments); + var r = new StringBuilder(); + foreach (var e in exprs) + { + if (r.Length > 0) r.Append(","); + r.Append(e); + } + return r.ToString(); + } + } + + protected virtual string VisitParameter(ParameterExpression p) + { + return p.Name; + } + + protected virtual string VisitConstant(ConstantExpression c) + { + if (c.Value == null) + return "null"; + + SqlParameters.Add(c.Value); + return Visited ? string.Empty : $"@{SqlParameters.Count - 1}"; + } + + protected virtual string VisitUnary(UnaryExpression u) + { + switch (u.NodeType) + { + case ExpressionType.Not: + return VisitNot(u.Operand); + default: + return Visit(u.Operand); + } + } + + private string VisitNot(Expression exp) + { + var o = Visit(exp); + + // use a "NOT (...)" syntax instead of "<>" since we don't know whether "<>" works in all sql servers + // also, x.StartsWith(...) translates to "x LIKE '...%'" which we cannot "<>" and have to "NOT (...") + + switch (exp.NodeType) + { + case ExpressionType.MemberAccess: + // false property , i.e. x => !Trashed + SqlParameters.Add(true); + return Visited ? string.Empty : $"NOT ({o} = @{SqlParameters.Count - 1})"; + default: + // could be anything else, such as: x => !x.Path.StartsWith("-20") + return Visited ? string.Empty : string.Concat("NOT (", o, ")"); + } + } + + private string VisitNotNot(Expression exp) + { + var o = Visit(exp); + + switch (exp.NodeType) + { + case ExpressionType.MemberAccess: + // true property, i.e. x => Trashed + SqlParameters.Add(true); + return Visited ? string.Empty : $"({o} = @{SqlParameters.Count - 1})"; + default: + // could be anything else, such as: x => x.Path.StartsWith("-20") + return Visited ? string.Empty : o; + } + } + + protected virtual string VisitNewArray(NewArrayExpression na) + { + var exprs = VisitExpressionList(na.Expressions); + + //don't execute if compiled + if (Visited == false) + { + var r = new StringBuilder(); + foreach (var e in exprs) + { + r.Append(r.Length > 0 ? "," + e : e); + } + + return r.ToString(); + } + //already compiled, return + return string.Empty; + } + + protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) + { + var exprs = VisitExpressionList(na.Expressions); + return exprs; + } + + protected virtual string BindOperant(ExpressionType e) + { + switch (e) + { + case ExpressionType.Equal: + return "="; + case ExpressionType.NotEqual: + return "<>"; + case ExpressionType.GreaterThan: + return ">"; + case ExpressionType.GreaterThanOrEqual: + return ">="; + case ExpressionType.LessThan: + return "<"; + case ExpressionType.LessThanOrEqual: + return "<="; + case ExpressionType.AndAlso: + return "AND"; + case ExpressionType.OrElse: + return "OR"; + case ExpressionType.Add: + return "+"; + case ExpressionType.Subtract: + return "-"; + case ExpressionType.Multiply: + return "*"; + case ExpressionType.Divide: + return "/"; + case ExpressionType.Modulo: + return "MOD"; + case ExpressionType.Coalesce: + return "COALESCE"; + default: + return e.ToString(); + } + } + + protected virtual string VisitMethodCall(MethodCallExpression m) + { + //Here's what happens with a MethodCallExpression: + // If a method is called that contains a single argument, + // then m.Object is the object on the left hand side of the method call, example: + // x.Path.StartsWith(content.Path) + // m.Object = x.Path + // and m.Arguments.Length == 1, therefor m.Arguments[0] == content.Path + // If a method is called that contains multiple arguments, then m.Object == null and the + // m.Arguments collection contains the left hand side of the method call, example: + // x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar) + // m.Object == null + // m.Arguments.Length == 3, therefor, m.Arguments[0] == x.Path, m.Arguments[1] == content.Path, m.Arguments[2] == TextColumnType.NVarchar + // So, we need to cater for these scenarios. + + var objectForMethod = m.Object ?? m.Arguments[0]; + var visitedObjectForMethod = Visit(objectForMethod); + var methodArgs = m.Object == null + ? m.Arguments.Skip(1).ToArray() + : m.Arguments.ToArray(); + + switch (m.Method.Name) + { + case "ToString": + SqlParameters.Add(objectForMethod.ToString()); + //don't execute if compiled + if (Visited == false) + return string.Format("@{0}", SqlParameters.Count - 1); + //already compiled, return + return string.Empty; + case "ToUpper": + //don't execute if compiled + if (Visited == false) + return string.Format("upper({0})", visitedObjectForMethod); + //already compiled, return + return string.Empty; + case "ToLower": + //don't execute if compiled + if (Visited == false) + return string.Format("lower({0})", visitedObjectForMethod); + //already compiled, return + return string.Empty; + case "SqlWildcard": + case "StartsWith": + case "EndsWith": + case "Contains": + case "Equals": + case "SqlStartsWith": + case "SqlEndsWith": + case "SqlContains": + case "SqlEquals": + case "InvariantStartsWith": + case "InvariantEndsWith": + case "InvariantContains": + case "InvariantEquals": + + //special case, if it is 'Contains' and the argumet that Contains is being called on is + //Enumerable and the methodArgs is the actual member access, then it's an SQL IN claus + if (m.Object == null + && m.Arguments[0].Type != typeof(string) + && m.Arguments.Count == 2 + && methodArgs.Length == 1 + && methodArgs[0].NodeType == ExpressionType.MemberAccess + && TypeHelper.IsTypeAssignableFrom(m.Arguments[0].Type)) + { + goto case "SqlIn"; + } + + string compareValue; + + if (methodArgs[0].NodeType != ExpressionType.Constant) + { + // if it's a field accessor, we could Visit(methodArgs[0]) and get [a].[b] + // but then, what if we want more, eg .StartsWith(node.Path + ',') ? => not + + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + compareValue = getter().ToString(); + } + else + { + compareValue = methodArgs[0].ToString(); + } + + //default column type + var colType = TextColumnType.NVarchar; + + //then check if the col type argument has been passed to the current method (this will be the case for methods like + // SqlContains and other Sql methods) + if (methodArgs.Length > 1) + { + var colTypeArg = methodArgs.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); + if (colTypeArg != null) + { + colType = (TextColumnType)((ConstantExpression)colTypeArg).Value; + } + } + + return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType); + + case "Replace": + string searchValue; + + if (methodArgs[0].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + searchValue = getter().ToString(); + } + else + { + searchValue = methodArgs[0].ToString(); + } + + if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + string replaceValue; + + if (methodArgs[1].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[1], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + replaceValue = getter().ToString(); + } + else + { + replaceValue = methodArgs[1].ToString(); + } + + if (methodArgs[1].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[1].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + SqlParameters.Add(RemoveQuote(searchValue)); + + SqlParameters.Add(RemoveQuote(replaceValue)); + + //don't execute if compiled + if (Visited == false) + return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1); + //already compiled, return + return string.Empty; + + //case "Substring": + // var startIndex = Int32.Parse(args[0].ToString()) + 1; + // if (args.Count == 2) + // { + // var length = Int32.Parse(args[1].ToString()); + // return string.Format("substring({0} from {1} for {2})", + // r, + // startIndex, + // length); + // } + // else + // return string.Format("substring({0} from {1})", + // r, + // startIndex); + //case "Round": + //case "Floor": + //case "Ceiling": + //case "Coalesce": + //case "Abs": + //case "Sum": + // return string.Format("{0}({1}{2})", + // m.Method.Name, + // r, + // args.Count == 1 ? string.Format(",{0}", args[0]) : ""); + //case "Concat": + // var s = new StringBuilder(); + // foreach (Object e in args) + // { + // s.AppendFormat(" || {0}", e); + // } + // return string.Format("{0}{1}", r, s); + + case "SqlIn": + + if (m.Object == null && methodArgs.Length == 1 && methodArgs[0].NodeType == ExpressionType.MemberAccess) + { + var memberAccess = VisitMemberAccess((MemberExpression) methodArgs[0]); + + var member = Expression.Convert(m.Arguments[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + + var inArgs = (IEnumerable)getter(); + + var sIn = new StringBuilder(); + foreach (var e in inArgs) + { + SqlParameters.Add(e); + + sIn.AppendFormat("{0}{1}", + sIn.Length > 0 ? "," : "", + string.Format("@{0}", SqlParameters.Count - 1)); + } + + return string.Format("{0} IN ({1})", memberAccess, sIn); + } + + throw new NotSupportedException("SqlIn must contain the member being accessed"); + + //case "Desc": + // return string.Format("{0} DESC", r); + //case "Alias": + //case "As": + // return string.Format("{0} As {1}", r, + // GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); + + case "SqlText": + if (m.Method.DeclaringType != typeof(NPocoSqlExtensions.Statics)) + goto default; + if (m.Arguments.Count == 2) + { + var n1 = Visit(m.Arguments[0]); + var f = m.Arguments[2]; + if (!(f is Expression> fl)) + throw new NotSupportedException("Expression is not a proper lambda."); + var ff = fl.Compile(); + return ff(n1); + } + else if (m.Arguments.Count == 3) + { + var n1 = Visit(m.Arguments[0]); + var n2 = Visit(m.Arguments[1]); + var f = m.Arguments[2]; + if (!(f is Expression> fl)) + throw new NotSupportedException("Expression is not a proper lambda."); + var ff = fl.Compile(); + return ff(n1, n2); + } + else if (m.Arguments.Count == 4) + { + var n1 = Visit(m.Arguments[0]); + var n2 = Visit(m.Arguments[1]); + var n3 = Visit(m.Arguments[3]); + var f = m.Arguments[3]; + if (!(f is Expression> fl)) + throw new NotSupportedException("Expression is not a proper lambda."); + var ff = fl.Compile(); + return ff(n1, n2, n3); + } + else + throw new NotSupportedException("Expression is not a proper lambda."); + + default: + + throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name); + + //var s2 = new StringBuilder(); + //foreach (Object e in args) + //{ + // s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); + //} + //return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); + } + } + + public virtual string GetQuotedTableName(string tableName) + { + return Visited ? tableName : string.Format("\"{0}\"", tableName); + } + + public virtual string GetQuotedColumnName(string columnName) + { + return Visited ? columnName : string.Format("\"{0}\"", columnName); + } + + public virtual string GetQuotedName(string name) + { + return Visited ? name : string.Format("\"{0}\"", name); + } + + protected string HandleStringComparison(string col, string val, string verb, TextColumnType columnType) + { + switch (verb) + { + case "SqlWildcard": + SqlParameters.Add(RemoveQuote(val)); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "Equals": + SqlParameters.Add(RemoveQuote(val)); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "StartsWith": + SqlParameters.Add(string.Format("{0}{1}", + RemoveQuote(val), + SqlSyntax.GetWildcardPlaceholder())); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "EndsWith": + SqlParameters.Add(string.Format("{0}{1}", + SqlSyntax.GetWildcardPlaceholder(), + RemoveQuote(val))); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "Contains": + SqlParameters.Add(string.Format("{0}{1}{0}", + SqlSyntax.GetWildcardPlaceholder(), + RemoveQuote(val))); + //don't execute if compiled + if (Visited == false) + return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); + //already compiled, return + return string.Empty; + case "InvariantEquals": + case "SqlEquals": + //recurse + return HandleStringComparison(col, val, "Equals", columnType); + case "InvariantStartsWith": + case "SqlStartsWith": + //recurse + return HandleStringComparison(col, val, "StartsWith", columnType); + case "InvariantEndsWith": + case "SqlEndsWith": + //recurse + return HandleStringComparison(col, val, "EndsWith", columnType); + case "InvariantContains": + case "SqlContains": + //recurse + return HandleStringComparison(col, val, "Contains", columnType); + default: + throw new ArgumentOutOfRangeException("verb"); + } + } + + //public virtual string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null) + //{ + // if (value == null) return "NULL"; + + // if (escapeCallback == null) + // { + // escapeCallback = EscapeParam; + // } + // if (shouldQuoteCallback == null) + // { + // shouldQuoteCallback = ShouldQuoteValue; + // } + + // if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string)) + // { + // //if (TypeSerializer.CanCreateFromString(fieldType)) + // //{ + // // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'"; + // //} + + // throw new NotSupportedException( + // string.Format("Property of type: {0} is not supported", fieldType.FullName)); + // } + + // if (fieldType == typeof(int)) + // return ((int)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(float)) + // return ((float)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(double)) + // return ((double)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(decimal)) + // return ((decimal)value).ToString(CultureInfo.InvariantCulture); + + // if (fieldType == typeof(DateTime)) + // { + // return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'"; + // } + + // if (fieldType == typeof(bool)) + // return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture); + + // return shouldQuoteCallback(fieldType) + // ? "'" + escapeCallback(value) + "'" + // : value.ToString(); + //} + + public virtual string EscapeParam(object paramValue, ISqlSyntaxProvider sqlSyntax) + { + return paramValue == null + ? string.Empty + : sqlSyntax.EscapeString(paramValue.ToString()); + } + + public virtual bool ShouldQuoteValue(Type fieldType) + { + return true; + } + + protected virtual string RemoveQuote(string exp) + { + if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) + && + (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) + { + exp = exp.Remove(0, 1); + exp = exp.Remove(exp.Length - 1, 1); + } + return exp; + } + + //protected virtual string RemoveQuoteFromAlias(string expression) + //{ + + // if ((expression.StartsWith("\"") || expression.StartsWith("`") || expression.StartsWith("'")) + // && + // (expression.EndsWith("\"") || expression.EndsWith("`") || expression.EndsWith("'"))) + // { + // expression = expression.Remove(0, 1); + // expression = expression.Remove(expression.Length - 1, 1); + // } + // return expression; + //} + } +} diff --git a/src/Umbraco.Core/Persistence/Querying/IQuery.cs b/src/Umbraco.Core/Persistence/Querying/IQuery.cs index cd08274c1b..d4c1d83f8a 100644 --- a/src/Umbraco.Core/Persistence/Querying/IQuery.cs +++ b/src/Umbraco.Core/Persistence/Querying/IQuery.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq.Expressions; @@ -17,6 +18,20 @@ namespace Umbraco.Core.Persistence.Querying /// This instance so calls to this method are chainable IQuery Where(Expression> predicate); + /// + /// Returns all translated where clauses and their sql parameters + /// + /// + IEnumerable> GetWhereClauses(); + + /// + /// Adds a where-in clause to the query + /// + /// + /// + /// This instance so calls to this method are chainable + IQuery WhereIn(Expression> fieldSelector, IEnumerable values); + /// /// Adds a set of OR-ed where clauses to the query. /// @@ -24,4 +39,4 @@ namespace Umbraco.Core.Persistence.Querying /// This instance so calls to this method are chainable. IQuery WhereAny(IEnumerable>> predicates); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs index da1f93a032..9f57e61db9 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs @@ -1,8 +1,9 @@ using System; using System.Linq.Expressions; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Persistence.Querying { @@ -13,21 +14,16 @@ namespace Umbraco.Core.Persistence.Querying /// This object is stateful and cannot be re-used to parse an expression. internal class ModelToSqlExpressionVisitor : ExpressionVisitorBase { - private readonly MappingResolver _mappingResolver; + private readonly IMapperCollection _mappers; private readonly BaseMapper _mapper; - public ModelToSqlExpressionVisitor(ISqlSyntaxProvider sqlSyntax, MappingResolver mappingResolver) + public ModelToSqlExpressionVisitor(ISqlSyntaxProvider sqlSyntax, IMapperCollection mappers) : base(sqlSyntax) { - _mapper = mappingResolver.ResolveMapperByType(typeof(T)); - _mappingResolver = mappingResolver; + _mappers = mappers; + _mapper = mappers[typeof(T)]; // throws if not found } - [Obsolete("Use the overload the specifies a SqlSyntaxProvider")] - public ModelToSqlExpressionVisitor() - : this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current) - { } - protected override string VisitMemberAccess(MemberExpression m) { if (m.Expression != null && @@ -37,11 +33,12 @@ namespace Umbraco.Core.Persistence.Querying //don't execute if compiled if (Visited == false) { - var field = _mapper.Map(m.Member.Name, true); + var field = _mapper.Map(SqlSyntax, m.Member.Name, true); if (field.IsNullOrWhiteSpace()) - throw new InvalidOperationException(string.Format("The mapper returned an empty field for the member name: {0} for type: {1}", m.Member.Name, m.Expression.Type)); + throw new InvalidOperationException($"The mapper returned an empty field for the member name: {m.Member.Name} for type: {m.Expression.Type}."); return field; } + //already compiled, return return string.Empty; } @@ -51,17 +48,18 @@ namespace Umbraco.Core.Persistence.Querying //don't execute if compiled if (Visited == false) { - var field = _mapper.Map(m.Member.Name, true); + var field = _mapper.Map(SqlSyntax, m.Member.Name, true); if (field.IsNullOrWhiteSpace()) - throw new InvalidOperationException(string.Format("The mapper returned an empty field for the member name: {0} for type: {1}", m.Member.Name, m.Expression.Type)); + throw new InvalidOperationException($"The mapper returned an empty field for the member name: {m.Member.Name} for type: {m.Expression.Type}."); return field; } + //already compiled, return return string.Empty; } - if (m.Expression != null - && m.Expression.Type != typeof(T) + if (m.Expression != null + && m.Expression.Type != typeof(T) && TypeHelper.IsTypeAssignableFrom(m.Expression.Type) && EndsWithConstant(m) == false) { @@ -71,12 +69,10 @@ namespace Umbraco.Core.Persistence.Querying //don't execute if compiled if (Visited == false) { - var subMapper = _mappingResolver.ResolveMapperByType(m.Expression.Type); - if (subMapper == null) - throw new NullReferenceException("No mapper found for type " + m.Expression.Type); - var field = subMapper.Map(m.Member.Name, true); + var subMapper = _mappers[m.Expression.Type]; // throws if not found + var field = subMapper.Map(SqlSyntax, m.Member.Name, true); if (field.IsNullOrWhiteSpace()) - throw new InvalidOperationException(string.Format("The mapper returned an empty field for the member name: {0} for type: {1}", m.Member.Name, m.Expression.Type)); + throw new InvalidOperationException($"The mapper returned an empty field for the member name: {m.Member.Name} for type: {m.Expression.Type}"); return field; } //already compiled, return @@ -98,10 +94,10 @@ namespace Umbraco.Core.Persistence.Querying //don't execute if compiled if (Visited == false) - return string.Format("@{0}", SqlParameters.Count - 1); + return $"@{SqlParameters.Count - 1}"; + //already compiled, return return string.Empty; - } /// @@ -109,18 +105,18 @@ namespace Umbraco.Core.Persistence.Querying /// /// /// - private bool EndsWithConstant(MemberExpression m) + private static bool EndsWithConstant(MemberExpression m) { Expression expr = m; - + while (expr is MemberExpression) { var memberExpr = expr as MemberExpression; expr = memberExpr.Expression; } - + var constExpr = expr as ConstantExpression; return constExpr != null; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs index 4569b95853..4d33977c72 100644 --- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs +++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionVisitor.cs @@ -1,58 +1,72 @@ using System; using System.Linq; using System.Linq.Expressions; -using Umbraco.Core.Persistence.SqlSyntax; +using NPoco; namespace Umbraco.Core.Persistence.Querying { /// - /// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression, - /// based on Umbraco's DTOs. + /// Represents an expression tree parser used to turn strongly typed expressions into SQL statements. /// - /// This object is stateful and cannot be re-used to parse an expression. - internal class PocoToSqlExpressionVisitor : ExpressionVisitorBase + /// The type of the DTO. + /// This visitor is stateful and cannot be reused. + internal class PocoToSqlExpressionVisitor : ExpressionVisitorBase { - private readonly Database.PocoData _pd; + private readonly PocoData _pd; + private readonly string _alias; - - public PocoToSqlExpressionVisitor(ISqlSyntaxProvider syntaxProvider) - : base(syntaxProvider) + public PocoToSqlExpressionVisitor(ISqlContext sqlContext, string alias) + : base(sqlContext.SqlSyntax) { - _pd = new Database.PocoData(typeof(T)); + _pd = sqlContext.PocoDataFactory.ForType(typeof(TDto)); + _alias = alias; } - [Obsolete("Use the overload the specifies a SqlSyntaxProvider")] - public PocoToSqlExpressionVisitor() - : this(SqlSyntaxContext.SqlSyntaxProvider) - { + protected override string VisitMethodCall(MethodCallExpression m) + { + var declaring = m.Method.DeclaringType; + if (declaring != typeof (SqlTemplate)) + return base.VisitMethodCall(m); + + if (m.Method.Name != "Arg" && m.Method.Name != "ArgIn") + throw new NotSupportedException($"Method SqlTemplate.{m.Method.Name} is not supported."); + + var parameters = m.Method.GetParameters(); + if (parameters.Length != 1 || parameters[0].ParameterType != typeof (string)) + throw new NotSupportedException($"Method SqlTemplate.{m.Method.Name}({string.Join(", ", parameters.Select(x => x.ParameterType))} is not supported."); + + var arg = m.Arguments[0]; + string name; + if (arg.NodeType == ExpressionType.Constant) + { + name = arg.ToString(); + } + else + { + // though... we probably should avoid doing this + var member = Expression.Convert(arg, typeof (object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + name = getter().ToString(); + } + + SqlParameters.Add(new SqlTemplate.TemplateArg(RemoveQuote(name))); + + return Visited + ? string.Empty + : $"@{SqlParameters.Count - 1}"; } protected override string VisitMemberAccess(MemberExpression m) { - if (m.Expression != null && - m.Expression.NodeType == ExpressionType.Parameter - && m.Expression.Type == typeof(T)) + if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter && m.Expression.Type == typeof(TDto)) { - //don't execute if compiled - if (Visited == false) - { - string field = GetFieldName(_pd, m.Member.Name); - return field; - } - //already compiled, return - return string.Empty; + return Visited ? string.Empty : GetFieldName(_pd, m.Member.Name, _alias); } if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert) { - //don't execute if compiled - if (Visited == false) - { - string field = GetFieldName(_pd, m.Member.Name); - return field; - } - //already compiled, return - return string.Empty; + return Visited ? string.Empty : GetFieldName(_pd, m.Member.Name, _alias); } var member = Expression.Convert(m, typeof(object)); @@ -62,19 +76,95 @@ namespace Umbraco.Core.Persistence.Querying SqlParameters.Add(o); - //don't execute if compiled - if (Visited == false) - return string.Format("@{0}", SqlParameters.Count - 1); - //already compiled, return - return string.Empty; + return Visited ? string.Empty : "@" + (SqlParameters.Count - 1); } - protected virtual string GetFieldName(Database.PocoData pocoData, string name) + protected virtual string GetFieldName(PocoData pocoData, string name, string alias) { - var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name); - return string.Format("{0}.{1}", - SqlSyntax.GetQuotedTableName(pocoData.TableInfo.TableName), - SqlSyntax.GetQuotedColumnName(column.Value.ColumnName)); + var column = pocoData.Columns.FirstOrDefault(x => x.Value.MemberInfoData.Name == name); + var tableName = SqlSyntax.GetQuotedTableName(alias ?? pocoData.TableInfo.TableName); + var columnName = SqlSyntax.GetQuotedColumnName(column.Value.ColumnName); + + return tableName + "." + columnName; } } -} \ No newline at end of file + + /// + /// Represents an expression tree parser used to turn strongly typed expressions into SQL statements. + /// + /// The type of DTO 1. + /// The type of DTO 2. + /// This visitor is stateful and cannot be reused. + internal class PocoToSqlExpressionVisitor : ExpressionVisitorBase + { + private readonly PocoData _pocoData1, _pocoData2; + private readonly string _alias1, _alias2; + private string _parameterName1, _parameterName2; + + public PocoToSqlExpressionVisitor(ISqlContext sqlContext, string alias1, string alias2) + : base(sqlContext.SqlSyntax) + { + _pocoData1 = sqlContext.PocoDataFactory.ForType(typeof (TDto1)); + _pocoData2 = sqlContext.PocoDataFactory.ForType(typeof (TDto2)); + _alias1 = alias1; + _alias2 = alias2; + } + + protected override string VisitLambda(LambdaExpression lambda) + { + if (lambda.Parameters.Count == 2) + { + _parameterName1 = lambda.Parameters[0].Name; + _parameterName2 = lambda.Parameters[1].Name; + } + else + { + _parameterName1 = _parameterName2 = null; + } + return base.VisitLambda(lambda); + } + + protected override string VisitMemberAccess(MemberExpression m) + { + if (m.Expression != null) + { + if (m.Expression.NodeType == ExpressionType.Parameter) + { + var pex = (ParameterExpression) m.Expression; + + if (pex.Name == _parameterName1) + return Visited ? string.Empty : GetFieldName(_pocoData1, m.Member.Name, _alias1); + + if (pex.Name == _parameterName2) + return Visited ? string.Empty : GetFieldName(_pocoData2, m.Member.Name, _alias2); + } + else if (m.Expression.NodeType == ExpressionType.Convert) + { + // here: which _pd should we use?! + throw new NotSupportedException(); + //return Visited ? string.Empty : GetFieldName(_pd, m.Member.Name); + } + } + + var member = Expression.Convert(m, typeof (object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + var o = getter(); + + SqlParameters.Add(o); + + // execute if not already compiled + return Visited ? string.Empty : "@" + (SqlParameters.Count - 1); + } + + protected virtual string GetFieldName(PocoData pocoData, string name, string alias) + { + var column = pocoData.Columns.FirstOrDefault(x => x.Value.MemberInfoData.Name == name); + var tableName = SqlSyntax.GetQuotedTableName(alias ?? pocoData.TableInfo.TableName); + var columnName = SqlSyntax.GetQuotedColumnName(column.Value.ColumnName); + + return tableName + "." + columnName; + } + } + +} diff --git a/src/Umbraco.Core/Persistence/Querying/Query.cs b/src/Umbraco.Core/Persistence/Querying/Query.cs index d49296c6d0..0c5dec7d4e 100644 --- a/src/Umbraco.Core/Persistence/Querying/Query.cs +++ b/src/Umbraco.Core/Persistence/Querying/Query.cs @@ -1,68 +1,73 @@ using System; +using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Text; +using NPoco; namespace Umbraco.Core.Persistence.Querying { /// - /// Represents the Query Builder for building LINQ translatable queries + /// Represents a query builder. /// - /// + /// A query builder translates Linq queries into Sql queries. public class Query : IQuery { + private readonly ISqlContext _sqlContext; private readonly List> _wheres = new List>(); - /// - /// Helper method to be used instead of manually creating an instance - /// - public static IQuery Builder + public Query(ISqlContext sqlContext) { - get { return new Query(); } + _sqlContext = sqlContext; } /// - /// Adds a where clause to the query + /// Adds a where clause to the query. /// - /// - /// This instance so calls to this method are chainable public virtual IQuery Where(Expression> predicate) { - if (predicate != null) - { - //TODO: This should have an SqlSyntax object passed in, this ctor is relying on a singleton - var expressionHelper = new ModelToSqlExpressionVisitor(); - string whereExpression = expressionHelper.Visit(predicate); + if (predicate == null) return this; - _wheres.Add(new Tuple(whereExpression, expressionHelper.GetSqlParameters())); - } + var expressionHelper = new ModelToSqlExpressionVisitor(_sqlContext.SqlSyntax, _sqlContext.Mappers); + var whereExpression = expressionHelper.Visit(predicate); + _wheres.Add(new Tuple(whereExpression, expressionHelper.GetSqlParameters())); + return this; + } + + /// + /// Adds a where-in clause to the query. + /// + public virtual IQuery WhereIn(Expression> fieldSelector, IEnumerable values) + { + if (fieldSelector == null) return this; + + var expressionHelper = new ModelToSqlExpressionVisitor(_sqlContext.SqlSyntax, _sqlContext.Mappers); + var whereExpression = expressionHelper.Visit(fieldSelector); + _wheres.Add(new Tuple(whereExpression + " IN (@values)", new object[] { new { values } })); return this; } /// /// Adds a set of OR-ed where clauses to the query. /// - /// - /// This instance so calls to this method are chainable. public virtual IQuery WhereAny(IEnumerable>> predicates) { if (predicates == null) return this; StringBuilder sb = null; List parameters = null; - Sql sql = null; + Sql sql = null; foreach (var predicate in predicates) { // see notes in Where() - var expressionHelper = new ModelToSqlExpressionVisitor(); + var expressionHelper = new ModelToSqlExpressionVisitor(_sqlContext.SqlSyntax, _sqlContext.Mappers); var whereExpression = expressionHelper.Visit(predicate); if (sb == null) { sb = new StringBuilder("("); parameters = new List(); - sql = new Sql(); + sql = Sql.BuilderFor(_sqlContext); } else { @@ -78,7 +83,6 @@ namespace Umbraco.Core.Persistence.Querying if (sb == null) return this; sb.Append(")"); - //_wheres.Add(Tuple.Create(sb.ToString(), parameters.ToArray())); _wheres.Add(Tuple.Create("(" + sql.SQL + ")", sql.Arguments)); return this; @@ -87,16 +91,9 @@ namespace Umbraco.Core.Persistence.Querying /// /// Returns all translated where clauses and their sql parameters /// - /// public IEnumerable> GetWhereClauses() { return _wheres; } - - [Obsolete("This is no longer used, use the GetWhereClauses method which includes the SQL parameters")] - public List WhereClauses() - { - return _wheres.Select(x => x.Item1).ToList(); - } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Querying/QueryExtensions.cs b/src/Umbraco.Core/Persistence/Querying/QueryExtensions.cs index 20c3409a40..c6a0131f9c 100644 --- a/src/Umbraco.Core/Persistence/Querying/QueryExtensions.cs +++ b/src/Umbraco.Core/Persistence/Querying/QueryExtensions.cs @@ -24,4 +24,4 @@ namespace Umbraco.Core.Persistence.Querying return q.GetWhereClauses(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Querying/SqlExpressionExtensions.cs b/src/Umbraco.Core/Persistence/Querying/SqlExpressionExtensions.cs index 593955734e..0f9eb47d77 100644 --- a/src/Umbraco.Core/Persistence/Querying/SqlExpressionExtensions.cs +++ b/src/Umbraco.Core/Persistence/Querying/SqlExpressionExtensions.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Persistence.Querying public static bool SqlWildcard(this string str, string txt, TextColumnType columnType) { var wildcardmatch = new Regex("^" + Regex.Escape(txt). - //deal with any wildcard chars % + //deal with any wildcard chars % Replace(@"\%", ".*") + "$"); return wildcardmatch.IsMatch(str); @@ -43,4 +43,4 @@ namespace Umbraco.Core.Persistence.Querying return str.InvariantEndsWith(txt); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs index b012293340..420b71b4eb 100644 --- a/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs +++ b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs @@ -1,4 +1,5 @@ using System; +using NPoco; namespace Umbraco.Core.Persistence.Querying { @@ -8,21 +9,16 @@ namespace Umbraco.Core.Persistence.Querying /// internal class SqlTranslator { - private readonly Sql _sql; + private readonly Sql _sql; - public SqlTranslator(Sql sql, IQuery query) + public SqlTranslator(Sql sql, IQuery query) { - if (sql == null) - throw new Exception("Sql cannot be null"); - - _sql = sql; + _sql = sql ?? throw new ArgumentNullException(nameof(sql)); foreach (var clause in query.GetWhereClauses()) - { _sql.Where(clause.Item1, clause.Item2); - } } - public Sql Translate() + public Sql Translate() { return _sql; } @@ -32,4 +28,4 @@ namespace Umbraco.Core.Persistence.Querying return _sql.SQL; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs index f45efb412a..f4c95c02b5 100644 --- a/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs +++ b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs @@ -12,4 +12,4 @@ //Deals with % as wildcard chars in a string Wildcard } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Querying/TextColumnType.cs b/src/Umbraco.Core/Persistence/Querying/TextColumnType.cs index f33022fcbe..b6ba94f7b9 100644 --- a/src/Umbraco.Core/Persistence/Querying/TextColumnType.cs +++ b/src/Umbraco.Core/Persistence/Querying/TextColumnType.cs @@ -5,4 +5,4 @@ NVarchar, NText } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs b/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs index f0c70a069d..14cba2bb17 100644 --- a/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs +++ b/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs @@ -11,4 +11,4 @@ GreaterThanOrEqualTo, LessThanOrEqualTo } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/RecordPersistenceType.cs b/src/Umbraco.Core/Persistence/RecordPersistenceType.cs index 53140afe81..3eb94c70ae 100644 --- a/src/Umbraco.Core/Persistence/RecordPersistenceType.cs +++ b/src/Umbraco.Core/Persistence/RecordPersistenceType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Persistence +namespace Umbraco.Core.Persistence { internal enum RecordPersistenceType { @@ -6,4 +6,4 @@ namespace Umbraco.Core.Persistence Update, Delete } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs b/src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs deleted file mode 100644 index bcb33f3597..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class AccessRulesRelator - { - internal AccessDto Current; - - internal AccessDto Map(AccessDto a, AccessRuleDto p) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same AccessDto as the current one we're processing - if (Current != null && Current.Id == a.Id) - { - // Yes, just add this AccessRuleDto to the current AccessDto's collection - if (p.Id != default(Guid)) - { - Current.Rules.Add(p); - } - - - // Return null to indicate we're not done with this AccessDto yet - return null; - } - - // This is a different AccessDto to the current one, or this is the - // first time through and we don't have a Tab yet - - // Save the current AccessDto - var prev = Current; - - // Setup the new current AccessDto - Current = a; - Current.Rules = new List(); - if (p.Id != default(Guid)) - { - Current.Rules.Add(p); - } - - // Return the now populated previous AccessDto (or null if first time through) - return prev; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/DictionaryLanguageTextRelator.cs b/src/Umbraco.Core/Persistence/Relators/DictionaryLanguageTextRelator.cs deleted file mode 100644 index 6e2173471c..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/DictionaryLanguageTextRelator.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class DictionaryLanguageTextRelator - { - internal DictionaryDto Current; - - internal DictionaryDto Map(DictionaryDto a, LanguageTextDto p) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same DictionaryItem as the current one we're processing - if (Current != null && Current.UniqueId == a.UniqueId) - { - // Yes, just add this LanguageTextDto to the current DictionaryItem's collection - Current.LanguageTextDtos.Add(p); - - // Return null to indicate we're not done with this DictionaryItem yet - return null; - } - - // This is a different DictionaryItem to the current one, or this is the - // first time through and we don't have a Tab yet - - // Save the current DictionaryItem - var prev = Current; - - // Setup the new current DictionaryItem - Current = a; - Current.LanguageTextDtos = new List(); - Current.LanguageTextDtos.Add(p); - - // Return the now populated previous DictionaryItem (or null if first time through) - return prev; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/GroupPropertyTypeRelator.cs b/src/Umbraco.Core/Persistence/Relators/GroupPropertyTypeRelator.cs deleted file mode 100644 index 616882f068..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/GroupPropertyTypeRelator.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class GroupPropertyTypeRelator - { - internal PropertyTypeGroupDto current; - - internal PropertyTypeGroupDto Map(PropertyTypeGroupDto a, PropertyTypeDto p, DataTypeDto d) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return current; - - //Set the PropertyTypeDto's DataTypeDto object - if (p.DataTypeId == d.DataTypeId) - p.DataTypeDto = d; - - // Is this the same Group as the current one we're processing - if (current != null && current.Id == a.Id) - { - // Yes, just add this PropertyType to the current Group's collection of PropertyTypes - current.PropertyTypeDtos.Add(p); - - // Return null to indicate we're not done with this Group yet - return null; - } - - // This is a different Group to the current one, or this is the - // first time through and we don't have a Tab yet - - // Save the current Group - var prev = current; - - // Setup the new current Group - current = a; - current.PropertyTypeDtos = new List(); - current.PropertyTypeDtos.Add(p); - - // Return the now populated previous Tab (or null if first time through) - return prev; - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/MacroPropertyRelator.cs b/src/Umbraco.Core/Persistence/Relators/MacroPropertyRelator.cs deleted file mode 100644 index fb563fa25c..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/MacroPropertyRelator.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class MacroPropertyRelator - { - internal MacroDto Current; - - internal MacroDto Map(MacroDto a, MacroPropertyDto p) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same DictionaryItem as the current one we're processing - if (Current != null && Current.Id == a.Id) - { - // Yes, just add this MacroPropertyDtos to the current item's collection - Current.MacroPropertyDtos.Add(p); - - // Return null to indicate we're not done with this Macro yet - return null; - } - - // This is a different Macro to the current one, or this is the - // first time through and we don't have one yet - - // Save the current Macro - var prev = Current; - - // Setup the new current Macro - Current = a; - Current.MacroPropertyDtos = new List(); - //this can be null since we are doing a left join - if (p.Alias != null) - { - Current.MacroPropertyDtos.Add(p); - } - - // Return the now populated previous Macro (or null if first time through) - return prev; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/PropertyTypePropertyGroupRelator.cs b/src/Umbraco.Core/Persistence/Relators/PropertyTypePropertyGroupRelator.cs deleted file mode 100644 index bf307ea594..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/PropertyTypePropertyGroupRelator.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class PropertyTypePropertyGroupRelator - { - internal MemberTypeReadOnlyDto Current; - - internal MemberTypeReadOnlyDto Map(MemberTypeReadOnlyDto a, PropertyTypeReadOnlyDto p, PropertyTypeGroupReadOnlyDto g) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same MemberTypeReadOnlyDto as the current one we're processing - if (Current != null && Current.UniqueId == a.UniqueId) - { - //This property may already be added so we need to check for that - if (p.Id.HasValue && Current.PropertyTypes.Any(x => x.Id == p.Id.Value) == false) - { - // Add this PropertyTypeReadOnlyDto to the current MemberTypeReadOnlyDto's collection - Current.PropertyTypes.Add(p); - } - - if (g.Id.HasValue && Current.PropertyTypeGroups != null && Current.PropertyTypeGroups.Any(x => x.Id == g.Id.Value) == false) - { - Current.PropertyTypeGroups.Add(g); - } - - // Return null to indicate we're not done with this MemberTypeReadOnlyDto yet - return null; - } - - // This is a different MemberTypeReadOnlyDto to the current one, or this is the - // first time through and we don't have a Tab yet - - // Save the current MemberTypeReadOnlyDto - var prev = Current; - - // Setup the new current MemberTypeReadOnlyDto - Current = a; - Current.PropertyTypes = new List(); - //this can be null since we are doing a left join - if (p.Id.HasValue) - { - Current.PropertyTypes.Add(p); - } - - Current.PropertyTypeGroups = new List(); - if (g.Id.HasValue) - { - Current.PropertyTypeGroups.Add(g); - } - - // Return the now populated previous MemberTypeReadOnlyDto (or null if first time through) - return prev; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs deleted file mode 100644 index fdea52498a..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/UserGroupRelator.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class UserGroupRelator - { - private UserDto _currentUser; - - internal UserDto Map(UserDto user, UserGroupDto group, UserGroup2AppDto section, UserStartNodeDto startNode) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (user == null) - return _currentUser; - - // Is this the same User as the current one we're processing - if (_currentUser != null && _currentUser.Id == user.Id) - { - AddOrUpdateGroup(group, section); - AddOrUpdateStartNode(startNode); - - // Return null to indicate we're not done with this object yet - return null; - } - - // This is a different user to the current one, or this is the - // first time through and we don't have one yet - - // Save the current user - var prev = _currentUser; - - // Setup the new current user - _currentUser = user; - _currentUser.UserGroupDtos = new List(); - _currentUser.UserStartNodeDtos = new HashSet(); - - AddOrUpdateGroup(group, section); - AddOrUpdateStartNode(startNode); - - // Return the now populated previous user (or null if first time through) - return prev; - } - - private void AddOrUpdateStartNode(UserStartNodeDto startNode) - { - //this can be null since we are left joining - if (startNode == null || startNode.Id == default(int)) - return; - - //add the current (new) start node - this is a hashset so it will only allow unique rows so no need to check for existence - _currentUser.UserStartNodeDtos.Add(startNode); - } - - private void AddOrUpdateGroup(UserGroupDto group, UserGroup2AppDto section) - { - //I don't even think this situation can happen but if it could, we'd want the section added to the latest group check if this is a new group - if (group == null && section != null) - { - AddSection(section); - } - - //this can be null since we are doing a left join - if (group == null || group.Alias.IsNullOrWhiteSpace()) - return; - - //check if this is a new group - var latestGroup = _currentUser.UserGroupDtos.Count > 0 - ? _currentUser.UserGroupDtos[_currentUser.UserGroupDtos.Count - 1] - : null; - - if (latestGroup == null || latestGroup.Id != group.Id) - { - //add the current (new) group - _currentUser.UserGroupDtos.Add(group); - } - - AddSection(section); - } - - private void AddSection(UserGroup2AppDto section) - { - //this can be null since we are left joining - if (section == null || section.AppAlias.IsNullOrWhiteSpace()) - return; - - var latestGroup = _currentUser.UserGroupDtos.Count > 0 - ? _currentUser.UserGroupDtos[_currentUser.UserGroupDtos.Count - 1] - : null; - - if (latestGroup == null || latestGroup.Id != section.UserGroupId) - throw new InvalidOperationException("The user group and section info don't match"); - - if (latestGroup.UserGroup2AppDtos == null) - latestGroup.UserGroup2AppDtos = new List(); - - //add it if it doesn't exist - if (latestGroup.UserGroup2AppDtos.TrueForAll(dto => dto.AppAlias != section.AppAlias)) - latestGroup.UserGroup2AppDtos.Add(section); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/UserGroupSectionRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserGroupSectionRelator.cs deleted file mode 100644 index 9b96bf6a8d..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/UserGroupSectionRelator.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class UserGroupSectionRelator - { - internal UserGroupDto Current; - - internal UserGroupDto Map(UserGroupDto a, UserGroup2AppDto p) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same object as the current one we're processing - if (Current != null && Current.Id == a.Id) - { - if (p.AppAlias.IsNullOrWhiteSpace() == false) - { - // Yes, just add this to the current item's collection - Current.UserGroup2AppDtos.Add(p); - } - - // Return null to indicate we're not done with this User yet - return null; - } - - // This is a different object to the current one, or this is the - // first time through and we don't have one yet - - // Save the current instance - var prev = Current; - - // Setup the new current instance - Current = a; - Current.UserGroup2AppDtos = new List(); - //this can be null since we are doing a left join - if (p.AppAlias.IsNullOrWhiteSpace() == false) - { - Current.UserGroup2AppDtos.Add(p); - } - - // Return the now populated previous user group (or null if first time through) - return prev; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs deleted file mode 100644 index 92eca1b832..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - internal class AuditRepository : PetaPocoRepositoryBase, IAuditRepository - { - public AuditRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - } - - /// - /// Return the audit items as paged result - /// - /// - /// The query coming from the service - /// - /// - /// - /// - /// - /// - /// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter - /// so we need to do that here - /// - /// - /// A user supplied custom filter - /// - /// - public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, - out long totalRecords, Direction orderDirection, - AuditType[] auditTypeFilter, - IQuery customFilter) - { - if (auditTypeFilter == null) auditTypeFilter = new AuditType[0]; - - var sql = GetBaseQuery(false); - - if (query == null) query = new Query(); - var translatorIds = new SqlTranslator(sql, query); - var translatedQuery = translatorIds.Translate(); - - var customFilterWheres = customFilter != null ? customFilter.GetWhereClauses().ToArray() : null; - var hasCustomFilter = customFilterWheres != null && customFilterWheres.Length > 0; - if (hasCustomFilter) - { - var filterSql = new Sql(); - var first = true; - foreach (var filterClaus in customFilterWheres) - { - if (first == false) - { - filterSql.Append(" AND "); - } - filterSql.Append(string.Format("({0})", filterClaus.Item1), filterClaus.Item2); - first = false; - } - - translatedQuery = GetFilteredSqlForPagedResults(translatedQuery, filterSql); - } - - if (auditTypeFilter.Length > 0) - { - var filterSql = new Sql(); - var first = true; - foreach (var filterClaus in auditTypeFilter) - { - if (first == false || hasCustomFilter) - { - filterSql.Append(" AND "); - } - filterSql.Append("(logHeader = @logHeader)", new {logHeader = filterClaus.ToString() }); - first = false; - } - - translatedQuery = GetFilteredSqlForPagedResults(translatedQuery, filterSql); - } - - if (orderDirection == Direction.Descending) - translatedQuery.OrderByDescending("Datestamp"); - else - translatedQuery.OrderBy("Datestamp"); - - // Get page of results and total count - var pagedResult = Database.Page(pageIndex + 1, pageSize, translatedQuery); - totalRecords = pagedResult.TotalItems; - - var pages = pagedResult.Items.Select( - dto => new AuditItem(dto.Id, dto.Comment, Enum.Parse(dto.Header), dto.UserId)).ToArray(); - - //Mapping the DateStamp - for (int i = 0; i < pages.Length; i++) - { - pages[i].CreateDate = pagedResult.Items[i].Datestamp; - } - - return pages; - } - - protected override void PersistUpdatedItem(IAuditItem entity) - { - Database.Insert(new LogDto - { - Comment = entity.Comment, - Datestamp = DateTime.Now, - Header = entity.AuditType.ToString(), - NodeId = entity.Id, - UserId = entity.UserId - }); - } - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = new Sql() - .Select(isCount ? "COUNT(*)" : "umbracoLog.id, umbracoLog.userId, umbracoLog.NodeId, umbracoLog.Datestamp, umbracoLog.logHeader, umbracoLog.logComment, umbracoUser.userName, umbracoUser.avatar as userAvatar") - .From(SqlSyntax); - if (isCount == false) - { - sql = sql.LeftJoin(SqlSyntax).On(SqlSyntax, dto => dto.Id, dto => dto.UserId); - } - return sql; - } - - #region Not Implemented - not needed currently - - protected override void PersistNewItem(IAuditItem entity) - { - throw new NotImplementedException(); - } - - protected override IAuditItem PerformGet(int id) - { - throw new NotImplementedException(); - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - throw new NotImplementedException(); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new NotImplementedException(); - } - - protected override string GetBaseWhereClause() - { - throw new NotImplementedException(); - } - - protected override IEnumerable GetDeleteClauses() - { - throw new NotImplementedException(); - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - #endregion - - private Sql GetFilteredSqlForPagedResults(Sql sql, Sql filterSql) - { - Sql filteredSql; - - // Apply filter - if (filterSql != null) - { - var sqlFilter = " WHERE " + filterSql.SQL.TrimStart("AND "); - - //NOTE: this is certainly strange - NPoco handles this much better but we need to re-create the sql - // instance a couple of times to get the parameter order correct, for some reason the first - // time the arguments don't show up correctly but the SQL argument parameter names are actually updated - // accordingly - so we re-create it again. In v8 we don't need to do this and it's already taken care of. - - filteredSql = new Sql(sql.SQL, sql.Arguments); - var args = filteredSql.Arguments.Concat(filterSql.Arguments).ToArray(); - filteredSql = new Sql( - string.Format("{0} {1}", filteredSql.SQL, sqlFilter), - args); - filteredSql = new Sql(filteredSql.SQL, args); - } - else - { - //copy to var so that the original isn't changed - filteredSql = new Sql(sql.SQL, sql.Arguments); - } - return filteredSql; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs b/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs deleted file mode 100644 index 05061c47af..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/BaseQueryType.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Umbraco.Core.Persistence.Repositories -{ - internal enum BaseQueryType - { - /// - /// A query to return all information for a single item - /// - /// - /// In some cases this will be the same as - /// - FullSingle, - - /// - /// A query to return all information for multiple items - /// - /// - /// In some cases this will be the same as - /// - FullMultiple, - - /// - /// A query to return the ids for items - /// - Ids, - - /// - /// A query to return the count for items - /// - Count - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentBlueprintRepository.cs deleted file mode 100644 index 4c9021d20a..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/ContentBlueprintRepository.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Override the base content repository so we can change the node object type - /// - /// - /// It would be nicer if we could separate most of this down into a smaller version of the ContentRepository class, however to do that - /// requires quite a lot of work since we'd need to re-organize the interhitance quite a lot or create a helper class to perform a lot of the underlying logic. - /// - /// TODO: Create a helper method to contain most of the underlying logic for the ContentRepository - /// - internal class ContentBlueprintRepository : ContentRepository - { - public ContentBlueprintRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) - : base(work, cacheHelper, logger, syntaxProvider, contentTypeRepository, templateRepository, tagRepository, contentSection) - { - } - - protected override Guid NodeObjectTypeId - { - get { return Constants.ObjectTypes.DocumentBlueprintGuid; } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs deleted file mode 100644 index ee19161ae0..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Private class to handle preview insert/update based on standard principles and units of work with transactions - /// - internal class ContentPreviewRepository : PetaPocoRepositoryBase> - where TContent : IContentBase - { - public ContentPreviewRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { } - - #region Not implemented (don't need to for the purposes of this repo) - protected override ContentPreviewEntity PerformGet(int id) - { - throw new NotImplementedException(); - } - - protected override IEnumerable> PerformGetAll(params int[] ids) - { - throw new NotImplementedException(); - } - - protected override IEnumerable> PerformGetByQuery(IQuery> query) - { - throw new NotImplementedException(); - } - - protected override Sql GetBaseQuery(bool isCount) - { - throw new NotImplementedException(); - } - - protected override string GetBaseWhereClause() - { - throw new NotImplementedException(); - } - - protected override IEnumerable GetDeleteClauses() - { - return new List(); - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - - protected override void PersistDeletedItem(ContentPreviewEntity entity) - { - throw new NotImplementedException(); - } - - //NOTE: Not implemented because all ContentPreviewEntity will always return false for having an Identity - protected override void PersistUpdatedItem(ContentPreviewEntity entity) - { - throw new NotImplementedException(); - } - - #endregion - - protected override void PersistNewItem(ContentPreviewEntity entity) - { - if (entity.Content.HasIdentity == false) - { - throw new InvalidOperationException("Cannot insert or update a preview for a content item that has no identity"); - } - - var previewPoco = new PreviewXmlDto - { - NodeId = entity.Id, - Timestamp = DateTime.Now, - VersionId = entity.Version, - Xml = entity.Xml.ToDataString() - }; - - //We need to do a special InsertOrUpdate here because we know that the PreviewXmlDto table has a composite key and thus - // a unique constraint which can be violated if 2+ threads try to execute the same insert sql at the same time. - Database.InsertOrUpdate(previewPoco, - //Since the table has a composite key, we need to specify an explit update statement - "SET xml = @Xml, timestamp = @Timestamp WHERE nodeId=@NodeId AND versionId=@VersionId", - new {NodeId = previewPoco.NodeId, VersionId = previewPoco.VersionId, Xml = previewPoco.Xml, Timestamp = previewPoco.Timestamp}); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs deleted file mode 100644 index 6c00964c15..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ /dev/null @@ -1,1310 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using System.Linq; -using System.Xml; -using System.Xml.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Represents a repository for doing CRUD operations for - /// - internal class ContentRepository : RecycleBinRepository, IContentRepository - { - private readonly IContentTypeRepository _contentTypeRepository; - private readonly ITemplateRepository _templateRepository; - private readonly ITagRepository _tagRepository; - private readonly ContentPreviewRepository _contentPreviewRepository; - private readonly ContentXmlRepository _contentXmlRepository; - private readonly PermissionRepository _permissionRepository; - private readonly ContentByGuidReadRepository _contentByGuidReadRepository; - - public ContentRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) - : base(work, cacheHelper, logger, syntaxProvider, contentSection) - { - if (contentTypeRepository == null) throw new ArgumentNullException("contentTypeRepository"); - if (templateRepository == null) throw new ArgumentNullException("templateRepository"); - if (tagRepository == null) throw new ArgumentNullException("tagRepository"); - _contentTypeRepository = contentTypeRepository; - _templateRepository = templateRepository; - _tagRepository = tagRepository; - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.NoCache, logger, syntaxProvider); - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.NoCache, logger, syntaxProvider); - _permissionRepository = new PermissionRepository(UnitOfWork, cacheHelper, Logger, SqlSyntax); - _contentByGuidReadRepository = new ContentByGuidReadRepository(this, work, cacheHelper, logger, syntaxProvider); - EnsureUniqueNaming = true; - } - - public bool EnsureUniqueNaming { get; set; } - - #region Overrides of RepositoryBase - - protected override IContent PerformGet(int id) - { - var sql = GetBaseQuery(BaseQueryType.FullSingle) - .Where(GetBaseWhereClause(), new { Id = id }) - .Where(x => x.Newest, SqlSyntax) - .OrderByDescending(x => x.VersionDate, SqlSyntax); - - var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateContentFromDto(dto, sql); - - return content; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - Func translate = s => - { - if (ids.Any()) - { - s.Where("umbracoNode.id in (@ids)", new { ids }); - } - //we only want the newest ones with this method - s.Where(x => x.Newest, SqlSyntax); - return s; - }; - - var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple); - var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids); - - return ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds))); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple); - var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids); - - Func, Sql> translate = (translator) => - { - return translator.Translate() - .Where(x => x.Newest, SqlSyntax) - .OrderByDescending(x => x.VersionDate, SqlSyntax) - .OrderBy(x => x.SortOrder, SqlSyntax); - }; - - var translatorFull = new SqlTranslator(sqlBaseFull, query); - var translatorIds = new SqlTranslator(sqlBaseIds, query); - - return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds))); - } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - - /// - /// Returns the base query to return Content - /// - /// - /// - /// - /// Content queries will differ depending on what needs to be returned: - /// * FullSingle: When querying for a single document, this will include the Outer join to fetch the content item's published version info - /// * FullMultiple: When querying for multiple documents, this will exclude the Outer join to fetch the content item's published version info - this info would need to be fetched separately - /// * Ids: This would essentially be the same as FullMultiple however the columns specified will only return the Ids for the documents - /// * Count: A query to return the count for documents - /// - protected override Sql GetBaseQuery(BaseQueryType queryType) - { - var sql = new Sql(); - sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsDocument.nodeId" : "*")) - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.VersionId, right => right.VersionId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.ContentTypeId); - //TODO: IF we want to enable querying on content type information this will need to be joined - //.InnerJoin(SqlSyntax) - //.On(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax); - - if (queryType == BaseQueryType.FullSingle) - { - //The only reason we apply this left outer join is to be able to pull back the DocumentPublishedReadOnlyDto - //information with the entire data set, so basically this will get both the latest document and also it's published - //version if it has one. When performing a count or when retrieving Ids like in paging, this is unecessary - //and causes huge performance overhead for the SQL server, especially when sorting the result. - //We also don't include this outer join when querying for multiple entities since it is much faster to fetch this information - //in a separate query. For a single entity this is ok. - - var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)", - SqlSyntax.GetQuotedTableName("cmsDocument"), - SqlSyntax.GetQuotedTableName("cmsDocument2"), - SqlSyntax.GetQuotedColumnName("nodeId"), - SqlSyntax.GetQuotedColumnName("published")); - - // cannot do this because PetaPoco does not know how to alias the table - //.LeftOuterJoin() - //.On(left => left.NodeId, right => right.NodeId) - // so have to rely on writing our own SQL - sql.Append(sqlx /*, new { @published = true }*/); - } - - sql.Where(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax); - - return sql; - } - - protected override Sql GetBaseQuery(bool isCount) - { - return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle); - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.id = @Id"; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM umbracoRedirectUrl WHERE contentKey IN (SELECT uniqueID FROM umbracoNode WHERE id = @Id)", - "DELETE FROM cmsTask WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @Id", - "DELETE FROM umbracoUserStartNode WHERE startNode = @Id", - "UPDATE umbracoUserGroup SET startContentId = NULL WHERE startContentId = @Id", - "DELETE FROM umbracoRelation WHERE parentId = @Id", - "DELETE FROM umbracoRelation WHERE childId = @Id", - "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM umbracoDomains WHERE domainRootStructureID = @Id", - "DELETE FROM cmsDocument WHERE nodeId = @Id", - "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", - "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", - "DELETE FROM cmsContentVersion WHERE ContentId = @Id", - "DELETE FROM cmsContentXml WHERE nodeId = @Id", - "DELETE FROM cmsContent WHERE nodeId = @Id", - "DELETE FROM umbracoAccess WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { return Constants.ObjectTypes.DocumentGuid; } - } - - #endregion - - #region Overrides of VersionableRepositoryBase - - public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) - { - // the previous way of doing this was to run it all in one big transaction, - // and to bulk-insert groups of xml rows - which works, until the transaction - // times out - and besides, because v7 transactions are ReadCommited, it does - // not bring much safety - so this reverts to updating each record individually, - // and it may be slower in the end, but should be more resilient. - - var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray(); - - Func translate = (bId, sql) => - { - if (contentTypeIdsA.Length > 0) - { - sql.WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); - } - - sql - .Where(x => x.NodeId > bId && x.Trashed == false, SqlSyntax) - .Where(x => x.Published, SqlSyntax) - .OrderBy(x => x.NodeId, SqlSyntax); - - return sql; - }; - - var baseId = 0; - - while (true) - { - // get the next group of nodes - var sqlFull = translate(baseId, GetBaseQuery(BaseQueryType.FullMultiple)); - var sqlIds = translate(baseId, GetBaseQuery(BaseQueryType.Ids)); - - var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), new PagingSqlQuery(SqlSyntax.SelectTop(sqlIds, groupSize))) - .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) - .ToList(); - - // no more nodes, break - if (xmlItems.Count == 0) break; - - foreach (var xmlItem in xmlItems) - { - try - { - // should happen in most cases, then it tries to insert, and it should work - // unless the node has been deleted, and we just report the exception - Database.InsertOrUpdate(xmlItem); - } - catch (Exception e) - { - Logger.Error("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e); - } - } - baseId = xmlItems[xmlItems.Count - 1].NodeId; - } - - //now delete the items that shouldn't be there - var sqlAllIds = translate(0, GetBaseQuery(BaseQueryType.Ids)); - var allContentIds = Database.Fetch(sqlAllIds); - var docObjectType = NodeObjectTypeId; - var xmlIdsQuery = new Sql() - .Select("DISTINCT cmsContentXml.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId); - - if (contentTypeIdsA.Length > 0) - { - xmlIdsQuery.InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); - } - - xmlIdsQuery.Where(dto => dto.NodeObjectType == docObjectType, SqlSyntax); - - var allXmlIds = Database.Fetch(xmlIdsQuery); - - var toRemove = allXmlIds.Except(allContentIds).ToArray(); - if (toRemove.Length > 0) - { - foreach (var idGroup in toRemove.InGroupsOf(2000)) - { - Database.Execute("DELETE FROM cmsContentXml WHERE nodeId IN (@ids)", new { ids = idGroup }); - } - } - - } - - public override IEnumerable GetAllVersions(int id) - { - Func translate = s => - { - return s.Where(GetBaseWhereClause(), new {Id = id}) - .OrderByDescending(x => x.VersionDate, SqlSyntax); - }; - - var sqlFull = translate(GetBaseQuery(BaseQueryType.FullMultiple)); - var sqlIds = translate(GetBaseQuery(BaseQueryType.Ids)); - - return ProcessQuery(sqlFull, new PagingSqlQuery(sqlIds), true, includeAllVersions:true); - } - - public override IContent GetByVersion(Guid versionId) - { - var sql = GetBaseQuery(BaseQueryType.FullSingle); - //TODO: cmsContentVersion.VersionId has a Unique Index constraint applied, seems silly then to also add OrderByDescending since it would be impossible to return more than one. - sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate, SqlSyntax); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateContentFromDto(dto, sql); - - return content; - } - - public override void DeleteVersion(Guid versionId) - { - var sql = new Sql() - .Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.VersionId, right => right.VersionId) - .Where(x => x.VersionId == versionId, SqlSyntax) - .Where(x => x.Newest != true, SqlSyntax); - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) return; - - using (var transaction = Database.GetTransaction()) - { - PerformDeleteVersion(dto.NodeId, versionId); - - transaction.Complete(); - } - } - - public override void DeleteVersions(int id, DateTime versionDate) - { - var sql = new Sql() - .Select("*") - .From() - .InnerJoin() - .On(left => left.VersionId, right => right.VersionId) - .Where(x => x.NodeId == id) - .Where(x => x.VersionDate < versionDate) - .Where(x => x.Newest != true); - var list = Database.Fetch(sql); - if (list.Any() == false) return; - - using (var transaction = Database.GetTransaction()) - { - foreach (var dto in list) - { - PerformDeleteVersion(id, dto.VersionId); - } - - transaction.Complete(); - } - } - - protected override void PerformDeleteVersion(int id, Guid versionId) - { - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - } - - #endregion - - #region Unit of Work Implementation - - protected override void PersistDeletedItem(IContent entity) - { - //We need to clear out all access rules but we need to do this in a manual way since - // nothing in that table is joined to a content id - var subQuery = new Sql() - .Select("umbracoAccessRule.accessId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.AccessId, right => right.Id) - .Where(dto => dto.NodeId == entity.Id); - Database.Execute(SqlSyntax.GetDeleteSubquery("umbracoAccessRule", "accessId", subQuery)); - - //now let the normal delete clauses take care of everything else - base.PersistDeletedItem(entity); - } - - protected override void PersistNewItem(IContent entity) - { - ((Content)entity).AddingEntity(); - - //ensure the default template is assigned - if (entity.Template == null) - { - entity.Template = entity.ContentType.DefaultTemplate; - } - - //Ensure unique name on the same level - entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - var factory = new ContentFactory(NodeObjectTypeId, entity.Id); - var dto = factory.BuildDto(entity); - - //NOTE Should the logic below have some kind of fallback for empty parent ids ? - //Logic for setting Path, Level and SortOrder - var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); - var level = parent.Level + 1; - var maxSortOrder = Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); - var sortOrder = maxSortOrder + 1; - - //Create the (base) node data - umbracoNode - var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; - nodeDto.Path = parent.Path; - nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); - nodeDto.SortOrder = sortOrder; - - // note: - // there used to be a check on Database.IsNew(nodeDto) here to either Insert or Update, - // but I cannot figure out what was the point, as the node should obviously be new if - // we reach that point - removed. - - // see if there's a reserved identifier for this unique id - var sql = new Sql("SELECT id FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", nodeDto.UniqueId, Constants.ObjectTypes.IdReservationGuid); - var id = Database.ExecuteScalar(sql); - if (id > 0) - { - nodeDto.NodeId = id; - Database.Update(nodeDto); - } - else - { - Database.Insert(nodeDto); - } - - //Update with new correct path - nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); - nodeDto.ValidatePathWithException(); - Database.Update(nodeDto); - - //Update entity with correct values - entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set - entity.Path = nodeDto.Path; - entity.SortOrder = sortOrder; - entity.Level = level; - - //Create the Content specific data - cmsContent - var contentDto = dto.ContentVersionDto.ContentDto; - contentDto.NodeId = nodeDto.NodeId; - Database.Insert(contentDto); - - //Create the first version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - var contentVersionDto = dto.ContentVersionDto; - contentVersionDto.NodeId = nodeDto.NodeId; - Database.Insert(contentVersionDto); - - //Create the Document specific data for this version - cmsDocument - //Assumes a new Version guid has been generated - dto.NodeId = nodeDto.NodeId; - Database.Insert(dto); - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - - //Update Properties with its newly set Id - foreach (var property in entity.Properties) - { - property.Id = keyDictionary[property.PropertyTypeId]; - } - - //lastly, check if we are a creating a published version , then update the tags table - if (entity.Published) - { - UpdatePropertyTags(entity, _tagRepository); - } - - // published => update published version infos, else leave it blank - if (entity.Published) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = dto.VersionId, - VersionDate = dto.UpdateDate, - Newest = true, - NodeId = dto.NodeId, - Published = true - }; - ((Content) entity).PublishedVersionGuid = dto.VersionId; - ((Content) entity).PublishedDate = dto.UpdateDate; - } - - entity.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(IContent entity) - { - var publishedState = ((Content)entity).PublishedState; - - //check if we need to make any database changes at all - if (entity.RequiresSaving(publishedState) == false) - { - entity.ResetDirtyProperties(); - return; - } - - //check if we need to create a new version - bool shouldCreateNewVersion = entity.ShouldCreateNewVersion(publishedState); - if (shouldCreateNewVersion) - { - //Updates Modified date and Version Guid - ((Content)entity).UpdatingEntity(); - } - else - { - if (entity.IsPropertyDirty("UpdateDate") == false || entity.UpdateDate == default(DateTime)) - entity.UpdateDate = DateTime.Now; - } - - //Ensure unique name on the same level - entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed - if (entity.IsPropertyDirty("ParentId")) - { - var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); - entity.Path = string.Concat(parent.Path, ",", entity.Id); - entity.Level = parent.Level + 1; - var maxSortOrder = - Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); - entity.SortOrder = maxSortOrder + 1; - - //Question: If we move a node, should we update permissions to inherit from the new parent if the parent has permissions assigned? - // if we do that, then we'd need to propogate permissions all the way downward which might not be ideal for many people. - // Gonna just leave it as is for now, and not re-propogate permissions. - } - - var factory = new ContentFactory(NodeObjectTypeId, entity.Id); - //Look up Content entry to get Primary for updating the DTO - var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { Id = entity.Id }); - factory.SetPrimaryKey(contentDto.PrimaryKey); - var dto = factory.BuildDto(entity); - - //Updates the (base) node data - umbracoNode - var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; - nodeDto.ValidatePathWithException(); - var o = Database.Update(nodeDto); - - //Only update this DTO if the contentType has actually changed - if (contentDto.ContentTypeId != entity.ContentTypeId) - { - //Create the Content specific data - cmsContent - var newContentDto = dto.ContentVersionDto.ContentDto; - Database.Update(newContentDto); - } - - //a flag that we'll use later to create the tags in the tag db table - var publishedStateChanged = false; - - //If Published state has changed then previous versions should have their publish state reset. - //If state has been changed to unpublished the previous versions publish state should also be reset. - //if (((ICanBeDirty)entity).IsPropertyDirty("Published") && (entity.Published || publishedState == PublishedState.Unpublished)) - if (entity.ShouldClearPublishedFlagForPreviousVersions(publishedState, shouldCreateNewVersion)) - { - //TODO: This perf can be improved, it could easily be UPDATE WHERE.... (one SQL call instead of many) - var publishedDocs = Database.Fetch("WHERE nodeId = @Id AND published = @IsPublished", new { Id = entity.Id, IsPublished = true }); - foreach (var doc in publishedDocs) - { - var docDto = doc; - docDto.Published = false; - Database.Update(docDto); - } - - //this is a newly published version so we'll update the tags table too (end of this method) - publishedStateChanged = true; - } - - //Look up (newest) entries by id in cmsDocument table to set newest = false - //TODO: This perf can be improved, it could easily be UPDATE WHERE.... (one SQL call instead of many) - var documentDtos = Database.Fetch("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true }); - foreach (var documentDto in documentDtos) - { - var docDto = documentDto; - docDto.Newest = false; - Database.Update(docDto); - } - - var contentVersionDto = dto.ContentVersionDto; - if (shouldCreateNewVersion) - { - //Create a new version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - Database.Insert(contentVersionDto); - //Create the Document specific data for this version - cmsDocument - //Assumes a new Version guid has been generated - Database.Insert(dto); - } - else - { - //In order to update the ContentVersion we need to retrieve its primary key id - var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { Version = entity.Version }); - if (contentVerDto != null) - { - contentVersionDto.Id = contentVerDto.Id; - Database.Update(contentVersionDto); - } - - Database.Update(dto); - } - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - if (shouldCreateNewVersion == false && propertyDataDto.Id > 0) - { - Database.Update(propertyDataDto); - } - else - { - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - } - - //Update Properties with its newly set Id - if (keyDictionary.Any()) - { - foreach (var property in entity.Properties) - { - if (keyDictionary.ContainsKey(property.PropertyTypeId) == false) continue; - - property.Id = keyDictionary[property.PropertyTypeId]; - } - } - - //lastly, check if we are a newly published version and then update the tags table - if (publishedStateChanged && entity.Published) - { - UpdatePropertyTags(entity, _tagRepository); - } - else if (publishedStateChanged && (entity.Trashed || entity.Published == false)) - { - //it's in the trash or not published remove all entity tags - ClearEntityTags(entity, _tagRepository); - } - - // published => update published version infos, - // else if unpublished then clear published version infos - if (entity.Published) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = dto.VersionId, - VersionDate = dto.UpdateDate, - Newest = true, - NodeId = dto.NodeId, - Published = true - }; - ((Content) entity).PublishedVersionGuid = dto.VersionId; - ((Content) entity).PublishedDate = dto.UpdateDate; - } - else if (publishedStateChanged) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = default (Guid), - VersionDate = default (DateTime), - Newest = false, - NodeId = dto.NodeId, - Published = false - }; - ((Content) entity).PublishedVersionGuid = default(Guid); - ((Content) entity).PublishedDate = default (DateTime); - } - - entity.ResetDirtyProperties(); - } - - - #endregion - - #region Implementation of IContentRepository - - public IEnumerable GetByPublishedVersion(IQuery query) - { - Func, Sql> translate = t => - { - return t.Translate() - .Where(x => x.Published, SqlSyntax) - .OrderBy(x => x.Level, SqlSyntax) - .OrderBy(x => x.SortOrder, SqlSyntax); - }; - - // we WANT to return contents in top-down order, ie parents should come before children - // ideal would be pure xml "document order" which can be achieved with: - // ORDER BY substring(path, 1, len(path) - charindex(',', reverse(path))), sortOrder - // but that's probably an overkill - sorting by level,sortOrder should be enough - - var sqlFull = GetBaseQuery(BaseQueryType.FullMultiple); - var translatorFull = new SqlTranslator(sqlFull, query); - var sqlIds = GetBaseQuery(BaseQueryType.Ids); - var translatorIds = new SqlTranslator(sqlIds, query); - - return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true); - } - - public IEnumerable GetBlueprints(IQuery query) - { - Func, Sql> translate = t => t.Translate(); - - var sqlFull = GetBaseQuery(BaseQueryType.FullMultiple); - var translatorFull = new SqlTranslator(sqlFull, query); - var sqlIds = GetBaseQuery(BaseQueryType.Ids); - var translatorIds = new SqlTranslator(sqlIds, query); - - return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true); - } - - /// - /// This builds the Xml document used for the XML cache - /// - /// - public XmlDocument BuildXmlCache() - { - //TODO: This is what we should do , but converting to use XDocument would be breaking unless we convert - // to XmlDocument at the end of this, but again, this would be bad for memory... though still not nearly as - // bad as what is happening before! - // We'll keep using XmlDocument for now though, but XDocument xml generation is much faster: - // https://blogs.msdn.microsoft.com/codejunkie/2008/10/08/xmldocument-vs-xelement-performance/ - // I think we already have code in here to convert XDocument to XmlDocument but in case we don't here - // it is: https://blogs.msdn.microsoft.com/marcelolr/2009/03/13/fast-way-to-convert-xmldocument-into-xdocument/ - - //// Prepare an XmlDocument with an appropriate inline DTD to match - //// the expected content - //var parent = new XElement("root", new XAttribute("id", "-1")); - //var xmlDoc = new XDocument( - // new XDocumentType("root", null, null, DocumentType.GenerateDtd()), - // parent); - - var xmlDoc = new XmlDocument(); - var doctype = xmlDoc.CreateDocumentType("root", null, null, - ApplicationContext.Current.Services.ContentTypeService.GetContentTypesDtd()); - xmlDoc.AppendChild(doctype); - var parent = xmlDoc.CreateElement("root"); - var pIdAtt = xmlDoc.CreateAttribute("id"); - pIdAtt.Value = "-1"; - parent.Attributes.Append(pIdAtt); - xmlDoc.AppendChild(parent); - - //Ensure that only nodes that have published versions are selected - var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.{0}, umbracoNode.{1} from umbracoNode -inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type -where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1) -order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", - SqlSyntax.GetQuotedColumnName("xml"), - SqlSyntax.GetQuotedColumnName("level"), - SqlSyntax.GetQuotedColumnName("level")); - - XmlElement last = null; - - //NOTE: Query creates a reader - does not load all into memory - foreach (var row in Database.Query(sql, new { type = NodeObjectTypeId })) - { - string parentId = ((int)row.parentID).ToInvariantString(); - string xml = row.xml; - int sortOrder = row.sortOrder; - - //if the parentid is changing - if (last != null && last.GetAttribute("parentID") != parentId) - { - parent = xmlDoc.GetElementById(parentId); - if (parent == null) - { - //Need to short circuit here, if the parent is not there it means that the parent is unpublished - // and therefore the child is not published either so cannot be included in the xml cache - continue; - } - } - - var xmlDocFragment = xmlDoc.CreateDocumentFragment(); - xmlDocFragment.InnerXml = xml; - - last = (XmlElement)parent.AppendChild(xmlDocFragment); - - // fix sortOrder - see notes in UpdateSortOrder - last.Attributes["sortOrder"].Value = sortOrder.ToInvariantString(); - } - - return xmlDoc; - - } - - public int CountPublished(string contentTypeAlias = null) - { - if (contentTypeAlias.IsNullOrWhiteSpace()) - { - var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true); - return Database.ExecuteScalar(sql); - } - else - { - var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true) - .Where(x => x.Alias == contentTypeAlias); - return Database.ExecuteScalar(sql); - } - } - - public void ReplaceContentPermissions(EntityPermissionSet permissionSet) - { - _permissionRepository.ReplaceEntityPermissions(permissionSet); - } - - public void ClearPublished(IContent content) - { - var sql = "UPDATE cmsDocument SET published=0 WHERE nodeId=@id AND published=1"; - Database.Execute(sql, new {id = content.Id}); - } - - /// - /// Assigns a single permission to the current content item for the specified user group ids - /// - /// - /// - /// - public void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds) - { - _permissionRepository.AssignEntityPermission(entity, permission, groupIds); - } - - /// - /// Gets the explicit list of permissions for the content item - /// - /// - /// - public EntityPermissionCollection GetPermissionsForEntity(int entityId) - { - return _permissionRepository.GetPermissionsForEntity(entityId); - } - - /// - /// Adds/updates content/published xml - /// - /// - /// - public void AddOrUpdateContentXml(IContent content, Func xml) - { - _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); - } - - /// - /// Used to add/update a permission for a content item - /// - /// - public void AddOrUpdatePermissions(ContentPermissionSet permission) - { - _permissionRepository.AddOrUpdate(permission); - } - - /// - /// Used to remove the content xml for a content item - /// - /// - public void DeleteContentXml(IContent content) - { - _contentXmlRepository.Delete(new ContentXmlEntity(content)); - } - - /// - /// Adds/updates preview xml - /// - /// - /// - public void AddOrUpdatePreviewXml(IContent content, Func xml) - { - _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml)); - } - - /// - /// Gets paged content results - /// - /// Query to excute - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null) - { - - //NOTE: This uses the GetBaseQuery method but that does not take into account the required 'newest' field which is - // what we always require for a paged result, so we'll ensure it's included in the filter - - var filterSql = new Sql().Append("AND (cmsDocument.newest = 1)"); - if (filter != null) - { - foreach (var filterClause in filter.GetWhereClauses()) - { - filterSql.Append(string.Format("AND ({0})", filterClause.Item1), filterClause.Item2); - } - } - - Func> filterCallback = () => new Tuple(filterSql.SQL, filterSql.Arguments); - - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - new Tuple("cmsDocument", "nodeId"), - (sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField, - filterCallback); - - } - - #endregion - - #region IRecycleBinRepository members - - protected override int RecycleBinId - { - get { return Constants.System.RecycleBinContent; } - } - - #endregion - - #region Read Repository implementation for GUID keys - public IContent Get(Guid id) - { - return _contentByGuidReadRepository.Get(id); - } - - IEnumerable IReadRepository.GetAll(params Guid[] ids) - { - return _contentByGuidReadRepository.GetAll(ids); - } - - public bool Exists(Guid id) - { - return _contentByGuidReadRepository.Exists(id); - } - - /// - /// A reading repository purely for looking up by GUID - /// - /// - /// TODO: This is ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things! - /// Then we can do the same thing with repository instances and we wouldn't need to leave all these methods as not implemented because we wouldn't need to implement them - /// - private class ContentByGuidReadRepository : PetaPocoRepositoryBase - { - private readonly ContentRepository _outerRepo; - - public ContentByGuidReadRepository(ContentRepository outerRepo, - IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - _outerRepo = outerRepo; - } - - protected override IContent PerformGet(Guid id) - { - var sql = _outerRepo.GetBaseQuery(BaseQueryType.FullSingle) - .Where(GetBaseWhereClause(), new { Id = id }) - .Where(x => x.Newest, SqlSyntax) - .OrderByDescending(x => x.VersionDate, SqlSyntax); - - var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); - - if (dto == null) - return null; - - var content = _outerRepo.CreateContentFromDto(dto, sql); - - return content; - } - - protected override IEnumerable PerformGetAll(params Guid[] ids) - { - Func translate = s => - { - if (ids.Any()) - { - s.Where("umbracoNode.uniqueID in (@ids)", new { ids }); - } - //we only want the newest ones with this method - s.Where(x => x.Newest, SqlSyntax); - return s; - }; - - var sqlBaseFull = _outerRepo.GetBaseQuery(BaseQueryType.FullMultiple); - var sqlBaseIds = _outerRepo.GetBaseQuery(BaseQueryType.Ids); - - return _outerRepo.ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds))); - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _outerRepo.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.uniqueID = @Id"; - } - - protected override Guid NodeObjectTypeId - { - get { return _outerRepo.NodeObjectTypeId; } - } - - #region Not needed to implement - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new NotImplementedException(); - } - protected override IEnumerable GetDeleteClauses() - { - throw new NotImplementedException(); - } - protected override void PersistNewItem(IContent entity) - { - throw new NotImplementedException(); - } - protected override void PersistUpdatedItem(IContent entity) - { - throw new NotImplementedException(); - } - #endregion - } - #endregion - - protected override string GetDatabaseFieldNameForOrderBy(string orderBy) - { - //Some custom ones - switch (orderBy.ToUpperInvariant()) - { - case "NAME": - return "cmsDocument.text"; - case "UPDATER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "cmsDocument.documentUser"; - } - - return base.GetDatabaseFieldNameForOrderBy(orderBy); - } - - /// - /// This is the underlying method that processes most queries for this repository - /// - /// - /// The FullMultiple SQL without the outer join to return all data required to create an IContent excluding it's published state data which this will query separately - /// - /// - /// The Id SQL without the outer join to just return all document ids - used to process the properties for the content item - /// - /// - /// - /// Generally when querying for content we only want to return the most recent version of the content item, however in some cases like when - /// we want to return all versions of a content item, we can't simply return the latest - /// - /// - private IEnumerable ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false, bool includeAllVersions = false) - { - // fetch returns a list so it's ok to iterate it in this method - var dtos = Database.Fetch(sqlFull); - if (dtos.Count == 0) return Enumerable.Empty(); - - //Go and get all of the published version data separately for this data, this is because when we are querying - //for multiple content items we don't include the outer join to fetch this data in the same query because - //it is insanely slow. Instead we just fetch the published version data separately in one query. - - //we need to parse the original SQL statement and reduce the columns to just cmsDocument.nodeId so that we can use - // the statement to go get the published data for all of the items by using an inner join - var parsedOriginalSql = "SELECT cmsDocument.nodeId " + sqlFull.SQL.Substring(sqlFull.SQL.IndexOf("FROM", StringComparison.Ordinal)); - //now remove everything from an Orderby clause and beyond - if (parsedOriginalSql.InvariantContains("ORDER BY ")) - { - parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); - } - - //order by update date DESC, if there is corrupted published flags we only want the latest! - var publishedSql = new Sql(@"SELECT cmsDocument.nodeId, cmsDocument.published, cmsDocument.versionId, cmsDocument.updateDate, cmsDocument.newest -FROM cmsDocument INNER JOIN cmsContentVersion ON cmsContentVersion.VersionId = cmsDocument.versionId -WHERE cmsDocument.published = 1 AND cmsDocument.nodeId IN -(" + parsedOriginalSql + @") -ORDER BY cmsContentVersion.id DESC -", sqlFull.Arguments); - - //go and get the published version data, we do a Query here and not a Fetch so we are - //not allocating a whole list to memory just to allocate another list in memory since - //we are assigning this data to a keyed collection for fast lookup below - var publishedData = Database.Query(publishedSql); - var publishedDataCollection = new DocumentPublishedReadOnlyDtoCollection(); - foreach (var publishedDto in publishedData) - { - //double check that there's no corrupt db data, there should only be a single published item - if (publishedDataCollection.Contains(publishedDto.NodeId) == false) - publishedDataCollection.Add(publishedDto); - } - - //This is a tuple list identifying if the content item came from the cache or not - var content = new List>(); - var defs = new DocumentDefinitionCollection(includeAllVersions); - var templateIds = new List(); - - //track the looked up content types, even though the content types are cached - // they still need to be deep cloned out of the cache and we don't want to add - // the overhead of deep cloning them on every item in this loop - var contentTypes = new Dictionary(); - - foreach (var dto in dtos) - { - DocumentPublishedReadOnlyDto publishedDto; - publishedDataCollection.TryGetValue(dto.NodeId, out publishedDto); - - // if the cache contains the published version, use it - if (withCache) - { - var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); - //only use this cached version if the dto returned is also the publish version, they must match and be teh same version - if (cached != null && cached.Version == dto.VersionId && cached.Published && dto.Published) - { - content.Add(new Tuple(cached, true)); - continue; - } - } - - // else, need to fetch from the database - // content type repository is full-cache so OK to get each one independently - - IContentType contentType; - if (contentTypes.ContainsKey(dto.ContentVersionDto.ContentDto.ContentTypeId)) - { - contentType = contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId]; - } - else - { - contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType; - } - - // track the definition and if it's successfully added or updated then processed - if (defs.AddOrUpdate(new DocumentDefinition(dto, contentType))) - { - // assign template - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - templateIds.Add(dto.TemplateId.Value); - - content.Add(new Tuple(ContentFactory.BuildEntity(dto, contentType, publishedDto), false)); - } - } - - // load all required templates in 1 query - var templates = _templateRepository.GetAll(templateIds.ToArray()) - .ToDictionary(x => x.Id, x => x); - - // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(pagingSqlQuery, defs); - - // assign template and property data - foreach (var contentItem in content) - { - var cc = contentItem.Item1; - var fromCache = contentItem.Item2; - - //if this has come from cache, we do not need to build up it's structure - if (fromCache) continue; - - var def = defs[includeAllVersions ? (ValueType)cc.Version : cc.Id]; - - ITemplate template = null; - if (def.DocumentDto.TemplateId.HasValue) - templates.TryGetValue(def.DocumentDto.TemplateId.Value, out template); // else null - cc.Template = template; - cc.Properties = propertyData[cc.Version]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - cc.ResetDirtyProperties(false); - } - - return content.Select(x => x.Item1).ToArray(); - } - - /// - /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. - /// - /// - /// - /// - private IContent CreateContentFromDto(DocumentDto dto, Sql docSql) - { - var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - - var content = ContentFactory.BuildEntity(dto, contentType); - - //Check if template id is set on DocumentDto, and get ITemplate if it is. - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - { - content.Template = _templateRepository.Get(dto.TemplateId.Value); - } - - var docDef = new DocumentDefinition(dto, contentType); - - var properties = GetPropertyCollection(docSql, new[] { docDef }); - - content.Properties = properties[dto.VersionId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)content).ResetDirtyProperties(false); - return content; - } - - private string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) - { - if (EnsureUniqueNaming == false) - return nodeName; - - var names = Database.Fetch("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId", - new { objectType = NodeObjectTypeId, parentId }); - - return SimilarNodeName.GetUniqueName(names, id, nodeName); - } - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - _contentTypeRepository.Dispose(); - _templateRepository.Dispose(); - _tagRepository.Dispose(); - _contentPreviewRepository.Dispose(); - _contentXmlRepository.Dispose(); - } - - /// - /// A keyed collection for fast lookup when retrieving a separate list of published version data - /// - private class DocumentPublishedReadOnlyDtoCollection : KeyedCollection - { - protected override int GetKeyForItem(DocumentPublishedReadOnlyDto item) - { - return item.NodeId; - } - - public bool TryGetValue(int key, out DocumentPublishedReadOnlyDto val) - { - if (Dictionary == null) - { - val = null; - return false; - } - return Dictionary.TryGetValue(key, out val); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs deleted file mode 100644 index 13081cb2a4..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ /dev/null @@ -1,1270 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Events; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; - -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Represent an abstract Repository for ContentType based repositories - /// - /// Exposes shared functionality - /// - internal abstract class ContentTypeBaseRepository : PetaPocoRepositoryBase, IReadRepository - where TEntity : class, IContentTypeComposition - { - protected ContentTypeBaseRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - } - - public IEnumerable> Move(TEntity toMove, EntityContainer container) - { - var parentId = Constants.System.Root; - if (container != null) - { - // Check on paths - if ((string.Format(",{0},", container.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - throw new DataOperationException(MoveOperationStatusType.FailedNotAllowedByPath); - } - parentId = container.Id; - } - - //used to track all the moved entities to be given to the event - var moveInfo = new List> - { - new MoveEventInfo(toMove, toMove.Path, parentId) - }; - - - // get the level delta (old pos to new pos) - var levelDelta = container == null - ? 1 - toMove.Level - : container.Level + 1 - toMove.Level; - - // move to parent (or -1), update path, save - toMove.ParentId = parentId; - var toMovePath = toMove.Path + ","; // save before changing - toMove.Path = (container == null ? Constants.System.Root.ToString() : container.Path) + "," + toMove.Id; - toMove.Level = container == null ? 1 : container.Level + 1; - AddOrUpdate(toMove); - - //update all descendants, update in order of level - var descendants = GetByQuery(new Query().Where(type => type.Path.StartsWith(toMovePath))); - var paths = new Dictionary(); - paths[toMove.Id] = toMove.Path; - - foreach (var descendant in descendants.OrderBy(x => x.Level)) - { - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); - - descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; - descendant.Level += levelDelta; - - AddOrUpdate(descendant); - } - - return moveInfo; - } - /// - /// Returns the content type ids that match the query - /// - /// - /// - protected IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = new Sql(); - sqlClause.Select("*") - .From(SqlSyntax) - .RightJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.PropertyTypeGroupId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId); - - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - //must be sorted this way for the relator to work - .OrderBy(x => x.Id, SqlSyntax); - - var dtos = Database.Fetch(new GroupPropertyTypeRelator().Map, sql); - - foreach (var dto in dtos.DistinctBy(x => x.ContentTypeNodeId)) - { - yield return dto.ContentTypeNodeId; - } - } - - protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, DataTypeDatabaseType dbType, string propertyTypeAlias) - { - return new PropertyType(propertyEditorAlias, dbType, propertyTypeAlias); - } - - protected void PersistNewBaseContentType(IContentTypeComposition entity) - { - var factory = new ContentTypeFactory(); - var dto = factory.BuildContentTypeDto(entity); - - //Cannot add a duplicate content type type - var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsContentType -INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id -WHERE cmsContentType." + SqlSyntax.GetQuotedColumnName("alias") + @"= @alias -AND umbracoNode.nodeObjectType = @objectType", - new { alias = entity.Alias, objectType = NodeObjectTypeId }); - if (exists > 0) - { - throw new DuplicateNameException("An item with the alias " + entity.Alias + " already exists"); - } - - //Logic for setting Path, Level and SortOrder - var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); - int level = parent.Level + 1; - int sortOrder = - Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); - - //Create the (base) node data - umbracoNode - var nodeDto = dto.NodeDto; - nodeDto.Path = parent.Path; - nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); - nodeDto.SortOrder = sortOrder; - var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); - - //Update with new correct path - nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); - Database.Update(nodeDto); - - //Update entity with correct values - entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set - entity.Path = nodeDto.Path; - entity.SortOrder = sortOrder; - entity.Level = level; - - //Insert new ContentType entry - dto.NodeId = nodeDto.NodeId; - Database.Insert(dto); - - //Insert ContentType composition in new table - foreach (var composition in entity.ContentTypeComposition) - { - if (composition.Id == entity.Id) continue;//Just to ensure that we aren't creating a reference to ourself. - - if (composition.HasIdentity) - { - Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id }); - } - else - { - //Fallback for ContentTypes with no identity - var contentTypeDto = Database.FirstOrDefault("WHERE alias = @Alias", new { Alias = composition.Alias }); - if (contentTypeDto != null) - { - Database.Insert(new ContentType2ContentTypeDto { ParentId = contentTypeDto.NodeId, ChildId = entity.Id }); - } - } - } - - //Insert collection of allowed content types - foreach (var allowedContentType in entity.AllowedContentTypes) - { - Database.Insert(new ContentTypeAllowedContentTypeDto - { - Id = entity.Id, - AllowedId = allowedContentType.Id.Value, - SortOrder = allowedContentType.SortOrder - }); - } - - var propertyFactory = new PropertyGroupFactory(nodeDto.NodeId); - - //Insert Tabs - foreach (var propertyGroup in entity.PropertyGroups) - { - var tabDto = propertyFactory.BuildGroupDto(propertyGroup); - var primaryKey = Convert.ToInt32(Database.Insert(tabDto)); - propertyGroup.Id = primaryKey;//Set Id on PropertyGroup - - //Ensure that the PropertyGroup's Id is set on the PropertyTypes within a group - //unless the PropertyGroupId has already been changed. - foreach (var propertyType in propertyGroup.PropertyTypes) - { - if (propertyType.IsPropertyDirty("PropertyGroupId") == false) - { - var tempGroup = propertyGroup; - propertyType.PropertyGroupId = new Lazy(() => tempGroup.Id); - } - } - } - - //Insert PropertyTypes - foreach (var propertyType in entity.PropertyTypes) - { - var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); - //If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias - if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) - { - AssignDataTypeFromPropertyEditor(propertyType); - } - var propertyTypeDto = propertyFactory.BuildPropertyTypeDto(tabId, propertyType); - int typePrimaryKey = Convert.ToInt32(Database.Insert(propertyTypeDto)); - propertyType.Id = typePrimaryKey; //Set Id on new PropertyType - - //Update the current PropertyType with correct PropertyEditorAlias and DatabaseType - var dataTypeDto = Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = propertyTypeDto.DataTypeId }); - propertyType.PropertyEditorAlias = dataTypeDto.PropertyEditorAlias; - propertyType.DataTypeDatabaseType = dataTypeDto.DbType.EnumParse(true); - } - } - - protected void PersistUpdatedBaseContentType(IContentTypeComposition entity) - { - var factory = new ContentTypeFactory(); - var dto = factory.BuildContentTypeDto(entity); - - // ensure the alias is not used already - var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsContentType -INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id -WHERE cmsContentType." + SqlSyntax.GetQuotedColumnName("alias") + @"= @alias -AND umbracoNode.nodeObjectType = @objectType -AND umbracoNode.id <> @id", - new { id = dto.NodeId, alias = dto.Alias, objectType = NodeObjectTypeId }); - if (exists > 0) - throw new DuplicateNameException("An item with the alias " + dto.Alias + " already exists"); - - // handle (update) the node - var nodeDto = dto.NodeDto; - Database.Update(nodeDto); - - // look up ContentType entry to get PrimaryKey for updating the DTO - // fixme - why? we are UPDATING so we should ALREADY have a PK! - var dtoPk = Database.First("WHERE nodeId = @Id", new { Id = entity.Id }); - dto.PrimaryKey = dtoPk.PrimaryKey; - Database.Update(dto); - - // handle (delete then recreate) compositions - Database.Delete("WHERE childContentTypeId = @Id", new { Id = entity.Id }); - foreach (var composition in entity.ContentTypeComposition) - Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id }); - - // removing a ContentType from a composition (U4-1690) - // 1. Find content based on the current ContentType: entity.Id - // 2. Find all PropertyTypes on the ContentType that was removed - tracked id (key) - // 3. Remove properties based on property types from the removed content type where the content ids correspond to those found in step one - var compositionBase = entity as ContentTypeCompositionBase; - if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null && - compositionBase.RemovedContentTypeKeyTracker.Any()) - { - // find Content based on the current ContentType - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document)) - .Where(x => x.ContentTypeId == entity.Id); - var contentDtos = Database.Fetch(sql); - - // loop through all tracked keys, which corresponds to the ContentTypes that has been removed from the composition - foreach (var key in compositionBase.RemovedContentTypeKeyTracker) - { - // find PropertyTypes for the removed ContentType - var propertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = key }); - // loop through the Content that is based on the current ContentType in order to remove the Properties that are - // based on the PropertyTypes that belong to the removed ContentType. - foreach (var contentDto in contentDtos) - { - foreach (var propertyType in propertyTypes) - { - var nodeId = contentDto.NodeId; - var propertyTypeId = propertyType.Id; - var propertySql = new Sql().Select("cmsPropertyData.id") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) - .Where(x => x.NodeId == nodeId) - .Where(x => x.Id == propertyTypeId); - - // finally delete the properties that match our criteria for removing a ContentType from the composition - Database.Delete(new Sql("WHERE id IN (" + propertySql.SQL + ")", propertySql.Arguments)); - } - } - } - } - - // delete the allowed content type entries before re-inserting the collectino of allowed content types - Database.Delete("WHERE Id = @Id", new { entity.Id }); - foreach (var allowedContentType in entity.AllowedContentTypes) - { - Database.Insert(new ContentTypeAllowedContentTypeDto - { - Id = entity.Id, - AllowedId = allowedContentType.Id.Value, - SortOrder = allowedContentType.SortOrder - }); - } - - // FIXME below, manage the property types - - // delete ??? fixme wtf is this?! - // by excepting entries from db with entries from collections - if (entity.IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) - { - var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = entity.Id }); - var dbPropertyTypeAlias = dbPropertyTypes.Select(x => x.Id); - var entityPropertyTypes = entity.PropertyTypes.Where(x => x.HasIdentity).Select(x => x.Id); - var items = dbPropertyTypeAlias.Except(entityPropertyTypes); - foreach (var item in items) - DeletePropertyType(entity.Id, item); - } - - // delete tabs - // by excepting entries from db with entries from collections - List orphanPropertyTypeIds = null; - if (entity.IsPropertyDirty("PropertyGroups") || entity.PropertyGroups.Any(x => x.IsDirty())) - { - // todo - // we used to try to propagate tabs renaming downstream, relying on ParentId, but - // 1) ParentId makes no sense (if a tab can be inherited from multiple composition - // types) so we would need to figure things out differently, visiting downstream - // content types and looking for tabs with the same name... - // 2) It was not deployable as changing a content type changes other content types - // that was not deterministic, because it would depend on the order of the changes. - // That last point could be fixed if (1) is fixed, but then it still is an issue with - // deploy because changing a content type changes other content types that are not - // dependencies but dependents, and then what? - // - // So... for the time being, all renaming propagation is disabled. We just don't do it. - - // (all gone) - - // delete tabs that do not exist anymore - // get the tabs that are currently existing (in the db), get the tabs that we want, - // now, and derive the tabs that we want to delete - var existingPropertyGroups = Database.Fetch("WHERE contentTypeNodeId = @id", new { id = entity.Id }) - .Select(x => x.Id) - .ToList(); - var newPropertyGroups = entity.PropertyGroups.Select(x => x.Id).ToList(); - var groupsToDelete = existingPropertyGroups - .Except(newPropertyGroups) - .ToArray(); - - // delete the tabs - if (groupsToDelete.Length > 0) - { - // if the tab contains properties, take care of them - // - move them to 'generic properties' so they remain consistent - // - keep track of them, later on we'll figure out what to do with them - // see http://issues.umbraco.org/issue/U4-8663 - orphanPropertyTypeIds = Database.Fetch("WHERE propertyTypeGroupId IN (@ids)", new { ids = groupsToDelete }) - .Select(x => x.Id).ToList(); - Database.Update("SET propertyTypeGroupId=NULL WHERE propertyTypeGroupId IN (@ids)", new { ids = groupsToDelete }); - - // now we can delete the tabs - Database.Delete("WHERE id IN (@ids)", new { ids = groupsToDelete }); - } - } - - var propertyGroupFactory = new PropertyGroupFactory(entity.Id); - - // insert or update groups, assign properties - foreach (var propertyGroup in entity.PropertyGroups) - { - // insert or update group - var groupDto = propertyGroupFactory.BuildGroupDto(propertyGroup); - var groupId = propertyGroup.HasIdentity - ? Database.Update(groupDto) - : Convert.ToInt32(Database.Insert(groupDto)); - if (propertyGroup.HasIdentity == false) - propertyGroup.Id = groupId; - else - groupId = propertyGroup.Id; - - // assign properties to the group - // (all of them, even those that have .IsPropertyDirty("PropertyGroupId") == true, - // because it should have been set to this group anyways and better be safe) - foreach (var propertyType in propertyGroup.PropertyTypes) - propertyType.PropertyGroupId = new Lazy(() => groupId); - } - - // insert or update properties - // all of them, no-group and in groups - foreach (var propertyType in entity.PropertyTypes) - { - var groupId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int); - - // if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias - if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int)) - AssignDataTypeFromPropertyEditor(propertyType); - - // validate the alias - ValidateAlias(propertyType); - - // insert or update property - var propertyTypeDto = propertyGroupFactory.BuildPropertyTypeDto(groupId, propertyType); - var typeId = propertyType.HasIdentity - ? Database.Update(propertyTypeDto) - : Convert.ToInt32(Database.Insert(propertyTypeDto)); - if (propertyType.HasIdentity == false) - propertyType.Id = typeId; - else - typeId = propertyType.Id; - - // not an orphan anymore - if (orphanPropertyTypeIds != null) - orphanPropertyTypeIds.Remove(typeId); - } - - // deal with orphan properties: those that were in a deleted tab, - // and have not been re-mapped to another tab or to 'generic properties' - if (orphanPropertyTypeIds != null) - foreach (var id in orphanPropertyTypeIds) - DeletePropertyType(entity.Id, id); - } - - private void DeletePropertyType(int contentTypeId, int propertyTypeId) - { - // first clear dependencies - Database.Delete("WHERE propertyTypeId = @Id", new { Id = propertyTypeId }); - Database.Delete("WHERE propertytypeid = @Id", new { Id = propertyTypeId }); - - // then delete the property type - Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId", new { Id = contentTypeId, PropertyTypeId = propertyTypeId }); - } - - protected IEnumerable GetAllowedContentTypeIds(int id) - { - var sql = new Sql(); - sql.Select("*") - .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 = new Sql(); - sql.Select("*") - .From() - .LeftJoin() - .On(left => left.Id, right => right.PropertyTypeGroupId) - .LeftJoin() - .On(left => left.DataTypeId, right => right.DataTypeId) - .Where(x => x.ContentTypeNodeId == id) - //must be sorted this way for the relator to work - .OrderBy(x => x.Id, SqlSyntax); - - var dtos = Database.Fetch(new GroupPropertyTypeRelator().Map, sql); - - var propertyGroupFactory = new PropertyGroupFactory(id, createDate, updateDate, CreatePropertyType); - var propertyGroups = propertyGroupFactory.BuildEntity(dtos); - return new PropertyGroupCollection(propertyGroups); - } - - protected PropertyTypeCollection GetPropertyTypeCollection(int id, DateTime createDate, DateTime updateDate) - { - var sql = new Sql(); - sql.Select("*") - .From() - .InnerJoin() - .On(left => left.DataTypeId, right => right.DataTypeId) - .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) == false)) - { - var propType = CreatePropertyType(dto.DataTypeDto.PropertyEditorAlias, dto.DataTypeDto.DbType.EnumParse(true), dto.Alias); - propType.DataTypeDefinitionId = 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(list); - } - - protected void ValidateAlias(PropertyType pt) - { - Mandate.That(string.IsNullOrEmpty(pt.Alias) == false, - () => - { - var message = - string.Format( - "{0} '{1}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias.", - "Property Type", - pt.Name); - var exception = new InvalidOperationException(message); - - Logger.Error>(message, exception); - throw exception; - }); - } - - protected void ValidateAlias(TEntity entity) - { - Mandate.That(string.IsNullOrEmpty(entity.Alias) == false, - () => - { - var message = - string.Format( - "{0} '{1}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias.", - typeof(TEntity).Name, - entity.Name); - var exception = new InvalidOperationException(message); - - Logger.Error>(message, exception); - throw exception; - }); - } - - /// - /// Try to set the data type id based on its ControlId - /// - /// - private void AssignDataTypeFromPropertyEditor(PropertyType propertyType) - { - //we cannot try to assign a data type of it's empty - if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false) - { - var sql = new Sql() - .Select("*") - .From() - .Where("propertyEditorAlias = @propertyEditorAlias", new { propertyEditorAlias = propertyType.PropertyEditorAlias }) - .OrderBy(typeDto => typeDto.DataTypeId); - var datatype = Database.FirstOrDefault(sql); - //we cannot assign a data type if one was not found - if (datatype != null) - { - propertyType.DataTypeDefinitionId = datatype.DataTypeId; - } - else - { - Logger.Warn>("Could not assign a data type for the property type " + propertyType.Alias + " since no data type was found with a property editor " + propertyType.PropertyEditorAlias); - } - } - } - - 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( - Database db, ISqlSyntaxProvider sqlSyntax, - TRepo contentTypeRepository) - where TRepo : IReadRepository - { - IDictionary> allParentMediaTypeIds; - var mediaTypes = MapMediaTypes(db, sqlSyntax, out allParentMediaTypeIds) - .ToArray(); - - MapContentTypeChildren(mediaTypes, db, sqlSyntax, contentTypeRepository, allParentMediaTypeIds); - - return mediaTypes; - } - - public static IEnumerable GetContentTypes( - Database db, ISqlSyntaxProvider sqlSyntax, - 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, contentTypeRepository, allParentContentTypeIds); - } - - return contentTypes; - } - - internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes, - Database db, ISqlSyntaxProvider sqlSyntax, - 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, 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) - } - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)contentType).ResetDirtyProperties(false); - } - } - } - - - } - - internal static void MapContentTypeTemplates(IContentType[] contentTypes, - Database 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.GetAll(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(Database db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> parentMediaTypeIds) - { - Mandate.ParameterNotNull(db, "db"); - - var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, - cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, 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 = new Guid(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 requried 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, - NodeId = currCt.ctId, - PrimaryKey = currCt.ctPk, - Thumbnail = currCt.ctThumb, - //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 factory = new ContentTypeFactory(); - var mediaType = factory.BuildMediaTypeEntity(contentTypeDto); - - //map the allowed content types - mediaType.AllowedContentTypes = currAllowedContentTypes; - - return mediaType; - } - - internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> associatedTemplates, - out IDictionary> parentContentTypeIds) - { - Mandate.ParameterNotNull(db, "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.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, 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 = new Guid(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, - NodeId = currCt.ctId, - PrimaryKey = currCt.ctPk, - Thumbnail = currCt.ctThumb, - //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 factory = new ContentTypeFactory(); - var contentType = factory.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, Database db, ISqlSyntaxProvider sqlSyntax, - 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; - - // first part Gets all property groups including property type data even when no property type exists on the group - // second part Gets all property types including ones that are not on a group - // therefore the union of the two contains all of the property type and property group information we need - // NOTE: MySQL requires a SELECT * FROM the inner union in order to be able to sort . lame. - - var sqlBuilder = new StringBuilder(@"SELECT PG.contenttypeNodeId as contentTypeId, - PT.ptUniqueId as ptUniqueID, PT.ptId, PT.ptAlias, PT.ptDesc,PT.ptMandatory,PT.ptName,PT.ptSortOrder,PT.ptRegExp, - PT.dtId,PT.dtDbType,PT.dtPropEdAlias, - PG.id as pgId, PG.uniqueID as pgKey, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText - FROM cmsPropertyTypeGroup as PG - LEFT JOIN - ( - SELECT PT.uniqueID as ptUniqueId, PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, - PT.mandatory as ptMandatory, PT.Name as ptName, PT.sortOrder as ptSortOrder, PT.validationRegExp as ptRegExp, - PT.propertyTypeGroupId as ptGroupId, - DT.dbType as dtDbType, DT.nodeId as dtId, DT.propertyEditorAlias as dtPropEdAlias - FROM cmsPropertyType as PT - INNER JOIN cmsDataType as DT - ON PT.dataTypeId = DT.nodeId - ) as PT - ON PT.ptGroupId = PG.id - WHERE (PG.contenttypeNodeId in (@contentTypeIds)) - - UNION - - SELECT PT.contentTypeId as contentTypeId, - PT.uniqueID as ptUniqueID, PT.id as ptId, PT.Alias as ptAlias, PT." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, - PT.mandatory as ptMandatory, PT.Name as ptName, PT.sortOrder as ptSortOrder, PT.validationRegExp as ptRegExp, - DT.nodeId as dtId, DT.dbType as dtDbType, DT.propertyEditorAlias as dtPropEdAlias, - PG.id as pgId, PG.uniqueID as pgKey, PG.sortorder as pgSortOrder, PG." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText - FROM cmsPropertyType as PT - INNER JOIN cmsDataType as DT - ON PT.dataTypeId = DT.nodeId - LEFT JOIN cmsPropertyTypeGroup as PG - ON PG.id = PT.propertyTypeGroupId - WHERE (PT.contentTypeId in (@contentTypeIds))"); - - sqlBuilder.AppendLine(" ORDER BY (pgId)"); - - //NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count! - // Since there are 2 groups of params, it will be half! - if (((contentTypeIds.Length / 2) - 1) > 2000) - throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters"); - - var result = db.Fetch(sqlBuilder.ToString(), new { contentTypeIds = contentTypeIds }); - - foreach (var contentTypeId in contentTypeIds) - { - //from this we need to make : - // * PropertyGroupCollection - Contains all property groups along with all property types associated with a group - // * PropertyTypeCollection - Contains all property types that do not belong to a group - - //create the property group collection first, this means all groups (even empty ones) and all groups with properties - - int currId = contentTypeId; - - var propertyGroupCollection = new PropertyGroupCollection(result - //get all rows that have a group id - .Where(x => x.pgId != null) - //filter based on the current content type - .Where(x => x.contentTypeId == currId) - //turn that into a custom object containing only the group info - .Select(x => new { GroupId = x.pgId, SortOrder = x.pgSortOrder, Text = x.pgText, Key = x.pgKey }) - //get distinct data by id - .DistinctBy(x => (int)x.GroupId) - //for each of these groups, create a group object with it's associated properties - .Select(group => new PropertyGroup(new PropertyTypeCollection( - result - .Where(row => row.pgId == group.GroupId && row.ptId != null) - .Select(row => new PropertyType(row.dtPropEdAlias, Enum.Parse(row.dtDbType), row.ptAlias) - { - //fill in the rest of the property type properties - Description = row.ptDesc, - DataTypeDefinitionId = row.dtId, - Id = row.ptId, - Key = row.ptUniqueID, - Mandatory = Convert.ToBoolean(row.ptMandatory), - Name = row.ptName, - PropertyGroupId = new Lazy(() => group.GroupId, false), - SortOrder = row.ptSortOrder, - ValidationRegExp = row.ptRegExp - }))) - { - //fill in the rest of the group properties - Id = group.GroupId, - Name = group.Text, - SortOrder = group.SortOrder, - Key = group.Key - }).ToArray()); - - allPropertyGroupCollection[currId] = propertyGroupCollection; - - //Create the property type collection now (that don't have groups) - - var propertyTypeCollection = new PropertyTypeCollection(result - .Where(x => x.pgId == null) - //filter based on the current content type - .Where(x => x.contentTypeId == currId) - .Select(row => new PropertyType(row.dtPropEdAlias, Enum.Parse(row.dtDbType), row.ptAlias) - { - //fill in the rest of the property type properties - Description = row.ptDesc, - DataTypeDefinitionId = row.dtId, - Id = row.ptId, - Key = row.ptUniqueID, - Mandatory = Convert.ToBoolean(row.ptMandatory), - Name = row.ptName, - PropertyGroupId = null, - SortOrder = row.ptSortOrder, - ValidationRegExp = row.ptRegExp - }).ToArray()); - - allPropertyTypeCollection[currId] = propertyTypeCollection; - } - - - } - - } - - protected abstract TEntity PerformGet(Guid id); - protected abstract TEntity PerformGet(string alias); - protected abstract IEnumerable PerformGetAll(params Guid[] ids); - protected abstract bool PerformExists(Guid id); - - /// - /// Gets an Entity by alias - /// - /// - /// - public TEntity Get(string alias) - { - return PerformGet(alias); - } - - /// - /// Gets an Entity by Id - /// - /// - /// - public TEntity Get(Guid id) - { - return PerformGet(id); - } - - /// - /// Gets all entities of the spefified type - /// - /// - /// - /// - /// Ensure explicit implementation, we don't want to have any accidental calls to this since it is essentially the same signature as the main GetAll when there are no parameters - /// - IEnumerable IReadRepository.GetAll(params Guid[] ids) - { - return PerformGetAll(ids); - } - - /// - /// Boolean indicating whether an Entity with the specified Id exists - /// - /// - /// - public bool Exists(Guid id) - { - return PerformExists(id); - } - - public string GetUniqueAlias(string alias) - { - // alias is unique accross ALL content types! - var aliasColumn = SqlSyntax.GetQuotedColumnName("alias"); - var aliases = Database.Fetch(@"SELECT cmsContentType." + aliasColumn + @" FROM cmsContentType -INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id -WHERE cmsContentType." + aliasColumn + @" LIKE @pattern", - new { pattern = alias + "%", objectType = NodeObjectTypeId }); - var i = 1; - string test; - while (aliases.Contains(test = alias + i)) i++; - return test; - } - - /// - /// Given the path of a content item, this will return true if the content item exists underneath a list view content item - /// - /// - /// - public bool HasContainerInPath(string contentPath) - { - var ids = contentPath.Split(',').Select(int.Parse); - var sql = new Sql(@"SELECT COUNT(*) FROM cmsContentType -INNER JOIN cmsContent ON cmsContentType.nodeId=cmsContent.contentType -WHERE cmsContent.nodeId IN (@ids) AND cmsContentType.isContainer=@isContainer", new { ids, isContainer = true }); - return Database.ExecuteScalar(sql) > 0; - } - } -} diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs deleted file mode 100644 index 4756d39d83..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ /dev/null @@ -1,315 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Represents a repository for doing CRUD operations for - /// - internal class ContentTypeRepository : ContentTypeBaseRepository, IContentTypeRepository - { - private readonly ITemplateRepository _templateRepository; - - public ContentTypeRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, ITemplateRepository templateRepository) - : base(work, cache, logger, sqlSyntax) - { - _templateRepository = templateRepository; - } - - protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) - { - return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ true); - } - - protected override IContentType PerformGet(int id) - { - //use the underlying GetAll which will force cache all content types - return GetAll().FirstOrDefault(x => x.Id == id); - } - - 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, this, _templateRepository) - .Where(x => ids.Contains(x.Id)); - } - - return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, this, _templateRepository); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - - var dtos = Database.Fetch(sql); - - return - //This returns a lookup from the GetAll cached looup - (dtos.Any() - ? GetAll(dtos.DistinctBy(x => x.ContentTypeDto.NodeId).Select(x => x.ContentTypeDto.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() - ? GetAll(ints) - : Enumerable.Empty(); - } - - /// - /// Gets all property type aliases. - /// - /// - public IEnumerable GetAllPropertyTypeAliases() - { - return Database.Fetch("SELECT DISTINCT Alias FROM cmsPropertyType ORDER BY Alias"); - } - - /// - /// Gets all content type aliases - /// - /// - /// If this list is empty, it will return all content type aliases for media, members and content, otherwise - /// it will only return content type aliases for the object types specified - /// - /// - public IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes) - { - var sql = new Sql().Select("cmsContentType.alias") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId); - - if (objectTypes.Any()) - { - sql = sql.Where("umbracoNode.nodeObjectType IN (@objectTypes)", new {objectTypes = objectTypes}); - } - - return Database.Fetch(sql); - } - - public IEnumerable GetAllContentTypeIds(string[] aliases) - { - if (aliases.Length == 0) return Enumerable.Empty(); - - var sql = new Sql().Select("cmsContentType.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId) - .Where(dto => aliases.Contains(dto.Alias), SqlSyntax); - - return Database.Fetch(sql); - } - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = new Sql(); - - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, left => left.ContentTypeNodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); - - return sql; - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.id = @Id"; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @Id", - "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @Id", - "DELETE FROM cmsContentTypeAllowedContentType WHERE AllowedId = @Id", - "DELETE FROM cmsContentType2ContentType WHERE parentContentTypeId = @Id", - "DELETE FROM cmsContentType2ContentType WHERE childContentTypeId = @Id", - "DELETE FROM cmsPropertyType WHERE contentTypeId = @Id", - "DELETE FROM cmsPropertyTypeGroup WHERE contenttypeNodeId = @Id", - "DELETE FROM cmsDocumentType WHERE contentTypeNodeId = @Id", - "DELETE FROM cmsContentType WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.DocumentType); } - } - - /// - /// Deletes a content type - /// - /// - /// - /// First checks for children and removes those first - /// - protected override void PersistDeletedItem(IContentType entity) - { - var query = Query.Builder.Where(x => x.ParentId == entity.Id); - var children = GetByQuery(query); - foreach (var child in children) - { - //NOTE: We must cast here so that it goes to the outter method to - // ensure the cache is updated. - PersistDeletedItem((IEntity)child); - } - - //Before we call the base class methods to run all delete clauses, we need to first - // delete all of the property data associated with this document type. Normally this will - // be done in the ContentTypeService by deleting all associated content first, but in some cases - // like when we switch a document type, there is property data left over that is linked - // to the previous document type. So we need to ensure it's removed. - var sql = new Sql().Select("DISTINCT cmsPropertyData.propertytypeid") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.PropertyTypeId, dto => dto.Id) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.ContentTypeId) - .Where(dto => dto.NodeId == entity.Id); - - //Delete all cmsPropertyData where propertytypeid EXISTS in the subquery above - Database.Execute(SqlSyntax.GetDeleteSubquery("cmsPropertyData", "propertytypeid", sql)); - - base.PersistDeletedItem(entity); - } - - protected override void PersistNewItem(IContentType entity) - { - Mandate.That(string.IsNullOrEmpty(entity.Alias) == false, - () => - { - var message = - string.Format( - "ContentType '{0}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias.", - entity.Name); - var exception = new Exception(message); - - Logger.Error(message, exception); - throw exception; - }); - - ((ContentType)entity).AddingEntity(); - - PersistNewBaseContentType(entity); - PersistTemplates(entity, false); - - entity.ResetDirtyProperties(); - } - - protected void PersistTemplates(IContentType entity, bool clearAll) - { - // remove and insert, if required - Database.Delete("WHERE contentTypeNodeId = @Id", new { Id = entity.Id }); - - // we could do it all in foreach if we assume that the default template is an allowed template?? - var defaultTemplateId = ((ContentType) entity).DefaultTemplateId; - if (defaultTemplateId > 0) - { - Database.Insert(new ContentTypeTemplateDto - { - ContentTypeNodeId = entity.Id, - TemplateNodeId = defaultTemplateId, - IsDefault = true - }); - } - foreach (var template in entity.AllowedTemplates.Where(x => x != null && x.Id != defaultTemplateId)) - { - Database.Insert(new ContentTypeTemplateDto - { - ContentTypeNodeId = entity.Id, - TemplateNodeId = template.Id, - IsDefault = false - }); - } - } - - protected override void PersistUpdatedItem(IContentType entity) - { - ValidateAlias(entity); - - //Updates Modified date - ((ContentType)entity).UpdatingEntity(); - - //Look up parent to get and set the correct Path if ParentId has changed - if (entity.IsPropertyDirty("ParentId")) - { - var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); - entity.Path = string.Concat(parent.Path, ",", entity.Id); - entity.Level = parent.Level + 1; - var maxSortOrder = - Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); - entity.SortOrder = maxSortOrder + 1; - } - - PersistUpdatedBaseContentType(entity); - PersistTemplates(entity, true); - - entity.ResetDirtyProperties(); - } - - protected override IContentType PerformGet(Guid id) - { - //use the underlying GetAll which will force cache all content types - return GetAll().FirstOrDefault(x => x.Key == id); - } - - protected override IContentType PerformGet(string alias) - { - //use the underlying GetAll which will force cache all content types - return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - - protected override IEnumerable PerformGetAll(params Guid[] ids) - { - //use the underlying GetAll which will force cache all content types - - if (ids.Any()) - { - return GetAll().Where(x => ids.Contains(x.Key)); - } - else - { - return GetAll(); - } - } - - protected override bool PerformExists(Guid id) - { - return GetAll().FirstOrDefault(x => x.Key == id) != null; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs deleted file mode 100644 index ef3ce3eec2..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Internal class to handle content/published xml insert/update based on standard principles and units of work with transactions - /// - internal class ContentXmlRepository : PetaPocoRepositoryBase> - where TContent : IContentBase - { - public ContentXmlRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - } - - #region Not implemented (don't need to for the purposes of this repo) - protected override ContentXmlEntity PerformGet(int id) - { - throw new NotImplementedException(); - } - - protected override IEnumerable> PerformGetAll(params int[] ids) - { - throw new NotImplementedException(); - } - - protected override IEnumerable> PerformGetByQuery(IQuery> query) - { - throw new NotImplementedException(); - } - - protected override Sql GetBaseQuery(bool isCount) - { - throw new NotImplementedException(); - } - - protected override string GetBaseWhereClause() - { - throw new NotImplementedException(); - } - - protected override IEnumerable GetDeleteClauses() - { - return new List(); - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - - //NOTE: Not implemented because all ContentXmlEntity will always return false for having an Identity - protected override void PersistUpdatedItem(ContentXmlEntity entity) - { - throw new NotImplementedException(); - } - - #endregion - - protected override void PersistDeletedItem(ContentXmlEntity entity) - { - //Remove 'published' xml from the cmsContentXml table for the unpublished content - Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); - - entity.DeletedDate = DateTime.Now; - } - - protected override void PersistNewItem(ContentXmlEntity entity) - { - if (entity.Content.HasIdentity == false) - { - throw new InvalidOperationException("Cannot insert or update an xml entry for a content item that has no identity"); - } - - var poco = new ContentXmlDto - { - NodeId = entity.Id, - Xml = entity.Xml.ToDataString() - }; - - //We need to do a special InsertOrUpdate here because we know that the ContentXmlDto table has a 1:1 relation - // with the content table and a record may or may not exist so the - // unique constraint which can be violated if 2+ threads try to execute the same insert sql at the same time. - Database.InsertOrUpdate(poco); - - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs deleted file mode 100644 index 44be068b4b..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ /dev/null @@ -1,624 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Globalization; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using Umbraco.Core.Cache; -using Umbraco.Core.Events; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; - -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Represents a repository for doing CRUD operations for - /// - internal class DataTypeDefinitionRepository : PetaPocoRepositoryBase, IDataTypeDefinitionRepository - { - private readonly IContentTypeRepository _contentTypeRepository; - private readonly DataTypePreValueRepository _preValRepository; - - public DataTypeDefinitionRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, - IContentTypeRepository contentTypeRepository) - : base(work, cache, logger, sqlSyntax) - { - _contentTypeRepository = contentTypeRepository; - _preValRepository = new DataTypePreValueRepository(work, CacheHelper.NoCache, logger, sqlSyntax); - } - - #region Overrides of RepositoryBase - - protected override IDataTypeDefinition PerformGet(int id) - { - return GetAll(new[] { id }).FirstOrDefault(); - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var factory = new DataTypeDefinitionFactory(NodeObjectTypeId); - var dataTypeSql = GetBaseQuery(false); - - if (ids.Any()) - { - dataTypeSql.Where("umbracoNode.id in (@ids)", new { ids = ids }); - } - else - { - dataTypeSql.Where(x => x.NodeObjectType == NodeObjectTypeId); - } - - var dtos = Database.Fetch(dataTypeSql); - return dtos.Select(factory.BuildEntity).ToArray(); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var factory = new DataTypeDefinitionFactory(NodeObjectTypeId); - - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - - var dtos = Database.Fetch(sql); - - return dtos.Select(factory.BuildEntity).ToArray(); - } - - /// - /// Override the delete method so that we can ensure that all related content type's are updated as part of the overall transaction - /// - /// - public override void Delete(IDataTypeDefinition entity) - { - //Find ContentTypes using this IDataTypeDefinition on a PropertyType - var query = Query.Builder.Where(x => x.DataTypeDefinitionId == entity.Id); - - //TODO: Don't we need to be concerned about media and member types here too ? - var contentTypes = _contentTypeRepository.GetByQuery(query); - - //Loop through the list of results and remove the PropertyTypes that references the DataTypeDefinition that is being deleted - foreach (var contentType in contentTypes) - { - if (contentType == null) continue; - - foreach (var group in contentType.PropertyGroups) - { - var types = @group.PropertyTypes.Where(x => x.DataTypeDefinitionId == entity.Id).ToList(); - foreach (var propertyType in types) - { - @group.PropertyTypes.Remove(propertyType); - } - } - - _contentTypeRepository.AddOrUpdate(contentType); - } - - //call the base method to queue the deletion of this data type - base.Delete(entity); - } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.DataTypeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); - return sql; - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.id = @Id"; - } - - protected override IEnumerable GetDeleteClauses() - { - return new List(); - } - - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.DataType); } - } - - #endregion - - #region Unit of Work Implementation - - protected override void PersistNewItem(IDataTypeDefinition entity) - { - ((DataTypeDefinition)entity).AddingEntity(); - - //ensure a datatype has a unique name before creating it - entity.Name = EnsureUniqueNodeName(entity.Name); - - //TODO: should the below be removed? - //Cannot add a duplicate data type - var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataType -INNER JOIN umbracoNode ON cmsDataType.nodeId = umbracoNode.id -WHERE umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + "= @name", new { name = entity.Name }); - if (exists > 0) - { - throw new DuplicateNameException("A data type with the name " + entity.Name + " already exists"); - } - - var factory = new DataTypeDefinitionFactory(NodeObjectTypeId); - var dto = factory.BuildDto(entity); - - //Logic for setting Path, Level and SortOrder - var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); - int level = parent.Level + 1; - int sortOrder = - Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); - - //Create the (base) node data - umbracoNode - var nodeDto = dto.NodeDto; - nodeDto.Path = parent.Path; - nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); - nodeDto.SortOrder = sortOrder; - var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); - - //Update with new correct path - nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); - Database.Update(nodeDto); - - //Update entity with correct values - entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set - entity.Path = nodeDto.Path; - entity.SortOrder = sortOrder; - entity.Level = level; - - dto.DataTypeId = nodeDto.NodeId; - Database.Insert(dto); - - entity.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(IDataTypeDefinition entity) - { - - entity.Name = EnsureUniqueNodeName(entity.Name, entity.Id); - - //Cannot change to a duplicate alias - var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataType -INNER JOIN umbracoNode ON cmsDataType.nodeId = umbracoNode.id -WHERE umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + @"= @name -AND umbracoNode.id <> @id", - new { id = entity.Id, name = entity.Name }); - if (exists > 0) - { - throw new DuplicateNameException("A data type with the name " + entity.Name + " already exists"); - } - - //Updates Modified date - ((DataTypeDefinition)entity).UpdatingEntity(); - - //Look up parent to get and set the correct Path if ParentId has changed - if (entity.IsPropertyDirty("ParentId")) - { - var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); - entity.Path = string.Concat(parent.Path, ",", entity.Id); - entity.Level = parent.Level + 1; - var maxSortOrder = - Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); - entity.SortOrder = maxSortOrder + 1; - } - - var factory = new DataTypeDefinitionFactory(NodeObjectTypeId); - //Look up DataTypeDefinition entry to get Primary for updating the DTO - var dataTypeDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { Id = entity.Id }); - factory.SetPrimaryKey(dataTypeDto.PrimaryKey); - var dto = factory.BuildDto(entity); - - //Updates the (base) node data - umbracoNode - var nodeDto = dto.NodeDto; - Database.Update(nodeDto); - Database.Update(dto); - - //NOTE: This is a special case, we need to clear the custom cache for pre-values here so they are not stale if devs - // are querying for them in the Saved event (before the distributed call cache is clearing it) - IsolatedCache.ClearCacheItem(GetPrefixedCacheKey(entity.Id)); - - entity.ResetDirtyProperties(); - } - - protected override void PersistDeletedItem(IDataTypeDefinition entity) - { - //Remove Notifications - Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); - - //Remove Permissions - Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); - - //Remove associated tags - Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); - - //PropertyTypes containing the DataType being deleted - var propertyTypeDtos = Database.Fetch("WHERE dataTypeId = @Id", new { Id = entity.Id }); - //Go through the PropertyTypes and delete referenced PropertyData before deleting the PropertyType - foreach (var dto in propertyTypeDtos) - { - Database.Delete("WHERE propertytypeid = @Id", new { Id = dto.Id }); - Database.Delete("WHERE id = @Id", new { Id = dto.Id }); - } - - //Delete the pre-values - Database.Delete("WHERE datatypeNodeId = @Id", new { Id = entity.Id }); - - //Delete Content specific data - Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); - - //Delete (base) node data - Database.Delete("WHERE uniqueID = @Id", new { Id = entity.Key }); - - entity.DeletedDate = DateTime.Now; - } - - #endregion - - public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId) - { - var collection = GetCachedPreValueCollection(dataTypeId); - return collection; - } - - /// - /// Gets a specific PreValue by its Id - /// - /// Id of the PreValue to retrieve the value from - /// PreValue as a string - public string GetPreValueAsString(int preValueId) - { - var collections = IsolatedCache.GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + "_"); - - var preValue = collections.SelectMany(x => x.FormatAsDictionary().Values).FirstOrDefault(x => x.Id == preValueId); - if (preValue != null) - return preValue.Value; - - var dto = Database.FirstOrDefault("WHERE id = @preValueId", new { preValueId }); - if (dto == null) - return string.Empty; - - var collection = GetCachedPreValueCollection(dto.DataTypeNodeId); - if (collection == null) - return string.Empty; - - preValue = collection.FormatAsDictionary().Values.FirstOrDefault(x => x.Id == preValueId); - return preValue == null ? string.Empty : preValue.Value; - } - - public void AddOrUpdatePreValues(int dataTypeId, IDictionary values) - { - var dtd = Get(dataTypeId); - if (dtd == null) - { - throw new InvalidOperationException("No data type found with id " + dataTypeId); - } - AddOrUpdatePreValues(dtd, values); - } - - public IEnumerable> Move(IDataTypeDefinition toMove, EntityContainer container) - { - var parentId = -1; - if (container != null) - { - // Check on paths - if ((string.Format(",{0},", container.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - throw new DataOperationException(MoveOperationStatusType.FailedNotAllowedByPath); - } - parentId = container.Id; - } - - //used to track all the moved entities to be given to the event - var moveInfo = new List> - { - new MoveEventInfo(toMove, toMove.Path, parentId) - }; - - var origPath = toMove.Path; - - //do the move to a new parent - toMove.ParentId = parentId; - - //set the updated path - toMove.Path = string.Concat(container == null ? parentId.ToInvariantString() : container.Path, ",", toMove.Id); - - //schedule it for updating in the transaction - AddOrUpdate(toMove); - - //update all descendants from the original path, update in order of level - var descendants = this.GetByQuery( - new Query().Where(type => type.Path.StartsWith(origPath + ","))); - - var lastParent = toMove; - foreach (var descendant in descendants.OrderBy(x => x.Level)) - { - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); - - descendant.ParentId = lastParent.Id; - descendant.Path = string.Concat(lastParent.Path, ",", descendant.Id); - - //schedule it for updating in the transaction - AddOrUpdate(descendant); - } - - return moveInfo; - } - - public void AddOrUpdatePreValues(IDataTypeDefinition dataType, IDictionary values) - { - var currentVals = new DataTypePreValueDto[] { }; - if (dataType.HasIdentity) - { - //first just get all pre-values for this data type so we can compare them to see if we need to insert or update or replace - var sql = new Sql().Select("*") - .From(SqlSyntax) - .Where(dto => dto.DataTypeNodeId == dataType.Id) - .OrderBy(dto => dto.SortOrder, SqlSyntax); - currentVals = Database.Fetch(sql).ToArray(); - } - - //already existing, need to be updated - var valueIds = values.Where(x => x.Value.Id > 0).Select(x => x.Value.Id).ToArray(); - var existingByIds = currentVals.Where(x => valueIds.Contains(x.Id)).ToArray(); - - //These ones need to be removed from the db, they no longer exist in the new values - var deleteById = currentVals.Where(x => valueIds.Contains(x.Id) == false); - - foreach (var d in deleteById) - { - _preValRepository.Delete(new PreValueEntity - { - Alias = d.Alias, - Id = d.Id, - Value = d.Value, - DataType = dataType, - SortOrder = d.SortOrder - }); - } - - var sortOrder = 1; - - foreach (var pre in values) - { - var existing = existingByIds.FirstOrDefault(valueDto => valueDto.Id == pre.Value.Id); - if (existing != null) - { - _preValRepository.AddOrUpdate(new PreValueEntity - { - //setting an id will update it - Id = existing.Id, - DataType = dataType, - //These are the new values to update - Alias = pre.Key, - SortOrder = sortOrder, - Value = pre.Value.Value - }); - } - else - { - _preValRepository.AddOrUpdate(new PreValueEntity - { - Alias = pre.Key, - SortOrder = sortOrder, - Value = pre.Value.Value, - DataType = dataType, - }); - } - - sortOrder++; - } - } - - private static string GetPrefixedCacheKey(int dataTypeId) - { - return CacheKeys.DataTypePreValuesCacheKey + "_" + dataTypeId; - } - - private PreValueCollection GetCachedPreValueCollection(int datetypeId) - { - var key = GetPrefixedCacheKey(datetypeId); - return IsolatedCache.GetCacheItem(key, () => - { - var dtos = Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = datetypeId }); - var list = dtos.Select(x => new Tuple(new PreValue(x.Id, x.Value, x.SortOrder), x.Alias, x.SortOrder)).ToList(); - var collection = PreValueConverter.ConvertToPreValuesCollection(list); - return collection; - }, TimeSpan.FromMinutes(20), isSliding: true); - } - - private string EnsureUniqueNodeName(string nodeName, int id = 0) - { - var names = Database.Fetch("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType", - new { objectType = NodeObjectTypeId }); - - return SimilarNodeName.GetUniqueName(names, id, nodeName); - } - - /// - /// Private class to handle pre-value crud based on units of work with transactions - /// - private class PreValueEntity : Entity, IAggregateRoot - { - public string Value { get; set; } - public string Alias { get; set; } - public IDataTypeDefinition DataType { get; set; } - public int SortOrder { get; set; } - } - - /// - /// Private class to handle pre-value crud based on standard principles and units of work with transactions - /// - private class DataTypePreValueRepository : PetaPocoRepositoryBase - { - public DataTypePreValueRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { } - - #region Not implemented (don't need to for the purposes of this repo) - protected override PreValueEntity PerformGet(int id) - { - throw new NotImplementedException(); - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - throw new NotImplementedException(); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new NotImplementedException(); - } - - protected override Sql GetBaseQuery(bool isCount) - { - throw new NotImplementedException(); - } - - protected override string GetBaseWhereClause() - { - throw new NotImplementedException(); - } - - protected override IEnumerable GetDeleteClauses() - { - return new List(); - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - #endregion - - protected override void PersistDeletedItem(PreValueEntity entity) - { - Database.Execute( - "DELETE FROM cmsDataTypePreValues WHERE id=@Id", - new { Id = entity.Id }); - - entity.DeletedDate = DateTime.Now; - } - - protected override void PersistNewItem(PreValueEntity entity) - { - if (entity.DataType.HasIdentity == false) - { - throw new InvalidOperationException("Cannot insert a pre value for a data type that has no identity"); - } - - //NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT - // in reality there is no need to check the uniqueness of this alias because the only way that this code executes is - // based on an IDictionary dictionary being passed to this repository and a dictionary - // must have unique aliases by definition, so there is no need for this additional check - - var dto = new DataTypePreValueDto - { - DataTypeNodeId = entity.DataType.Id, - Value = entity.Value, - SortOrder = entity.SortOrder, - Alias = entity.Alias - }; - Database.Insert(dto); - } - - protected override void PersistUpdatedItem(PreValueEntity entity) - { - if (entity.DataType.HasIdentity == false) - { - throw new InvalidOperationException("Cannot update a pre value for a data type that has no identity"); - } - - //NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT - // this causes issues when sorting the pre-values (http://issues.umbraco.org/issue/U4-5670) but in reality - // there is no need to check the uniqueness of this alias because the only way that this code executes is - // based on an IDictionary dictionary being passed to this repository and a dictionary - // must have unique aliases by definition, so there is no need for this additional check - - var dto = new DataTypePreValueDto - { - DataTypeNodeId = entity.DataType.Id, - Id = entity.Id, - Value = entity.Value, - SortOrder = entity.SortOrder, - Alias = entity.Alias - }; - Database.Update(dto); - } - - - } - - internal static class PreValueConverter - { - /// - /// Converts the tuple to a pre-value collection - /// - /// - /// - internal static PreValueCollection ConvertToPreValuesCollection(IEnumerable> list) - { - //now we need to determine if they are dictionary based, otherwise they have to be array based - var dictionary = new Dictionary(); - - //need to check all of the keys, if there's only one and it is empty then it's an array - var keys = list.Select(x => x.Item2).Distinct().ToArray(); - if (keys.Length == 1 && keys[0].IsNullOrWhiteSpace()) - { - return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); - } - - foreach (var item in list - .OrderBy(x => x.Item3) //we'll order them first so we maintain the order index in the dictionary - .GroupBy(x => x.Item2)) //group by alias - { - if (item.Count() > 1) - { - //if there's more than 1 item per key, then it cannot be a dictionary, just return the array - return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); - } - - dictionary.Add(item.Key, item.First().Item1); - } - - return new PreValueCollection(dictionary); - } - } - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - _contentTypeRepository.Dispose(); - _preValRepository.Dispose(); - } - } - - -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs deleted file mode 100644 index 7196165e6f..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ /dev/null @@ -1,420 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; - -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Represents a repository for doing CRUD operations for - /// - internal class DictionaryRepository : PetaPocoRepositoryBase, IDictionaryRepository - { - public DictionaryRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider syntax) - : base(work, cache, logger, syntax) - { } - - protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) - { - var options = new RepositoryCachePolicyOptions - { - //allow zero to be cached - GetAllCacheAllowZeroCount = true - }; - - return new SingleItemsOnlyRepositoryCachePolicy(runtimeCache, options); - } - - #region Overrides of RepositoryBase - - protected override IDictionaryItem PerformGet(int id) - { - var sql = GetBaseQuery(false) - .Where(GetBaseWhereClause(), new { Id = id }) - //must be sorted this way for the relator to work - .OrderBy(x => x.UniqueId, SqlSyntax); - - var dto = Database.Fetch(new DictionaryLanguageTextRelator().Map, sql).FirstOrDefault(); - if (dto == null) - return null; - - var entity = ConvertFromDto(dto); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)entity).ResetDirtyProperties(false); - - return entity; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var sql = GetBaseQuery(false).Where("cmsDictionary.pk > 0"); - if (ids.Any()) - { - sql.Where("cmsDictionary.pk in (@ids)", new { ids = ids }); - } - - //must be sorted this way for the relator to work - sql.OrderBy(x => x.UniqueId, SqlSyntax); - - return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(dto => ConvertFromDto(dto)); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - //must be sorted this way for the relator to work - sql.OrderBy(x => x.UniqueId, SqlSyntax); - - return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(x => ConvertFromDto(x)); - } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = new Sql(); - if (isCount) - { - sql.Select("COUNT(*)") - .From(SqlSyntax); - } - else - { - sql.Select("*") - .From(SqlSyntax) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, left => left.UniqueId, right => right.UniqueId); - } - return sql; - } - - protected override string GetBaseWhereClause() - { - return "cmsDictionary.pk = @Id"; - } - - protected override IEnumerable GetDeleteClauses() - { - return new List(); - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - - #endregion - - #region Unit of Work Implementation - - protected override void PersistNewItem(IDictionaryItem entity) - { - var dictionaryItem = ((DictionaryItem) entity); - - dictionaryItem.AddingEntity(); - - foreach (var translation in dictionaryItem.Translations) - translation.Value = translation.Value.ToValidXmlString(); - - var factory = new DictionaryItemFactory(); - var dto = factory.BuildDto(dictionaryItem); - - var id = Convert.ToInt32(Database.Insert(dto)); - dictionaryItem.Id = id; - - var translationFactory = new DictionaryTranslationFactory(dictionaryItem.Key); - foreach (var translation in dictionaryItem.Translations) - { - var textDto = translationFactory.BuildDto(translation); - translation.Id = Convert.ToInt32(Database.Insert(textDto)); - translation.Key = dictionaryItem.Key; - } - - dictionaryItem.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(IDictionaryItem entity) - { - ((Entity)entity).UpdatingEntity(); - - foreach (var translation in entity.Translations) - translation.Value = translation.Value.ToValidXmlString(); - - var factory = new DictionaryItemFactory(); - var dto = factory.BuildDto(entity); - - Database.Update(dto); - - var translationFactory = new DictionaryTranslationFactory(entity.Key); - foreach (var translation in entity.Translations) - { - var textDto = translationFactory.BuildDto(translation); - if (translation.HasIdentity) - { - Database.Update(textDto); - } - else - { - translation.Id = Convert.ToInt32(Database.Insert(textDto)); - translation.Key = entity.Key; - } - } - - entity.ResetDirtyProperties(); - - //Clear the cache entries that exist by uniqueid/item key - IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); - IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.Key)); - } - - protected override void PersistDeletedItem(IDictionaryItem entity) - { - RecursiveDelete(entity.Key); - - Database.Delete("WHERE UniqueId = @Id", new { Id = entity.Key }); - Database.Delete("WHERE id = @Id", new { Id = entity.Key }); - - //Clear the cache entries that exist by uniqueid/item key - IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); - IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.Key)); - - entity.DeletedDate = DateTime.Now; - } - - private void RecursiveDelete(Guid parentId) - { - var list = Database.Fetch("WHERE parent = @ParentId", new { ParentId = parentId }); - foreach (var dto in list) - { - RecursiveDelete(dto.UniqueId); - - Database.Delete("WHERE UniqueId = @Id", new { Id = dto.UniqueId }); - Database.Delete("WHERE id = @Id", new { Id = dto.UniqueId }); - - //Clear the cache entries that exist by uniqueid/item key - IsolatedCache.ClearCacheItem(GetCacheIdKey(dto.Key)); - IsolatedCache.ClearCacheItem(GetCacheIdKey(dto.UniqueId)); - } - } - - #endregion - - protected IDictionaryItem ConvertFromDto(DictionaryDto dto) - { - var factory = new DictionaryItemFactory(); - var entity = factory.BuildEntity(dto); - - var list = new List(); - foreach (var textDto in dto.LanguageTextDtos) - { - if (textDto.LanguageId <= 0) - continue; - - var translationFactory = new DictionaryTranslationFactory(dto.UniqueId); - list.Add(translationFactory.BuildEntity(textDto)); - } - entity.Translations = list; - - return entity; - } - - public IDictionaryItem Get(Guid uniqueId) - { - // note: normal to use GlobalCache here since we're passing it to a new repository - using (var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, UnitOfWork, GlobalCache, Logger, SqlSyntax)) - { - return uniqueIdRepo.Get(uniqueId); - } - } - - public IDictionaryItem Get(string key) - { - // note: normal to use GlobalCache here since we're passing it to a new repository - using (var keyRepo = new DictionaryByKeyRepository(this, UnitOfWork, GlobalCache, Logger, SqlSyntax)) - { - return keyRepo.Get(key); - } - } - - private IEnumerable GetRootDictionaryItems() - { - var query = Query.Builder.Where(x => x.ParentId == null); - return GetByQuery(query); - } - - public Dictionary GetDictionaryItemKeyMap() - { - var columns = new[] { "key", "id" }.Select(x => (object) SqlSyntax.GetQuotedColumnName(x)).ToArray(); - var sql = new Sql().Select(columns).From(SqlSyntax); - return Database.Fetch(sql).ToDictionary(x => x.Key, x => x.Id); - } - - private class DictionaryItemKeyIdDto - { - public string Key { get; set; } - public Guid Id { get; set; } - } - - public IEnumerable GetDictionaryItemDescendants(Guid? parentId) - { - //This methods will look up children at each level, since we do not store a path for dictionary (ATM), we need to do a recursive - // lookup to get descendants. Currently this is the most efficient way to do it - - Func>> getItemsFromParents = guids => - { - //needs to be in groups of 2000 because we are doing an IN clause and there's a max parameter count that can be used. - return guids.InGroupsOf(2000) - .Select(@group => - { - var sqlClause = GetBaseQuery(false) - .Where(x => x.Parent != null) - .Where(string.Format("{0} IN (@parentIds)", SqlSyntax.GetQuotedColumnName("parent")), new { parentIds = @group }); - - var translator = new SqlTranslator(sqlClause, Query.Builder); - var sql = translator.Translate(); - - //must be sorted this way for the relator to work - sql.OrderBy(x => x.UniqueId, SqlSyntax); - - return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(x => ConvertFromDto(x)); - }); - }; - - var childItems = parentId.HasValue == false - ? new[] { GetRootDictionaryItems() } - : getItemsFromParents(new[] { parentId.Value }); - - return childItems.SelectRecursive(items => getItemsFromParents(items.Select(x => x.Key).ToArray())).SelectMany(items => items); - - } - - private class DictionaryByUniqueIdRepository : SimpleGetRepository - { - private readonly DictionaryRepository _dictionaryRepository; - - public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - _dictionaryRepository = dictionaryRepository; - } - - protected override IEnumerable PerformFetch(Sql sql) - { - //must be sorted this way for the relator to work - sql.OrderBy(x => x.UniqueId, SqlSyntax); - - return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql); - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _dictionaryRepository.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("id") + " = @Id"; - } - - protected override IDictionaryItem ConvertToEntity(DictionaryDto dto) - { - return _dictionaryRepository.ConvertFromDto(dto); - } - - protected override object GetBaseWhereClauseArguments(Guid id) - { - return new { Id = id }; - } - - protected override string GetWhereInClauseForGetAll() - { - return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("id") + " in (@ids)"; - } - - protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) - { - var options = new RepositoryCachePolicyOptions - { - //allow zero to be cached - GetAllCacheAllowZeroCount = true - }; - - return new SingleItemsOnlyRepositoryCachePolicy(runtimeCache, options); - } - } - - private class DictionaryByKeyRepository : SimpleGetRepository - { - private readonly DictionaryRepository _dictionaryRepository; - - public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - _dictionaryRepository = dictionaryRepository; - } - - protected override IEnumerable PerformFetch(Sql sql) - { - //must be sorted this way for the relator to work - sql.OrderBy(x => x.UniqueId, SqlSyntax); - - return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql); - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _dictionaryRepository.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " = @Id"; - } - - protected override IDictionaryItem ConvertToEntity(DictionaryDto dto) - { - return _dictionaryRepository.ConvertFromDto(dto); - } - - protected override object GetBaseWhereClauseArguments(string id) - { - return new { Id = id }; - } - - protected override string GetWhereInClauseForGetAll() - { - return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " in (@ids)"; - } - - protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) - { - var options = new RepositoryCachePolicyOptions - { - //allow zero to be cached - GetAllCacheAllowZeroCount = true - }; - - return new SingleItemsOnlyRepositoryCachePolicy(runtimeCache, options); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs deleted file mode 100644 index bc23d7201c..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ /dev/null @@ -1,297 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// An internal repository for managing entity containers such as doc type, media type, data type containers. - /// - internal class EntityContainerRepository : PetaPocoRepositoryBase - { - private readonly Guid _containerObjectType; - - public EntityContainerRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, Guid containerObjectType) - : base(work, cache, logger, sqlSyntax) - { - var allowedContainers = new[] {Constants.ObjectTypes.DocumentTypeContainerGuid, Constants.ObjectTypes.MediaTypeContainerGuid, Constants.ObjectTypes.DataTypeContainerGuid}; - _containerObjectType = containerObjectType; - if (allowedContainers.Contains(_containerObjectType) == false) - throw new InvalidOperationException("No container type exists with ID: " + _containerObjectType); - } - - // never cache - private static readonly IRuntimeCacheProvider NullCache = new NullCacheProvider(); - protected override IRuntimeCacheProvider GetIsolatedCache(IsolatedRuntimeCache provider) - { - return NullCache; - } - - protected override EntityContainer PerformGet(int id) - { - var sql = GetBaseQuery(false).Where(GetBaseWhereClause(), new { id = id, NodeObjectType = NodeObjectTypeId }); - - var nodeDto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); - return nodeDto == null ? null : CreateEntity(nodeDto); - } - - // temp - so we don't have to implement GetByQuery - public EntityContainer Get(Guid id) - { - var sql = GetBaseQuery(false).Where("UniqueId=@uniqueId", new { uniqueId = id }); - - var nodeDto = Database.Fetch(sql).FirstOrDefault(); - return nodeDto == null ? null : CreateEntity(nodeDto); - } - - public IEnumerable Get(string name, int level) - { - var sql = GetBaseQuery(false).Where("text=@name AND level=@level AND nodeObjectType=@umbracoObjectTypeId", new { name, level, umbracoObjectTypeId = NodeObjectTypeId }); - return Database.Fetch(sql).Select(CreateEntity); - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - if (ids.Any()) - { - //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit - return ids.InGroupsOf(2000).SelectMany(@group => - { - var sql = GetBaseQuery(false) - .Where("nodeObjectType=@umbracoObjectTypeId", new {umbracoObjectTypeId = NodeObjectTypeId}) - .Where(string.Format("{0} IN (@ids)", SqlSyntax.GetQuotedColumnName("id")), new {ids = @group}); - - sql.OrderBy(x => x.Level, SqlSyntax); - - return Database.Fetch(sql).Select(CreateEntity); - }); - } - else - { - var sql = GetBaseQuery(false) - .Where("nodeObjectType=@umbracoObjectTypeId", new {umbracoObjectTypeId = NodeObjectTypeId}); - sql.OrderBy(x => x.Level, SqlSyntax); - return Database.Fetch(sql).Select(CreateEntity); - } - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new NotImplementedException(); - } - - private static EntityContainer CreateEntity(NodeDto nodeDto) - { - if (nodeDto.NodeObjectType.HasValue == false) - throw new InvalidOperationException("Node with id " + nodeDto.NodeId + " has no object type."); - - // throws if node is not a container - var containedObjectType = EntityContainer.GetContainedObjectType(nodeDto.NodeObjectType.Value); - - var entity = new EntityContainer(nodeDto.NodeId, nodeDto.UniqueId, - nodeDto.ParentId, nodeDto.Path, nodeDto.Level, nodeDto.SortOrder, - containedObjectType, - nodeDto.Text, nodeDto.UserId ?? 0); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - entity.ResetDirtyProperties(false); - - return entity; - } - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = new Sql(); - if (isCount) - { - sql.Select("COUNT(*)").From(SqlSyntax); - } - else - { - sql.Select("*").From(SqlSyntax); - } - return sql; - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.id = @id and nodeObjectType = @NodeObjectType"; - } - - protected override IEnumerable GetDeleteClauses() - { - throw new NotImplementedException(); - } - - protected override Guid NodeObjectTypeId - { - get { return _containerObjectType; } - } - - protected override void PersistDeletedItem(EntityContainer entity) - { - EnsureContainerType(entity); - - var nodeDto = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); - - if (nodeDto == null) return; - - // move children to the parent so they are not orphans - var childDtos = Database.Fetch(new Sql().Select("*") - .From(SqlSyntax) - .Where("parentID=@parentID AND (nodeObjectType=@containedObjectType OR nodeObjectType=@containerObjectType)", - new - { - parentID = entity.Id, - containedObjectType = entity.ContainedObjectType, - containerObjectType = entity.ContainerObjectType - })); - - foreach (var childDto in childDtos) - { - childDto.ParentId = nodeDto.ParentId; - Database.Update(childDto); - } - - // delete - Database.Delete(nodeDto); - - entity.DeletedDate = DateTime.Now; - } - - protected override void PersistNewItem(EntityContainer entity) - { - EnsureContainerType(entity); - - entity.Name = entity.Name.Trim(); - Mandate.ParameterNotNullOrEmpty(entity.Name, "entity.Name"); - - // guard against duplicates - var nodeDto = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); - if (nodeDto != null) - throw new InvalidOperationException("A container with the same name already exists."); - - // create - var level = 0; - var path = "-1"; - if (entity.ParentId > -1) - { - var parentDto = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); - - if (parentDto == null) - throw new NullReferenceException("Could not find parent container with id " + entity.ParentId); - - level = parentDto.Level; - path = parentDto.Path; - } - - // note: sortOrder is NOT managed and always zero for containers - - nodeDto = new NodeDto - { - CreateDate = DateTime.Now, - Level = Convert.ToInt16(level + 1), - NodeObjectType = entity.ContainerObjectType, - ParentId = entity.ParentId, - Path = path, - SortOrder = 0, - Text = entity.Name, - UserId = entity.CreatorId, - UniqueId = entity.Key - }; - - // insert, get the id, update the path with the id - var id = Convert.ToInt32(Database.Insert(nodeDto)); - nodeDto.Path = nodeDto.Path + "," + nodeDto.NodeId; - Database.Save(nodeDto); - - // refresh the entity - entity.Id = id; - entity.Path = nodeDto.Path; - entity.Level = nodeDto.Level; - entity.SortOrder = 0; - entity.CreateDate = nodeDto.CreateDate; - entity.ResetDirtyProperties(); - } - - // beware! does NOT manage descendants in case of a new parent - // - protected override void PersistUpdatedItem(EntityContainer entity) - { - EnsureContainerType(entity); - - entity.Name = entity.Name.Trim(); - Mandate.ParameterNotNullOrEmpty(entity.Name, "entity.Name"); - - // find container to update - var nodeDto = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); - if (nodeDto == null) - throw new InvalidOperationException("Could not find container with id " + entity.Id); - - // guard against duplicates - var dupNodeDto = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); - if (dupNodeDto != null && dupNodeDto.NodeId != nodeDto.NodeId) - throw new InvalidOperationException("A container with the same name already exists."); - - // update - nodeDto.Text = entity.Name; - if (nodeDto.ParentId != entity.ParentId) - { - nodeDto.Level = 0; - nodeDto.Path = "-1"; - if (entity.ParentId > -1) - { - var parent = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); - - if (parent == null) - throw new NullReferenceException("Could not find parent container with id " + entity.ParentId); - - nodeDto.Level = Convert.ToInt16(parent.Level + 1); - nodeDto.Path = parent.Path + "," + nodeDto.NodeId; - } - nodeDto.ParentId = entity.ParentId; - } - - // note: sortOrder is NOT managed and always zero for containers - - // update - Database.Update(nodeDto); - - // refresh the entity - entity.Path = nodeDto.Path; - entity.Level = nodeDto.Level; - entity.SortOrder = 0; - entity.ResetDirtyProperties(); - } - - private void EnsureContainerType(EntityContainer entity) - { - if (entity.ContainerObjectType != NodeObjectTypeId) - { - throw new InvalidOperationException("The container type does not match the repository object type"); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs deleted file mode 100644 index 40d35d2fee..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ /dev/null @@ -1,854 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Represents the EntityRepository used to query objects. - /// - /// - /// This is limited to objects that are based in the umbracoNode-table. - /// - internal class EntityRepository : DisposableObjectSlim, IEntityRepository - { - private readonly IDatabaseUnitOfWork _work; - - public EntityRepository(IDatabaseUnitOfWork work) - { - _work = work; - } - - /// - /// Returns the Unit of Work added to the repository - /// - protected internal IDatabaseUnitOfWork UnitOfWork - { - get { return _work; } - } - - /// - /// Internal for testing purposes - /// - internal Guid UnitKey - { - get { return (Guid)_work.Key; } - } - - #region Query Methods - - public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectTypeId, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, IQuery filter = null) - { - bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - var factory = new UmbracoEntityFactory(); - - var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql => - { - if (filter != null) - { - foreach (var filterClause in filter.GetWhereClauses()) - { - sql.Where(filterClause.Item1, filterClause.Item2); - } - } - }, objectTypeId); - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); - var pagedSql = entitySql.Append(GetGroupBy(isContent, isMedia, false)).OrderBy("umbracoNode.id"); - - IEnumerable result; - - if (isMedia) - { - //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - - var ids = pagedResult.Items.Select(x => (int) x.id).InGroupsOf(2000); - var entities = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - - //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before - foreach (var idGroup in ids) - { - var propSql = GetPropertySql(Constants.ObjectTypes.Media) - .Where("contentNodeId IN (@ids)", new {ids = idGroup}) - .OrderBy("contentNodeId"); - - //This does NOT fetch all data into memory in a list, this will read - // over the records as a data reader, this is much better for performance and memory, - // but it means that during the reading of this data set, nothing else can be read - // from SQL server otherwise we'll get an exception. - var allPropertyData = _work.Database.Query(propSql); - - //keep track of the current property data item being enumerated - var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); - var hasCurrent = false; // initially there is no enumerator.Current - - try - { - //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above, - // which allows us to more efficiently iterate over the large data set of property values. - foreach (var entity in entities) - { - // assemble the dtos for this def - // use the available enumerator.Current if any else move to next - while (hasCurrent || propertyDataSetEnumerator.MoveNext()) - { - if (propertyDataSetEnumerator.Current.contentNodeId == entity.Id) - { - hasCurrent = false; // enumerator.Current is not available - - //the property data goes into the additional data - entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias, - Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.dataNtext) - ? propertyDataSetEnumerator.Current.dataNvarchar - : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.dataNtext) - }; - } - else - { - hasCurrent = true; // enumerator.Current is available for another def - break; // no more propertyDataDto for this def - } - } - } - } - finally - { - propertyDataSetEnumerator.Dispose(); - } - } - - result = entities; - } - else - { - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - } - - //The total items from the PetaPoco page query will be wrong due to the Outer join used on parent, depending on the search this will - //return duplicate results when the COUNT is used in conjuction with it, so we need to get the total on our own. - - //generate a query that does not contain the LEFT Join for parent, this would cause - //the COUNT(*) query to return the wrong - var sqlCountClause = GetBaseWhere( - (isC, isM, f) => GetBase(isC, isM, f, true), //true == is a count query - isContent, isMedia, sql => - { - if (filter != null) - { - foreach (var filterClause in filter.GetWhereClauses()) - { - sql.Where(filterClause.Item1, filterClause.Item2); - } - } - }, objectTypeId); - var translatorCount = new SqlTranslator(sqlCountClause, query); - var countSql = translatorCount.Translate(); - - totalRecords = _work.Database.ExecuteScalar(countSql); - - return result; - } - - public IUmbracoEntity GetByKey(Guid key) - { - var sql = GetBaseWhere(GetBase, false, false, key); - var nodeDto = _work.Database.FirstOrDefault(sql); - if (nodeDto == null) - return null; - - var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntityFromDynamic(nodeDto); - - return entity; - } - - public IUmbracoEntity GetByKey(Guid key, Guid objectTypeId) - { - bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - - var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - - return entities.FirstOrDefault(); - } - else - { - - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); - } - var found = collection.FirstOrDefault(); - return found != null ? found.BuildFromDynamic() : null; - } - - - } - - public virtual IUmbracoEntity Get(int id) - { - var sql = GetBaseWhere(GetBase, false, false, id); - var nodeDto = _work.Database.FirstOrDefault(sql); - if (nodeDto == null) - return null; - - var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntityFromDynamic(nodeDto); - - return entity; - } - - public virtual IUmbracoEntity Get(int id, Guid objectTypeId) - { - bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - - var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - - return entities.FirstOrDefault(); - } - else - { - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); - } - var found = collection.FirstOrDefault(); - return found != null ? found.BuildFromDynamic() : null; - } - } - - public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) - { - return ids.Any() - ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.id in (@ids)", new { ids })) - : PerformGetAll(objectTypeId); - } - - public virtual IEnumerable GetAll(Guid objectTypeId, params Guid[] keys) - { - return keys.Any() - ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.uniqueID in (@keys)", new { keys })) - : PerformGetAll(objectTypeId); - } - - private IEnumerable PerformGetAll(Guid objectTypeId, Action filter = null) - { - var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, filter); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - return entities; - } - else - { - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); - } - return collection.Select(x => x.BuildFromDynamic()).ToList(); - } - } - - public virtual IEnumerable GetAllPaths(Guid objectTypeId, params int[] ids) - { - return ids.Any() - ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.id in (@ids)", new { ids })) - : PerformGetAllPaths(objectTypeId); - } - - public virtual IEnumerable GetAllPaths(Guid objectTypeId, params Guid[] keys) - { - return keys.Any() - ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.uniqueID in (@keys)", new { keys })) - : PerformGetAllPaths(objectTypeId); - } - - private IEnumerable PerformGetAllPaths(Guid objectTypeId, Action filter = null) - { - var sql = new Sql("SELECT id, path FROM umbracoNode WHERE umbracoNode.nodeObjectType=@type", new { type = objectTypeId }); - if (filter != null) filter(sql); - return _work.Database.Fetch(sql); - } - - public virtual IEnumerable GetByQuery(IQuery query) - { - var sqlClause = GetBase(false, false, null); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate().Append(GetGroupBy(false, false)); - - var dtos = _work.Database.Fetch(sql); - - var factory = new UmbracoEntityFactory(); - var list = dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - - return list; - } - - public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) - { - - bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - - var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId); - - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - var wheres = query.GetWhereClauses().ToArray(); - - var mediaSql = GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), sql => - { - //adds the additional filters - foreach (var whereClause in wheres) - { - sql.Where(whereClause.Item1, whereClause.Item2); - } - }); - - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, mediaSql); - return entities; - } - else - { - //use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData - var finalSql = entitySql.Append(GetGroupBy(isContent, false)); - - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(finalSql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); - } - return collection.Select(x => x.BuildFromDynamic()).ToList(); - } - } - - #endregion - - - #region Sql Statements - - protected Sql GetFullSqlForEntityType(Guid key, bool isContent, bool isMedia, Guid objectTypeId) - { - var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key); - - if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); - - return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false))); - } - - protected Sql GetFullSqlForEntityType(int id, bool isContent, bool isMedia, Guid objectTypeId) - { - var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id); - - if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); - - return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false))); - } - - protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectTypeId, Action filter) - { - var entitySql = GetBaseWhere(GetBase, isContent, isMedia, filter, objectTypeId); - - if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); - - return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), filter); - } - - private Sql GetPropertySql(string nodeObjectType) - { - var sql = new Sql() - .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias") - .From() - .InnerJoin() - .On(dto => dto.NodeId, dto => dto.NodeId) - .InnerJoin() - .On(dto => dto.Id, dto => dto.PropertyTypeId) - .InnerJoin() - .On(dto => dto.DataTypeId, dto => dto.DataTypeId) - .Where("umbracoNode.nodeObjectType = @nodeObjectType", new { nodeObjectType = nodeObjectType }); - - return sql; - } - - private Sql GetFullSqlForMedia(Sql entitySql, Action filter = null) - { - //this will add any dataNvarchar property to the output which can be added to the additional properties - - var joinSql = GetPropertySql(Constants.ObjectTypes.Media); - - if (filter != null) - { - filter(joinSql); - } - - //We're going to create a query to query against the entity SQL - // because we cannot group by nText columns and we have a COUNT in the entitySql we cannot simply left join - // the entitySql query, we have to join the wrapped query to get the ntext in the result - - var wrappedSql = new Sql("SELECT * FROM (") - .Append(entitySql) - .Append(new Sql(") tmpTbl LEFT JOIN (")) - .Append(joinSql) - .Append(new Sql(") as property ON id = property.contentNodeId")) - .OrderBy("sortOrder, id"); - - return wrappedSql; - } - - protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) - { - return GetBase(isContent, isMedia, customFilter, false); - } - - protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter, bool isCount) - { - var columns = new List(); - if (isCount) - { - columns.Add("COUNT(*)"); - } - else - { - columns.AddRange(new List - { - "umbracoNode.id", - "umbracoNode.trashed", - "umbracoNode.parentID", - "umbracoNode.nodeUser", - "umbracoNode.level", - "umbracoNode.path", - "umbracoNode.sortOrder", - "umbracoNode.uniqueID", - "umbracoNode.text", - "umbracoNode.nodeObjectType", - "umbracoNode.createDate", - "COUNT(parent.parentID) as children" - }); - - if (isContent || isMedia) - { - if (isContent) - { - //only content has/needs this info - columns.Add("published.versionId as publishedVersion"); - columns.Add("document.versionId as newestVersion"); - columns.Add("contentversion.id as versionId"); - } - - columns.Add("contenttype.alias"); - columns.Add("contenttype.icon"); - columns.Add("contenttype.thumbnail"); - columns.Add("contenttype.isContainer"); - } - } - - //Creates an SQL query to return a single row for the entity - - var entitySql = new Sql() - .Select(columns.ToArray()) - .From("umbracoNode umbracoNode"); - - if (isContent || isMedia) - { - entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id"); - - if (isContent) - { - //only content has/needs this info - entitySql - .InnerJoin("cmsDocument document").On("document.nodeId = umbracoNode.id") - .InnerJoin("cmsContentVersion contentversion").On("contentversion.VersionId = document.versionId") - .LeftJoin("(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1) as published") - .On("umbracoNode.id = published.nodeId"); - } - - entitySql.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType"); - } - - if (isCount == false) - { - entitySql.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id"); - } - - if (customFilter != null) - { - customFilter(entitySql); - } - - return entitySql; - } - - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Action filter, Guid nodeObjectType) - { - var sql = baseQuery(isContent, isMedia, filter) - .Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = nodeObjectType }); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - return sql; - } - - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, int id) - { - var sql = baseQuery(isContent, isMedia, null) - .Where("umbracoNode.id = @Id", new { Id = id }); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - sql.Append(GetGroupBy(isContent, isMedia)); - - return sql; - } - - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid key) - { - var sql = baseQuery(isContent, isMedia, null) - .Where("umbracoNode.uniqueID = @UniqueID", new {UniqueID = key}); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - sql.Append(GetGroupBy(isContent, isMedia)); - - return sql; - } - - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id) - { - var sql = baseQuery(isContent, isMedia, null) - .Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType", - new {Id = id, NodeObjectType = nodeObjectType}); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - return sql; - } - - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key) - { - var sql = baseQuery(isContent, isMedia, null) - .Where("umbracoNode.uniqueID = @UniqueID AND umbracoNode.nodeObjectType = @NodeObjectType", - new { UniqueID = key, NodeObjectType = nodeObjectType }); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - return sql; - } - - protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true) - { - var columns = new List - { - "umbracoNode.id", - "umbracoNode.trashed", - "umbracoNode.parentID", - "umbracoNode.nodeUser", - "umbracoNode.level", - "umbracoNode.path", - "umbracoNode.sortOrder", - "umbracoNode.uniqueID", - "umbracoNode.text", - "umbracoNode.nodeObjectType", - "umbracoNode.createDate" - }; - - if (isContent || isMedia) - { - if (isContent) - { - columns.Add("published.versionId"); - columns.Add("document.versionId"); - columns.Add("contentversion.id"); - } - columns.Add("contenttype.alias"); - columns.Add("contenttype.icon"); - columns.Add("contenttype.thumbnail"); - columns.Add("contenttype.isContainer"); - } - - var sql = new Sql() - .GroupBy(columns.ToArray()); - - if (includeSort) - { - sql = sql.OrderBy("umbracoNode.sortOrder"); - } - - return sql; - } - - #endregion - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - UnitOfWork.DisposeIfDisposable(); - } - - public bool Exists(Guid key) - { - var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("uniqueID=@uniqueID", new {uniqueID = key}); - return _work.Database.ExecuteScalar(sql) > 0; - } - - public bool Exists(int id) - { - var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("id=@id", new { id = id }); - return _work.Database.ExecuteScalar(sql) > 0; - } - - #region private classes - - [ExplicitColumns] - internal class UmbracoPropertyDto - { - [Column("propertyEditorAlias")] - public string PropertyEditorAlias { get; set; } - - [Column("propertyTypeAlias")] - public string PropertyAlias { get; set; } - - [Column("dataNvarchar")] - public string NVarcharValue { get; set; } - - [Column("dataNtext")] - public string NTextValue { get; set; } - } - - /// - /// This is a special relator in that it is not returning a DTO but a real resolved entity and that it accepts - /// a dynamic instance. - /// - /// - /// We're doing this because when we query the db, we want to use dynamic so that it returns all available fields not just the ones - /// defined on the entity so we can them to additional data - /// - internal class UmbracoEntityRelator - { - internal UmbracoEntity Current; - private readonly UmbracoEntityFactory _factory = new UmbracoEntityFactory(); - - internal UmbracoEntity Map(dynamic a, UmbracoPropertyDto p) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same UmbracoEntity as the current one we're processing - if (Current != null && Current.Key == a.uniqueID) - { - if (p != null && p.PropertyAlias.IsNullOrWhiteSpace() == false) - { - // Add this UmbracoProperty to the current additional data - Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = p.PropertyEditorAlias, - Value = p.NTextValue.IsNullOrWhiteSpace() - ? p.NVarcharValue - : p.NTextValue.ConvertToJsonIfPossible() - }; - } - - // Return null to indicate we're not done with this UmbracoEntity yet - return null; - } - - // This is a different UmbracoEntity to the current one, or this is the - // first time through and we don't have a Tab yet - - // Save the current UmbracoEntityDto - var prev = Current; - - // Setup the new current UmbracoEntity - - Current = _factory.BuildEntityFromDynamic(a); - - if (p != null && p.PropertyAlias.IsNullOrWhiteSpace() == false) - { - //add the property/create the prop list if null - Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = p.PropertyEditorAlias, - Value = p.NTextValue.IsNullOrWhiteSpace() - ? p.NVarcharValue - : p.NTextValue.ConvertToJsonIfPossible() - }; - } - - // Return the now populated previous UmbracoEntity (or null if first time through) - return prev; - } - } - - private class EntityDefinitionCollection : KeyedCollection - { - protected override int GetKeyForItem(EntityDefinition item) - { - return item.Id; - } - - /// - /// if this key already exists if it does then we need to check - /// if the existing item is 'older' than the new item and if that is the case we'll replace the older one - /// - /// - /// - public bool AddOrUpdate(EntityDefinition item) - { - if (Dictionary == null) - { - base.Add(item); - return true; - } - - var key = GetKeyForItem(item); - EntityDefinition found; - if (TryGetValue(key, out found)) - { - //it already exists and it's older so we need to replace it - if (item.VersionId > found.VersionId) - { - var currIndex = Items.IndexOf(found); - if (currIndex == -1) - throw new IndexOutOfRangeException("Could not find the item in the list: " + found.Id); - - //replace the current one with the newer one - SetItem(currIndex, item); - return true; - } - //could not add or update - return false; - } - - base.Add(item); - return true; - } - - private bool TryGetValue(int key, out EntityDefinition val) - { - if (Dictionary == null) - { - val = null; - return false; - } - return Dictionary.TryGetValue(key, out val); - } - } - - private class EntityDefinition - { - private readonly UmbracoEntityFactory _factory; - private readonly dynamic _entity; - private readonly bool _isContent; - private readonly bool _isMedia; - - public EntityDefinition(UmbracoEntityFactory factory, dynamic entity, bool isContent, bool isMedia) - { - _factory = factory; - _entity = entity; - _isContent = isContent; - _isMedia = isMedia; - } - - public IUmbracoEntity BuildFromDynamic() - { - return _factory.BuildEntityFromDynamic(_entity); - } - - public int Id - { - get { return _entity.id; } - } - - public int VersionId - { - get - { - if (_isContent || _isMedia) - { - return _entity.versionId; - } - return _entity.id; - } - } - } - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs deleted file mode 100644 index fcaee87975..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNet.Identity; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - internal class ExternalLoginRepository : PetaPocoRepositoryBase, IExternalLoginRepository - { - public ExternalLoginRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - } - - public void DeleteUserLogins(int memberId) - { - using (var t = Database.GetTransaction()) - { - Database.Execute("DELETE FROM ExternalLogins WHERE UserId=@userId", new { userId = memberId }); - - t.Complete(); - } - } - - public void SaveUserLogins(int memberId, IEnumerable logins) - { - using (var t = Database.GetTransaction()) - { - //clear out logins for member - Database.Execute("DELETE FROM umbracoExternalLogin WHERE userId=@userId", new { userId = memberId }); - - //add them all - foreach (var l in logins) - { - Database.Insert(new ExternalLoginDto - { - LoginProvider = l.LoginProvider, - ProviderKey = l.ProviderKey, - UserId = memberId, - CreateDate = DateTime.Now - }); - } - - t.Complete(); - } - } - - protected override IIdentityUserLogin PerformGet(int id) - { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - - var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); - if (dto == null) - return null; - - var factory = new ExternalLoginFactory(); - var entity = factory.BuildEntity(dto); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - entity.ResetDirtyProperties(false); - - return entity; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - if (ids.Any()) - { - return PerformGetAllOnIds(ids); - } - - var sql = GetBaseQuery(false); - - return ConvertFromDtos(Database.Fetch(sql)) - .ToArray();// we don't want to re-iterate again! - } - - private IEnumerable PerformGetAllOnIds(params int[] ids) - { - if (ids.Any() == false) yield break; - foreach (var id in ids) - { - yield return Get(id); - } - } - - private IEnumerable ConvertFromDtos(IEnumerable dtos) - { - var factory = new ExternalLoginFactory(); - foreach (var entity in dtos.Select(factory.BuildEntity)) - { - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((TracksChangesEntityBase)entity).ResetDirtyProperties(false); - - yield return entity; - } - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - - var dtos = Database.Fetch(sql); - - foreach (var dto in dtos) - { - yield return Get(dto.Id); - } - } - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = new Sql(); - if (isCount) - { - sql.Select("COUNT(*)").From(SqlSyntax); - } - else - { - sql.Select("*").From(SqlSyntax); - } - return sql; - } - - protected override string GetBaseWhereClause() - { - return "umbracoExternalLogin.id = @Id"; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM umbracoExternalLogin WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - - protected override void PersistNewItem(IIdentityUserLogin entity) - { - ((Entity)entity).AddingEntity(); - - var factory = new ExternalLoginFactory(); - var dto = factory.BuildDto(entity); - - var id = Convert.ToInt32(Database.Insert(dto)); - entity.Id = id; - - entity.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(IIdentityUserLogin entity) - { - ((Entity)entity).UpdatingEntity(); - - var factory = new ExternalLoginFactory(); - var dto = factory.BuildDto(entity); - - Database.Update(dto); - - entity.ResetDirtyProperties(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs deleted file mode 100644 index 32b486f3b5..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs +++ /dev/null @@ -1,263 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - internal abstract class FileRepository : DisposableObjectSlim, IUnitOfWorkRepository, IRepository - where TEntity : IFile - { - private IUnitOfWork _work; - private readonly IFileSystem _fileSystem; - - protected FileRepository(IUnitOfWork work, IFileSystem fileSystem) - { - _work = work; - _fileSystem = fileSystem; - } - - /// - /// Returns the Unit of Work added to the repository - /// - protected IUnitOfWork UnitOfWork - { - get { return _work; } - } - - protected IFileSystem FileSystem - { - get { return _fileSystem; } - } - - public virtual void AddFolder(string folderPath) - { - _work.RegisterAdded(new Folder(folderPath), this); - } - - public virtual void DeleteFolder(string folderPath) - { - _work.RegisterRemoved(new Folder(folderPath), this); - } - - #region Implementation of IRepository - - public virtual void AddOrUpdate(TEntity entity) - { - if (FileSystem.FileExists(entity.OriginalPath) == false) - { - _work.RegisterAdded(entity, this); - } - else - { - _work.RegisterChanged(entity, this); - } - } - - public virtual void Delete(TEntity entity) - { - if (_work != null) - { - _work.RegisterRemoved(entity, this); - } - } - - public abstract TEntity Get(TId id); - - public abstract IEnumerable GetAll(params TId[] ids); - - public virtual bool Exists(TId id) - { - return _fileSystem.FileExists(id.ToString()); - } - - public void SetUnitOfWork(IUnitOfWork work) - { - _work = work; - } - - #endregion - - #region Implementation of IUnitOfWorkRepository - - public void PersistNewItem(IEntity entity) - { - //special case for folder - var folder = entity as Folder; - if (folder != null) - { - PersistNewFolder(folder); - } - else - { - PersistNewItem((TEntity)entity); - } - } - - public void PersistUpdatedItem(IEntity entity) - { - PersistUpdatedItem((TEntity)entity); - } - - public void PersistDeletedItem(IEntity entity) - { - //special case for folder - var folder = entity as Folder; - if (folder != null) - { - PersistDeletedFolder(folder); - } - else - { - PersistDeletedItem((TEntity)entity); - } - } - - #endregion - - internal virtual void PersistNewFolder(Folder entity) - { - _fileSystem.CreateFolder(entity.Path); - } - - internal virtual void PersistDeletedFolder(Folder entity) - { - _fileSystem.DeleteDirectory(entity.Path); - } - - #region Abstract IUnitOfWorkRepository Methods - - protected virtual void PersistNewItem(TEntity entity) - { - using (var stream = GetContentStream(entity.Content)) - { - FileSystem.AddFile(entity.Path, stream, true); - entity.CreateDate = FileSystem.GetCreated(entity.Path).UtcDateTime; - entity.UpdateDate = FileSystem.GetLastModified(entity.Path).UtcDateTime; - //the id can be the hash - entity.Id = entity.Path.GetHashCode(); - entity.Key = entity.Path.EncodeAsGuid(); - entity.VirtualPath = FileSystem.GetUrl(entity.Path); - } - } - - protected virtual void PersistUpdatedItem(TEntity entity) - { - using (var stream = GetContentStream(entity.Content)) - { - FileSystem.AddFile(entity.Path, stream, true); - entity.CreateDate = FileSystem.GetCreated(entity.Path).UtcDateTime; - entity.UpdateDate = FileSystem.GetLastModified(entity.Path).UtcDateTime; - //the id can be the hash - entity.Id = entity.Path.GetHashCode(); - entity.Key = entity.Path.EncodeAsGuid(); - entity.VirtualPath = FileSystem.GetUrl(entity.Path); - } - - //now that the file has been written, we need to check if the path had been changed - if (entity.Path.InvariantEquals(entity.OriginalPath) == false) - { - //delete the original file - FileSystem.DeleteFile(entity.OriginalPath); - //reset the original path on the file - entity.ResetOriginalPath(); - } - } - - protected virtual void PersistDeletedItem(TEntity entity) - { - if (_fileSystem.FileExists(entity.Path)) - { - _fileSystem.DeleteFile(entity.Path); - } - } - - #endregion - - /// - /// Gets a stream that is used to write to the file - /// - /// - /// - protected virtual Stream GetContentStream(string content) - { - return new MemoryStream(Encoding.UTF8.GetBytes(content)); - } - - /// - /// Returns all files in the file system - /// - /// - /// - /// - /// Returns a list of all files with their paths. For example: - /// - /// \hello.txt - /// \folder1\test.txt - /// \folder1\blah.csv - /// \folder1\folder2\blahhhhh.svg - /// - protected IEnumerable FindAllFiles(string path, string filter) - { - var list = new List(); - list.AddRange(FileSystem.GetFiles(path, filter)); - - var directories = FileSystem.GetDirectories(path); - foreach (var directory in directories) - { - list.AddRange(FindAllFiles(directory, filter)); - } - - return list; - } - - protected string GetFileContent(string filename) - { - if (FileSystem.FileExists(filename) == false) - return null; - - try - { - using (var stream = FileSystem.OpenFile(filename)) - using (var reader = new StreamReader(stream, Encoding.UTF8, true)) - { - return reader.ReadToEnd(); - } - } - catch - { - return null; // deal with race conds - } - } - - public long GetFileSize(string filename) - { - if (FileSystem.FileExists(filename) == false) - return -1; - - try - { - return FileSystem.GetSize(filename); - } - catch - { - return -1; // deal with race conds - } - } - - /// - /// Dispose any disposable properties - /// - /// - /// Dispose the unit of work - /// - protected override void DisposeResources() - { - _work.DisposeIfDisposable(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/IAuditEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IAuditEntryRepository.cs new file mode 100644 index 0000000000..1d4d2fe531 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IAuditEntryRepository.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Represents a repository for entities. + /// + public interface IAuditEntryRepository : IReadWriteQueryRepository + { + /// + /// Gets a page of entries. + /// + IEnumerable GetPage(long pageIndex, int pageCount, out long records); + + /// + /// Determines whether the repository is available. + /// + /// During an upgrade, the repository may not be available, until the table has been created. + bool IsAvailable(); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IAuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs similarity index 84% rename from src/Umbraco.Core/Persistence/Repositories/Interfaces/IAuditRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs index e55afc4346..7c8a82bb85 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IAuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs @@ -1,14 +1,14 @@ -using System.Collections; -using System.Collections.Generic; -using Umbraco.Core.Auditing; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IAuditRepository : IRepository + public interface IAuditRepository : IReadRepository, IWriteRepository, IQueryRepository { + void CleanLogs(int maximumAgeOfLogsInMinutes); + /// /// Return the audit items as paged result /// @@ -34,4 +34,4 @@ namespace Umbraco.Core.Persistence.Repositories AuditType[] auditTypeFilter, IQuery customFilter); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IConsentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IConsentRepository.cs new file mode 100644 index 0000000000..85cfb52ba3 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IConsentRepository.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Represents a repository for entities. + /// + public interface IConsentRepository : IReadWriteQueryRepository + { + /// + /// Clears the current flag. + /// + void ClearCurrent(string source, string context, string action); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs new file mode 100644 index 0000000000..fea0a61589 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Defines the base implementation of a repository for content items. + /// + public interface IContentRepository : IReadWriteQueryRepository + where TEntity : IUmbracoEntity + { + /// + /// Gets versions. + /// + /// Current version is first, and then versions are ordered with most recent first. + IEnumerable GetAllVersions(int nodeId); + + /// + /// Gets version identifiers. + /// + /// Current version is first, and then versions are ordered with most recent first. + IEnumerable GetVersionIds(int id, int topRows); + + /// + /// Gets a version. + /// + TEntity GetVersion(int versionId); + + /// + /// Deletes a version. + /// + void DeleteVersion(int versionId); + + /// + /// Deletes all versions older than a date. + /// + void DeleteVersions(int nodeId, DateTime versionDate); + + /// + /// Gets the recycle bin identifier. + /// + int RecycleBinId { get; } + + /// + /// Gets the recycle bin content. + /// + IEnumerable GetRecycleBin(); + + /// + /// Gets the count of content items of a given content type. + /// + int Count(string contentTypeAlias = null); + + /// + /// Gets the count of child content items of a given parent content, of a given content type. + /// + int CountChildren(int parentId, string contentTypeAlias = null); + + /// + /// Gets the count of descendant content items of a given parent content, of a given content type. + /// + int CountDescendants(int parentId, string contentTypeAlias = null); + + /// + /// Gets paged content items. + /// + IEnumerable GetPage(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs new file mode 100644 index 0000000000..d0fec639fe --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IContentTypeRepository : IContentTypeRepositoryBase + { + /// + /// Gets all entities of the specified query + /// + /// + /// An enumerable list of objects + IEnumerable GetByQuery(IQuery query); + + /// + /// Gets all property type aliases. + /// + /// + IEnumerable GetAllPropertyTypeAliases(); + + /// + /// Gets all content type aliases + /// + /// + /// If this list is empty, it will return all content type aliases for media, members and content, otherwise + /// it will only return content type aliases for the object types specified + /// + /// + IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes); + + IEnumerable GetAllContentTypeIds(string[] aliases); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs new file mode 100644 index 0000000000..f5e9013082 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Events; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IContentTypeRepositoryBase : IReadWriteQueryRepository, IReadRepository + where TItem : IContentTypeComposition + { + TItem Get(string alias); + IEnumerable> Move(TItem moving, EntityContainer container); + IEnumerable GetTypesDirectlyComposedOf(int id); + + /// + /// Derives a unique alias from an existing alias. + /// + /// The original alias. + /// The original alias with a number appended to it, so that it is unique. + /// Unique accross all content, media and member types. + string GetUniqueAlias(string alias); + + + /// + /// Gets a value indicating whether there is a list view content item in the path. + /// + /// + /// + bool HasContainerInPath(string contentPath); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs new file mode 100644 index 0000000000..460bed71d3 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Persistence.Repositories +{ + interface IDataTypeContainerRepository : IEntityContainerRepository + { } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs new file mode 100644 index 0000000000..afb419ebd6 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Umbraco.Core.Events; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IDataTypeRepository : IReadWriteQueryRepository + { + IEnumerable> Move(IDataType toMove, EntityContainer container); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDictionaryRepository.cs similarity index 78% rename from src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IDictionaryRepository.cs index 5c120c76cb..6314b01374 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDictionaryRepository.cs @@ -1,14 +1,14 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Persistence.Repositories -{ - public interface IDictionaryRepository : IRepositoryQueryable - { - IDictionaryItem Get(Guid uniqueId); - IDictionaryItem Get(string key); - IEnumerable GetDictionaryItemDescendants(Guid? parentId); - Dictionary GetDictionaryItemKeyMap(); - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IDictionaryRepository : IReadWriteQueryRepository + { + IDictionaryItem Get(Guid uniqueId); + IDictionaryItem Get(string key); + IEnumerable GetDictionaryItemDescendants(Guid? parentId); + Dictionary GetDictionaryItemKeyMap(); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs new file mode 100644 index 0000000000..c52601d629 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Persistence.Repositories +{ + interface IDocumentBlueprintRepository : IDocumentRepository + { } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs new file mode 100644 index 0000000000..a3fb50149d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IDocumentRepository : IContentRepository, IReadRepository + { + /// + /// Get the count of published items + /// + /// + /// + /// We require this on the repo because the IQuery{IContent} cannot supply the 'newest' parameter + /// + int CountPublished(string contentTypeAlias = null); + + bool IsPathPublished(IContent content); + + /// + /// Used to bulk update the permissions set for a content item. This will replace all permissions + /// assigned to an entity with a list of user id & permission pairs. + /// + /// + void ReplaceContentPermissions(EntityPermissionSet permissionSet); + + /// + /// Assigns a single permission to the current content item for the specified user group ids + /// + /// + /// + /// + void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds); + + /// + /// Gets the explicit list of permissions for the content item + /// + /// + /// + EntityPermissionCollection GetPermissionsForEntity(int entityId); + + /// + /// Used to add/update a permission for a content item + /// + /// + void AddOrUpdatePermissions(ContentPermissionSet permission); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs new file mode 100644 index 0000000000..ccf8df268d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Persistence.Repositories +{ + interface IDocumentTypeContainerRepository : IEntityContainerRepository + { } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDomainRepository.cs similarity index 81% rename from src/Umbraco.Core/Persistence/Repositories/Interfaces/IDomainRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IDomainRepository.cs index 0371d70f2e..1542f54801 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDomainRepository.cs @@ -3,11 +3,11 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IDomainRepository : IRepositoryQueryable + public interface IDomainRepository : IReadWriteQueryRepository { IDomain GetByName(string domainName); bool Exists(string domainName); IEnumerable GetAll(bool includeWildcards); IEnumerable GetAssignedDomains(int contentId, bool includeWildcards); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs new file mode 100644 index 0000000000..47f0cee52b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs @@ -0,0 +1,7 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + interface IEntityContainerRepository : IReadRepository, IWriteRepository + { } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs new file mode 100644 index 0000000000..b53b117a1a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IEntityRepository : IRepository + { + IEntitySlim Get(int id); + IEntitySlim Get(Guid key); + IEntitySlim Get(int id, Guid objectTypeId); + IEntitySlim Get(Guid key, Guid objectTypeId); + + IEnumerable GetAll(Guid objectType, params int[] ids); + IEnumerable GetAll(Guid objectType, params Guid[] keys); + + IEnumerable GetByQuery(IQuery query); + IEnumerable GetByQuery(IQuery query, Guid objectType); + + UmbracoObjectTypes GetObjectType(int id); + UmbracoObjectTypes GetObjectType(Guid key); + + IEnumerable GetAllPaths(Guid objectType, params int[] ids); + IEnumerable GetAllPaths(Guid objectType, params Guid[] keys); + + bool Exists(int id); + bool Exists(Guid key); + + IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, IQuery filter = null); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs new file mode 100644 index 0000000000..6d145e9961 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Microsoft.AspNet.Identity; +using Umbraco.Core.Models.Identity; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IExternalLoginRepository : IReadWriteQueryRepository + { + void SaveUserLogins(int memberId, IEnumerable logins); + void DeleteUserLogins(int memberId); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs new file mode 100644 index 0000000000..43f7e9de04 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface ILanguageRepository : IReadWriteQueryRepository + { + ILanguage GetByCultureName(string cultureName); + ILanguage GetByIsoCode(string isoCode); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs new file mode 100644 index 0000000000..eefb48933e --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs @@ -0,0 +1,12 @@ +using System; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + internal interface IMacroRepository : IReadWriteQueryRepository, IReadRepository + { + + //IEnumerable GetAll(params string[] aliases); + + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs new file mode 100644 index 0000000000..d4ec08a0df --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs @@ -0,0 +1,10 @@ +using System; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IMediaRepository : IContentRepository, IReadRepository + { + IMedia GetMediaByPath(string mediaPath); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs new file mode 100644 index 0000000000..a1872df0cd --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Persistence.Repositories +{ + interface IMediaTypeContainerRepository : IEntityContainerRepository + { } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs new file mode 100644 index 0000000000..09059089e7 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs @@ -0,0 +1,16 @@ +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/Interfaces/IMemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberGroupRepository.cs similarity index 90% rename from src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberGroupRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IMemberGroupRepository.cs index 2750457271..9c75c051bd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberGroupRepository.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IMemberGroupRepository : IRepositoryQueryable + public interface IMemberGroupRepository : IReadWriteQueryRepository { /// /// Gets a member group by it's name @@ -39,5 +39,7 @@ namespace Umbraco.Core.Persistence.Repositories void AssignRoles(int[] memberIds, string[] roleNames); void DissociateRoles(int[] memberIds, string[] roleNames); + + int[] GetMemberIds(string[] names); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs new file mode 100644 index 0000000000..245c024356 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IMemberRepository : IContentRepository + { + /// + /// Finds members in a given role + /// + /// + /// + /// + /// + IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + + /// + /// Get all members in a specific group + /// + /// + /// + IEnumerable GetByMemberGroup(string groupName); + + /// + /// Checks if a member with the username exists + /// + /// + /// + bool Exists(string username); + + /// + /// Gets the count of items based on a complex query + /// + /// + /// + int GetCountByQuery(IQuery query); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs new file mode 100644 index 0000000000..3b78d9de57 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs @@ -0,0 +1,7 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IMemberTypeRepository : IContentTypeRepositoryBase + { } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs new file mode 100644 index 0000000000..d24e981085 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface INotificationsRepository : IRepository + { + Notification CreateNotification(IUser user, IEntity entity, string action); + int DeleteNotifications(IUser user); + int DeleteNotifications(IEntity entity); + int DeleteNotifications(IUser user, IEntity entity); + IEnumerable GetEntityNotifications(IEntity entity); + IEnumerable GetUserNotifications(IUser user); + IEnumerable GetUsersNotifications(IEnumerable userIds, string action, IEnumerable nodeIds, Guid objectType); + IEnumerable SetNotifications(IUser user, IEntity entity, string[] actions); + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IPartialViewMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IPartialViewMacroRepository.cs new file mode 100644 index 0000000000..fd83ffdfcc --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IPartialViewMacroRepository.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Persistence.Repositories +{ + // this only exists to differenciate with IPartialViewRepository in IoC + // without resorting to constants, names, whatever - and IPartialViewRepository + // is implemented by PartialViewRepository and IPartialViewMacroRepository by + // PartialViewMacroRepository - just to inject the proper filesystem. + public interface IPartialViewMacroRepository : IPartialViewRepository + { } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IPartialViewRepository.cs similarity index 77% rename from src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IPartialViewRepository.cs index 27342fe643..5a7bae1ce3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IPartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IPartialViewRepository.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - internal interface IPartialViewRepository : IRepository + public interface IPartialViewRepository : IReadRepository, IWriteRepository { void AddFolder(string folderPath); void DeleteFolder(string folderPath); @@ -12,4 +12,4 @@ namespace Umbraco.Core.Persistence.Repositories void SetFileContent(string filepath, Stream content); long GetFileSize(string filepath); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs new file mode 100644 index 0000000000..8ed8b17114 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs @@ -0,0 +1,8 @@ +using System; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IPublicAccessRepository : IReadWriteQueryRepository + { } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs similarity index 97% rename from src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs index adf0816196..47a56bb530 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Defines the repository. /// - public interface IRedirectUrlRepository : IRepositoryQueryable + public interface IRedirectUrlRepository : IReadWriteQueryRepository { /// /// Gets a redirect url. diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs new file mode 100644 index 0000000000..51d7656d8a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IRelationRepository : IReadWriteQueryRepository + { + + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationTypeRepository.cs new file mode 100644 index 0000000000..393bf5a7ca --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationTypeRepository.cs @@ -0,0 +1,8 @@ +using System; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IRelationTypeRepository : IReadWriteQueryRepository, IReadRepository + { } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IScriptRepository.cs similarity index 75% rename from src/Umbraco.Core/Persistence/Repositories/Interfaces/IScriptRepository.cs rename to src/Umbraco.Core/Persistence/Repositories/IScriptRepository.cs index c2c0a0ae84..ea88cb7618 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IScriptRepository.cs @@ -1,13 +1,13 @@ -using System.IO; -using Umbraco.Core.Models; - -namespace Umbraco.Core.Persistence.Repositories -{ - public interface IScriptRepository : IRepository - { - bool ValidateScript(Script script); - Stream GetFileContentStream(string filepath); - void SetFileContent(string filepath, Stream content); - long GetFileSize(string filepath); - } -} \ No newline at end of file +using System.IO; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IScriptRepository : IReadRepository, IWriteRepository diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/border.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/border.html deleted file mode 100644 index 651cd67446..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/border.html +++ /dev/null @@ -1,20 +0,0 @@ - -
- -
-
    -
  • -
-
- -
-
- - -
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/color.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/color.html deleted file mode 100644 index 94a412f128..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/color.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.html deleted file mode 100644 index 6ff7aa2a72..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/googlefontpicker.html +++ /dev/null @@ -1,34 +0,0 @@ - -
- -
-
- Aa - {{ item.values.fontFamily }} - -
-
- -
- - diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/gridRow.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/gridRow.html deleted file mode 100644 index bcfc79ef63..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/gridRow.html +++ /dev/null @@ -1,8 +0,0 @@ - -
- -
- -
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/layout.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/layout.html deleted file mode 100644 index 5a0dfed448..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/layout.html +++ /dev/null @@ -1,10 +0,0 @@ - -
- -
- Box - Wide - Full -
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/margin.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/margin.html deleted file mode 100644 index 145c38f2f3..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/margin.html +++ /dev/null @@ -1,14 +0,0 @@ - -
- -
-
    -
  • -
-
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/padding.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/padding.html deleted file mode 100644 index e645ce0a67..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/padding.html +++ /dev/null @@ -1,14 +0,0 @@ - -
- -
-
    -
  • -
-
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/radius.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/radius.html deleted file mode 100644 index 328698b44b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/radius.html +++ /dev/null @@ -1,21 +0,0 @@ - -
- -
-
    - -
  • - - - - -
  • - -
-
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/shadow.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/shadow.html deleted file mode 100644 index f9f88401c4..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/shadow.html +++ /dev/null @@ -1,8 +0,0 @@ - -
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/slider.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/slider.html deleted file mode 100644 index f33d314841..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/editors/slider.html +++ /dev/null @@ -1,8 +0,0 @@ - -
- -
-
-
- -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html b/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html deleted file mode 100644 index e5778a9d9b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/index.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - Umbraco Canvas Designer - - - - - - - -
- -
- -
- -
- -
- -
- -
- - - - - -
- -
- -
-

Palette Style

-
- - - - - -
- -
- -
-
-

Select

-
-
-
    -
  • - {{configItem.name}} -
  • -
-
-
- -
-
-

{{configItem.name}}

-
-
-
-

- {{category}} - - -

-
-
-
{{item.name}}
-
-
-
-
-
-
- - - -
- -
- -
- -
-

Styles saved and published

-
- - - - - - - diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js index 29f327881b..461101a66c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js @@ -64,14 +64,19 @@ }; scope.avatarClick = function () { - scope.userDialog = { - view: "user", - show: true, - close: function (oldModel) { - scope.userDialog.show = false; - scope.userDialog = null; - } - }; + if(!scope.userDialog) { + scope.userDialog = { + view: "user", + show: true, + close: function (oldModel) { + scope.userDialog.show = false; + scope.userDialog = null; + } + }; + } else { + scope.userDialog.show = false; + scope.userDialog = null; + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js index 4b0bdb6c71..89f244f4d7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js @@ -125,6 +125,12 @@ Use this directive to render an umbraco button. The directive can be used to gen }); + scope.clickButton = function(event) { + if(scope.action) { + scope.action({$event: event}); + } + }; + scope.$on('$destroy', function() { unbindStateWatcher(); }); 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 86400d13fa..e02bb36764 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 @@ -1,536 +1,539 @@ (function () { - 'use strict'; + 'use strict'; - function ContentEditController($rootScope, $scope, $routeParams, $q, $timeout, $window, $location, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http, eventsService, relationResource) { + function ContentEditController($rootScope, $scope, $routeParams, $q, $timeout, $window, $location, + appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, + serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, + keyboardService, umbModelMapper, editorState, $http, eventsService, relationResource, overlayService) { - var evts = []; + var evts = []; - //setup scope vars - $scope.defaultButton = null; - $scope.subButtons = []; + //setup scope vars + $scope.defaultButton = null; + $scope.subButtons = []; - $scope.page = {}; - $scope.page.loading = false; - $scope.page.menu = {}; - $scope.page.menu.currentNode = null; - $scope.page.menu.currentSection = appState.getSectionState("currentSection"); - $scope.page.listViewPath = null; - $scope.page.isNew = $scope.isNew ? true : false; - $scope.page.buttonGroupState = "init"; - $scope.allowOpen = true; - - // add all editors to an editors array to support split view - $scope.editors = []; - $scope.splitView = { - "leftIsOpen": true, - "rightIsOpen": false - }; + $scope.page = {}; + $scope.page.loading = false; + $scope.page.menu = {}; + $scope.page.menu.currentNode = null; + $scope.page.menu.currentSection = appState.getSectionState("currentSection"); + $scope.page.listViewPath = null; + $scope.page.isNew = $scope.isNew ? true : false; + $scope.page.buttonGroupState = "init"; + $scope.page.languageId = $scope.languageId; + $scope.allowOpen = true; - function init(content) { + // add all editors to an editors array to support split view + $scope.editors = []; + $scope.splitView = { + "leftIsOpen": true, + "rightIsOpen": false + }; - createButtons(content); + function init(content) { - editorState.set($scope.content); + createButtons(content); - //We fetch all ancestors of the node to generate the footer breadcrumb navigation - if (!$scope.page.isNew) { - if (content.parentId && content.parentId !== -1) { - entityResource.getAncestors(content.id, "document") - .then(function (anc) { - $scope.ancestors = anc; - }); + editorState.set($scope.content); + + //We fetch all ancestors of the node to generate the footer breadcrumb navigation + if (!$scope.page.isNew) { + if (content.parentId && content.parentId !== -1) { + entityResource.getAncestors(content.id, "document") + .then(function (anc) { + $scope.ancestors = anc; + }); + } + } + + //init can be called more than once and we don't want to have multiple bound events + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + + evts.push(eventsService.on("editors.content.changePublishDate", function (event, args) { + createButtons(args.node); + })); + + evts.push(eventsService.on("editors.content.changeUnpublishDate", function (event, args) { + createButtons(args.node); + })); + + // We don't get the info tab from the server from version 7.8 so we need to manually add it + //contentEditingHelper.addInfoTab($scope.content.tabs); + + // prototype content and info apps + var contentApp = { + "name": "Content", + "alias": "content", + "icon": "icon-document", + "view": "views/content/apps/content/content.html" + }; + + var infoApp = { + "name": "Info", + "alias": "info", + "icon": "icon-info", + "view": "views/content/apps/info/info.html" + }; + + var listview = { + "name": "Child items", + "alias": "childItems", + "icon": "icon-list", + "view": "views/content/apps/listview/listview.html" + }; + + $scope.content.apps = []; + + if ($scope.content.isContainer) { + // add list view app + $scope.content.apps.push(listview); + + // remove the list view tab + angular.forEach($scope.content.tabs, function (tab, index) { + if (tab.alias === "umbContainerView") { + tab.hide = true; + } + }); + } + + $scope.content.apps.push(contentApp); + $scope.content.apps.push(infoApp); + + // set first app to active + $scope.content.apps[0].active = true; + + // create new editor for split view + if ($scope.editors.length === 0) { + var editor = { + content: $scope.content + }; + $scope.editors.push(editor); + } + else if ($scope.editors.length === 1) { + $scope.editors[0].content = $scope.content; + } + else { + //fixme - need to fix something here if we are re-loading a content item that is in a split view + } } - } - evts.push(eventsService.on("editors.content.changePublishDate", function (event, args) { - createButtons(args.node); - })); + /** + * This does the content loading and initializes everything, called on load and changing variants + * @param {any} languageId + */ + function getNode(languageId) { - evts.push(eventsService.on("editors.content.changeUnpublishDate", function (event, args) { - createButtons(args.node); - })); + $scope.page.loading = true; - // We don't get the info tab from the server from version 7.8 so we need to manually add it - //contentEditingHelper.addInfoTab($scope.content.tabs); + //we are editing so get the content item from the server + $scope.getMethod()($scope.contentId, languageId) + .then(function (data) { - // prototype variants - $scope.content.variants = [ - { - "cultureDisplayName": "English (United States)", - "culture": "en-US", - "current": true, - "segments" : [ - { - "name": "Mobile" + $scope.content = data; + + if (data.isChildOfListView && data.trashed === false) { + $scope.page.listViewPath = ($routeParams.page) ? + "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page : + "/content/content/edit/" + data.parentId; + } + + init($scope.content); + + //in one particular special case, after we've created a new item we redirect back to the edit + // route but there might be server validation errors in the collection which we need to display + // after the redirect, so we will bind all subscriptions which will show the server validation errors + // if there are any and then clear them so the collection no longer persists them. + serverValidationManager.executeAndClearAllSubscriptions(); + + syncTreeNode($scope.content, data.path, true); + + resetLastListPageNumber($scope.content); + + $scope.page.loading = false; + + }); + + } + + function createButtons(content) { + $scope.page.buttonGroupState = "init"; + var buttons = contentEditingHelper.configureContentEditorButtons({ + create: $scope.page.isNew, + content: content, + methods: { + saveAndPublish: $scope.saveAndPublish, + sendToPublish: $scope.sendToPublish, + save: $scope.save, + unPublish: $scope.unPublish + } + }); + + $scope.defaultButton = buttons.defaultButton; + $scope.subButtons = buttons.subButtons; + + } + + /** Syncs the content item to it's tree node - this occurs on first load and after saving */ + function syncTreeNode(content, path, initialLoad) { + + if (!$scope.content.isChildOfListView) { + navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { + $scope.page.menu.currentNode = syncArgs.node; + }); + } + else if (initialLoad === true) { + + //it's a child item, just sync the ui node to the parent + navigationService.syncTree({ tree: $scope.treeAlias, path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true }); + + //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node + // from the server so that we can load in the actions menu. + umbRequestHelper.resourcePromise( + $http.get(content.treeNodeUrl), + 'Failed to retrieve data for child node ' + content.id).then(function (node) { + $scope.page.menu.currentNode = node; + }); + } + } + + // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish + function performSave(args) { + + $scope.page.buttonGroupState = "busy"; + + return contentEditingHelper.contentEditorPerformSave({ + saveMethod: args.saveMethod, + scope: $scope, + content: $scope.content, + action: args.action + }).then(function (data) { + //success + init($scope.content); + syncTreeNode($scope.content, data.path); + + $scope.page.buttonGroupState = "success"; + return $q.when(data); }, - { - "name": "Job campign" + function (err) { + //error + if (err) { + editorState.set($scope.content); + } + + $scope.page.buttonGroupState = "error"; + return $q.reject(err); + }); + } + + function resetLastListPageNumber(content) { + // We're using rootScope to store the page number for list views, so if returning to the list + // we can restore the page. If we've moved on to edit a piece of content that's not the list or it's children + // we should remove this so as not to confuse if navigating to a different list + if (!content.isChildOfListView && !content.isContainer) { + $rootScope.lastListViewPageViewed = null; } - ], - "state": "Published" - }, - { - "cultureDisplayName": "Danish", - "culture": "da-DK", - "current": false, - "segments" : [ - { - "name": "Mobile" - } - ], - "state": "Published" - }, - { - "cultureDisplayName": "Spanish (Spain)", - "culture": "es-ES", - "current": false, - "state": "Published (pending changes)" - }, - { - "cultureDisplayName": "French (France)", - "culture": "fr-FR", - "current": false, - "segments" : [ - { - "name": "Mobile" - }, - { - "name": "Job campign" - } - ], - "state": "Draft" - }, - { - "cultureDisplayName": "German (Germany)", - "culture": "de-DE", - "current": false, - "state": "Draft" - } - ]; - - // prototype content and info apps - var contentApp = { - "name": "Content", - "alias": "content", - "icon": "icon-document", - "view": "views/content/apps/content/content.html" - }; - - var infoApp = { - "name": "Info", - "alias": "info", - "icon": "icon-info", - "view": "views/content/apps/info/info.html" - }; - - var listview = { - "name": "Child items", - "alias": "childItems", - "icon": "icon-list", - "view": "views/content/apps/listview/listview.html" - }; - - $scope.content.apps = []; - - if($scope.content.isContainer) { - // add list view app - $scope.content.apps.push(listview); - - // remove the list view tab - angular.forEach($scope.content.tabs, function(tab, index){ - if(tab.alias === "umbContainerView") { - tab.hide = true; - } - }); - - } - - $scope.content.apps.push(contentApp); - $scope.content.apps.push(infoApp); - - // set first app to active - $scope.content.apps[0].active = true; - - // create new editor for split view - if($scope.editors.length === 0) { - var editor = {}; - editor.content = $scope.content; - $scope.editors.push(editor); - } - - } - - function getNode() { - - $scope.page.loading = true; - - //we are editing so get the content item from the server - $scope.getMethod()($scope.contentId) - .then(function (data) { - - $scope.content = data; - - if (data.isChildOfListView && data.trashed === false) { - $scope.page.listViewPath = ($routeParams.page) ? - "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page : - "/content/content/edit/" + data.parentId; - } - - init($scope.content); - - //in one particular special case, after we've created a new item we redirect back to the edit - // route but there might be server validation errors in the collection which we need to display - // after the redirect, so we will bind all subscriptions which will show the server validation errors - // if there are any and then clear them so the collection no longer persists them. - serverValidationManager.executeAndClearAllSubscriptions(); - - syncTreeNode($scope.content, data.path, true); - - resetLastListPageNumber($scope.content); - - $scope.page.loading = false; - - }); - - } - - function createButtons(content) { - $scope.page.buttonGroupState = "init"; - var buttons = contentEditingHelper.configureContentEditorButtons({ - create: $scope.page.isNew, - content: content, - methods: { - saveAndPublish: $scope.saveAndPublish, - sendToPublish: $scope.sendToPublish, - save: $scope.save, - unPublish: $scope.unPublish - } - }); - - $scope.defaultButton = buttons.defaultButton; - $scope.subButtons = buttons.subButtons; - - } - - /** Syncs the content item to it's tree node - this occurs on first load and after saving */ - function syncTreeNode(content, path, initialLoad) { - - if (!$scope.content.isChildOfListView) { - navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { - $scope.page.menu.currentNode = syncArgs.node; - }); - } - else if (initialLoad === true) { - - //it's a child item, just sync the ui node to the parent - navigationService.syncTree({ tree: $scope.treeAlias, path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true }); - - //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node - // from the server so that we can load in the actions menu. - umbRequestHelper.resourcePromise( - $http.get(content.treeNodeUrl), - 'Failed to retrieve data for child node ' + content.id).then(function (node) { - $scope.page.menu.currentNode = node; - }); - } - } - - // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish - function performSave(args) { - var deferred = $q.defer(); - - $scope.page.buttonGroupState = "busy"; - - contentEditingHelper.contentEditorPerformSave({ - statusMessage: args.statusMessage, - saveMethod: args.saveMethod, - scope: $scope, - content: $scope.content, - action: args.action - }).then(function (data) { - //success - init($scope.content); - syncTreeNode($scope.content, data.path); - - $scope.page.buttonGroupState = "success"; - - deferred.resolve(data); - }, function (err) { - //error - if (err) { - editorState.set($scope.content); } - $scope.page.buttonGroupState = "error"; + if ($scope.page.isNew) { - deferred.reject(err); - }); + $scope.page.loading = true; - return deferred.promise; - } + //we are creating so get an empty content item + $scope.getScaffoldMethod()() + .then(function (data) { - function resetLastListPageNumber(content) { - // We're using rootScope to store the page number for list views, so if returning to the list - // we can restore the page. If we've moved on to edit a piece of content that's not the list or it's children - // we should remove this so as not to confuse if navigating to a different list - if (!content.isChildOfListView && !content.isContainer) { - $rootScope.lastListViewPageViewed = null; - } - } + $scope.content = data; - if ($scope.page.isNew) { + init($scope.content); - $scope.page.loading = true; + resetLastListPageNumber($scope.content); - //we are creating so get an empty content item - $scope.getScaffoldMethod()() - .then(function (data) { + $scope.page.loading = false; - $scope.content = data; - - init($scope.content); - - resetLastListPageNumber($scope.content); - - $scope.page.loading = false; - - }); - } - else { - - getNode(); - - } - - - $scope.unPublish = function () { - - if (formHelper.submitForm({ scope: $scope, statusMessage: "Unpublishing...", skipValidation: true })) { - - $scope.page.buttonGroupState = "busy"; - - contentResource.unPublish($scope.content.id) - .then(function (data) { - - formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - savedContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) - }); - - init($scope.content); - - syncTreeNode($scope.content, data.path); - - $scope.page.buttonGroupState = "success"; - - }, function(err) { - $scope.page.buttonGroupState = 'error'; - }); - } - - }; - - $scope.sendToPublish = function () { - return performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending...", action: "sendToPublish" }); - }; - - $scope.saveAndPublish = function () { - return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing...", action: "publish" }); - }; - - $scope.save = function () { - return performSave({ saveMethod: $scope.saveMethod(), statusMessage: "Saving...", action: "save" }); - }; - - $scope.preview = function (content) { - - - if (!$scope.busy) { - - // Chromes popup blocker will kick in if a window is opened - // without the initial scoped request. This trick will fix that. - // - var previewWindow = $window.open('preview/?init=true&id=' + content.id, 'umbpreview'); - - // Build the correct path so both /#/ and #/ work. - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id; - - //The user cannot save if they don't have access to do that, in which case we just want to preview - //and that's it otherwise they'll get an unauthorized access message - if (!_.contains(content.allowedActions, "A")) { - previewWindow.location.href = redirect; + }); } else { - $scope.save().then(function (data) { - previewWindow.location.href = redirect; - }); + + //Browse content nodes based on the selected tree language variant + $scope.page.languageId ? getNode($scope.page.languageId) : getNode(); + } - } + $scope.unPublish = function () { - }; + if (formHelper.submitForm({ scope: $scope, skipValidation: true })) { - $scope.backToListView = function() { - $location.path($scope.page.listViewPath); - }; + $scope.page.buttonGroupState = "busy"; - $scope.restore = function (content) { + contentResource.unPublish($scope.content.id) + .then(function (data) { - $scope.page.buttonRestore = "busy"; + formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - relationResource.getByChildId(content.id, "relateParentDocumentOnDelete").then(function (data) { + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + savedContent: data, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); - var relation = null; - var target = null; - var error = { headline: "Cannot automatically restore this item", content: "Use the Move menu item to move it manually"}; + init($scope.content); - if (data.length == 0) { - notificationsService.error(error.headline, "There is no 'restore' relation found for this node. Use the Move menu item to move it manually."); - $scope.page.buttonRestore = "error"; - return; - } + syncTreeNode($scope.content, data.path); - relation = data[0]; + $scope.page.buttonGroupState = "success"; - if (relation.parentId == -1) { - target = { id: -1, name: "Root" }; - moveNode(content, target); - } else { - contentResource.getById(relation.parentId).then(function (data) { - target = data; - - // make sure the target item isn't in the recycle bin - if(target.path.indexOf("-20") !== -1) { - notificationsService.error(error.headline, "The item you want to restore it under (" + target.name + ") is in the recycle bin. Use the Move menu item to move the item manually."); - $scope.page.buttonRestore = "error"; - return; + }, function (err) { + $scope.page.buttonGroupState = 'error'; + }); } - moveNode(content, target); + }; + + $scope.sendToPublish = function () { + return performSave({ saveMethod: contentResource.sendToPublish, action: "sendToPublish" }); + }; + + $scope.saveAndPublish = function () { + + // TODO: Add "..." to publish button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant + if ($scope.content.variants.length > 1) { + //before we launch the dialog we want to execute all client side validations first + if (formHelper.submitForm({ scope: $scope, action: "publish" })) { + var dialog = { + title: "Ready to Publish?", //TODO: localize + view: "publish", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabel: "Publish", + submit: function (model) { + model.submitButtonState = "busy"; + + //we need to return this promise so that the dialog can handle the result and wire up the validation response + return performSave({ + saveMethod: contentResource.publish, + action: "publish" + }).then(function (data) { + overlayService.close(); + return $q.when(data); + }, + function (err) { + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + + //check the error list for specific variant errors, if none exist that means that only server side validation + //for the current variant's properties failed, in this case we want to close the publish dialog since the user + //will need to fix validation errors on the properties + if (err.data && err.data.ModelState) { + var keys = _.keys(err.data.ModelState); + var foundVariantError = _.find(keys, + function (k) { + return k.startsWith("publish_variant_"); + }); + if (!foundVariantError) { + //no variant errors, close the dialog + overlayService.close(); + } + } + + return $q.reject(err); + }); + }, + close: function (oldModel) { + overlayService.close(); + } + }; + + overlayService.open(dialog); + } + } + else { + return performSave({ saveMethod: contentResource.publish, action: "publish" }); + } + }; + + $scope.save = function () { + return performSave({ saveMethod: $scope.saveMethod(), action: "save" }); + }; + + $scope.preview = function (content) { + + + if (!$scope.busy) { + + // Chromes popup blocker will kick in if a window is opened + // without the initial scoped request. This trick will fix that. + // + var previewWindow = $window.open('preview/?init=true&id=' + content.id, 'umbpreview'); + + // Build the correct path so both /#/ and #/ work. + var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id; + + //The user cannot save if they don't have access to do that, in which case we just want to preview + //and that's it otherwise they'll get an unauthorized access message + if (!_.contains(content.allowedActions, "A")) { + previewWindow.location.href = redirect; + } + else { + $scope.save().then(function (data) { + previewWindow.location.href = redirect; + }); + } + } + }; + + $scope.backToListView = function () { + $location.path($scope.page.listViewPath); + }; + + $scope.restore = function (content) { + + $scope.page.buttonRestore = "busy"; + + relationResource.getByChildId(content.id, "relateParentDocumentOnDelete").then(function (data) { + + var relation = null; + var target = null; + var error = { headline: "Cannot automatically restore this item", content: "Use the Move menu item to move it manually" }; + + if (data.length == 0) { + notificationsService.error(error.headline, "There is no 'restore' relation found for this node. Use the Move menu item to move it manually."); + $scope.page.buttonRestore = "error"; + return; + } + + relation = data[0]; + + if (relation.parentId == -1) { + target = { id: -1, name: "Root" }; + moveNode(content, target); + } else { + contentResource.getById(relation.parentId).then(function (data) { + target = data; + + // make sure the target item isn't in the recycle bin + if (target.path.indexOf("-20") !== -1) { + notificationsService.error(error.headline, "The item you want to restore it under (" + target.name + ") is in the recycle bin. Use the Move menu item to move the item manually."); + $scope.page.buttonRestore = "error"; + return; + } + + moveNode(content, target); + + }, function (err) { + $scope.page.buttonRestore = "error"; + notificationsService.error(error.headline, error.content); + }); + } + + }, function (err) { + $scope.page.buttonRestore = "error"; + notificationsService.error(error.headline, error.content); + }); + }; + + $scope.closeSplitView = function (index, editor) { + // hacky animation stuff - it will be much better when angular is upgraded + editor.loading = true; + editor.collapsed = true; + $timeout(function () { + $scope.editors.splice(index, 1); + }, 400); + }; + + $scope.openInSplitView = function (selectedVariant) { + + console.log(selectedVariant); + + var editor = {}; + // hacking animation states - these should hopefully be easier to do when we upgrade angular + editor.collapsed = true; + editor.loading = true; + $scope.editors.push(editor); + var editorIndex = $scope.editors.length - 1; + $timeout(function () { + $scope.editors[editorIndex].collapsed = false; + }, 100); + + // fake loading of content + // TODO: Make this real, but how do we deal with saving since currently we only save one variant at a time?! + $timeout(function () { + $scope.editors[editorIndex].content = angular.copy($scope.content); + $scope.editors[editorIndex].content.name = "What a variant"; + // set selected variant on split view content + console.log($scope.editors[editorIndex].content.variants); + angular.forEach($scope.editors[editorIndex].content.variants, function (variant) { + if (variant.culture === selectedVariant.culture) { + variant.current = true; + } else { + variant.current = false; + } + }); + $scope.editors[editorIndex].loading = false; + }, 500); + }; + + function moveNode(node, target) { + + contentResource.move({ "parentId": target.id, "id": node.id }) + .then(function (path) { + + // remove the node that we're working on + if ($scope.page.menu.currentNode) { + treeService.removeNode($scope.page.menu.currentNode); + } + + // sync the destination node + navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }); + + $scope.page.buttonRestore = "success"; + notificationsService.success("Successfully restored " + node.name + " to " + target.name); + + // reload the node + getNode(); + + }, function (err) { + $scope.page.buttonRestore = "error"; + notificationsService.error("Cannot automatically restore this item", err); + }); - }, function (err) { - $scope.page.buttonRestore = "error"; - notificationsService.error(error.headline, error.content); - }); } - }, function (err) { - $scope.page.buttonRestore = "error"; - notificationsService.error(error.headline, error.content); - }); - - - }; - - $scope.selectVariant = function(variant, variants) { - $scope.content.name = "You changed to a variant"; - angular.forEach(variants, function(variant) { - variant.current = false; - }); - variant.current = true; - }; - - $scope.closeSplitView = function(index, editor) { - // hacky animation stuff - it will be much better when angular is upgraded - editor.loading = true; - editor.collapsed = true; - $timeout(function(){ - $scope.editors.splice(index, 1); - }, 400); - }; - - $scope.openInSplitView = function(selectedVariant) { - - console.log(selectedVariant); - - var editor = {}; - // hacking animation states - these should hopefully be easier to do when we upgrade angular - editor.collapsed = true; - editor.loading = true; - $scope.editors.push(editor); - var editorIndex = $scope.editors.length - 1; - $timeout(function(){ - $scope.editors[editorIndex].collapsed = false; - }, 100); - - // fake loading of content - $timeout(function(){ - $scope.editors[editorIndex].content = angular.copy($scope.content); - $scope.editors[editorIndex].content.name = "What a variant"; - // set selected variant on split view content - console.log($scope.editors[editorIndex].content.variants); - angular.forEach($scope.editors[editorIndex].content.variants, function(variant) { - if(variant.culture === selectedVariant.culture) { - variant.current = true; - } else { - variant.current = false; - } - }); - $scope.editors[editorIndex].loading = false; - }, 500); - }; - - function moveNode(node, target) { - - contentResource.move({ "parentId": target.id, "id": node.id }) - .then(function (path) { - - // remove the node that we're working on - if($scope.page.menu.currentNode) { - treeService.removeNode($scope.page.menu.currentNode); - } - - // sync the destination node - navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }); - - $scope.page.buttonRestore = "success"; - notificationsService.success("Successfully restored " + node.name + " to " + target.name); - - // reload the node - getNode(); - - }, function (err) { - $scope.page.buttonRestore = "error"; - notificationsService.error("Cannot automatically restore this item", err); + //ensure to unregister from all events! + $scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } }); } - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); + function createDirective() { - } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/content/edit.html', + controller: 'Umbraco.Editors.Content.EditorDirectiveController', + scope: { + contentId: "=", + isNew: "=?", + treeAlias: "@", + page: "=?", + saveMethod: "&", + getMethod: "&", + getScaffoldMethod: "&?", + languageId: "=?" + } + }; - function createDirective() { + return directive; - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/content/edit.html', - controller: 'Umbraco.Editors.Content.EditorDirectiveController', - scope: { - contentId: "=", - isNew: "=?", - treeAlias: "@", - page: "=?", - saveMethod: "&", - getMethod: "&", - getScaffoldMethod: "&?" - } - }; + } - return directive; - - } - - angular.module('umbraco.directives').controller('Umbraco.Editors.Content.EditorDirectiveController', ContentEditController); - angular.module('umbraco.directives').directive('contentEditor', createDirective); + angular.module('umbraco.directives').controller('Umbraco.Editors.Content.EditorDirectiveController', ContentEditController); + angular.module('umbraco.directives').directive('contentEditor', createDirective); })(); 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 dfae22c8be..703d1b8717 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 @@ -8,6 +8,8 @@ var evts = []; var isInfoTab = false; scope.publishStatus = {}; + + scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; function onInit() { @@ -50,7 +52,7 @@ scope.openDocumentType = function (documentType) { var url = "/settings/documenttypes/edit/" + documentType.id; - $location.path(url); + $location.url(url); }; scope.updateTemplate = function (templateAlias) { @@ -134,13 +136,13 @@ } // published node - if(node.hasPublishedVersion === true && node.publishDate && node.published === true) { + if(node.publishDate && node.published === true) { scope.publishStatus.label = localizationService.localize("content_published"); scope.publishStatus.color = "success"; } // published node with pending changes - if(node.hasPublishedVersion === true && node.publishDate && node.published === false) { + if (node.edited === true && node.publishDate) { scope.publishStatus.label = localizationService.localize("content_publishedPendingChanges"); scope.publishStatus.color = "success" } @@ -149,8 +151,17 @@ function setPublishDate(date) { + if (!date) { + return; + } + + //The date being passed in here is the user's local date/time that they have selected + //we need to convert this date back to the server date on the model. + + var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset); + // update publish value - scope.node.releaseDate = date; + scope.node.releaseDate = serverTime; // make sure dates are formatted to the user's locale formatDatesToLocal(); @@ -174,8 +185,17 @@ function setUnpublishDate(date) { + if (!date) { + return; + } + + //The date being passed in here is the user's local date/time that they have selected + //we need to convert this date back to the server date on the model. + + var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset); + // update publish value - scope.node.removeDate = date; + scope.node.removeDate = serverTime; // make sure dates are formatted to the user's locale formatDatesToLocal(); @@ -223,7 +243,7 @@ // load audit trail when on the info tab evts.push(eventsService.on("app.tabChange", function (event, args) { $timeout(function(){ - if (args.id === -1) { + if (args.alias === "info") { isInfoTab = true; loadAuditTrail(); } else { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index 3ac751bc64..520962a084 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -204,7 +204,7 @@ Use this directive to construct a header inside the main editor window. (function() { 'use strict'; - function EditorHeaderDirective(iconHelper) { + function EditorHeaderDirective(iconHelper, $location) { function link(scope, el, attr, ctrl) { @@ -246,12 +246,9 @@ Use this directive to construct a header inside the main editor window. } }; - scope.selectVariant = function(event, variant) { - console.log("selec variant called"); - if(scope.onSelectVariant) { - scope.onSelectVariant({"variant": variant}); - scope.vm.dropdownOpen = false; - } + scope.selectVariant = function (event, variant) { + scope.vm.dropdownOpen = false; + $location.search({ languageId: variant.language.id }); }; scope.openIconPicker = function() { @@ -324,7 +321,6 @@ Use this directive to construct a header inside the main editor window. hideDescription: "@", descriptionLocked: "@", variants: "=", - onSelectVariant: "&", navigation: "=", key: "=", onBack: "&?", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js index cceb3ac86f..6a8896b609 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js @@ -1,64 +1,64 @@ -(function() { - 'use strict'; +(function () { + 'use strict'; - function EditorNavigationDirective() { + function EditorNavigationDirective(eventsService) { - function link(scope, el, attr, ctrl) { + function link(scope, el, attr, ctrl) { - scope.showNavigation = true; + scope.showNavigation = true; - scope.clickNavigationItem = function(selectedItem) { - setItemToActive(selectedItem); - runItemAction(selectedItem); - }; + scope.clickNavigationItem = function (selectedItem) { + setItemToActive(selectedItem); + runItemAction(selectedItem); + eventsService.emit("app.tabChange", selectedItem); + }; - function runItemAction(selectedItem) { - if (selectedItem.action) { - selectedItem.action(selectedItem); - } - } - - function setItemToActive(selectedItem) { - // set all other views to inactive - if (selectedItem.view) { - - for (var index = 0; index < scope.navigation.length; index++) { - var item = scope.navigation[index]; - item.active = false; - } - - // set view to active - selectedItem.active = true; - - } - } - - function activate() { - - // hide navigation if there is only 1 item - if (scope.navigation.length <= 1) { - scope.showNavigation = false; + function runItemAction(selectedItem) { + if (selectedItem.action) { + selectedItem.action(selectedItem); + } } - } + function setItemToActive(selectedItem) { + // set all other views to inactive + if (selectedItem.view) { - activate(); + for (var index = 0; index < scope.navigation.length; index++) { + var item = scope.navigation[index]; + item.active = false; + } - } + // set view to active + selectedItem.active = true; + } + } - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-navigation.html', - scope: { - navigation: "=" - }, - link: link - }; + function activate() { - return directive; - } + // hide navigation if there is only 1 item + if (scope.navigation.length <= 1) { + scope.showNavigation = false; + } - angular.module('umbraco.directives.html').directive('umbEditorNavigation', EditorNavigationDirective); + } + + activate(); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-navigation.html', + scope: { + navigation: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives.html').directive('umbEditorNavigation', EditorNavigationDirective); })(); 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 0b2853a174..e18137085b 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 @@ -115,7 +115,9 @@ angular.module("umbraco.directives") toolbar: toolbar, content_css: stylesheets, style_formats: styleFormats, - autoresize_bottom_margin: 0 + autoresize_bottom_margin: 0, + //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix + cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster }; @@ -143,6 +145,12 @@ angular.module("umbraco.directives") } } } + if (val === "true") { + tinyMceConfig.customConfig[i] = true; + } + if (val === "false") { + tinyMceConfig.customConfig[i] = false; + } } angular.extend(baseLineConfigObj, tinyMceConfig.customConfig); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js index d728865015..64d5afdd27 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js @@ -59,7 +59,6 @@

General Options

-Lorem ipsum dolor sit amet.. @@ -74,7 +73,7 @@ Lorem ipsum dolor sit amet.. - + @@ -88,6 +87,11 @@ Lorem ipsum dolor sit amet.. + + + + + @@ -408,7 +412,7 @@ Opens an overlay to show a custom YSOD.
(function() { 'use strict'; - function OverlayDirective($timeout, formHelper, overlayHelper, localizationService) { + function OverlayDirective($timeout, formHelper, overlayHelper, localizationService, $q) { function link(scope, el, attr, ctrl) { @@ -439,7 +443,7 @@ Opens an overlay to show a custom YSOD.
// this has to be done inside a timeout to ensure the destroy // event on other overlays is run before registering a new one registerOverlay(); - + setOverlayIndent(); }); @@ -496,6 +500,7 @@ Opens an overlay to show a custom YSOD.
var activeElementType = document.activeElement.tagName; var clickableElements = ["A", "BUTTON"]; var submitOnEnter = document.activeElement.hasAttribute("overlay-submit-on-enter"); + var submitOnEnterValue = submitOnEnter ? document.activeElement.getAttribute("overlay-submit-on-enter") : ""; if(clickableElements.indexOf(activeElementType) === 0) { document.activeElement.click(); @@ -503,7 +508,9 @@ Opens an overlay to show a custom YSOD.
} else if(activeElementType === "TEXTAREA" && !submitOnEnter) { - } else { + } else if (submitOnEnter && submitOnEnterValue === "false") { + // don't do anything + }else { scope.$apply(function () { scope.submitForm(scope.model); }); @@ -554,8 +561,8 @@ Opens an overlay to show a custom YSOD.
var overlayWidth = el.context.clientWidth; el.css('width', overlayWidth - indentSize); - - if(scope.position === "center" || scope.position === "target") { + + if(scope.position === "center" && overlayIndex > 0 || scope.position === "target" && overlayIndex > 0) { var overlayTopPosition = el.context.offsetTop; el.css('top', overlayTopPosition + indentSize); } @@ -630,15 +637,22 @@ Opens an overlay to show a custom YSOD.
scope.submitForm = function(model) { if(scope.model.submit) { - if (formHelper.submitForm({scope: scope})) { - formHelper.resetForm({ scope: scope }); - - if(scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) { - scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton); - } else { - unregisterOverlay(); - scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton); - } + if (formHelper.submitForm({ scope: scope, skipValidation: scope.model.skipFormValidation})) { + + if (scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) { + //wrap in a when since we don't know if this is a promise or not + $q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then( + function() { + formHelper.resetForm({ scope: scope }); + }, angular.noop); + } else { + unregisterOverlay(); + //wrap in a when since we don't know if this is a promise or not + $q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then( + function() { + formHelper.resetForm({ scope: scope }); + }, angular.noop); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index adbc3a96a7..69457a6f10 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -4,29 +4,32 @@ * @restrict E **/ angular.module("umbraco.directives") - .directive('umbProperty', function (umbPropEditorHelper) { + .directive('umbProperty', function (umbPropEditorHelper, userService) { return { scope: { property: "=" }, transclude: true, restrict: 'E', - replace: true, + replace: true, templateUrl: 'views/components/property/umb-property.html', - link: function(scope) { - scope.propertyAlias = Umbraco.Sys.ServerVariables.isDebuggingEnabled === true ? scope.property.alias : null; + link: function (scope) { + userService.getCurrentUser().then(function (u) { + var isAdmin = u.userGroups.indexOf('admin') !== -1; + scope.propertyAlias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.property.alias : null; + }); }, //Define a controller for this directive to expose APIs to other directives controller: function ($scope, $timeout) { - + var self = this; //set the API properties/methods - + self.property = $scope.property; - self.setPropertyError = function(errorMsg) { + self.setPropertyError = function (errorMsg) { $scope.property.propertyErrorMessage = errorMsg; }; } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index cce4e0d792..335d27d5c4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -420,8 +420,8 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat emitEvent("treeNodeAltSelect", { element: elem, tree: scope.tree, node: n, event: ev }); }; - //watch for section changes - scope.$watch("section", function (newVal, oldVal) { + //watch for section changes and customtreeparams changes + scope.$watchCollection("[section, customtreeparams]", function (newVal, oldVal) { if (!scope.tree) { loadTree(); @@ -440,7 +440,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat //clear any active trees to reset lookups lastSection = newVal; } - }); + }); setupExternalEvents(); loadTree(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js index 40a1a2cc6d..ca389a74a7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js @@ -125,7 +125,7 @@ function link(scope, el, attr, ngModel) { // Load in ace library - assetsService.load(['lib/ace-builds/src-min-noconflict/ace.js', 'lib/ace-builds/src-min-noconflict/ext-language_tools.js']).then(function () { + assetsService.load(['lib/ace-builds/src-min-noconflict/ace.js', 'lib/ace-builds/src-min-noconflict/ext-language_tools.js'], scope).then(function () { if (angular.isUndefined(window.ace)) { throw new Error('ui-ace need ace to work... (o rly?)'); } else { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbclipboard.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbclipboard.directive.js index bb49a60eca..575b5bd698 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbclipboard.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbclipboard.directive.js @@ -101,7 +101,7 @@ var clipboard; var target = element[0]; - assetsService.loadJs("lib/clipboard/clipboard.min.js") + assetsService.loadJs("lib/clipboard/clipboard.min.js", scope) .then(function () { if(scope.umbClipboardTarget) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js index 7047fdb0a2..19ab789363 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js @@ -92,10 +92,10 @@ Use this directive to render a date time picker scope.hasTranscludedContent = element.find('.js-datePicker__transcluded-content')[0].children.length > 0; // load css file for the date picker - assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css'); + assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css', scope); // load the js file for the date picker - assetsService.loadJs('lib/datetimepicker/bootstrap-datetimepicker.js').then(function () { + assetsService.loadJs('lib/datetimepicker/bootstrap-datetimepicker.js', scope).then(function () { // init date picker initDatePicker(); }); 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 caa79439be..c0be05addf 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 @@ -489,7 +489,7 @@ scope.editPropertyTypeSettings = function(property, group) { - if (!property.inherited && !property.locked) { + if (!property.inherited) { scope.propertySettingsDialogModel = {}; scope.propertySettingsDialogModel.title = "Property settings"; @@ -547,6 +547,7 @@ property.validation.pattern = oldModel.property.validation.pattern; property.showOnMemberProfile = oldModel.property.showOnMemberProfile; property.memberCanEdit = oldModel.property.memberCanEdit; + property.isSensitiveValue = oldModel.property.isSensitiveValue; // because we set state to active, to show a preview, we have to check if has been filled out // label is required so if it is not filled we know it is a placeholder diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js index e15f62e8df..104736530f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/nodirtycheck.directive.js @@ -10,12 +10,12 @@ function noDirtyCheck() { require: 'ngModel', link: function (scope, elm, attrs, ctrl) { - elm.focus(function () { - scope.$watch(function() { - ctrl.$pristine = false; - }); - }); - + var alwaysFalse = { + get: function () { return false; }, + set: function () { } + }; + Object.defineProperty(ctrl, '$pristine', alwaysFalse); + Object.defineProperty(ctrl, '$dirty', alwaysFalse); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js index 0308ab6197..bf9a94a446 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js @@ -255,8 +255,6 @@ angular.module('umbraco.mocks'). "errors_missingTitle": "Please enter a title", "errors_missingType": "Please choose a type", "errors_pictureResizeBiggerThanOrg": "You're about to make the picture larger than the original size. Are you sure that you want to proceed?", - "errors_pythonErrorHeader": "Error in python script", - "errors_pythonErrorText": "The python script has not been saved, because it contained error(s)", "errors_startNodeDoesNotExists": "Startnode deleted, please contact your administrator", "errors_stylesMustMarkBeforeSelect": "Please mark content before changing style", "errors_stylesNoStylesOnPage": "No active styles available", @@ -596,10 +594,6 @@ angular.module('umbraco.mocks'). "speechBubbles_fileSavedHeader": "File saved", "speechBubbles_fileSavedText": "File saved without any errors", "speechBubbles_languageSaved": "Language saved", - "speechBubbles_pythonErrorHeader": "Python script not saved", - "speechBubbles_pythonErrorText": "Python script could not be saved due to error", - "speechBubbles_pythonSavedHeader": "Python script saved", - "speechBubbles_pythonSavedText": "No errors in python script", "speechBubbles_templateErrorHeader": "Template not saved", "speechBubbles_templateErrorText": "Please make sure that you do not have 2 templates with the same alias", "speechBubbles_templateSavedHeader": "Template saved", @@ -702,7 +696,6 @@ angular.module('umbraco.mocks'). "treeHeaders_nodeTypes": "Document Types", "treeHeaders_packager": "Packages", "treeHeaders_packages": "Packages", - "treeHeaders_python": "Python Files", "treeHeaders_repositories": "Install from repository", "treeHeaders_runway": "Install Runway", "treeHeaders_runwayModules": "Runway modules", diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index 3680b32245..d29aa0218c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -71,7 +71,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { performLogin: function (username, password) { if (!username || !password) { - return angularHelper.rejectedPromise({ + return $q.reject({ errorMsg: 'Username or password cannot be empty' }); } @@ -125,7 +125,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { performRequestPasswordReset: function (email) { if (!email) { - return angularHelper.rejectedPromise({ + return $q.reject({ errorMsg: 'Email address cannot be empty' }); } @@ -135,7 +135,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { // be done properly. var emailRegex = /\S+@\S+\.\S+/; if (!emailRegex.test(email)) { - return angularHelper.rejectedPromise({ + return $q.reject({ errorMsg: 'Email address is not valid' }); } @@ -173,13 +173,13 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { performValidatePasswordResetCode: function (userId, resetCode) { if (!userId) { - return angularHelper.rejectedPromise({ + return $q.reject({ errorMsg: 'User Id cannot be empty' }); } if (!resetCode) { - return angularHelper.rejectedPromise({ + return $q.reject({ errorMsg: 'Reset code cannot be empty' }); } @@ -238,25 +238,25 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { performSetPassword: function (userId, password, confirmPassword, resetCode) { if (userId === undefined || userId === null) { - return angularHelper.rejectedPromise({ + return $q.reject({ errorMsg: 'User Id cannot be empty' }); } if (!password) { - return angularHelper.rejectedPromise({ + return $q.reject({ errorMsg: 'Password cannot be empty' }); } if (password !== confirmPassword) { - return angularHelper.rejectedPromise({ + return $q.reject({ errorMsg: 'Password and confirmation do not match' }); } if (!resetCode) { - return angularHelper.rejectedPromise({ + return $q.reject({ errorMsg: 'Reset code cannot be empty' }); } @@ -276,7 +276,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { unlinkLogin: function (loginProvider, providerKey) { if (!loginProvider || !providerKey) { - return angularHelper.rejectedPromise({ + return $q.reject({ errorMsg: 'loginProvider or providerKey cannot be empty' }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 642e4fb13b..fe3a86e930 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -313,17 +313,18 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * }); * * - * @param {Int} id id of content item to return + * @param {Int} id id of content item to return + * @param {Int} languageId optional ID of the language to retrieve the item in * @returns {Promise} resourcePromise object containing the content item. * */ - getById: function (id) { + getById: function (id, languageId) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetById", - [{ id: id }])), + { id: id, languageId: languageId })), 'Failed to retrieve data for content id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js index 0a6b3cd6bc..32e9683dd8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/currentuser.resource.js @@ -13,7 +13,7 @@ function currentUserResource($q, $http, umbRequestHelper, umbDataFormatter) { saveTourStatus: function (tourStatus) { if (!tourStatus) { - return angularHelper.rejectedPromise({ errorMsg: 'tourStatus cannot be empty' }); + return $q.reject({ errorMsg: 'tourStatus cannot be empty' }); } return umbRequestHelper.resourcePromise( @@ -37,7 +37,7 @@ function currentUserResource($q, $http, umbRequestHelper, umbDataFormatter) { performSetInvitedUserPassword: function (newPassword) { if (!newPassword) { - return angularHelper.rejectedPromise({ errorMsg: 'newPassword cannot be empty' }); + return $q.reject({ errorMsg: 'newPassword cannot be empty' }); } return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/language.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/language.resource.js new file mode 100644 index 0000000000..e193d6a2bb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/language.resource.js @@ -0,0 +1,64 @@ +/** + * @ngdoc service + * @name umbraco.resources.languageResource + * @description Handles retrieving and updating language data + **/ +function languageResource($http, umbRequestHelper) { + return { + + getCultures: function() { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "languageApiBaseUrl", + "GetAllCultures")), + "Failed to get cultures"); + }, + + getAll: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "languageApiBaseUrl", + "GetAllLanguages")), + "Failed to get languages"); + }, + + getById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "languageApiBaseUrl", + "GetLanguage", + { id: id })), + "Failed to get language with id " + id); + }, + + save: function (lang) { + if (!lang) + throw "'lang' parameter cannot be null"; + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "languageApiBaseUrl", + "SaveLanguage"), lang), + "Failed to save language " + lang.id); + }, + + deleteById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "languageApiBaseUrl", + "DeleteLanguage", + { id: id })), + "Failed to delete item " + id); + } + }; +} + +angular.module('umbraco.resources').factory('languageResource', languageResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index d1810f56ee..93a92db1ec 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -493,7 +493,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * @methodOf umbraco.resources.mediaResource * * @description - * Empties the media recycle bin + * Paginated search for media items starting on the supplied nodeId * * ##usage *
@@ -506,7 +506,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
           * @param {string} query The search query
           * @param {int} pageNumber The page number
           * @param {int} pageSize The number of media items on a page
-          * @param {int} searchFrom Id to search from
+          * @param {int} searchFrom NodeId to search from (-1 for root)
           * @returns {Promise} resourcePromise object.
           *
           */
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js
index 2073307db9..353fe12cb8 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js
@@ -10,8 +10,8 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
 
         return umbRequestHelper.postSaveContent({
             restApiUrl: umbRequestHelper.getApiUrl(
-                   "memberApiBaseUrl",
-                   "PostSave"),
+                "memberApiBaseUrl",
+                "PostSave"),
             content: content,
             action: action,
             files: files,
@@ -22,8 +22,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
     }
 
     return {
-
-        getPagedResults: function (memberTypeAlias, options) {
+        getPagedResults: function(memberTypeAlias, options) {
 
             if (memberTypeAlias === 'all-members') {
                 memberTypeAlias = null;
@@ -67,35 +66,35 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
             }
 
             var params = [
-                   { pageNumber: options.pageNumber },
-                   { pageSize: options.pageSize },
-                   { orderBy: options.orderBy },
-                   { orderDirection: options.orderDirection },
-                   { orderBySystemField: toBool(options.orderBySystemField) },
-                   { filter: options.filter }
+                { pageNumber: options.pageNumber },
+                { pageSize: options.pageSize },
+                { orderBy: options.orderBy },
+                { orderDirection: options.orderDirection },
+                { orderBySystemField: toBool(options.orderBySystemField) },
+                { filter: options.filter }
             ];
             if (memberTypeAlias != null) {
                 params.push({ memberTypeAlias: memberTypeAlias });
             }
 
             return umbRequestHelper.resourcePromise(
-                  $http.get(
-                        umbRequestHelper.getApiUrl(
-                              "memberApiBaseUrl",
-                              "GetPagedResults",
-                              params)),
-                  'Failed to retrieve member paged result');
+                $http.get(
+                    umbRequestHelper.getApiUrl(
+                        "memberApiBaseUrl",
+                        "GetPagedResults",
+                        params)),
+                'Failed to retrieve member paged result');
         },
 
-        getListNode: function (listName) {
+        getListNode: function(listName) {
 
             return umbRequestHelper.resourcePromise(
-                  $http.get(
-                        umbRequestHelper.getApiUrl(
-                              "memberApiBaseUrl",
-                              "GetListNodeDisplay",
-                              [{ listName: listName }])),
-                  'Failed to retrieve data for member list ' + listName);
+                $http.get(
+                    umbRequestHelper.getApiUrl(
+                        "memberApiBaseUrl",
+                        "GetListNodeDisplay",
+                        [{ listName: listName }])),
+                'Failed to retrieve data for member list ' + listName);
         },
 
         /**
@@ -119,15 +118,15 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
           * @returns {Promise} resourcePromise object containing the member item.
           *
           */
-        getByKey: function (key) {
+        getByKey: function(key) {
 
             return umbRequestHelper.resourcePromise(
-                  $http.get(
-                        umbRequestHelper.getApiUrl(
-                              "memberApiBaseUrl",
-                              "GetByKey",
-                              [{ key: key }])),
-                  'Failed to retrieve data for member id ' + key);
+                $http.get(
+                    umbRequestHelper.getApiUrl(
+                        "memberApiBaseUrl",
+                        "GetByKey",
+                        [{ key: key }])),
+                'Failed to retrieve data for member id ' + key);
         },
 
         /**
@@ -150,14 +149,14 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
           * @returns {Promise} resourcePromise object.
           *
           */
-        deleteByKey: function (key) {
+        deleteByKey: function(key) {
             return umbRequestHelper.resourcePromise(
-                   $http.post(
-                         umbRequestHelper.getApiUrl(
-                               "memberApiBaseUrl",
-                               "DeleteByKey",
-                               [{ key: key }])),
-                   'Failed to delete item ' + key);
+                $http.post(
+                    umbRequestHelper.getApiUrl(
+                        "memberApiBaseUrl",
+                        "DeleteByKey",
+                        [{ key: key }])),
+                'Failed to delete item ' + key);
         },
 
         /**
@@ -190,24 +189,24 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
           * @returns {Promise} resourcePromise object containing the member scaffold.
           *
           */
-        getScaffold: function (alias) {
+        getScaffold: function(alias) {
 
             if (alias) {
                 return umbRequestHelper.resourcePromise(
-                        $http.get(
-                              umbRequestHelper.getApiUrl(
-                                    "memberApiBaseUrl",
-                                    "GetEmpty",
-                                    [{ contentTypeAlias: alias }])),
-                        'Failed to retrieve data for empty member item type ' + alias);
+                    $http.get(
+                        umbRequestHelper.getApiUrl(
+                            "memberApiBaseUrl",
+                            "GetEmpty",
+                            [{ contentTypeAlias: alias }])),
+                    'Failed to retrieve data for empty member item type ' + alias);
             }
             else {
                 return umbRequestHelper.resourcePromise(
-                        $http.get(
-                              umbRequestHelper.getApiUrl(
-                                    "memberApiBaseUrl",
-                                    "GetEmpty")),
-                        'Failed to retrieve data for empty member item type ' + alias);
+                    $http.get(
+                        umbRequestHelper.getApiUrl(
+                            "memberApiBaseUrl",
+                            "GetEmpty")),
+                    'Failed to retrieve data for empty member item type ' + alias);
             }
 
         },
@@ -240,7 +239,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
           * @returns {Promise} resourcePromise object containing the saved media item.
           *
           */
-        save: function (member, isNew, files) {
+        save: function(member, isNew, files) {
             return saveMember(member, "save" + (isNew ? "New" : ""), files);
         }
     };
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/membergroup.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/membergroup.resource.js
new file mode 100644
index 0000000000..8bfd5bc998
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/membergroup.resource.js
@@ -0,0 +1,73 @@
+/**
+    * @ngdoc service
+    * @name umbraco.resources.memberGroupResource
+    * @description Loads in data for member groups
+    **/
+function memberGroupResource($q, $http, umbRequestHelper) {
+
+    return {
+
+        //return all member types
+        getGroups: function () {
+
+            return umbRequestHelper.resourcePromise(
+               $http.get(
+                   umbRequestHelper.getApiUrl(
+                       "memberGroupApiBaseUrl",
+                       "GetAllGroups")),
+               "Failed to retrieve data for member groups");
+        },       
+
+        getById: function (id) {
+
+            return umbRequestHelper.resourcePromise(
+               $http.get(
+                   umbRequestHelper.getApiUrl(
+                       "memberGroupApiBaseUrl",
+                       "GetById",
+                       [{ id: id }])),
+               "Failed to retrieve member group");
+        },
+
+        deleteById: function (id) {
+
+            return umbRequestHelper.resourcePromise(
+               $http.post(
+                   umbRequestHelper.getApiUrl(
+                       "memberGroupApiBaseUrl",
+                       "DeleteById",
+                       [{ id: id }])),
+               "Failed to delete member group");
+        },
+
+        getScaffold: function() {
+
+            return umbRequestHelper.resourcePromise(
+                $http.get(
+                    umbRequestHelper.getApiUrl(
+                        "memberGroupApiBaseUrl",
+                        "GetEmpty")),
+                "Failed to retrieve data for member group");
+        },
+
+        /**
+         * @ngdoc method
+         * @name umbraco.resources.memberGroupResource#save
+         * @methodOf umbraco.resources.memberGroupResource
+         *
+         * @description
+         * Saves or update a member group
+         *
+         * @param {Object} member group object to create/update
+         * @returns {Promise} resourcePromise object.
+         *
+         */
+        save: function (memberGroup) {
+            return umbRequestHelper.resourcePromise(
+                 $http.post(umbRequestHelper.getApiUrl("memberGroupApiBaseUrl", "PostSave"), memberGroup),
+                "Failed to save data for member group, id: " + memberGroup.id);
+        }
+
+    };
+}
+angular.module('umbraco.resources').factory('memberGroupResource', memberGroupResource);
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js
index fcc3cd59b1..fb4d8216e3 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js
@@ -8,26 +8,7 @@
  */
 function angularHelper($log, $q) {
     return {
-
-        /**
-         * @ngdoc function
-         * @name umbraco.services.angularHelper#rejectedPromise
-         * @methodOf umbraco.services.angularHelper
-         * @function
-         *
-         * @description
-         * In some situations we need to return a promise as a rejection, normally based on invalid data. This
-         * is a wrapper to do that so we can save on writing a bit of code.
-         *
-         * @param {object} objReject The object to send back with the promise rejection
-         */
-        rejectedPromise: function (objReject) {
-            var deferred = $q.defer();
-            //return an error object including the error message for UI
-            deferred.reject(objReject);
-            return deferred.promise;
-        },
-
+        
         /**
          * @ngdoc function
          * @name safeApply
@@ -153,4 +134,4 @@ function angularHelper($log, $q) {
         }
     };
 }
-angular.module('umbraco.services').factory('angularHelper', angularHelper);
\ No newline at end of file
+angular.module('umbraco.services').factory('angularHelper', angularHelper);
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js
index 16330f5493..3b49df10a6 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js
@@ -129,14 +129,12 @@ angular.module('umbraco.services')
                 asset.state = "loading";
                 LazyLoad.css(appendRnd(path), function () {
                     if (!scope) {
-                        asset.state = "loaded";
-                        asset.deferred.resolve(true);
-                    } else {
-                        asset.state = "loaded";
-                        angularHelper.safeApply(scope, function () {
-                            asset.deferred.resolve(true);
-                        });
+                        scope = $rootScope;
                     }
+                    asset.state = "loaded";
+                    angularHelper.safeApply(scope, function () {
+                        asset.deferred.resolve(true);
+                    });
                 });
             } else if (asset.state === "loaded") {
                 asset.deferred.resolve(true);
@@ -171,14 +169,12 @@ angular.module('umbraco.services')
 
                 LazyLoad.js(appendRnd(path), function () {
                     if (!scope) {
-                        asset.state = "loaded";
-                        asset.deferred.resolve(true);
-                    } else {
-                        asset.state = "loaded";
-                        angularHelper.safeApply(scope, function () {
-                            asset.deferred.resolve(true);
-                        });
+                        scope = $rootScope;
                     }
+                    asset.state = "loaded";
+                    angularHelper.safeApply(scope, function () {
+                        asset.deferred.resolve(true);
+                    });
                 });
 
             } else if (asset.state === "loaded") {
@@ -234,8 +230,7 @@ angular.module('umbraco.services')
                         assets.push(asset);
                     }
 
-                    //we need to always push to the promises collection to monitor correct
-                    //execution
+                    //we need to always push to the promises collection to monitor correct execution
                     promises.push(asset.deferred.promise);
                 }
             });
@@ -256,8 +251,7 @@ angular.module('umbraco.services')
             function assetLoaded(asset) {
                 asset.state = "loaded";
                 if (!scope) {
-                    asset.deferred.resolve(true);
-                    return;
+                    scope = $rootScope;
                 }
                 angularHelper.safeApply(scope,
                     function () {
@@ -280,4 +274,4 @@ angular.module('umbraco.services')
     };
 
     return service;
-});
\ No newline at end of file
+});
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 6aab9a655c..dde6e2e612 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
@@ -40,9 +40,6 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
             if (!args.content) {
                 throw "args.content is not defined";
             }
-            if (!args.statusMessage) {
-                throw "args.statusMessage is not defined";
-            }
             if (!args.saveMethod) {
                 throw "args.saveMethod is not defined";
             }
@@ -54,13 +51,11 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
             //we will use the default one for content if not specified
             var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback;
 
-            var deferred = $q.defer();
-
-            if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage, action: args.action })) {
+            if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, action: args.action })) {
 
                 args.scope.busy = true;
 
-                args.saveMethod(args.content, $routeParams.create, fileManager.getFiles())
+                return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles())
                     .then(function (data) {
 
                         formHelper.resetForm({ scope: args.scope, notifications: data.notifications });
@@ -74,7 +69,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
                         });
 
                         args.scope.busy = false;
-                        deferred.resolve(data);
+                        return $q.when(data);
 
                     }, function (err) {
                         self.handleSaveError({
@@ -91,14 +86,13 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
                             }
                         }
                         args.scope.busy = false;
-                        deferred.reject(err);
+                        return $q.reject(err);
                     });
             }
             else {
-                deferred.reject();
+                return $q.reject();
             }
-
-            return deferred.promise;
+            
         },
         
         /** Used by the content editor and media editor to add an info tab to the tabs array (normally known as the properties tab) */
@@ -159,7 +153,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
                         //publish action
                         return {
                             letter: ch,
-                            labelKey: "buttons_saveAndPublish",
+                            labelKey: args.content.variants && args.content.variants.length > 1 ? "buttons_saveAndPublishMany" : "buttons_saveAndPublish",
                             handler: args.methods.saveAndPublish,
                             hotKey: "ctrl+p",
                             hotKeyWhenHidden: true,
@@ -440,7 +434,25 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
 
             //a method to ignore built-in prop changes
             var shouldIgnore = function(propName) {
-                return _.some(["tabs", "notifications", "ModelState", "tabs", "properties"], function(i) {
+                return _.some([
+                    "tabs",
+                    "notifications",
+                    "ModelState",
+                    "tabs",
+                    "properties",
+                    "apps",
+                    "createDateFormatted",
+                    "releaseDateYear",
+                    "releaseDateMonth",
+                    "releaseDateDayNumber",
+                    "releaseDateDay",
+                    "releaseDateTime",
+                    "removeDateYear",
+                    "removeDateMonth",
+                    "removeDateDayNumber",
+                    "removeDateDay",
+                    "removeDateTime",
+                ], function (i) {
                     return i === propName;
                 });
             };
@@ -489,6 +501,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
          *
          * @description
          * A function to handle what happens when we have validation issues from the server side
+         *
          */
         handleSaveError: function (args) {
 
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js
index 21c7742c61..801fc7675c 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js
@@ -40,20 +40,12 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati
             else {
                 currentForm = args.formCtrl;
             }
-            //if no statusPropertyName is set we'll default to formStatus.
-            if (!args.statusPropertyName) {
-                args.statusPropertyName = "formStatus";
-            }
-            //if no statusTimeout is set, we'll  default to 2500 ms
-            if (!args.statusTimeout) {
-                args.statusTimeout = 2500;
-            }
             
             //the first thing any form must do is broadcast the formSubmitting event
             args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action });
 
             //then check if the form is valid
-            if (!args.skipValidation) {                
+            if (!args.skipValidation) {
                 if (currentForm.$invalid) {
                     return false;
                 }
@@ -62,16 +54,6 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati
             //reset the server validations
             serverValidationManager.reset();
             
-            //check if a form status should be set on the scope
-            if (args.statusMessage) {
-                args.scope[args.statusPropertyName] = args.statusMessage;
-
-                //clear the message after the timeout
-                $timeout(function () {
-                    args.scope[args.statusPropertyName] = undefined;
-                }, args.statusTimeout);
-            }
-
             return true;
         },
         
@@ -95,14 +77,7 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati
                 throw "args.scope cannot be null";
             }
             
-            //if no statusPropertyName is set we'll default to formStatus.
-            if (!args.statusPropertyName) {
-                args.statusPropertyName = "formStatus";
-            }
-            //clear the status
-            args.scope[args.statusPropertyName] = null;
-
-            this.showNotifications(args);            
+            this.showNotifications(args);
 
             args.scope.$broadcast("formSubmitted", { scope: args.scope });
         },
@@ -216,4 +191,4 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati
         }
     };
 }
-angular.module('umbraco.services').factory('formHelper', formHelper);
\ No newline at end of file
+angular.module('umbraco.services').factory('formHelper', formHelper);
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibrary.service.js b/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibrary.service.js
new file mode 100644
index 0000000000..4829dff506
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibrary.service.js
@@ -0,0 +1,37 @@
+(function () {
+    "use strict";
+
+    function javascriptLibraryService($q, $http, umbRequestHelper) {
+
+        var existingLocales = [];
+
+        function getSupportedLocalesForMoment() {
+            var deferred = $q.defer();
+
+            if (existingLocales.length === 0) {
+                umbRequestHelper.resourcePromise(
+                    $http.get(
+                        umbRequestHelper.getApiUrl(
+                            "backOfficeAssetsApiBaseUrl",
+                            "GetSupportedMomentLocales")),
+                    "Failed to get cultures").then(function(locales) {
+                    existingLocales = locales;
+                    deferred.resolve(existingLocales);
+                });
+            } else {
+                deferred.resolve(existingLocales);
+            }
+
+            return deferred.promise;
+        }
+
+        var service = {
+            getSupportedLocalesForMoment: getSupportedLocalesForMoment
+        };
+
+        return service;
+    }
+
+    angular.module("umbraco.services").factory("javascriptLibraryService", javascriptLibraryService);
+
+})();
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 2ffdc3f1dd..e4ba9b337c 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
@@ -181,11 +181,13 @@
 
             var firstAllowedLayout = {};
 
-            for (var i = 0; layouts.length > i; i++) {
-                var layout = layouts[i];
-                if (layout.selected === true) {
-                    firstAllowedLayout = layout;
-                    break;
+            if (layouts != null) {
+                for (var i = 0; layouts.length > i; i++) {
+                    var layout = layouts[i];
+                    if (layout.selected === true) {
+                        firstAllowedLayout = layout;
+                        break;
+                    }
                 }
             }
 
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js
index bcfb5df0a7..7200e1f6f8 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js
@@ -8,10 +8,39 @@
  * @description
  * Defines the methods that are called when menu items declare only an action to execute
  */
-function umbracoMenuActions($q, treeService, $location, navigationService, appState) {
+function umbracoMenuActions($q, treeService, $location, navigationService, appState, localizationService, userResource, umbRequestHelper, notificationsService) {
     
     return {
+
+        "ExportMember": function(args) {
+            var url = umbRequestHelper.getApiUrl(
+                "memberApiBaseUrl",
+                "ExportMemberData",
+                [{ key: args.entity.id }]);
+
+            umbRequestHelper.downloadFile(url).then(function() {
+                localizationService.localize("speechBubbles_memberExportedSuccess").then(function (value) {
+                    notificationsService.success(value);
+                })    
+            }, function(data) {
+                localizationService.localize("speechBubbles_memberExportedError").then(function (value) {
+                    notificationsService.error(value);
+                })    
+            });
+            
+        },
         
+        "DisableUser": function(args) {
+            localizationService.localize("defaultdialogs_confirmdisable").then(function (txtConfirmDisable) {
+                var currentMenuNode = UmbClientMgr.mainTree().getActionNode();
+                if (confirm(txtConfirmDisable + ' "' + args.entity.name + '"?\n\n')) {
+                    userResource.disableUser(args.entity.id).then(function () {
+                        navigationService.syncTree({ tree: args.treeAlias, path: [args.entity.parentId, args.entity.id], forceReload: true });
+                    });
+                }
+            });
+        },
+
         /**
          * @ngdoc method
          * @name umbraco.services.umbracoMenuActions#RefreshNode
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 20e73f9e06..d3250ee64b 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
@@ -288,13 +288,16 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
                 throw "args.tree cannot be null";
             }
 
-            if (mainTreeEventHandler) {
-                //returns a promise
-                return mainTreeEventHandler.syncTree(args);
+            if (mainTreeEventHandler) {                
+               
+                if (mainTreeEventHandler.syncTree) {
+                    //returns a promise,
+                    return mainTreeEventHandler.syncTree(args);
+                }
             }
 
             //couldn't sync
-            return angularHelper.rejectedPromise();
+            return $q.reject();
         },
 
         /**
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js
new file mode 100644
index 0000000000..a202c6ca6b
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js
@@ -0,0 +1,38 @@
+/**
+ @ngdoc service
+ * @name umbraco.services.overlayService
+ *
+ * @description
+ * Added in Umbraco 8.0. Application-wide service for handling overlays.
+ */
+(function () {
+    "use strict";
+
+    function overlayService(eventsService, backdropService) {
+
+        function open(overlay) {
+            if(!overlay.position) {
+                overlay.position = "center";
+            }
+            overlay.show = true;
+            backdropService.open();
+            eventsService.emit("appState.overlay", overlay);
+        }
+
+        function close() {
+            backdropService.close();
+            eventsService.emit("appState.overlay", null);
+        }
+
+        var service = {
+            open: open,
+            close: close
+        };
+
+        return service;
+
+    }
+
+    angular.module("umbraco.services").factory("overlayService", overlayService);
+
+})();
\ No newline at end of file
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 f09e31924f..439720e461 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
@@ -56,7 +56,7 @@
                     });
 
                     var saveProperties = _.map(realProperties, function (p) {
-                        var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile');
+                        var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData');
                         return saveProperty;
                     });
 
@@ -267,10 +267,10 @@
                             // by looking at the key
                             switch (foundAlias[0]) {
                                 case "umbracoMemberLockedOut":
-                                    saveModel.isLockedOut = prop.value.toString() === "1" ? true : false;
+                                    saveModel.isLockedOut = prop.value ? (prop.value.toString() === "1" ? true : false) : false;
                                     break;
                                 case "umbracoMemberApproved":
-                                    saveModel.isApproved = prop.value.toString() === "1" ? true : false;
+                                    saveModel.isApproved = prop.value ? (prop.value.toString() === "1" ? true : false) : true;
                                     break;
                                 case "umbracoMemberComments":
                                     saveModel.comments = prop.value;
@@ -304,14 +304,14 @@
                     _.each(tab.properties, function (prop) {
 
                         //don't include the custom generic tab properties
-                        if (!prop.alias.startsWith("_umb_")) {
+                        //don't include a property that is marked readonly
+                        if (!prop.alias.startsWith("_umb_") && !prop.readonly) {
                             saveModel.properties.push({
                                 id: prop.id,
                                 alias: prop.alias,
                                 value: prop.value
                             });
                         }
-
                     });
                 });
 
@@ -324,6 +324,22 @@
                 //this is basically the same as for media but we need to explicitly add some extra properties
                 var saveModel = this.formatMediaPostData(displayModel, action);
 
+                //get the selected variant and build the additional published variants
+                saveModel.publishVariations = [];
+                _.each(displayModel.variants,
+                    function (d) {
+                        //set the selected variant if this is current
+                        if (d.current === true) {
+                            saveModel.languageId = d.language.id;
+                        }
+                        if (d.publish === true) {
+                            saveModel.publishVariations.push({
+                                languageId: d.language.id,
+                                segment: d.segment
+                            });
+                        }
+                    });
+
                 var propExpireDate = displayModel.removeDate;
                 var propReleaseDate = displayModel.releaseDate;
                 var propTemplate = displayModel.template;
@@ -338,4 +354,4 @@
     }
     angular.module('umbraco.services').factory('umbDataFormatter', umbDataFormatter);
 
-})();
\ No newline at end of file
+})();
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
index 6488ee7515..d950d39619 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
@@ -333,6 +333,122 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
                     failureCallback.apply(this, [data, status, headers, config]);
                 }
             });
+        },
+        
+        /**
+         * Downloads a file to the client using AJAX/XHR
+         * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html
+         * See https://stackoverflow.com/a/24129082/694494
+         */
+        downloadFile : function (httpPath) {
+
+            var deferred = $q.defer();
+
+            // Use an arraybuffer
+            $http.get(httpPath, { responseType: 'arraybuffer' })
+                .success(function (data, status, headers) {
+
+                    var octetStreamMime = 'application/octet-stream';
+                    var success = false;
+
+                    // Get the headers
+                    headers = headers();
+
+                    // Get the filename from the x-filename header or default to "download.bin"
+                    var filename = headers['x-filename'] || 'download.bin';
+
+                    // Determine the content type from the header or default to "application/octet-stream"
+                    var contentType = headers['content-type'] || octetStreamMime;
+
+                    try {
+                        // Try using msSaveBlob if supported
+                        console.log("Trying saveBlob method ...");
+                        var blob = new Blob([data], { type: contentType });
+                        if (navigator.msSaveBlob)
+                            navigator.msSaveBlob(blob, filename);
+                        else {
+                            // Try using other saveBlob implementations, if available
+                            var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
+                            if (saveBlob === undefined) throw "Not supported";
+                            saveBlob(blob, filename);
+                        }
+                        console.log("saveBlob succeeded");
+                        success = true;
+                    } catch (ex) {
+                        console.log("saveBlob method failed with the following exception:");
+                        console.log(ex);
+                    }
+
+                    if (!success) {
+                        // Get the blob url creator
+                        var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
+                        if (urlCreator) {
+                            // Try to use a download link
+                            var link = document.createElement('a');
+                            if ('download' in link) {
+                                // Try to simulate a click
+                                try {
+                                    // Prepare a blob URL
+                                    console.log("Trying download link method with simulated click ...");
+                                    var blob = new Blob([data], { type: contentType });
+                                    var url = urlCreator.createObjectURL(blob);
+                                    link.setAttribute('href', url);
+
+                                    // Set the download attribute (Supported in Chrome 14+ / Firefox 20+)
+                                    link.setAttribute("download", filename);
+
+                                    // Simulate clicking the download link
+                                    var event = document.createEvent('MouseEvents');
+                                    event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
+                                    link.dispatchEvent(event);
+                                    console.log("Download link method with simulated click succeeded");
+                                    success = true;
+
+                                } catch (ex) {
+                                    console.log("Download link method with simulated click failed with the following exception:");
+                                    console.log(ex);
+                                }
+                            }
+
+                            if (!success) {
+                                // Fallback to window.location method
+                                try {
+                                    // Prepare a blob URL
+                                    // Use application/octet-stream when using window.location to force download
+                                    console.log("Trying download link method with window.location ...");
+                                    var blob = new Blob([data], { type: octetStreamMime });
+                                    var url = urlCreator.createObjectURL(blob);
+                                    window.location = url;
+                                    console.log("Download link method with window.location succeeded");
+                                    success = true;
+                                } catch (ex) {
+                                    console.log("Download link method with window.location failed with the following exception:");
+                                    console.log(ex);
+                                }
+                            }
+
+                        }
+                    }
+
+                    if (!success) {
+                        // Fallback to window.open method
+                        console.log("No methods worked for saving the arraybuffer, using last resort window.open");
+                        window.open(httpPath, '_blank', '');
+                    }
+
+                    deferred.resolve();
+                })
+                .error(function (data, status) {
+                    console.log("Request failed with status: " + status);
+
+                    deferred.reject({
+                        errorMsg: "An error occurred downloading the file",
+                        data: data,
+                        status: status
+                    });
+                });
+
+            return deferred.promise;
         }
     };
 }
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
index f20c3df44f..80565c23e3 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
@@ -1,285 +1,325 @@
 angular.module('umbraco.services')
-  .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) {
+    .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http, javascriptLibraryService) {
 
-    var currentUser = null;
-    var lastUserId = null;
-    var loginDialog = null;
+        var currentUser = null;
+        var lastUserId = null;
+        var loginDialog = null;
 
-    //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server
-    // this is used so that we know when to go and get the user's remaining seconds directly.
-    var lastServerTimeoutSet = null;
+        //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server
+        // this is used so that we know when to go and get the user's remaining seconds directly.
+        var lastServerTimeoutSet = null;
 
-    function openLoginDialog(isTimedOut) {
-      if (!loginDialog) {
-        loginDialog = dialogService.open({
+        function openLoginDialog(isTimedOut) {
+            if (!loginDialog) {
+                loginDialog = dialogService.open({
 
-          //very special flag which means that global events cannot close this dialog
-          manualClose: true,
+                    //very special flag which means that global events cannot close this dialog
+                    manualClose: true,
 
-          template: 'views/common/dialogs/login.html',
-          modalClass: "login-overlay",
-          animation: "slide",
-          show: true,
-          callback: onLoginDialogClose,
-          dialogData: {
-            isTimedOut: isTimedOut
-          }
-        });
-      }
-    }
-
-    function onLoginDialogClose(success) {
-      loginDialog = null;
-
-      if (success) {
-        securityRetryQueue.retryAll(currentUser.name);
-      }
-      else {
-        securityRetryQueue.cancelAll();
-        $location.path('/');
-      }
-    }
-
-    /**
-    This methods will set the current user when it is resolved and
-    will then start the counter to count in-memory how many seconds they have
-    remaining on the auth session
-    */
-    function setCurrentUser(usr) {
-      if (!usr.remainingAuthSeconds) {
-        throw "The user object is invalid, the remainingAuthSeconds is required.";
-      }
-      currentUser = usr;
-      lastServerTimeoutSet = new Date();
-      //start the timer
-      countdownUserTimeout();
-    }
-
-    /**
-    Method to count down the current user's timeout seconds,
-    this will continually count down their current remaining seconds every 5 seconds until
-    there are no more seconds remaining.
-    */
-    function countdownUserTimeout() {
-
-      $timeout(function () {
-
-        if (currentUser) {
-          //countdown by 5 seconds since that is how long our timer is for.
-          currentUser.remainingAuthSeconds -= 5;
-
-          //if there are more than 30 remaining seconds, recurse!
-          if (currentUser.remainingAuthSeconds > 30) {
-
-            //we need to check when the last time the timeout was set from the server, if
-            // it has been more than 30 seconds then we'll manually go and retrieve it from the
-            // server - this helps to keep our local countdown in check with the true timeout.
-            if (lastServerTimeoutSet != null) {
-              var now = new Date();
-              var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000;
-
-              if (seconds > 30) {
-
-                //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
-                // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
-                lastServerTimeoutSet = null;
-
-                //now go get it from the server
-                //NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
-                angularHelper.safeApply($rootScope, function () {
-                  authResource.getRemainingTimeoutSeconds().then(function (result) {
-                    setUserTimeoutInternal(result);
-                  });
+                    template: 'views/common/dialogs/login.html',
+                    modalClass: "login-overlay",
+                    animation: "slide",
+                    show: true,
+                    callback: onLoginDialogClose,
+                    dialogData: {
+                        isTimedOut: isTimedOut
+                    }
                 });
-              }
             }
+        }
 
-            //recurse the countdown!
-            countdownUserTimeout();
-          }
-          else {
+        function onLoginDialogClose(success) {
+            loginDialog = null;
 
-            //we are either timed out or very close to timing out so we need to show the login dialog.
-            if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) {
-              //NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
-              angularHelper.safeApply($rootScope, function () {
-                try {
-                  //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we
-                  // don't actually care about this result.
-                  authResource.getRemainingTimeoutSeconds();
-                }
-                finally {
-                  userAuthExpired();
-                }
-              });
+            if (success) {
+                securityRetryQueue.retryAll(currentUser.name);
             }
             else {
-              //we've got less than 30 seconds remaining so let's check the server
-
-              if (lastServerTimeoutSet != null) {
-                //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
-                // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
-                lastServerTimeoutSet = null;
-
-                //now go get it from the server
-                //NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
-                angularHelper.safeApply($rootScope, function () {
-                  authResource.getRemainingTimeoutSeconds().then(function (result) {
-                    setUserTimeoutInternal(result);
-                  });
-                });
-              }
-
-              //recurse the countdown!
-              countdownUserTimeout();
-
+                securityRetryQueue.cancelAll();
+                $location.path('/');
             }
-          }
         }
-      }, 5000, //every 5 seconds
-        false); //false = do NOT execute a digest for every iteration
-    }
 
-    /** Called to update the current user's timeout */
-    function setUserTimeoutInternal(newTimeout) {
-
-
-      var asNumber = parseFloat(newTimeout);
-      if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) {
-        currentUser.remainingAuthSeconds = newTimeout;
-        lastServerTimeoutSet = new Date();
-      }
-    }
-
-    /** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */
-    function userAuthExpired(isLogout) {
-      //store the last user id and clear the user
-      if (currentUser && currentUser.id !== undefined) {
-        lastUserId = currentUser.id;
-      }
-
-      if (currentUser) {
-        currentUser.remainingAuthSeconds = 0;
-      }
-
-      lastServerTimeoutSet = null;
-      currentUser = null;
-
-      //broadcast a global event that the user is no longer logged in
-      eventsService.emit("app.notAuthenticated");
-
-      openLoginDialog(isLogout === undefined ? true : !isLogout);
-    }
-
-    // Register a handler for when an item is added to the retry queue
-    securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) {
-      if (securityRetryQueue.hasMore()) {
-        userAuthExpired();
-      }
-    });
-
-    return {
-
-      /** Internal method to display the login dialog */
-      _showLoginDialog: function () {
-        openLoginDialog();
-      },
-      /** Returns a promise, sends a request to the server to check if the current cookie is authorized  */
-      isAuthenticated: function () {
-        //if we've got a current user then just return true
-        if (currentUser) {
-          var deferred = $q.defer();
-          deferred.resolve(true);
-          return deferred.promise;
+        /**
+        This methods will set the current user when it is resolved and
+        will then start the counter to count in-memory how many seconds they have
+        remaining on the auth session
+        */
+        function setCurrentUser(usr) {
+            if (!usr.remainingAuthSeconds) {
+                throw "The user object is invalid, the remainingAuthSeconds is required.";
+            }
+            currentUser = usr;
+            lastServerTimeoutSet = new Date();
+            //start the timer
+            countdownUserTimeout();
         }
-        return authResource.isAuthenticated();
-      },
 
-      /** Returns a promise, sends a request to the server to validate the credentials  */
-      authenticate: function (login, password) {
+        /**
+        Method to count down the current user's timeout seconds,
+        this will continually count down their current remaining seconds every 5 seconds until
+        there are no more seconds remaining.
+        */
+        function countdownUserTimeout() {
 
-        return authResource.performLogin(login, password)
-          .then(this.setAuthenticationSuccessful);
-      },
-      setAuthenticationSuccessful: function (data) {
+            $timeout(function () {
 
-        //when it's successful, return the user data
-        setCurrentUser(data);
+                if (currentUser) {
+                    //countdown by 5 seconds since that is how long our timer is for.
+                    currentUser.remainingAuthSeconds -= 5;
 
-        var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" };
+                    //if there are more than 30 remaining seconds, recurse!
+                    if (currentUser.remainingAuthSeconds > 30) {
 
-        //broadcast a global event
-        eventsService.emit("app.authenticated", result);
-        return result;
-      },
+                        //we need to check when the last time the timeout was set from the server, if
+                        // it has been more than 30 seconds then we'll manually go and retrieve it from the
+                        // server - this helps to keep our local countdown in check with the true timeout.
+                        if (lastServerTimeoutSet != null) {
+                            var now = new Date();
+                            var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000;
 
-      /** Logs the user out
-       */
-      logout: function () {
+                            if (seconds > 30) {
 
-        return authResource.performLogout()
-          .then(function (data) {
-            userAuthExpired();
-            //done!
-            return null;
-          });
-      },
+                                //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
+                                // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
+                                lastServerTimeoutSet = null;
 
-      /** Refreshes the current user data with the data stored for the user on the server and returns it */
-      refreshCurrentUser: function() {
-          var deferred = $q.defer();
+                                //now go get it from the server
+                                //NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
+                                angularHelper.safeApply($rootScope, function () {
+                                    authResource.getRemainingTimeoutSeconds().then(function (result) {
+                                        setUserTimeoutInternal(result);
+                                    });
+                                });
+                            }
+                        }
 
-          authResource.getCurrentUser()
-              .then(function (data) {
+                        //recurse the countdown!
+                        countdownUserTimeout();
+                    }
+                    else {
 
-                  var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
-                  
-                  setCurrentUser(data);
+                        //we are either timed out or very close to timing out so we need to show the login dialog.
+                        if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) {
+                            //NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
+                            angularHelper.safeApply($rootScope, function () {
+                                try {
+                                    //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we
+                                    // don't actually care about this result.
+                                    authResource.getRemainingTimeoutSeconds();
+                                }
+                                finally {
+                                    userAuthExpired();
+                                }
+                            });
+                        }
+                        else {
+                            //we've got less than 30 seconds remaining so let's check the server
 
-                  deferred.resolve(currentUser);
-              }, function () {
-                  //it failed, so they are not logged in
-                  deferred.reject();
-              });
+                            if (lastServerTimeoutSet != null) {
+                                //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
+                                // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
+                                lastServerTimeoutSet = null;
 
-          return deferred.promise;
-      },
+                                //now go get it from the server
+                                //NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
+                                angularHelper.safeApply($rootScope, function () {
+                                    authResource.getRemainingTimeoutSeconds().then(function (result) {
+                                        setUserTimeoutInternal(result);
+                                    });
+                                });
+                            }
 
-      /** Returns the current user object in a promise  */
-      getCurrentUser: function (args) {
-        var deferred = $q.defer();
+                            //recurse the countdown!
+                            countdownUserTimeout();
 
-        if (!currentUser) {
-          authResource.getCurrentUser()
-            .then(function (data) {
+                        }
+                    }
+                }
+            }, 5000, //every 5 seconds
+                false); //false = do NOT execute a digest for every iteration
+        }
 
-              var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
+        /** Called to update the current user's timeout */
+        function setUserTimeoutInternal(newTimeout) {
 
-              if (args && args.broadcastEvent) {
-                //broadcast a global event, will inform listening controllers to load in the user specific data
+
+            var asNumber = parseFloat(newTimeout);
+            if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) {
+                currentUser.remainingAuthSeconds = newTimeout;
+                lastServerTimeoutSet = new Date();
+            }
+        }
+
+        /** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */
+        function userAuthExpired(isLogout) {
+            //store the last user id and clear the user
+            if (currentUser && currentUser.id !== undefined) {
+                lastUserId = currentUser.id;
+            }
+
+            if (currentUser) {
+                currentUser.remainingAuthSeconds = 0;
+            }
+
+            lastServerTimeoutSet = null;
+            currentUser = null;
+
+            //broadcast a global event that the user is no longer logged in
+            eventsService.emit("app.notAuthenticated");
+
+            openLoginDialog(isLogout === undefined ? true : !isLogout);
+        }
+
+        // Register a handler for when an item is added to the retry queue
+        securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) {
+            if (securityRetryQueue.hasMore()) {
+                userAuthExpired();
+            }
+        });
+
+        return {
+
+            /** Internal method to display the login dialog */
+            _showLoginDialog: function () {
+                openLoginDialog();
+            },
+            /** Returns a promise, sends a request to the server to check if the current cookie is authorized  */
+            isAuthenticated: function () {
+                //if we've got a current user then just return true
+                if (currentUser) {
+                    var deferred = $q.defer();
+                    deferred.resolve(true);
+                    return deferred.promise;
+                }
+                return authResource.isAuthenticated();
+            },
+
+            /** Returns a promise, sends a request to the server to validate the credentials  */
+            authenticate: function (login, password) {
+
+                return authResource.performLogin(login, password)
+                    .then(this.setAuthenticationSuccessful);
+            },
+            setAuthenticationSuccessful: function (data) {
+
+                //when it's successful, return the user data
+                setCurrentUser(data);
+
+                var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" };
+
+                //broadcast a global event
                 eventsService.emit("app.authenticated", result);
-              }
+                return result;
+            },
 
-              setCurrentUser(data);
+            /** Logs the user out
+             */
+            logout: function () {
 
-              deferred.resolve(currentUser);
-            }, function () {
-              //it failed, so they are not logged in
-              deferred.reject();
-            });
+                return authResource.performLogout()
+                    .then(function (data) {
+                        userAuthExpired();
+                        //done!
+                        return null;
+                    });
+            },
 
-        }
-        else {
-          deferred.resolve(currentUser);
-        }
+            /** Refreshes the current user data with the data stored for the user on the server and returns it */
+            refreshCurrentUser: function () {
+                var deferred = $q.defer();
 
-        return deferred.promise;
-      },
+                authResource.getCurrentUser()
+                    .then(function (data) {
 
-      /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */
-      setUserTimeout: function (newTimeout) {
-        setUserTimeoutInternal(newTimeout);
-      }
-    };
+                        var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
 
-  });
+                        setCurrentUser(data);
+
+                        deferred.resolve(currentUser);
+                    }, function () {
+                        //it failed, so they are not logged in
+                        deferred.reject();
+                    });
+
+                return deferred.promise;
+            },
+
+            /** Returns the current user object in a promise  */
+            getCurrentUser: function (args) {
+                var deferred = $q.defer();
+
+                if (!currentUser) {
+                    authResource.getCurrentUser()
+                        .then(function (data) {
+
+                            var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
+
+                            if (args && args.broadcastEvent) {
+                                //broadcast a global event, will inform listening controllers to load in the user specific data
+                                eventsService.emit("app.authenticated", result);
+                            }
+
+                            setCurrentUser(data);
+
+                            deferred.resolve(currentUser);
+                        }, function () {
+                            //it failed, so they are not logged in
+                            deferred.reject();
+                        });
+
+                }
+                else {
+                    deferred.resolve(currentUser);
+                }
+
+                return deferred.promise;
+            },
+
+            /** Loads the Moment.js Locale for the current user. */
+            loadMomentLocaleForCurrentUser: function () {
+
+                function loadLocales(currentUser, supportedLocales) {
+                    var locale = currentUser.locale.toLowerCase();
+                    if (locale !== 'en-us') {
+                        var localeUrls = [];
+                        if (supportedLocales.indexOf(locale + '.js') > -1) {
+                            localeUrls.push('lib/moment/' + locale + '.js');
+                        }
+                        if (locale.indexOf('-') > -1) {
+                            var majorLocale = locale.split('-')[0] + '.js';
+                            if (supportedLocales.indexOf(majorLocale) > -1) {
+                                localeUrls.push('lib/moment/' + majorLocale);
+                            }
+                        }
+                        return assetsService.load(localeUrls, $rootScope);
+                    }
+                    else {
+                        //return a noop promise
+                        var deferred = $q.defer();
+                        var promise = deferred.promise;
+                        deferred.resolve(true);
+                        return promise;
+                    }
+                }
+
+                var promises = {
+                    currentUser: this.getCurrentUser(),
+                    supportedLocales: javascriptLibraryService.getSupportedLocalesForMoment()
+                }
+
+                return $q.all(promises).then(function (values) {
+                    return loadLocales(values.currentUser, values.supportedLocales);
+                });
+                
+                
+
+            },
+
+            /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */
+            setUserTimeout: function (newTimeout) {
+                setUserTimeoutInternal(newTimeout);
+            }
+        };
+
+    });
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js
index 21fe84ff8d..3d6ad8b265 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js
@@ -1,79 +1,79 @@
-(function () {
-    'use strict';
-
-    function usersHelperService(localizationService) {
-
-        var userStates = [
-            { "name": "All", "key": "All"} ,
-            { "value": 0, "name": "Active", "key": "Active", "color": "success" },
-            { "value": 1, "name": "Disabled", "key": "Disabled", "color": "danger" },
-            { "value": 2, "name": "Locked out", "key": "LockedOut", "color": "danger" },
-            { "value": 3, "name": "Invited", "key": "Invited", "color": "warning" }
-        ];
-
-        angular.forEach(userStates, function (userState) {
-            var key = "user_state" + userState.key;
-            localizationService.localize(key).then(function (value) {
-                var reg = /^\[[\S\s]*]$/g;
-                var result = reg.test(value);
-                if (result === false) {
-                    // Only translate if key exists
-                    userState.name = value;
-                }
-            });
-        });
-
-        function getUserStateFromValue(value) {
-            var foundUserState;
-            angular.forEach(userStates, function (userState) {
-                if(userState.value === value) {
-                    foundUserState = userState;
-                }
-            });
-            return foundUserState;
-        }
-
-        function getUserStateByKey(key) {
-            var foundUserState;
-            angular.forEach(userStates, function (userState) {
-                if(userState.key === key) {
-                    foundUserState = userState;
-                }
-            });
-            return foundUserState;
-        }
-
-        function getUserStatesFilter(userStatesObject) {
-
-            var userStatesFilter = [];
-
-            for (var key in userStatesObject) {
-                if (userStatesObject.hasOwnProperty(key)) {
-                    var userState = getUserStateByKey(key);
-                    if(userState) {
-                        userState.count = userStatesObject[key];
-                        userStatesFilter.push(userState);
-                    }
-                }
-            }
-
-            return userStatesFilter;
-
-        }
-
-        ////////////
-
-        var service = {
-            getUserStateFromValue: getUserStateFromValue,
-            getUserStateByKey: getUserStateByKey,
-            getUserStatesFilter: getUserStatesFilter
-        };
-
-        return service;
-
-    }
-
-    angular.module('umbraco.services').factory('usersHelper', usersHelperService);
-
-
-})();
+(function () {
+    'use strict';
+
+    function usersHelperService(localizationService) {
+
+        var userStates = [
+            { "name": "All", "key": "All"} ,
+            { "value": 0, "name": "Active", "key": "Active", "color": "success" },
+            { "value": 1, "name": "Disabled", "key": "Disabled", "color": "danger" },
+            { "value": 2, "name": "Locked out", "key": "LockedOut", "color": "danger" },
+            { "value": 3, "name": "Invited", "key": "Invited", "color": "warning" }
+        ];
+
+        angular.forEach(userStates, function (userState) {
+            var key = "user_state" + userState.key;
+            localizationService.localize(key).then(function (value) {
+                var reg = /^\[[\S\s]*]$/g;
+                var result = reg.test(value);
+                if (result === false) {
+                    // Only translate if key exists
+                    userState.name = value;
+                }
+            });
+        });
+
+        function getUserStateFromValue(value) {
+            var foundUserState;
+            angular.forEach(userStates, function (userState) {
+                if(userState.value === value) {
+                    foundUserState = userState;
+                }
+            });
+            return foundUserState;
+        }
+
+        function getUserStateByKey(key) {
+            var foundUserState;
+            angular.forEach(userStates, function (userState) {
+                if(userState.key === key) {
+                    foundUserState = userState;
+                }
+            });
+            return foundUserState;
+        }
+
+        function getUserStatesFilter(userStatesObject) {
+
+            var userStatesFilter = [];
+
+            for (var key in userStatesObject) {
+                if (userStatesObject.hasOwnProperty(key)) {
+                    var userState = getUserStateByKey(key);
+                    if(userState) {
+                        userState.count = userStatesObject[key];
+                        userStatesFilter.push(userState);
+                    }
+                }
+            }
+
+            return userStatesFilter;
+
+        }
+
+        ////////////
+
+        var service = {
+            getUserStateFromValue: getUserStateFromValue,
+            getUserStateByKey: getUserStateByKey,
+            getUserStatesFilter: getUserStatesFilter
+        };
+
+        return service;
+
+    }
+
+    angular.module('umbraco.services').factory('usersHelper', usersHelperService);
+
+
+})();
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
index d6847bd30c..11444ff65b 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
@@ -1,435 +1,444 @@
 /*Contains multiple services for various helper tasks */
 function versionHelper() {
 
-  return {
+    return {
 
-    //see: https://gist.github.com/TheDistantSea/8021359
-    versionCompare: function (v1, v2, options) {
-      var lexicographical = options && options.lexicographical,
-        zeroExtend = options && options.zeroExtend,
-        v1parts = v1.split('.'),
-        v2parts = v2.split('.');
+        //see: https://gist.github.com/TheDistantSea/8021359
+        versionCompare: function (v1, v2, options) {
+            var lexicographical = options && options.lexicographical,
+                zeroExtend = options && options.zeroExtend,
+                v1parts = v1.split('.'),
+                v2parts = v2.split('.');
 
-      function isValidPart(x) {
-        return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
-      }
+            function isValidPart(x) {
+                return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
+            }
 
-      if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
-        return NaN;
-      }
+            if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
+                return NaN;
+            }
 
-      if (zeroExtend) {
-        while (v1parts.length < v2parts.length) {
-          v1parts.push("0");
+            if (zeroExtend) {
+                while (v1parts.length < v2parts.length) {
+                    v1parts.push("0");
+                }
+                while (v2parts.length < v1parts.length) {
+                    v2parts.push("0");
+                }
+            }
+
+            if (!lexicographical) {
+                v1parts = v1parts.map(Number);
+                v2parts = v2parts.map(Number);
+            }
+
+            for (var i = 0; i < v1parts.length; ++i) {
+                if (v2parts.length === i) {
+                    return 1;
+                }
+
+                if (v1parts[i] === v2parts[i]) {
+                    continue;
+                }
+                else if (v1parts[i] > v2parts[i]) {
+                    return 1;
+                }
+                else {
+                    return -1;
+                }
+            }
+
+            if (v1parts.length !== v2parts.length) {
+                return -1;
+            }
+
+            return 0;
         }
-        while (v2parts.length < v1parts.length) {
-          v2parts.push("0");
-        }
-      }
-
-      if (!lexicographical) {
-        v1parts = v1parts.map(Number);
-        v2parts = v2parts.map(Number);
-      }
-
-      for (var i = 0; i < v1parts.length; ++i) {
-        if (v2parts.length === i) {
-          return 1;
-        }
-
-        if (v1parts[i] === v2parts[i]) {
-          continue;
-        }
-        else if (v1parts[i] > v2parts[i]) {
-          return 1;
-        }
-        else {
-          return -1;
-        }
-      }
-
-      if (v1parts.length !== v2parts.length) {
-        return -1;
-      }
-
-      return 0;
-    }
-  };
+    };
 }
 angular.module('umbraco.services').factory('versionHelper', versionHelper);
 
 function dateHelper() {
 
-  return {
+    return {
 
-    convertToServerStringTime: function (momentLocal, serverOffsetMinutes, format) {
+        convertToServerStringTime: function (momentLocal, serverOffsetMinutes, format) {
 
-      //get the formatted offset time in HH:mm (server time offset is in minutes)
-      var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") +
-        moment()
-          .startOf('day')
-          .minutes(Math.abs(serverOffsetMinutes))
-          .format('HH:mm');
+            //get the formatted offset time in HH:mm (server time offset is in minutes)
+            var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") +
+                moment()
+                    .startOf('day')
+                    .minutes(Math.abs(serverOffsetMinutes))
+                    .format('HH:mm');
 
-      var server = moment.utc(momentLocal).utcOffset(formattedOffset);
-      return server.format(format ? format : "YYYY-MM-DD HH:mm:ss");
-    },
+            var server = moment.utc(momentLocal).utcOffset(formattedOffset);
+            return server.format(format ? format : "YYYY-MM-DD HH:mm:ss");
+        },
 
-    convertToLocalMomentTime: function (strVal, serverOffsetMinutes) {
+        convertToLocalMomentTime: function (strVal, serverOffsetMinutes) {
 
-      //get the formatted offset time in HH:mm (server time offset is in minutes)
-      var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") +
-        moment()
-          .startOf('day')
-          .minutes(Math.abs(serverOffsetMinutes))
-          .format('HH:mm');
+            //get the formatted offset time in HH:mm (server time offset is in minutes)
+            var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") +
+                moment()
+                    .startOf('day')
+                    .minutes(Math.abs(serverOffsetMinutes))
+                    .format('HH:mm');
 
-      //convert to the iso string format
-      var isoFormat = moment(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset;
+            //if the string format already denotes that it's in "Roundtrip UTC" format (i.e. "2018-02-07T00:20:38.173Z")
+            //otherwise known as https://en.wikipedia.org/wiki/ISO_8601. This is the default format returned from the server
+            //since that is the default formatter for newtonsoft.json. When it is in this format, we need to tell moment
+            //to load the date as UTC so it's not changed, otherwise load it normally
+            var isoFormat;
+            if (strVal.indexOf("T") > -1 && strVal.endsWith("Z")) {
+                isoFormat = moment.utc(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset;
+            }
+            else {
+                isoFormat = moment(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset;
+            }
 
-      //create a moment with the iso format which will include the offset with the correct time
-      // then convert it to local time
-      return moment.parseZone(isoFormat).local();
-    },
+            //create a moment with the iso format which will include the offset with the correct time
+            // then convert it to local time
+            return moment.parseZone(isoFormat).local();
+        },
 
-    getLocalDate: function (date, culture, format) {
-      if (date) {
-        var dateVal;
-        var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
-        var localOffset = new Date().getTimezoneOffset();
-        var serverTimeNeedsOffsetting = -serverOffset !== localOffset;
-        if (serverTimeNeedsOffsetting) {
-          dateVal = dateHelper.convertToLocalMomentTime(date, serverOffset);
-        } else {
-          dateVal = moment(date, 'YYYY-MM-DD HH:mm:ss');
+        getLocalDate: function (date, culture, format) {
+            if (date) {
+                var dateVal;
+                var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
+                var localOffset = new Date().getTimezoneOffset();
+                var serverTimeNeedsOffsetting = -serverOffset !== localOffset;
+                if (serverTimeNeedsOffsetting) {
+                    dateVal = this.convertToLocalMomentTime(date, serverOffset);
+                } else {
+                    dateVal = moment(date, 'YYYY-MM-DD HH:mm:ss');
+                }
+                return dateVal.locale(culture).format(format);
+            }
         }
-        return dateVal.locale(culture).format(format);
-      }
-    }
 
-  };
+    };
 }
 angular.module('umbraco.services').factory('dateHelper', dateHelper);
 
 function packageHelper(assetsService, treeService, eventsService, $templateCache) {
 
-  return {
+    return {
 
-    /** Called when a package is installed, this resets a bunch of data and ensures the new package assets are loaded in */
-    packageInstalled: function () {
+        /** Called when a package is installed, this resets a bunch of data and ensures the new package assets are loaded in */
+        packageInstalled: function () {
 
-      //clears the tree
-      treeService.clearCache();
+            //clears the tree
+            treeService.clearCache();
 
-      //clears the template cache
-      $templateCache.removeAll();
+            //clears the template cache
+            $templateCache.removeAll();
 
-      //emit event to notify anything else
-      eventsService.emit("app.reInitialize");
-    }
+            //emit event to notify anything else
+            eventsService.emit("app.reInitialize");
+        }
 
-  };
+    };
 }
 angular.module('umbraco.services').factory('packageHelper', packageHelper);
 
 //TODO: I believe this is obsolete
 function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, mediaHelper, umbRequestHelper) {
-  return {
-    /** sets the image's url, thumbnail and if its a folder */
-    setImageData: function (img) {
+    return {
+        /** sets the image's url, thumbnail and if its a folder */
+        setImageData: function (img) {
 
-      img.isFolder = !mediaHelper.hasFilePropertyType(img);
+            img.isFolder = !mediaHelper.hasFilePropertyType(img);
 
-      if (!img.isFolder) {
-        img.thumbnail = mediaHelper.resolveFile(img, true);
-        img.image = mediaHelper.resolveFile(img, false);
-      }
-    },
+            if (!img.isFolder) {
+                img.thumbnail = mediaHelper.resolveFile(img, true);
+                img.image = mediaHelper.resolveFile(img, false);
+            }
+        },
 
-    /** sets the images original size properties - will check if it is a folder and if so will just make it square */
-    setOriginalSize: function (img, maxHeight) {
-      //set to a square by default
-      img.originalWidth = maxHeight;
-      img.originalHeight = maxHeight;
+        /** sets the images original size properties - will check if it is a folder and if so will just make it square */
+        setOriginalSize: function (img, maxHeight) {
+            //set to a square by default
+            img.originalWidth = maxHeight;
+            img.originalHeight = maxHeight;
 
-      var widthProp = _.find(img.properties, function (v) { return (v.alias === "umbracoWidth"); });
-      if (widthProp && widthProp.value) {
-        img.originalWidth = parseInt(widthProp.value, 10);
-        if (isNaN(img.originalWidth)) {
-          img.originalWidth = maxHeight;
+            var widthProp = _.find(img.properties, function (v) { return (v.alias === "umbracoWidth"); });
+            if (widthProp && widthProp.value) {
+                img.originalWidth = parseInt(widthProp.value, 10);
+                if (isNaN(img.originalWidth)) {
+                    img.originalWidth = maxHeight;
+                }
+            }
+            var heightProp = _.find(img.properties, function (v) { return (v.alias === "umbracoHeight"); });
+            if (heightProp && heightProp.value) {
+                img.originalHeight = parseInt(heightProp.value, 10);
+                if (isNaN(img.originalHeight)) {
+                    img.originalHeight = maxHeight;
+                }
+            }
+        },
+
+        /** sets the image style which get's used in the angular markup */
+        setImageStyle: function (img, width, height, rightMargin, bottomMargin) {
+            img.style = { width: width + "px", height: height + "px", "margin-right": rightMargin + "px", "margin-bottom": bottomMargin + "px" };
+            img.thumbStyle = {
+                "background-image": "url('" + img.thumbnail + "')",
+                "background-repeat": "no-repeat",
+                "background-position": "center",
+                "background-size": Math.min(width, img.originalWidth) + "px " + Math.min(height, img.originalHeight) + "px"
+            };
+        },
+
+        /** gets the image's scaled wdith based on the max row height */
+        getScaledWidth: function (img, maxHeight) {
+            var scaled = img.originalWidth * maxHeight / img.originalHeight;
+            return scaled;
+            //round down, we don't want it too big even by half a pixel otherwise it'll drop to the next row
+            //return Math.floor(scaled);
+        },
+
+        /** returns the target row width taking into account how many images will be in the row and removing what the margin is */
+        getTargetWidth: function (imgsPerRow, maxRowWidth, margin) {
+            //take into account the margin, we will have 1 less margin item than we have total images
+            return (maxRowWidth - ((imgsPerRow - 1) * margin));
+        },
+
+        /** 
+            This will determine the row/image height for the next collection of images which takes into account the 
+            ideal image count per row. It will check if a row can be filled with this ideal count and if not - if there
+            are additional images available to fill the row it will keep calculating until they fit.
+    
+            It will return the calculated height and the number of images for the row.
+    
+            targetHeight = optional;
+        */
+        getRowHeightForImages: function (imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, targetHeight) {
+
+            var idealImages = imgs.slice(0, idealImgPerRow);
+            //get the target row width without margin
+            var targetRowWidth = this.getTargetWidth(idealImages.length, maxRowWidth, margin);
+            //this gets the image with the smallest height which equals the maximum we can scale up for this image block
+            var maxScaleableHeight = this.getMaxScaleableHeight(idealImages, maxRowHeight);
+            //if the max scale height is smaller than the min display height, we'll use the min display height
+            targetHeight = targetHeight !== undefined ? targetHeight : Math.max(maxScaleableHeight, minDisplayHeight);
+
+            var attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight);
+
+            if (attemptedRowHeight != null) {
+
+                //if this is smaller than the min display then we need to use the min display,
+                // which means we'll need to remove one from the row so we can scale up to fill the row
+                if (attemptedRowHeight < minDisplayHeight) {
+
+                    if (idealImages.length > 1) {
+
+                        //we'll generate a new targetHeight that is halfway between the max and the current and recurse, passing in a new targetHeight                        
+                        targetHeight += Math.floor((maxRowHeight - targetHeight) / 2);
+                        return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow - 1, margin, targetHeight);
+                    }
+                    else {
+                        //this will occur when we only have one image remaining in the row but it's still going to be too wide even when 
+                        // using the minimum display height specified. In this case we're going to have to just crop the image in it's center
+                        // using the minimum display height and the full row width
+                        return { height: minDisplayHeight, imgCount: 1 };
+                    }
+                }
+                else {
+                    //success!
+                    return { height: attemptedRowHeight, imgCount: idealImages.length };
+                }
+            }
+
+            //we know the width will fit in a row, but we now need to figure out if we can fill 
+            // the entire row in the case that we have more images remaining than the idealImgPerRow.
+
+            if (idealImages.length === imgs.length) {
+                //we have no more remaining images to fill the space, so we'll just use the calc height
+                return { height: targetHeight, imgCount: idealImages.length };
+            }
+            else if (idealImages.length === 1) {
+                //this will occur when we only have one image remaining in the row to process but it's not really going to fit ideally
+                // in the row. 
+                return { height: minDisplayHeight, imgCount: 1 };
+            }
+            else if (idealImages.length === idealImgPerRow && targetHeight < maxRowHeight) {
+
+                //if we're already dealing with the ideal images per row and it's not quite wide enough, we can scale up a little bit so 
+                // long as the targetHeight is currently less than the maxRowHeight. The scale up will be half-way between our current
+                // target height and the maxRowHeight (we won't loop forever though - if there's a difference of 5 px we'll just quit)
+
+                while (targetHeight < maxRowHeight && (maxRowHeight - targetHeight) > 5) {
+                    targetHeight += Math.floor((maxRowHeight - targetHeight) / 2);
+                    attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight);
+                    if (attemptedRowHeight != null) {
+                        //success!
+                        return { height: attemptedRowHeight, imgCount: idealImages.length };
+                    }
+                }
+
+                //Ok, we couldn't actually scale it up with the ideal row count we'll just recurse with a lesser image count.
+                return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow - 1, margin);
+            }
+            else if (targetHeight === maxRowHeight) {
+
+                //This is going to happen when:
+                // * We can fit a list of images in a row, but they come up too short (based on minDisplayHeight)
+                // * Then we'll try to remove an image, but when we try to scale to fit, the width comes up too narrow but the images are already at their
+                //      maximum height (maxRowHeight)
+                // * So we're stuck, we cannot precicely fit the current list of images, so we'll render a row that will be max height but won't be wide enough
+                //      which is better than rendering a row that is shorter than the minimum since that could be quite small.
+
+                return { height: targetHeight, imgCount: idealImages.length };
+            }
+            else {
+
+                //we have additional images so we'll recurse and add 1 to the idealImgPerRow until it fits
+                return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow + 1, margin);
+            }
+
+        },
+
+        performGetRowHeight: function (idealImages, targetRowWidth, minDisplayHeight, targetHeight) {
+
+            var currRowWidth = 0;
+
+            for (var i = 0; i < idealImages.length; i++) {
+                var scaledW = this.getScaledWidth(idealImages[i], targetHeight);
+                currRowWidth += scaledW;
+            }
+
+            if (currRowWidth > targetRowWidth) {
+                //get the new scaled height to fit
+                var newHeight = targetRowWidth * targetHeight / currRowWidth;
+
+                return newHeight;
+            }
+            else if (idealImages.length === 1 && (currRowWidth <= targetRowWidth) && !idealImages[0].isFolder) {
+                //if there is only one image, then return the target height
+                return targetHeight;
+            }
+            else if (currRowWidth / targetRowWidth > 0.90) {
+                //it's close enough, it's at least 90% of the width so we'll accept it with the target height
+                return targetHeight;
+            }
+            else {
+                //if it's not successful, return null
+                return null;
+            }
+        },
+
+        /** builds an image grid row */
+        buildRow: function (imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, totalRemaining) {
+            var currRowWidth = 0;
+            var row = { images: [] };
+
+            var imageRowHeight = this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin);
+            var targetWidth = this.getTargetWidth(imageRowHeight.imgCount, maxRowWidth, margin);
+
+            var sizes = [];
+            //loop through the images we know fit into the height
+            for (var i = 0; i < imageRowHeight.imgCount; i++) {
+                //get the lower width to ensure it always fits
+                var scaledWidth = Math.floor(this.getScaledWidth(imgs[i], imageRowHeight.height));
+
+                if (currRowWidth + scaledWidth <= targetWidth) {
+                    currRowWidth += scaledWidth;
+                    sizes.push({
+                        width: scaledWidth,
+                        //ensure that the height is rounded
+                        height: Math.round(imageRowHeight.height)
+                    });
+                    row.images.push(imgs[i]);
+                }
+                else if (imageRowHeight.imgCount === 1 && row.images.length === 0) {
+                    //the image is simply too wide, we'll crop/center it
+                    sizes.push({
+                        width: maxRowWidth,
+                        //ensure that the height is rounded
+                        height: Math.round(imageRowHeight.height)
+                    });
+                    row.images.push(imgs[i]);
+                }
+                else {
+                    //the max width has been reached
+                    break;
+                }
+            }
+
+            //loop through the images for the row and apply the styles
+            for (var j = 0; j < row.images.length; j++) {
+                var bottomMargin = margin;
+                //make the margin 0 for the last one
+                if (j === (row.images.length - 1)) {
+                    margin = 0;
+                }
+                this.setImageStyle(row.images[j], sizes[j].width, sizes[j].height, margin, bottomMargin);
+            }
+
+            if (row.images.length === 1 && totalRemaining > 1) {
+                //if there's only one image on the row and there are more images remaining, set the container to max width
+                row.images[0].style.width = maxRowWidth + "px";
+            }
+
+
+            return row;
+        },
+
+        /** Returns the maximum image scaling height for the current image collection */
+        getMaxScaleableHeight: function (imgs, maxRowHeight) {
+
+            var smallestHeight = _.min(imgs, function (item) { return item.originalHeight; }).originalHeight;
+
+            //adjust the smallestHeight if it is larger than the static max row height
+            if (smallestHeight > maxRowHeight) {
+                smallestHeight = maxRowHeight;
+            }
+            return smallestHeight;
+        },
+
+        /** Creates the image grid with calculated widths/heights for images to fill the grid nicely */
+        buildGrid: function (images, maxRowWidth, maxRowHeight, startingIndex, minDisplayHeight, idealImgPerRow, margin, imagesOnly) {
+
+            var rows = [];
+            var imagesProcessed = 0;
+
+            //first fill in all of the original image sizes and URLs
+            for (var i = startingIndex; i < images.length; i++) {
+                var item = images[i];
+
+                this.setImageData(item);
+                this.setOriginalSize(item, maxRowHeight);
+
+                if (imagesOnly && !item.isFolder && !item.thumbnail) {
+                    images.splice(i, 1);
+                    i--;
+                }
+            }
+
+            while ((imagesProcessed + startingIndex) < images.length) {
+                //get the maxHeight for the current un-processed images
+                var currImgs = images.slice(imagesProcessed);
+
+                //build the row
+                var remaining = images.length - imagesProcessed;
+                var row = this.buildRow(currImgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, remaining);
+                if (row.images.length > 0) {
+                    rows.push(row);
+                    imagesProcessed += row.images.length;
+                }
+                else {
+
+                    if (currImgs.length > 0) {
+                        throw "Could not fill grid with all images, images remaining: " + currImgs.length;
+                    }
+
+                    //if there was nothing processed, exit
+                    break;
+                }
+            }
+
+            return rows;
         }
-      }
-      var heightProp = _.find(img.properties, function (v) { return (v.alias === "umbracoHeight"); });
-      if (heightProp && heightProp.value) {
-        img.originalHeight = parseInt(heightProp.value, 10);
-        if (isNaN(img.originalHeight)) {
-          img.originalHeight = maxHeight;
-        }
-      }
-    },
-
-    /** sets the image style which get's used in the angular markup */
-    setImageStyle: function (img, width, height, rightMargin, bottomMargin) {
-      img.style = { width: width + "px", height: height + "px", "margin-right": rightMargin + "px", "margin-bottom": bottomMargin + "px" };
-      img.thumbStyle = {
-        "background-image": "url('" + img.thumbnail + "')",
-        "background-repeat": "no-repeat",
-        "background-position": "center",
-        "background-size": Math.min(width, img.originalWidth) + "px " + Math.min(height, img.originalHeight) + "px"
-      };
-    },
-
-    /** gets the image's scaled wdith based on the max row height */
-    getScaledWidth: function (img, maxHeight) {
-      var scaled = img.originalWidth * maxHeight / img.originalHeight;
-      return scaled;
-      //round down, we don't want it too big even by half a pixel otherwise it'll drop to the next row
-      //return Math.floor(scaled);
-    },
-
-    /** returns the target row width taking into account how many images will be in the row and removing what the margin is */
-    getTargetWidth: function (imgsPerRow, maxRowWidth, margin) {
-      //take into account the margin, we will have 1 less margin item than we have total images
-      return (maxRowWidth - ((imgsPerRow - 1) * margin));
-    },
-
-    /** 
-        This will determine the row/image height for the next collection of images which takes into account the 
-        ideal image count per row. It will check if a row can be filled with this ideal count and if not - if there
-        are additional images available to fill the row it will keep calculating until they fit.
-
-        It will return the calculated height and the number of images for the row.
-
-        targetHeight = optional;
-    */
-    getRowHeightForImages: function (imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, targetHeight) {
-
-      var idealImages = imgs.slice(0, idealImgPerRow);
-      //get the target row width without margin
-      var targetRowWidth = this.getTargetWidth(idealImages.length, maxRowWidth, margin);
-      //this gets the image with the smallest height which equals the maximum we can scale up for this image block
-      var maxScaleableHeight = this.getMaxScaleableHeight(idealImages, maxRowHeight);
-      //if the max scale height is smaller than the min display height, we'll use the min display height
-      targetHeight = targetHeight !== undefined ? targetHeight : Math.max(maxScaleableHeight, minDisplayHeight);
-
-      var attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight);
-
-      if (attemptedRowHeight != null) {
-
-        //if this is smaller than the min display then we need to use the min display,
-        // which means we'll need to remove one from the row so we can scale up to fill the row
-        if (attemptedRowHeight < minDisplayHeight) {
-
-          if (idealImages.length > 1) {
-
-            //we'll generate a new targetHeight that is halfway between the max and the current and recurse, passing in a new targetHeight                        
-            targetHeight += Math.floor((maxRowHeight - targetHeight) / 2);
-            return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow - 1, margin, targetHeight);
-          }
-          else {
-            //this will occur when we only have one image remaining in the row but it's still going to be too wide even when 
-            // using the minimum display height specified. In this case we're going to have to just crop the image in it's center
-            // using the minimum display height and the full row width
-            return { height: minDisplayHeight, imgCount: 1 };
-          }
-        }
-        else {
-          //success!
-          return { height: attemptedRowHeight, imgCount: idealImages.length };
-        }
-      }
-
-      //we know the width will fit in a row, but we now need to figure out if we can fill 
-      // the entire row in the case that we have more images remaining than the idealImgPerRow.
-
-      if (idealImages.length === imgs.length) {
-        //we have no more remaining images to fill the space, so we'll just use the calc height
-        return { height: targetHeight, imgCount: idealImages.length };
-      }
-      else if (idealImages.length === 1) {
-        //this will occur when we only have one image remaining in the row to process but it's not really going to fit ideally
-        // in the row. 
-        return { height: minDisplayHeight, imgCount: 1 };
-      }
-      else if (idealImages.length === idealImgPerRow && targetHeight < maxRowHeight) {
-
-        //if we're already dealing with the ideal images per row and it's not quite wide enough, we can scale up a little bit so 
-        // long as the targetHeight is currently less than the maxRowHeight. The scale up will be half-way between our current
-        // target height and the maxRowHeight (we won't loop forever though - if there's a difference of 5 px we'll just quit)
-
-        while (targetHeight < maxRowHeight && (maxRowHeight - targetHeight) > 5) {
-          targetHeight += Math.floor((maxRowHeight - targetHeight) / 2);
-          attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight);
-          if (attemptedRowHeight != null) {
-            //success!
-            return { height: attemptedRowHeight, imgCount: idealImages.length };
-          }
-        }
-
-        //Ok, we couldn't actually scale it up with the ideal row count we'll just recurse with a lesser image count.
-        return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow - 1, margin);
-      }
-      else if (targetHeight === maxRowHeight) {
-
-        //This is going to happen when:
-        // * We can fit a list of images in a row, but they come up too short (based on minDisplayHeight)
-        // * Then we'll try to remove an image, but when we try to scale to fit, the width comes up too narrow but the images are already at their
-        //      maximum height (maxRowHeight)
-        // * So we're stuck, we cannot precicely fit the current list of images, so we'll render a row that will be max height but won't be wide enough
-        //      which is better than rendering a row that is shorter than the minimum since that could be quite small.
-
-        return { height: targetHeight, imgCount: idealImages.length };
-      }
-      else {
-
-        //we have additional images so we'll recurse and add 1 to the idealImgPerRow until it fits
-        return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow + 1, margin);
-      }
-
-    },
-
-    performGetRowHeight: function (idealImages, targetRowWidth, minDisplayHeight, targetHeight) {
-
-      var currRowWidth = 0;
-
-      for (var i = 0; i < idealImages.length; i++) {
-        var scaledW = this.getScaledWidth(idealImages[i], targetHeight);
-        currRowWidth += scaledW;
-      }
-
-      if (currRowWidth > targetRowWidth) {
-        //get the new scaled height to fit
-        var newHeight = targetRowWidth * targetHeight / currRowWidth;
-
-        return newHeight;
-      }
-      else if (idealImages.length === 1 && (currRowWidth <= targetRowWidth) && !idealImages[0].isFolder) {
-        //if there is only one image, then return the target height
-        return targetHeight;
-      }
-      else if (currRowWidth / targetRowWidth > 0.90) {
-        //it's close enough, it's at least 90% of the width so we'll accept it with the target height
-        return targetHeight;
-      }
-      else {
-        //if it's not successful, return null
-        return null;
-      }
-    },
-
-    /** builds an image grid row */
-    buildRow: function (imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, totalRemaining) {
-      var currRowWidth = 0;
-      var row = { images: [] };
-
-      var imageRowHeight = this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin);
-      var targetWidth = this.getTargetWidth(imageRowHeight.imgCount, maxRowWidth, margin);
-
-      var sizes = [];
-      //loop through the images we know fit into the height
-      for (var i = 0; i < imageRowHeight.imgCount; i++) {
-        //get the lower width to ensure it always fits
-        var scaledWidth = Math.floor(this.getScaledWidth(imgs[i], imageRowHeight.height));
-
-        if (currRowWidth + scaledWidth <= targetWidth) {
-          currRowWidth += scaledWidth;
-          sizes.push({
-            width: scaledWidth,
-            //ensure that the height is rounded
-            height: Math.round(imageRowHeight.height)
-          });
-          row.images.push(imgs[i]);
-        }
-        else if (imageRowHeight.imgCount === 1 && row.images.length === 0) {
-          //the image is simply too wide, we'll crop/center it
-          sizes.push({
-            width: maxRowWidth,
-            //ensure that the height is rounded
-            height: Math.round(imageRowHeight.height)
-          });
-          row.images.push(imgs[i]);
-        }
-        else {
-          //the max width has been reached
-          break;
-        }
-      }
-
-      //loop through the images for the row and apply the styles
-      for (var j = 0; j < row.images.length; j++) {
-        var bottomMargin = margin;
-        //make the margin 0 for the last one
-        if (j === (row.images.length - 1)) {
-          margin = 0;
-        }
-        this.setImageStyle(row.images[j], sizes[j].width, sizes[j].height, margin, bottomMargin);
-      }
-
-      if (row.images.length === 1 && totalRemaining > 1) {
-        //if there's only one image on the row and there are more images remaining, set the container to max width
-        row.images[0].style.width = maxRowWidth + "px";
-      }
-
-
-      return row;
-    },
-
-    /** Returns the maximum image scaling height for the current image collection */
-    getMaxScaleableHeight: function (imgs, maxRowHeight) {
-
-      var smallestHeight = _.min(imgs, function (item) { return item.originalHeight; }).originalHeight;
-
-      //adjust the smallestHeight if it is larger than the static max row height
-      if (smallestHeight > maxRowHeight) {
-        smallestHeight = maxRowHeight;
-      }
-      return smallestHeight;
-    },
-
-    /** Creates the image grid with calculated widths/heights for images to fill the grid nicely */
-    buildGrid: function (images, maxRowWidth, maxRowHeight, startingIndex, minDisplayHeight, idealImgPerRow, margin, imagesOnly) {
-
-      var rows = [];
-      var imagesProcessed = 0;
-
-      //first fill in all of the original image sizes and URLs
-      for (var i = startingIndex; i < images.length; i++) {
-        var item = images[i];
-
-        this.setImageData(item);
-        this.setOriginalSize(item, maxRowHeight);
-
-        if (imagesOnly && !item.isFolder && !item.thumbnail) {
-          images.splice(i, 1);
-          i--;
-        }
-      }
-
-      while ((imagesProcessed + startingIndex) < images.length) {
-        //get the maxHeight for the current un-processed images
-        var currImgs = images.slice(imagesProcessed);
-
-        //build the row
-        var remaining = images.length - imagesProcessed;
-        var row = this.buildRow(currImgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, remaining);
-        if (row.images.length > 0) {
-          rows.push(row);
-          imagesProcessed += row.images.length;
-        }
-        else {
-
-          if (currImgs.length > 0) {
-            throw "Could not fill grid with all images, images remaining: " + currImgs.length;
-          }
-
-          //if there was nothing processed, exit
-          break;
-        }
-      }
-
-      return rows;
-    }
-  };
+    };
 }
 angular.module("umbraco.services").factory("umbPhotoFolderHelper", umbPhotoFolderHelper);
 
@@ -443,40 +452,40 @@ angular.module("umbraco.services").factory("umbPhotoFolderHelper", umbPhotoFolde
  */
 function umbModelMapper() {
 
-  return {
+    return {
 
 
-    /**
-     * @ngdoc function
-     * @name umbraco.services.umbModelMapper#convertToEntityBasic
-     * @methodOf umbraco.services.umbModelMapper
-     * @function
-     *
-     * @description
-     * Converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model.
-     * @param {Object} source The source model
-     * @param {Number} source.id The node id of the model
-     * @param {String} source.name The node name
-     * @param {String} source.icon The models icon as a css class (.icon-doc)
-     * @param {Number} source.parentId The parentID, if no parent, set to -1
-     * @param {path} source.path comma-separated string of ancestor IDs (-1,1234,1782,1234)
-     */
+        /**
+         * @ngdoc function
+         * @name umbraco.services.umbModelMapper#convertToEntityBasic
+         * @methodOf umbraco.services.umbModelMapper
+         * @function
+         *
+         * @description
+         * Converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model.
+         * @param {Object} source The source model
+         * @param {Number} source.id The node id of the model
+         * @param {String} source.name The node name
+         * @param {String} source.icon The models icon as a css class (.icon-doc)
+         * @param {Number} source.parentId The parentID, if no parent, set to -1
+         * @param {path} source.path comma-separated string of ancestor IDs (-1,1234,1782,1234)
+         */
 
-    /** This converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model */
-    convertToEntityBasic: function (source) {
-      var required = ["id", "name", "icon", "parentId", "path"];
-      _.each(required, function (k) {
-        if (!_.has(source, k)) {
-          throw "The source object does not contain the property " + k;
+        /** This converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model */
+        convertToEntityBasic: function (source) {
+            var required = ["id", "name", "icon", "parentId", "path"];
+            _.each(required, function (k) {
+                if (!_.has(source, k)) {
+                    throw "The source object does not contain the property " + k;
+                }
+            });
+            var optional = ["metaData", "key", "alias"];
+            //now get the basic object
+            var result = _.pick(source, required.concat(optional));
+            return result;
         }
-      });
-      var optional = ["metaData", "key", "alias"];
-      //now get the basic object
-      var result = _.pick(source, required.concat(optional));
-      return result;
-    }
 
-  };
+    };
 }
 angular.module('umbraco.services').factory('umbModelMapper', umbModelMapper);
 
@@ -491,21 +500,21 @@ angular.module('umbraco.services').factory('umbModelMapper', umbModelMapper);
  */
 function umbSessionStorage($window) {
 
-  //gets the sessionStorage object if available, otherwise just uses a normal object
-  // - required for unit tests.
-  var storage = $window['sessionStorage'] ? $window['sessionStorage'] : {};
+    //gets the sessionStorage object if available, otherwise just uses a normal object
+    // - required for unit tests.
+    var storage = $window['sessionStorage'] ? $window['sessionStorage'] : {};
 
-  return {
+    return {
 
-    get: function (key) {
-      return angular.fromJson(storage["umb_" + key]);
-    },
+        get: function (key) {
+            return angular.fromJson(storage["umb_" + key]);
+        },
 
-    set: function (key, value) {
-      storage["umb_" + key] = angular.toJson(value);
-    }
+        set: function (key, value) {
+            storage["umb_" + key] = angular.toJson(value);
+        }
 
-  };
+    };
 }
 angular.module('umbraco.services').factory('umbSessionStorage', umbSessionStorage);
 
@@ -518,28 +527,28 @@ angular.module('umbraco.services').factory('umbSessionStorage', umbSessionStorag
  * used to check for updates and display a notifcation
  */
 function updateChecker($http, umbRequestHelper) {
-  return {
+    return {
 
-    /**
-     * @ngdoc function
-     * @name umbraco.services.updateChecker#check
-     * @methodOf umbraco.services.updateChecker
-     * @function
-     *
-     * @description
-     * Called to load in the legacy tree js which is required on startup if a user is logged in or 
-     * after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded. 
-     */
-    check: function () {
+        /**
+         * @ngdoc function
+         * @name umbraco.services.updateChecker#check
+         * @methodOf umbraco.services.updateChecker
+         * @function
+         *
+         * @description
+         * Called to load in the legacy tree js which is required on startup if a user is logged in or 
+         * after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded. 
+         */
+        check: function () {
 
-      return umbRequestHelper.resourcePromise(
-        $http.get(
-          umbRequestHelper.getApiUrl(
-            "updateCheckApiBaseUrl",
-            "GetCheck")),
-        'Failed to retrieve update status');
-    }
-  };
+            return umbRequestHelper.resourcePromise(
+                $http.get(
+                    umbRequestHelper.getApiUrl(
+                        "updateCheckApiBaseUrl",
+                        "GetCheck")),
+                'Failed to retrieve update status');
+        }
+    };
 }
 angular.module('umbraco.services').factory('updateChecker', updateChecker);
 
@@ -549,43 +558,43 @@ angular.module('umbraco.services').factory('updateChecker', updateChecker);
 * @description A helper object used for property editors
 **/
 function umbPropEditorHelper() {
-  return {
-    /**
-     * @ngdoc function
-     * @name getImagePropertyValue
-     * @methodOf umbraco.services.umbPropertyEditorHelper
-     * @function    
-     *
-     * @description
-     * Returns the correct view path for a property editor, it will detect if it is a full virtual path but if not then default to the internal umbraco one
-     * 
-     * @param {string} input the view path currently stored for the property editor
-     */
-    getViewPath: function (input, isPreValue) {
-      var path = String(input);
+    return {
+        /**
+         * @ngdoc function
+         * @name getImagePropertyValue
+         * @methodOf umbraco.services.umbPropertyEditorHelper
+         * @function    
+         *
+         * @description
+         * Returns the correct view path for a property editor, it will detect if it is a full virtual path but if not then default to the internal umbraco one
+         * 
+         * @param {string} input the view path currently stored for the property editor
+         */
+        getViewPath: function (input, isPreValue) {
+            var path = String(input);
 
-      if (path.startsWith('/')) {
+            if (path.startsWith('/')) {
 
-        //This is an absolute path, so just leave it
-        return path;
-      } else {
+                //This is an absolute path, so just leave it
+                return path;
+            } else {
 
-        if (path.indexOf("/") >= 0) {
-          //This is a relative path, so just leave it
-          return path;
-        } else {
-          if (!isPreValue) {
-            //i.e. views/propertyeditors/fileupload/fileupload.html
-            return "views/propertyeditors/" + path + "/" + path + ".html";
-          } else {
-            //i.e. views/prevalueeditors/requiredfield.html
-            return "views/prevalueeditors/" + path + ".html";
-          }
+                if (path.indexOf("/") >= 0) {
+                    //This is a relative path, so just leave it
+                    return path;
+                } else {
+                    if (!isPreValue) {
+                        //i.e. views/propertyeditors/fileupload/fileupload.html
+                        return "views/propertyeditors/" + path + "/" + path + ".html";
+                    } else {
+                        //i.e. views/prevalueeditors/requiredfield.html
+                        return "views/prevalueeditors/" + path + ".html";
+                    }
+                }
+
+            }
         }
-
-      }
-    }
-  };
+    };
 }
 angular.module('umbraco.services').factory('umbPropEditorHelper', umbPropEditorHelper);
 
@@ -596,24 +605,24 @@ angular.module('umbraco.services').factory('umbPropEditorHelper', umbPropEditorH
 **/
 function queryStrings($window) {
 
-  var pl = /\+/g;  // Regex for replacing addition symbol with a space
-  var search = /([^&=]+)=?([^&]*)/g;
-  var decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); };
+    var pl = /\+/g;  // Regex for replacing addition symbol with a space
+    var search = /([^&=]+)=?([^&]*)/g;
+    var decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); };
 
-  return {
-   
-    getParams: function () {
-      var match;
-      var query = $window.location.search.substring(1);
+    return {
 
-      var urlParams = {};
-      while (match = search.exec(query)) {
-        urlParams[decode(match[1])] = decode(match[2]);
-      }
+        getParams: function () {
+            var match;
+            var query = $window.location.search.substring(1);
 
-      return urlParams;
-    }
-  };
+            var urlParams = {};
+            while (match = search.exec(query)) {
+                urlParams[decode(match[1])] = decode(match[2]);
+            }
+
+            return urlParams;
+        }
+    };
 }
 angular.module('umbraco.services').factory('queryStrings', queryStrings);
 
diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
index e83e716dda..77a233c3e8 100644
--- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
@@ -14,7 +14,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
     $scope.authenticated = null;
     $scope.touchDevice = appState.getGlobalState("touchDevice");
     $scope.editors = [];
-
+    $scope.overlay = {};
 
     $scope.removeNotification = function (index) {
         notificationsService.remove(index);
@@ -112,6 +112,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
         };
     }));
 
+    // events for drawer
     // manage the help dialog by subscribing to the showHelp appState
     $scope.drawer = {};
     evts.push(eventsService.on("appState.drawerState.changed", function (e, args) {
@@ -129,6 +130,12 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
         }
     }));
 
+    // events for overlays
+    evts.push(eventsService.on("appState.overlay", function (name, args) {
+        $scope.overlay = args;
+    }));
+    
+    // events for tours
     evts.push(eventsService.on("appState.tour.start", function (name, args) {
         $scope.tour = args;
         $scope.tour.show = true;
@@ -142,6 +149,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
         $scope.tour = null;
     }));
 
+    // events for backdrop
     evts.push(eventsService.on("appState.backdrop", function (name, args) {
         $scope.backdrop = args;
     }));
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 14c11c97f4..6330f5a30a 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, $routeParams, $timeout, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper) {
+function NavigationController($scope, $rootScope, $location, $log, $routeParams, $timeout, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper, languageResource) {
 
     //TODO: Need to think about this and an nicer way to acheive what this is doing.
     //the tree event handler i used to subscribe to the main tree click events
@@ -31,6 +31,10 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams,
     $scope.menuDialogTitle = null;
     $scope.menuActions = [];
     $scope.menuNode = null;
+    $scope.languages = [];
+    $scope.selectedLanguage = {};
+    $scope.page = {};
+    $scope.page.languageSelectorIsOpen = false;
 
     $scope.currentSection = appState.getSectionState("currentSection");
     $scope.showNavigation = appState.getGlobalState("showNavigation");
@@ -98,6 +102,19 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams,
         }
     }));
 
+    // Listen for language updates
+    evts.push(eventsService.on("editors.languages.languageDeleted", function(e, args) {
+        languageResource.getAll().then(function(languages) {
+            $scope.languages = languages;
+        });
+    }));
+
+    evts.push(eventsService.on("editors.languages.languageCreated", function(e, args) {
+        languageResource.getAll().then(function(languages) {
+            $scope.languages = languages;
+        });
+    }));
+
     //This reacts to clicks passed to the body element which emits a global call to close all dialogs
     evts.push(eventsService.on("app.closeDialogs", function(event) {
         if (appState.getGlobalState("stickyNavigation")) {
@@ -115,8 +132,28 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams,
     //when the application is ready and the user is authorized setup the data
     evts.push(eventsService.on("app.ready", function(evt, data) {
         $scope.authenticated = true;
+        
+        // load languages
+        languageResource.getAll().then(function(languages) {
+            $scope.languages = languages;
+
+            // select the default language
+            $scope.languages.forEach(function(language) {
+                if(language.isDefault) {
+                    $scope.selectLanguage(language);
+                }
+            });
+
+        });
+
     }));
 
+    $scope.selectLanguage = function(language, languages) {
+        $scope.selectedLanguage = language;
+        // close the language selector
+        $scope.page.languageSelectorIsOpen = false;
+    };
+
     //this reacts to the options item in the tree
     //todo, migrate to nav service
     $scope.searchShowMenu = function (ev, args) {
@@ -155,8 +192,8 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams,
         }
     };
     
-    $scope.test = function() {
-        $scope.open = !$scope.open;
+    $scope.toggleLanguageSelector = function() {
+        $scope.page.languageSelectorIsOpen = !$scope.page.languageSelectorIsOpen;
     };
 
     //ensure to unregister from all events!
diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js
index cbd3649ca8..32462826fe 100644
--- a/src/Umbraco.Web.UI.Client/src/init.js
+++ b/src/Umbraco.Web.UI.Client/src/init.js
@@ -18,14 +18,26 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi
         eventsService.on("app.authenticated", function(evt, data) {
             
             assetsService._loadInitAssets().then(function() {
-                
-                //Register all of the tours on the server
-                tourService.registerAllTours().then(function (dashboards) {
-                    appReady(data);
-                }, function(){
-                    appReady(data);
-                });
 
+                // Loads the user's locale settings for Moment.
+                userService.loadMomentLocaleForCurrentUser().then(function() {
+
+                    //Register all of the tours on the server
+                    tourService.registerAllTours().then(function () {
+                        appReady(data);
+                        
+                        // Auto start intro tour
+                        tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) {
+                            // start intro tour if it hasn't been completed or disabled
+                            if (introTour && introTour.disabled !== true && introTour.completed !== true) {
+                                tourService.startTour(introTour);
+                            }
+                        });
+
+                    }, function(){
+                        appReady(data);
+                    });
+                });
             });
 
         });
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html b/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html
index 4edf2eda25..472ceb7135 100644
--- a/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html
+++ b/src/Umbraco.Web.UI.Client/src/installer/steps/upgrade.html
@@ -1,18 +1,19 @@
 

Upgrading Umbraco

- Welcome to the Umbraco installer. You see this screen because your Umbraco installation needs a quick upgrade of its database and files, which will ensure your website is kept as fast, secure and up to date as possible. + Welcome to the Umbraco installer. You see this screen because your Umbraco installation needs a quick upgrade + of its database and files, which will ensure your website is kept as fast, secure and up to date as possible.

-

- To read a report of changes between your current version {{installer.current.model.currentVersion}} and this version you're upgrading to {{installer.current.model.newVersion}} + Detected current version {{installer.current.model.currentVersion}} ({{installer.current.model.currentState}}), + which needs to be upgraded to {{installer.current.model.newVersion}} ({{installer.current.model.newState}}). + To compare versions and read a report of changes between versions, use the View Report button below.

View Report

-

- Simply click continue below to be guided through the rest of the upgrade + Simply click continue below to be guided through the rest of the upgrade.

diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js index 297db6ac4a..10c0d596eb 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js @@ -1,7 +1,7 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", function($scope, installerService) { $scope.passwordPattern = /.*/; - $scope.installer.current.model.subscribeToNewsLetter = true; + $scope.installer.current.model.subscribeToNewsLetter = false; if ($scope.installer.current.model.minNonAlphaNumericLength > 0) { var exp = ""; diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html index f2abb99f17..8fbe4263e8 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html @@ -1,7 +1,7 @@

-

Install Umbraco 7

+

Install Umbraco 8 (alpha)

-

Enter your name, email and password to install Umbraco 7 with its default settings, alternatively you can customize your installation

+

Enter your name, email and password to install Umbraco 8 with its default settings, alternatively you can customize your installation

diff --git a/src/Umbraco.Web.UI.Client/src/less/alerts.less b/src/Umbraco.Web.UI.Client/src/less/alerts.less index 7a760b05ea..7973c6e207 100644 --- a/src/Umbraco.Web.UI.Client/src/less/alerts.less +++ b/src/Umbraco.Web.UI.Client/src/less/alerts.less @@ -67,15 +67,14 @@ } .alert-form { - background-color: @gray-10; + background-color: @white; border: 1px solid @gray-3 !important; color: @gray-3; + box-shadow: 0 -1px 6px 0 rgba(0,0,0,0.16); } .alert-form.-no-border { border: none !important; - margin-left: 1px; - margin-bottom: 1px; } .alert-form h4 { diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index b51ddc500d..4448bf2192 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -62,10 +62,6 @@ body.umb-drawer-is-visible #mainwrapper{ margin: 0 } -#contentwrapper { - top: @appHeaderHeight; -} - #umb-notifications-wrapper { left: 80px; } @@ -87,7 +83,7 @@ body.umb-drawer-is-visible #mainwrapper{ z-index: 1100; float: left; position: absolute; - top: @appHeaderHeight; + top: 0; } #navigation { @@ -98,17 +94,18 @@ body.umb-drawer-is-visible #mainwrapper{ z-index: 100; background: @white; height: 100%; - } -.navigation-inner-container{ - position: absolute; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; - border-right: 1px solid @gray-8; - z-index: 100; +.navigation-inner-container { + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + border-right: 1px solid @gray-9; + z-index: 100; + display: flex; + flex-direction: column; } #dialog { @@ -124,6 +121,7 @@ body.umb-drawer-is-visible #mainwrapper{ padding: 0px; z-index: 100 !important; overflow: auto; + height: 100%; } #tree .umb-tree { diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 7a014fc1e9..311f8fe666 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -82,12 +82,15 @@ // Umbraco Components @import "components/application/umb-app-header.less"; +@import "components/application/umb-app-content.less"; @import "components/application/umb-tour.less"; @import "components/application/umb-backdrop.less"; @import "components/application/umb-drawer.less"; @import "components/application/umb-language-picker.less"; +@import "components/application/umb-dashboard.less"; @import "components/html/umb-expansion-panel.less"; +@import "components/html/umb-alert.less"; @import "components/editor.less"; @import "components/overlays.less"; @@ -173,6 +176,9 @@ //used for property editors @import "property-editors.less"; +//used for prevalue editors +@import "components/prevalues/multivalues.less"; + @import "typeahead.less"; @import "hacks.less"; @@ -180,4 +186,4 @@ @import "healthcheck.less"; // cleanup properties.less when it is done -@import "properties.less"; \ No newline at end of file +@import "properties.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-content.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-content.less new file mode 100644 index 0000000000..b3e0015d9a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-content.less @@ -0,0 +1,7 @@ +.umb-app-content { + position: absolute; + top: @appHeaderHeight; + right: 0; + bottom: 0; + left: 0; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less index 45e73df6da..1d9aaabd44 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less @@ -2,7 +2,7 @@ height: 100%; width: 100%; position: fixed; - z-index: 9999; + z-index: @zindexOverlayBackdrop; top: 0; left: 0; pointer-events: none; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less new file mode 100644 index 0000000000..73eba1e6b3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less @@ -0,0 +1,19 @@ +.umb-dashboard { + position: absolute; + height: 100%; + width: 100%; + top: 0; + display: flex; + flex-direction: column; + flex-wrap: nowrap; +} + +.umb-dashboard__header { + padding: 20px 20px 0 20px; + border-bottom: 1px solid @gray-9; +} + +.umb-dashboard__content { + padding: 20px; + overflow: auto; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less index d1a8bed432..81eb3ab59d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less @@ -1,15 +1,17 @@ .umb-language-picker { position: relative; - z-index: 1; + z-index: @zindexDropdown; } .umb-language-picker__toggle { display: flex; align-items: center; justify-content: space-between; - padding: 15px 20px; + padding: 0 20px; cursor: pointer; border-bottom: 1px solid @gray-9; + height: 50px; + box-sizing: border-box; } .umb-language-picker__toggle:hover { @@ -24,9 +26,12 @@ .umb-language-picker__dropdown { width: 100%; background: @white; - box-shadow: 0 5px 5px rgba(0,0,0,.2); + box-shadow: 0 3px 6px rgba(0,0,0,.16); box-sizing: border-box; position: absolute; + border-radius: 0 0 3px 3px; + max-height: 200px; + overflow: scroll; } .umb-language-picker__dropdown a { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less index bceef1767b..60aec5c036 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -185,3 +185,30 @@ border: none !important; background: none !important; } + +.umb-training-videos { + display: grid; + grid-gap: 10px; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); +} + +.umb-training-video { + background: @white; + box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); + border-radius: @baseBorderRadius; + transition: box-shadow 0.1s ease-in-out, border 0.1s ease-in-out; + box-sizing: border-box; + padding: 20px; +} + +.umb-training-video:hover { + box-shadow: 0 3px 6px 0 rgba(0,0,0,0.16); + text-decoration: none; +} + +.umb-training-video__label { + font-weight: bold; + margin-top: 20px; + display: block; + text-align: center; +} 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 90d1ff8e80..ccb3b271b2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -100,9 +100,9 @@ input.umb-editor-header__name-input { height: 32px; line-height: 32px; width: 100%; - padding: 0 15px; - background: @gray-10; - border-radius: 3px; + padding: 0 10px; + background: @white; + border: 1px solid @gray-8; &:hover { background-color: @gray-10; border: 1px solid @gray-8; @@ -127,15 +127,17 @@ a.umb-editor-header__close-split-view:hover { .umb-variant-switcher__toggle { display: flex; align-items: center; - padding: 0 15px; + padding: 0 10px; text-decoration: none !important; font-size: 13px; - border-left: none; color: @gray-4; - background-color: @gray-10; + background-color: @white; + border: 1px solid @gray-8; + border-left: none; transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; + margin-left: -1px; &:hover { - background-color: @gray-9; + background-color: @gray-10; } } @@ -157,6 +159,11 @@ a.umb-editor-header__close-split-view:hover { border-bottom: none; } +.umb-variant-switcher_item--current .umb-variant-switcher__name-wrapper { + background-color: @gray-10; + border-left: 2px solid @turquoise; +} + .umb-variant-switcher__item:hover, .umb-variant-switcher__item:focus, .umb-variant-switcher__name-wrapper:hover, @@ -172,11 +179,11 @@ a.umb-editor-header__close-split-view:hover { .umb-variant-switcher__name-wrapper { font-size: 14px; - padding: 5px 20px; flex: 1; cursor: pointer; - padding-top: 6px; - padding-bottom: 6px; + padding-top: 6px !important; + padding-bottom: 6px !important; + border-left: 2px solid transparent; } .umb-variant-switcher__name { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-alert.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-alert.less new file mode 100644 index 0000000000..ef8834f767 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-alert.less @@ -0,0 +1,11 @@ +.umb-alert { + padding: 15px; + box-sizing: border-box; + background-color: @turquoise-washed; + border: 1px solid @turquoise; +} + +.umb-alert--info { + background-color: @turquoise-washed; + border: 1px solid @turquoise; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less index 3ff5d6dbc4..c6009bce7b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less @@ -2,7 +2,7 @@ //border: 1px solid @gray-8; background: @white; border-radius: 3px; - margin-bottom: 8px; + margin-bottom: 16px; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.16); } 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 77dc6c6995..b29c340dce 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 @@ -1,7 +1,7 @@ .umb-notifications { z-index: 1000; position: absolute; - bottom: 52px; + bottom: @editorFooterHeight; left: 0; right: 0; border-bottom: none; 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 f9cd315825..fd52929357 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -75,21 +75,35 @@ /* ---------- OVERLAY CENTER ---------- */ .umb-overlay.umb-overlay-center { position: absolute; - width: 600px; - height: 500px; - top: 10px; + width: 500px; + height: auto; + top: 50%; left: 50%; transform: translate(-50%, 0); + transform: translate(-50%, -50%); + border-radius: @baseBorderRadius; } .umb-overlay.umb-overlay-center .umb-overlay-header { - text-align: center; + border: none; + background: transparent; + padding: 20px 20px 0 20px; +} + +.umb-overlay.umb-overlay-center .umb-overlay__form { + max-height: 80vh; } .umb-overlay.umb-overlay-center .umb-overlay-container { padding: 20px; } +.umb-overlay.umb-overlay-center .umb-overlay-drawer { + border: none; + background: transparent; + padding: 0 20px 20px 20px; +} + /* ---------- OVERLAY TARGET ---------- */ .umb-overlay.umb-overlay-target { width: 400px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-overlay-backdrop.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-overlay-backdrop.less index 65b3f34c2d..129ae9e757 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-overlay-backdrop.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-overlay-backdrop.less @@ -4,7 +4,7 @@ height: 100%; background-color: rgba(255, 255, 255, 0.50); z-index: @zindexOverlayBackdrop; - top: 0; + top: @appHeaderHeight; left: 0; animation: fadeIn 0.2s; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less new file mode 100644 index 0000000000..a307e5c585 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less @@ -0,0 +1,53 @@ +.umb-prevalues-multivalues { + width: 400px; +} + +.umb-prevalues-multivalues__left { + display: flex; + flex: 1 1 auto; +} + +.umb-prevalues-multivalues__right { + display: flex; + flex: 0 0 auto; + align-items: center; +} + +.umb-prevalues-multivalues__add { + display: flex; +} + +.umb-prevalues-multivalues__add input { + width: 320px; +} + +.umb-prevalues-multivalues__add input { + display: flex; +} + +.umb-prevalues-multivalues__add button { + margin: 0 6px 0 0; + float: right +} + +.umb-prevalues-multivalues__listitem { + display: flex; + padding: 6px; + margin: 10px 0px !important; + background: #F3F3F5; + cursor: move; +} + +.umb-prevalues-multivalues__listitem i { + display: flex; + align-items: center; + margin-right: 5px +} + +.umb-prevalues-multivalues__listitem a { + cursor: pointer; +} + +.umb-prevalues-multivalues__listitem input { + width: 295px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less index b6a8f060f3..f2cacc26b3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less @@ -7,6 +7,7 @@ .umb-box-header { padding: 10px 20px; + border-bottom: 1px solid @gray-9; } .umb-box-header-title { @@ -24,5 +25,4 @@ .umb-box-content { padding: 20px; - border-top: 1px solid @gray-9; } \ No newline at end of file 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 b26ca2aede..5cc4817142 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 @@ -280,7 +280,6 @@ .umb-grid .umb-control { position: relative; display: block; - overflow: hidden; margin-left: 10px; margin-right: 10px; margin-bottom: 10px; 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 c05d33db04..eecbf51ec5 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 @@ -426,103 +426,114 @@ input.umb-group-builder__group-title-input { .content-type-editor-dialog.edit-property-settings { - .validation-wrapper { - position: relative; - } - - .validation-label { - position: absolute; - top: 50%; - right: 0; - font-size: 12px; - color: @red; - transform: translate(0, -50%); - } - - textarea.editor-label { - border-color:transparent; - box-shadow: none; - width: 100%; - box-sizing: border-box; - margin-bottom: 0; - font-size: 16px; - font-weight: bold; - resize: none; - line-height: 1.5em; - padding-left: 0; - border: none; - &:focus { - outline: none; - box-shadow: none !important; + .validation-wrapper { + position: relative; } - } - .editor-placeholder { - border: 1px dashed @gray-8; - width: 100%; - height: 80px; - line-height: 80px; - text-align: center; - display: block; - border-radius: 5px; - color: @gray-3; - font-weight: bold; - font-size: 14px; - color: @turquoise-d1; - &:hover { - text-decoration: none; - } - } - - .editor { - margin-bottom: 10px; - .editor-icon-wrapper { - border: 1px solid @gray-8; - width: 60px; - height: 60px; - text-align: center; - line-height: 60px; - border-radius: 5px; - float: left; - margin-right: 20px; - .icon { - font-size: 26px; - } - } - .editor-details { - float: left; - margin-top: 10px; - .editor-name { - display: block; - font-weight: bold; - } - .editor-editor { - display: block; + .validation-label { + position: absolute; + top: 50%; + right: 0; font-size: 12px; - } + color: @red; + transform: translate(0, -50%); } - .editor-settings-icon { - font-size: 18px; - margin-top: 8px; - } - } - .checkbox { - margin-bottom: 20px; - } + textarea.editor-label { + border-color: transparent; + box-shadow: none; + width: 100%; + box-sizing: border-box; + margin-bottom: 0; + font-size: 16px; + font-weight: bold; + resize: none; + line-height: 1.5em; + padding-left: 0; + border: none; - .editor-description, - .editor-validation-pattern { - min-width: 100%; - min-height: 25px; - resize: none; - box-sizing: border-box; - border: none; - overflow: hidden; - } + &:focus { + outline: none; + box-shadow: none !important; + } + } - .umb-dropdown { - width: 100%; - } + .editor-placeholder { + border: 1px dashed @gray-8; + width: 100%; + height: 80px; + line-height: 80px; + text-align: center; + display: block; + border-radius: 5px; + color: @gray-3; + font-weight: bold; + font-size: 14px; + color: @turquoise-d1; + &:hover { + text-decoration: none; + } + } + + .editor { + margin-bottom: 10px; + + .editor-icon-wrapper { + border: 1px solid @gray-8; + width: 60px; + height: 60px; + text-align: center; + line-height: 60px; + border-radius: 5px; + float: left; + margin-right: 20px; + + .icon { + font-size: 26px; + } + } + + .editor-details { + float: left; + margin-top: 10px; + + .editor-name { + display: block; + font-weight: bold; + } + + .editor-editor { + display: block; + font-size: 12px; + } + } + + .editor-settings-icon { + font-size: 18px; + margin-top: 8px; + } + } + + .checkbox { + margin-bottom: 20px; + } + + .editor-description, + .editor-validation-pattern { + min-width: 100%; + min-height: 25px; + resize: none; + box-sizing: border-box; + border: none; + overflow: hidden; + } + + .umb-dropdown { + width: 100%; + } + + label.checkbox.no-indent { + width: 100%; + } } 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 3d16b76167..846b7d7aee 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 @@ -1,3 +1,10 @@ +.umb-list--condensed { + .umb-list-item { + padding-top: 5px; + padding-bottom: 5px; + } +} + .umb-list-item { border-bottom: 1px solid @gray-9; padding-top: 15px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less index 6d60e969a0..38e0b643a2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-load-indicator.less @@ -48,7 +48,7 @@ } 50% { transform: scale(1); - background: white; + background: transparent; } 100% { transform: scale(0.5); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less index cba4219b0c..4f3fd94bde 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less @@ -1,15 +1,3 @@ .umb-nav-tabs { - position: absolute; - z-index: 10; -} - -.umb-nav-tabs.-padding-left { - padding-left: 20px; -} - -.umb-tab-content { - padding-top: 20px; position: relative; - top: 22px; - border-top: 1px solid @purple-l3; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less index faed098fc8..54116e9093 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-permission.less @@ -4,6 +4,10 @@ padding: 7px 0; } +.umb-permission--disabled { + opacity: 0.8; +} + .umb-permission:last-of-type { border-bottom: none; } diff --git a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less index 9a8c55d08b..7a96c906e3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/healthcheck.less +++ b/src/Umbraco.Web.UI.Client/src/less/healthcheck.less @@ -24,17 +24,18 @@ display: flex; flex-wrap: wrap; flex-direction: column; - background: @gray-10; + background: @white; border-radius: 3px; - padding: 15px 10px; + padding: 20px; box-sizing: border-box; text-align: center; - border: 1px solid @gray-8; + box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); height: 100%; + box-sizing: border-box; } .umb-healthcheck-group:hover { - border: 1px solid @turquoise; + box-shadow: 0 3px 6px 0 rgba(0,0,0,0.16); cursor: pointer; } @@ -60,7 +61,7 @@ .umb-healthcheck-message { position: relative; - background: @white; + background: @gray-10; border-radius: 50px; display: inline-flex; align-items: center; @@ -125,6 +126,12 @@ margin-bottom: 40px; } +.umb-healthcheck-group__details-group { + background: @white; + box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); + border-radius: @baseBorderRadius; +} + .umb-healthcheck-group__details-group-title { background-color: @purple-l1; padding: 10px 20px; @@ -141,9 +148,9 @@ } .umb-healthcheck-group__details-checks { - border: 1px solid @gray-8; border-top: none; border-radius: 0 0 3px 3px; + background: @white; } .umb-healthcheck-group__details-check { @@ -152,14 +159,13 @@ .umb-healthcheck-group__details-check-title { padding: 15px 20px; - background-color: @gray-10; } .umb-healthcheck-group__details-check-name { font-size: 15px; color: @black; font-weight: bold; - margin-bottom: 5px; + margin-bottom: 3px; } .umb-healthcheck-group__details-check-description { @@ -171,7 +177,7 @@ .umb-healthcheck-group__details-status { padding: 15px 0; display: flex; - border-bottom: 2px solid @gray-10; + border-top: 2px solid @gray-10; } .umb-healthcheck-group__details-status-overlay { diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index d697053066..00afa49934 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -11,7 +11,7 @@ min-height: 100%; } .shadow { - box-shadow: 3px 0px 7px @gray-8; + box-shadow: 3px 0px 7px rgba(0,0,0,0.16); } .umb-scrollable, .umb-auto-overflow { diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 702f80a67f..426fcbcd79 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -7,10 +7,12 @@ } .umb-modalcolumn-header { - background: @gray-10; - border-bottom: 1px solid @purple-l3; - height: 94px; - padding: 5px 20px 0px 20px; + border-bottom: 1px solid @gray-9; + height: @editorHeaderHeight; + box-sizing: border-box; + padding: 0 20px; + display: flex; + align-items: center; white-space: nowrap } @@ -19,13 +21,12 @@ white-space: nowrap; font-size: @fontSizeLarge; font-weight: 400; - padding-top: 10px !important; } .umb-modalcolumn-body { padding: 0px; background: @white; - top: 100px; + top: @editorHeaderHeight; position: absolute; left: 0px; right: 0px; @@ -181,7 +182,7 @@ width: 640px !important; } .umb-modal i { - font-size: 14px; + font-size: 20px; } .umb-modal .breadcrumb { background: none; diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less index de73fdd5a7..0fa57669a5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/navs.less +++ b/src/Umbraco.Web.UI.Client/src/less/navs.less @@ -109,11 +109,7 @@ } .nav-tabs > li > a, .nav-pills > li > a { - padding-right: 8px; - padding-left: 8px; - margin-right: 2px; - line-height: 14px; // keeps the overall height an even number - border-radius: 3px 3px 0 0; + margin-right: 15px; } // TABS @@ -121,26 +117,24 @@ // Give the tabs something to sit on .nav-tabs { - border-bottom: 1px solid @purple-l3; + // border-bottom: 1px solid @gray-9; } // Make the list-items overlay the bottom border .nav-tabs > li { - margin-bottom: -1px; + // margin-bottom: -1px; } // Actual tabs (as links) .nav-tabs > li > a { color: @gray-3; - padding-top: 5px; - padding-bottom: 4px; - line-height: @baseLineHeight; - border: 1px solid transparent; + border-bottom: 2px solid transparent; + padding-bottom: 15px; &:hover { color: @black; } &:hover, &:focus { - border-color: transparent transparent @purple-l3; + // border-color: transparent transparent @purple-l3; } } // Active state, and it's :hover/:focus to override normal :hover/:focus @@ -148,9 +142,7 @@ .nav-tabs > .active > a:hover, .nav-tabs > .active > a:focus { color: @black; - background-color: @bodyBackground; - border: 1px solid @purple-l3; - border-bottom-color: transparent; + border-bottom-color: @turquoise; cursor: default; } @@ -175,10 +167,6 @@ .show-validation .nav-tabs > li.active.error > a:focus { } -.umb-nav-tabs { - margin: -8px 0 0 0; -} - // PILLS // ----- @@ -247,6 +235,8 @@ .dropdown-menu { border-radius: @dropdownBorderRadius; box-shadow: 0 5px 20px rgba(0,0,0,.3); + padding-top: 0; + padding-bottom: 0; } // fix dropdown with checkbox + long text in label 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 2fe214bf07..5443134dd2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -74,10 +74,6 @@ padding-right: 10px; } -.login-overlay .btn-success { - padding: 12px 24px; -} - .login-overlay .form label { font-weight: bold; } diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index 9b439c2d60..1596605762 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -428,10 +428,6 @@ font-size: 10px; } -.umb-panel-header .umb-nav-tabs { - bottom: -1px; -} - input.umb-panel-header-name-input.name-is-empty { border: 1px dashed @gray-8; background: @white; 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 cd5427fe96..759d51858c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -40,6 +40,16 @@ padding: 10px; } +.umb-contentpicker__min-max-help { + font-size: 13px; + margin-top: 5px; + color: @gray-4; +} + +.show-validation .umb-contentpicker__min-max-help { + display: none; +} + .umb-contentpicker small { &:not(:last-child) { @@ -203,10 +213,22 @@ ul.color-picker li a { -.umb-thumbnails{ - position: relative; +.umb-thumbnails { + position: relative; + display: flex; + -ms-flex-direction: row; + -webkit-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: wrap; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + justify-content: flex-start; } +.umb-thumbnails > li.icon { + width: 14%; + text-align: center; +} .umb-thumbnails i{margin: auto;} .umb-thumbnails a{ @@ -243,7 +265,7 @@ ul.color-picker li a { .umb-mediapicker .umb-sortable-thumbnails li { flex-direction: column; - margin: 0 5px 0 0; + margin: 0 5px 5px 0; padding: 5px; } @@ -282,8 +304,8 @@ ul.color-picker li a { } .umb-sortable-thumbnails .umb-sortable-thumbnails__wrapper { - width: 120px; - height: 114px; + width: 124px; + height: 124px; overflow: hidden; } @@ -298,6 +320,12 @@ ul.color-picker li a { visibility: hidden; } +.umb-sortable-thumbnails.ui-sortable:not(.ui-sortable-disabled) { + > li:not(.unsortable) { + cursor: move; + } +} + .umb-sortable-thumbnails li:hover .umb-sortable-thumbnails__actions { opacity: 1; visibility: visible; diff --git a/src/Umbraco.Web.UI.Client/src/less/tables.less b/src/Umbraco.Web.UI.Client/src/less/tables.less index 3c63785957..7317d93d4a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tables.less +++ b/src/Umbraco.Web.UI.Client/src/less/tables.less @@ -13,22 +13,20 @@ table { border-spacing: 0; } -table thead { - background-color: @gray-10; -} - - // BASELINE STYLES // --------------- .table { width: 100%; margin-bottom: @baseLineHeight; - border: 1px solid @gray-8; + box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); + background: @tableBackground; + border-radius: @baseBorderRadius; + font-size: 14px; // Cells th, td { - padding: 10px 8px; + padding: 10px 20px; line-height: @baseLineHeight; text-align: left; border-top: 1px solid @tableBorder; @@ -38,6 +36,9 @@ table thead { } // Bottom align for column headings thead th { + padding-top: 15px; + padding-bottom: 15px; + color: @gray-3; vertical-align: bottom; } // Remove top border from thead by default @@ -81,6 +82,7 @@ table thead { border-collapse: separate; // Done so we can round those corners! *border-collapse: collapse; // IE7 can't round corners anyway border-left: 0; + box-shadow: none; .border-radius(@baseBorderRadius); th, td { diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 0c93ad027b..eae5799459 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -100,6 +100,27 @@ .color-green, .color-green i{color: @green-d1 !important;} .color-yellow, .color-yellow i{color: @yellow-d1 !important;} +/* Colors based on http://zavoloklom.github.io/material-design-color-palette/colors.html */ +.color-black, .color-black i { color: #000 !important; } +.color-blue-grey, .color-blue-grey i { color: #607d8b !important; } +.color-grey, .color-grey i { color: #9e9e9e !important; } +.color-brown, .color-brown i { color: #795548 !important; } +.color-blue, .color-blue i { color: #2196f3 !important; } +.color-light-blue, .color-light-blue i {color: #03a9f4 !important; } +.color-cyan, .color-cyan i { color: #00bcd4 !important; } +.color-green, .color-green i { color: #4caf50 !important; } +.color-light-green, .color-light-green i {color: #8bc34a !important; } +.color-lime, .color-lime i { color: #cddc39 !important; } +.color-yellow, .color-yellow i { color: #ffeb3b !important; } +.color-amber, .color-amber i { color: #ffc107 !important; } +.color-orange, .color-orange i { color: #ff9800 !important; } +.color-deep-orange, .color-deep-orange i { color: #ff5722 !important; } +.color-red, .color-red i { color: #f44336 !important; } +.color-pink, .color-pink i { color: #e91e63 !important; } +.color-purple,.color-purple i { color: #9c27b0 !important; } +.color-deep-purple, .color-deep-purple i { color: #673ab7 !important; } +.color-indigo, .color-indigo i { color: #3f51b5 !important; } + // Scaffolding // ------------------------- @@ -154,10 +175,10 @@ // Tables // ------------------------- -@tableBackground: transparent; // overall background-color +@tableBackground: @white; // overall background-color @tableBackgroundAccent: @gray-10; // for striping @tableBackgroundHover: @gray-10; // for hover -@tableBorder: @gray-8; // table and cell border +@tableBorder: @gray-9; // table and cell border // Buttons // ------------------------- @@ -231,6 +252,7 @@ // ------------------------- // Used for a bird's eye view of components dependent on the z-axis // Try to avoid customizing these :) +@zIndexTree: 100; @zindexDropdown: 1000; @zindexPopover: 1010; @zindexTooltip: 1030; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html index 3136bc28e7..7925db7000 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html @@ -1,51 +1,41 @@
- + - + - +
- - +
+ +
- +
- - + + + +
+ +
+

{{property.caption}}

+
+
+ +
+

{{property.caption}}

+ +
+ +
+ +
+
-
+
-
-

{{property.caption}}

-
-
+
-
-

{{property.caption}}

- - -
- -
- - - - - - -
- -  -
+ + +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js index 95a39e04aa..5e5363ae6c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js @@ -1,6 +1,6 @@ angular.module("umbraco") .controller("Umbraco.Dialogs.ApprovedColorPickerController", function ($scope, $http, umbPropEditorHelper, assetsService) { - assetsService.loadJs("lib/cssparser/cssparser.js") + assetsService.loadJs("lib/cssparser/cssparser.js", $scope) .then(function () { var cssPath = $scope.dialogData.cssPath; @@ -19,7 +19,7 @@ angular.module("umbraco") $scope.classes.splice(0, 0, "noclass"); }) - assetsService.loadCss("/App_Plugins/Lecoati.uSky.Grid/lib/uSky.Grid.ApprovedColorPicker.css"); - assetsService.loadCss(cssPath); + assetsService.loadCss("/App_Plugins/Lecoati.uSky.Grid/lib/uSky.Grid.ApprovedColorPicker.css", $scope); + assetsService.loadCss(cssPath, $scope); }); -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js index 04e926742f..c4aa39930d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/content/edit.controller.js @@ -7,7 +7,6 @@ function ContentEditDialogController($scope, editorState, $routeParams, $q, $tim // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { contentEditingHelper.contentEditorPerformSave({ - statusMessage: args.statusMessage, saveMethod: args.saveMethod, scope: $scope, content: $scope.content @@ -74,15 +73,15 @@ function ContentEditDialogController($scope, editorState, $routeParams, $q, $tim } $scope.sendToPublish = function () { - performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending..." }); + performSave({ saveMethod: contentResource.sendToPublish }); }; $scope.saveAndPublish = function () { - performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing..." }); + performSave({ saveMethod: contentResource.publish }); }; $scope.save = function () { - performSave({ saveMethod: contentResource.save, statusMessage: "Saving..." }); + performSave({ saveMethod: contentResource.save }); }; // this method is called for all action buttons and then we proxy based on the btn definition @@ -101,4 +100,4 @@ function ContentEditDialogController($scope, editorState, $routeParams, $q, $tim angular.module("umbraco") - .controller("Umbraco.Dialogs.Content.EditController", ContentEditDialogController); \ No newline at end of file + .controller("Umbraco.Dialogs.Content.EditController", ContentEditDialogController); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.controller.js index ec1ad6e663..7f7eed8e4c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.controller.js @@ -7,14 +7,13 @@ angular.module("umbraco") $scope.icons = icons; }); - $scope.submitClass = function (icon) { + $scope.submitClass = function(icon){ if($scope.color) { $scope.submit(icon + " " + $scope.color); } - else { - $scope.submit(icon); + else { + $scope.submit(icon); } }; - } ); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index 34dbf90de6..62fc5779c7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -1,403 +1,412 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", - function ($scope, $cookies, $location, currentUserResource, formHelper, mediaHelper, umbRequestHelper, Upload, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService, $q) { + function ($scope, $cookies, $location, currentUserResource, formHelper, mediaHelper, umbRequestHelper, Upload, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService, $q) { - $scope.invitedUser = null; - $scope.invitedUserPasswordModel = { - password: "", - confirmPassword: "", - buttonState: "", - passwordPolicies: null, - passwordPolicyText: "" - } - $scope.avatarFile = { - filesHolder: null, - uploadStatus: null, - uploadProgress: 0, - maxFileSize: Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB", - acceptedFileTypes: mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes), - uploaded: false - } - $scope.togglePassword = function () { - var elem = $("form[name='loginForm'] input[name='password']"); - elem.attr("type", (elem.attr("type") === "text" ? "password" : "text")); - } + $scope.invitedUser = null; + $scope.invitedUserPasswordModel = { + password: "", + confirmPassword: "", + buttonState: "", + passwordPolicies: null, + passwordPolicyText: "" + } + $scope.loginStates = { + submitButton: "init" + } + $scope.avatarFile = { + filesHolder: null, + uploadStatus: null, + uploadProgress: 0, + maxFileSize: Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB", + acceptedFileTypes: mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes), + uploaded: false + } + $scope.togglePassword = function () { + var elem = $("form[name='loginForm'] input[name='password']"); + elem.attr("type", (elem.attr("type") === "text" ? "password" : "text")); + } - function init() { - // Check if it is a new user - var inviteVal = $location.search().invite; - if (inviteVal && (inviteVal === "1" || inviteVal === "2")) { + function init() { + // Check if it is a new user + var inviteVal = $location.search().invite; + if (inviteVal && (inviteVal === "1" || inviteVal === "2")) { - $q.all([ - //get the current invite user - authResource.getCurrentInvitedUser().then(function (data) { - $scope.invitedUser = data; - }, - function() { - //it failed so we should remove the search - $location.search('invite', null); - }), - //get the membership provider config for password policies - authResource.getMembershipProviderConfig().then(function (data) { - $scope.invitedUserPasswordModel.passwordPolicies = data; + $q.all([ + //get the current invite user + authResource.getCurrentInvitedUser().then(function (data) { + $scope.invitedUser = data; + }, + function () { + //it failed so we should remove the search + $location.search('invite', null); + }), + //get the membership provider config for password policies + authResource.getMembershipProviderConfig().then(function (data) { + $scope.invitedUserPasswordModel.passwordPolicies = data; - //localize the text - localizationService.localize("errorHandling_errorInPasswordFormat", - [ - $scope.invitedUserPasswordModel.passwordPolicies.minPasswordLength, - $scope.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars - ]).then(function(data) { - $scope.invitedUserPasswordModel.passwordPolicyText = data; + //localize the text + localizationService.localize("errorHandling_errorInPasswordFormat", + [ + $scope.invitedUserPasswordModel.passwordPolicies.minPasswordLength, + $scope.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars + ]).then(function (data) { + $scope.invitedUserPasswordModel.passwordPolicyText = data; + }); + }) + ]).then(function () { + + $scope.inviteStep = Number(inviteVal); + + }); + } + } + + $scope.changeAvatar = function (files, event) { + if (files && files.length > 0) { + upload(files[0]); + } + }; + + $scope.getStarted = function () { + $location.search('invite', null); + $scope.submit(true); + } + + function upload(file) { + + $scope.avatarFile.uploadProgress = 0; + + Upload.upload({ + url: umbRequestHelper.getApiUrl("currentUserApiBaseUrl", "PostSetAvatar"), + fields: {}, + file: file + }).progress(function (evt) { + + if ($scope.avatarFile.uploadStatus !== "done" && $scope.avatarFile.uploadStatus !== "error") { + // set uploading status on file + $scope.avatarFile.uploadStatus = "uploading"; + + // calculate progress in percentage + var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); + + // set percentage property on file + $scope.avatarFile.uploadProgress = progressPercentage; + } + + }).success(function (data, status, headers, config) { + + $scope.avatarFile.uploadProgress = 100; + + // set done status on file + $scope.avatarFile.uploadStatus = "done"; + + $scope.invitedUser.avatars = data; + + $scope.avatarFile.uploaded = true; + + }).error(function (evt, status, headers, config) { + + // set status done + $scope.avatarFile.uploadStatus = "error"; + + // If file not found, server will return a 404 and display this message + if (status === 404) { + $scope.avatarFile.serverErrorMessage = "File not found"; + } + else if (status == 400) { + //it's a validation error + $scope.avatarFile.serverErrorMessage = evt.message; + } + else { + //it's an unhandled error + //if the service returns a detailed error + if (evt.InnerException) { + $scope.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage; + + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { + $scope.avatarFile.serverErrorMessage = "File too large to upload"; + } + + } else if (evt.Message) { + $scope.avatarFile.serverErrorMessage = evt.Message; + } + } }); - }) - ]).then(function () { - - $scope.inviteStep = Number(inviteVal); - - }); - } - } - - $scope.changeAvatar = function (files, event) { - if (files && files.length > 0) { - upload(files[0]); - } - }; - - $scope.getStarted = function() { - $location.search('invite', null); - $scope.submit(true); - } - - function upload(file) { - - $scope.avatarFile.uploadProgress = 0; - - Upload.upload({ - url: umbRequestHelper.getApiUrl("currentUserApiBaseUrl", "PostSetAvatar"), - fields: {}, - file: file - }).progress(function (evt) { - - if ($scope.avatarFile.uploadStatus !== "done" && $scope.avatarFile.uploadStatus !== "error") { - // set uploading status on file - $scope.avatarFile.uploadStatus = "uploading"; - - // calculate progress in percentage - var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); - - // set percentage property on file - $scope.avatarFile.uploadProgress = progressPercentage; } - }).success(function (data, status, headers, config) { + $scope.inviteSavePassword = function () { - $scope.avatarFile.uploadProgress = 100; + if (formHelper.submitForm({ scope: $scope })) { - // set done status on file - $scope.avatarFile.uploadStatus = "done"; + $scope.invitedUserPasswordModel.buttonState = "busy"; - $scope.invitedUser.avatars = data; + currentUserResource.performSetInvitedUserPassword($scope.invitedUserPasswordModel.password) + .then(function (data) { - $scope.avatarFile.uploaded = true; + //success + formHelper.resetForm({ scope: $scope, notifications: data.notifications }); + $scope.invitedUserPasswordModel.buttonState = "success"; + //set the user and set them as logged in + $scope.invitedUser = data; + userService.setAuthenticationSuccessful(data); - }).error(function (evt, status, headers, config) { + $scope.inviteStep = 2; - // set status done - $scope.avatarFile.uploadStatus = "error"; + }, function (err) { - // If file not found, server will return a 404 and display this message - if (status === 404) { - $scope.avatarFile.serverErrorMessage = "File not found"; + //error + formHelper.handleError(err); + + $scope.invitedUserPasswordModel.buttonState = "error"; + + }); + } + }; + + var setFieldFocus = function (form, field) { + $timeout(function () { + $("form[name='" + form + "'] input[name='" + field + "']").focus(); + }); } - else if (status == 400) { - //it's a validation error - $scope.avatarFile.serverErrorMessage = evt.message; - } - else { - //it's an unhandled error - //if the service returns a detailed error - if (evt.InnerException) { - $scope.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage; - //Check if its the common "too large file" exception - if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { - $scope.avatarFile.serverErrorMessage = "File too large to upload"; + var twoFactorloginDialog = null; + function show2FALoginDialog(view, callback) { + if (!twoFactorloginDialog) { + twoFactorloginDialog = dialogService.open({ + + //very special flag which means that global events cannot close this dialog + manualClose: true, + template: view, + modalClass: "login-overlay", + animation: "slide", + show: true, + callback: callback, + + }); + } + } + + function resetInputValidation() { + $scope.confirmPassword = ""; + $scope.password = ""; + $scope.login = ""; + if ($scope.loginForm) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + if ($scope.requestPasswordResetForm) { + $scope.requestPasswordResetForm.email.$setValidity("auth", true); + } + if ($scope.setPasswordForm) { + $scope.setPasswordForm.password.$setValidity('auth', true); + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + } + + $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; + + $scope.showLogin = function () { + $scope.errorMsg = ""; + resetInputValidation(); + $scope.view = "login"; + setFieldFocus("loginForm", "username"); + } + + $scope.showRequestPasswordReset = function () { + $scope.errorMsg = ""; + resetInputValidation(); + $scope.view = "request-password-reset"; + $scope.showEmailResetConfirmation = false; + setFieldFocus("requestPasswordResetForm", "email"); + } + + $scope.showSetPassword = function () { + $scope.errorMsg = ""; + resetInputValidation(); + $scope.view = "set-password"; + setFieldFocus("setPasswordForm", "password"); + } + + var d = new Date(); + var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday"); + var konamiMode = $cookies.konamiLogin; + if (konamiMode == "1") { + $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; + } else { + localizationService.localize("login_greeting" + d.getDay()).then(function (label) { + $scope.greeting = label; + }); // weekday[d.getDay()]; + } + $scope.errorMsg = ""; + + $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl; + $scope.externalLoginProviders = externalLoginInfo.providers; + $scope.externalLoginInfo = externalLoginInfo; + $scope.resetPasswordCodeInfo = resetPasswordCodeInfo; + $scope.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage; + + $scope.activateKonamiMode = function () { + if ($cookies.konamiLogin == "1") { + // somehow I can't update the cookie value using $cookies, so going native + document.cookie = "konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; + document.location.reload(); + } else { + document.cookie = "konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;"; + $scope.$apply(function () { + $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; + }); + } + } + + $scope.loginSubmit = function (login, password) { + + //TODO: Do validation properly like in the invite password update + + //if the login and password are not empty we need to automatically + // validate them - this is because if there are validation errors on the server + // then the user has to change both username & password to resubmit which isn't ideal, + // so if they're not empty, we'll just make sure to set them to valid. + if (login && password && login.length > 0 && password.length > 0) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); } - } else if (evt.Message) { - $scope.avatarFile.serverErrorMessage = evt.Message; - } + if ($scope.loginForm.$invalid) { + return; + } + + $scope.loginStates.submitButton = "busy"; + + userService.authenticate(login, password) + .then(function (data) { + $scope.loginStates.submitButton = "success"; + $scope.submit(true); + }, + function (reason) { + + //is Two Factor required? + if (reason.status === 402) { + $scope.errorMsg = "Additional authentication required"; + show2FALoginDialog(reason.data.twoFactorView, $scope.submit); + } + else { + $scope.loginStates.submitButton = "error"; + $scope.errorMsg = reason.errorMsg; + + //set the form inputs to invalid + $scope.loginForm.username.$setValidity("auth", false); + $scope.loginForm.password.$setValidity("auth", false); + } + }); + + //setup a watch for both of the model values changing, if they change + // while the form is invalid, then revalidate them so that the form can + // be submitted again. + $scope.loginForm.username.$viewChangeListeners.push(function () { + if ($scope.loginForm.$invalid) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + }); + $scope.loginForm.password.$viewChangeListeners.push(function () { + if ($scope.loginForm.$invalid) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + }); + }; + + $scope.requestPasswordResetSubmit = function (email) { + + //TODO: Do validation properly like in the invite password update + + if (email && email.length > 0) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + + $scope.showEmailResetConfirmation = false; + + if ($scope.requestPasswordResetForm.$invalid) { + return; + } + + $scope.errorMsg = ""; + + authResource.performRequestPasswordReset(email) + .then(function () { + //remove the email entered + $scope.email = ""; + $scope.showEmailResetConfirmation = true; + }, function (reason) { + $scope.errorMsg = reason.errorMsg; + $scope.requestPasswordResetForm.email.$setValidity("auth", false); + }); + + $scope.requestPasswordResetForm.email.$viewChangeListeners.push(function () { + if ($scope.requestPasswordResetForm.email.$invalid) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + }); + }; + + $scope.setPasswordSubmit = function (password, confirmPassword) { + + $scope.showSetPasswordConfirmation = false; + + if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) { + $scope.setPasswordForm.password.$setValidity('auth', true); + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + + if ($scope.setPasswordForm.$invalid) { + return; + } + + //TODO: All of this logic can/should be shared! We should do validation the nice way instead of all of this manual stuff, see: inviteSavePassword + authResource.performSetPassword($scope.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, $scope.resetPasswordCodeInfo.resetCodeModel.resetCode) + .then(function () { + $scope.showSetPasswordConfirmation = true; + $scope.resetComplete = true; + + //reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again + resetPasswordCodeInfo.resetCodeModel = null; + + }, function (reason) { + if (reason.data && reason.data.Message) { + $scope.errorMsg = reason.data.Message; + } + else { + $scope.errorMsg = reason.errorMsg; + } + $scope.setPasswordForm.password.$setValidity("auth", false); + $scope.setPasswordForm.confirmPassword.$setValidity("auth", false); + }); + + $scope.setPasswordForm.password.$viewChangeListeners.push(function () { + if ($scope.setPasswordForm.password.$invalid) { + $scope.setPasswordForm.password.$setValidity('auth', true); + } + }); + $scope.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () { + if ($scope.setPasswordForm.confirmPassword.$invalid) { + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + }); } - }); - } - $scope.inviteSavePassword = function () { - - if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { - - $scope.invitedUserPasswordModel.buttonState = "busy"; - currentUserResource.performSetInvitedUserPassword($scope.invitedUserPasswordModel.password) - .then(function (data) { + //Now, show the correct panel: - //success - formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - $scope.invitedUserPasswordModel.buttonState = "success"; - //set the user and set them as logged in - $scope.invitedUser = data; - userService.setAuthenticationSuccessful(data); - - $scope.inviteStep = 2; - - }, function(err) { - - //error - formHelper.handleError(err); - - $scope.invitedUserPasswordModel.buttonState = "error"; - - }); - } - }; - - var setFieldFocus = function (form, field) { - $timeout(function () { - $("form[name='" + form + "'] input[name='" + field + "']").focus(); - }); - } - - var twoFactorloginDialog = null; - function show2FALoginDialog(view, callback) { - if (!twoFactorloginDialog) { - twoFactorloginDialog = dialogService.open({ - - //very special flag which means that global events cannot close this dialog - manualClose: true, - template: view, - modalClass: "login-overlay", - animation: "slide", - show: true, - callback: callback, - - }); - } - } - - function resetInputValidation() { - $scope.confirmPassword = ""; - $scope.password = ""; - $scope.login = ""; - if ($scope.loginForm) { - $scope.loginForm.username.$setValidity('auth', true); - $scope.loginForm.password.$setValidity('auth', true); - } - if ($scope.requestPasswordResetForm) { - $scope.requestPasswordResetForm.email.$setValidity("auth", true); - } - if ($scope.setPasswordForm) { - $scope.setPasswordForm.password.$setValidity('auth', true); - $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); - } - } - - $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; - - $scope.showLogin = function () { - $scope.errorMsg = ""; - resetInputValidation(); - $scope.view = "login"; - setFieldFocus("loginForm", "username"); - } - - $scope.showRequestPasswordReset = function () { - $scope.errorMsg = ""; - resetInputValidation(); - $scope.view = "request-password-reset"; - $scope.showEmailResetConfirmation = false; - setFieldFocus("requestPasswordResetForm", "email"); - } - - $scope.showSetPassword = function () { - $scope.errorMsg = ""; - resetInputValidation(); - $scope.view = "set-password"; - setFieldFocus("setPasswordForm", "password"); - } - - var d = new Date(); - var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday"); - var konamiMode = $cookies.konamiLogin; - if (konamiMode == "1") { - $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; - } else { - localizationService.localize("login_greeting" + d.getDay()).then(function (label) { - $scope.greeting = label; - }); // weekday[d.getDay()]; - } - $scope.errorMsg = ""; - - $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl; - $scope.externalLoginProviders = externalLoginInfo.providers; - $scope.externalLoginInfo = externalLoginInfo; - $scope.resetPasswordCodeInfo = resetPasswordCodeInfo; - $scope.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage; - - $scope.activateKonamiMode = function () { - if ($cookies.konamiLogin == "1") { - // somehow I can't update the cookie value using $cookies, so going native - document.cookie = "konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; - document.location.reload(); - } else { - document.cookie = "konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;"; - $scope.$apply(function () { - $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; - }); - } - } - - $scope.loginSubmit = function (login, password) { - - //TODO: Do validation properly like in the invite password update - - //if the login and password are not empty we need to automatically - // validate them - this is because if there are validation errors on the server - // then the user has to change both username & password to resubmit which isn't ideal, - // so if they're not empty, we'll just make sure to set them to valid. - if (login && password && login.length > 0 && password.length > 0) { - $scope.loginForm.username.$setValidity('auth', true); - $scope.loginForm.password.$setValidity('auth', true); - } - - if ($scope.loginForm.$invalid) { - return; - } - - userService.authenticate(login, password) - .then(function (data) { - $scope.submit(true); - }, - function (reason) { - - //is Two Factor required? - if (reason.status === 402) { - $scope.errorMsg = "Additional authentication required"; - show2FALoginDialog(reason.data.twoFactorView, $scope.submit); - } - else { - $scope.errorMsg = reason.errorMsg; - - //set the form inputs to invalid - $scope.loginForm.username.$setValidity("auth", false); - $scope.loginForm.password.$setValidity("auth", false); - } - }); - - //setup a watch for both of the model values changing, if they change - // while the form is invalid, then revalidate them so that the form can - // be submitted again. - $scope.loginForm.username.$viewChangeListeners.push(function () { - if ($scope.loginForm.username.$invalid) { - $scope.loginForm.username.$setValidity('auth', true); + if ($scope.resetPasswordCodeInfo.resetCodeModel) { + $scope.showSetPassword(); } - }); - $scope.loginForm.password.$viewChangeListeners.push(function () { - if ($scope.loginForm.password.$invalid) { - $scope.loginForm.password.$setValidity('auth', true); + else if ($scope.resetPasswordCodeInfo.errors.length > 0) { + $scope.view = "password-reset-code-expired"; } - }); - }; - - $scope.requestPasswordResetSubmit = function (email) { - - //TODO: Do validation properly like in the invite password update - - if (email && email.length > 0) { - $scope.requestPasswordResetForm.email.$setValidity('auth', true); - } - - $scope.showEmailResetConfirmation = false; - - if ($scope.requestPasswordResetForm.$invalid) { - return; - } - - $scope.errorMsg = ""; - - authResource.performRequestPasswordReset(email) - .then(function () { - //remove the email entered - $scope.email = ""; - $scope.showEmailResetConfirmation = true; - }, function (reason) { - $scope.errorMsg = reason.errorMsg; - $scope.requestPasswordResetForm.email.$setValidity("auth", false); - }); - - $scope.requestPasswordResetForm.email.$viewChangeListeners.push(function () { - if ($scope.requestPasswordResetForm.email.$invalid) { - $scope.requestPasswordResetForm.email.$setValidity('auth', true); + else { + $scope.showLogin(); } - }); - }; - $scope.setPasswordSubmit = function (password, confirmPassword) { + init(); - $scope.showSetPasswordConfirmation = false; - - if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) { - $scope.setPasswordForm.password.$setValidity('auth', true); - $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); - } - - if ($scope.setPasswordForm.$invalid) { - return; - } - - //TODO: All of this logic can/should be shared! We should do validation the nice way instead of all of this manual stuff, see: inviteSavePassword - authResource.performSetPassword($scope.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, $scope.resetPasswordCodeInfo.resetCodeModel.resetCode) - .then(function () { - $scope.showSetPasswordConfirmation = true; - $scope.resetComplete = true; - - //reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again - resetPasswordCodeInfo.resetCodeModel = null; - - }, function (reason) { - if (reason.data && reason.data.Message) { - $scope.errorMsg = reason.data.Message; - } - else { - $scope.errorMsg = reason.errorMsg; - } - $scope.setPasswordForm.password.$setValidity("auth", false); - $scope.setPasswordForm.confirmPassword.$setValidity("auth", false); - }); - - $scope.setPasswordForm.password.$viewChangeListeners.push(function () { - if ($scope.setPasswordForm.password.$invalid) { - $scope.setPasswordForm.password.$setValidity('auth', true); - } - }); - $scope.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () { - if ($scope.setPasswordForm.confirmPassword.$invalid) { - $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); - } - }); - } - - - //Now, show the correct panel: - - if ($scope.resetPasswordCodeInfo.resetCodeModel) { - $scope.showSetPassword(); - } - else if ($scope.resetPasswordCodeInfo.errors.length > 0) { - $scope.view = "password-reset-code-expired"; - } - else { - $scope.showLogin(); - } - - init(); - - }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 301d5e1087..3fc4698565 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -1,244 +1,249 @@ 
-
+
- + - - -
@@ -35,7 +34,7 @@ var docWidth = jQuery(document).width(); jQuery("iframe").height(docHeight - 140).width((docWidth / 2) - 31); - }); + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/tree.aspx b/src/Umbraco.Web.UI/umbraco/tree.aspx deleted file mode 100644 index 3434b1eda5..0000000000 --- a/src/Umbraco.Web.UI/umbraco/tree.aspx +++ /dev/null @@ -1 +0,0 @@ -<%@ Page language="c#" Codebehind="tree.aspx.cs" AutoEventWireup="True" Inherits="umbraco.cms.presentation.tree" trace="false" ContentType="text/xml" ResponseEncoding="UTF-8" %> diff --git a/src/Umbraco.Web.UI/umbraco/treeInit.aspx b/src/Umbraco.Web.UI/umbraco/treeInit.aspx index 2f6d6ca494..14677d7638 100644 --- a/src/Umbraco.Web.UI/umbraco/treeInit.aspx +++ b/src/Umbraco.Web.UI/umbraco/treeInit.aspx @@ -1,7 +1,7 @@ <%@ Page Language="c#" CodeBehind="TreeInit.aspx.cs" AutoEventWireup="True" Inherits="Umbraco.Web.UI.Umbraco.TreeInit" %> <%@ Register Src="controls/Tree/TreeControl.ascx" TagName="TreeControl" TagPrefix="umbraco" %> <%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> +<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web.UI.JavaScript" Assembly="Umbraco.Web" %> diff --git a/src/Umbraco.Web.UI/umbraco/users/EditUser.aspx b/src/Umbraco.Web.UI/umbraco/users/EditUser.aspx new file mode 100644 index 0000000000..dc202b6642 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/users/EditUser.aspx @@ -0,0 +1,14 @@ +<%@ Register Namespace="umbraco" TagPrefix="umb" Assembly="umbraco" %> +<%@ Page Language="c#" Codebehind="EditUser.aspx.cs" MasterPageFile="../masterpages/umbracoPage.Master" AutoEventWireup="True" Inherits="Umbraco.Web.UI.Umbraco.Users.EditUser" %> +<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> + +<%@ Import Namespace="umbraco.cms.presentation.Trees" %> + + + + + diff --git a/src/Umbraco.Web.UI/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web.UI/umbraco/users/EditUser.aspx.cs new file mode 100644 index 0000000000..ce322501ce --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/users/EditUser.aspx.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Umbraco.Web.UI.Umbraco.Users +{ + public partial class EditUser : global::umbraco.cms.presentation.user.EditUser + { + } +} diff --git a/src/Umbraco.Web.UI/umbraco/users/EditUser.aspx.designer.cs b/src/Umbraco.Web.UI/umbraco/users/EditUser.aspx.designer.cs new file mode 100644 index 0000000000..f386114ed0 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/users/EditUser.aspx.designer.cs @@ -0,0 +1,15 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Umbraco.Web.UI.Umbraco.Users { + + + public partial class EditUser { + } +} diff --git a/src/Umbraco.Web.UI/umbraco/users/EditUserType.aspx b/src/Umbraco.Web.UI/umbraco/users/EditUserType.aspx new file mode 100644 index 0000000000..d8671f0596 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/users/EditUserType.aspx @@ -0,0 +1,27 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="EditUserType.aspx.cs" MasterPageFile="../masterpages/umbracoPage.Master" + Inherits="umbraco.cms.presentation.user.EditUserType" %> + +<%@ Register TagPrefix="cc2" Namespace="umbraco.uicontrols" Assembly="controls" %> + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/umbraco/users/NodePermissions.ascx b/src/Umbraco.Web.UI/umbraco/users/NodePermissions.ascx new file mode 100644 index 0000000000..694b3e9ee4 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/users/NodePermissions.ascx @@ -0,0 +1,35 @@ +<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="NodePermissions.ascx.cs" Inherits="umbraco.cms.presentation.user.NodePermissions" %> +

+ <%=Services.TextService.Localize("user/permissionSelectedPages")%> +
+ +

+ + +

+ + + + +

+
+ + + + +
    + + +
  • + /> + +
  • +
    + +
+ +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/users/PermissionEditor.aspx b/src/Umbraco.Web.UI/umbraco/users/PermissionEditor.aspx new file mode 100644 index 0000000000..75b5d6e66a --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/users/PermissionEditor.aspx @@ -0,0 +1,47 @@ +<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="../masterpages/umbracoPage.Master" CodeBehind="PermissionEditor.aspx.cs" Inherits="umbraco.cms.presentation.user.PermissionEditor" %> +<%@ Import Namespace="Umbraco.Web" %> +<%@ Register Src="../controls/Tree/TreeControl.ascx" TagName="TreeControl" TagPrefix="umbraco" %> +<%@ Register Src="NodePermissions.ascx" TagName="NodePermissions" TagPrefix="user" %> +<%@ Register TagPrefix="ui" Namespace="umbraco.uicontrols" Assembly="controls" %> +<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> + + + + + + + + + + + +
+ +
+ +
+ +
+ + + +
+
+ + +
diff --git a/src/Umbraco.Web.UI/umbraco/users/PermissionsEditor.js b/src/Umbraco.Web.UI/umbraco/users/PermissionsEditor.js new file mode 100644 index 0000000000..c193b24394 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/users/PermissionsEditor.js @@ -0,0 +1,144 @@ +/// +/// +/// +/// + +Umbraco.Sys.registerNamespace("Umbraco.Controls"); + +(function($) { + $.fn.PermissionsEditor = function(opts) { + return this.each(function() { + var conf = $.extend({ + userId: -1, + pPanelSelector: "", + replacePChkBoxSelector: "" + }, opts); + new Umbraco.Controls.PermissionsEditor().init($(this), conf.userId, $(conf.pPanelSelector), conf.replacePChkBoxSelector); + }); + }; + $.fn.PermissionsEditorAPI = function() { + /// exposes the Permissions Editor API for the selected object + return $(this).data("PermissionsEditor") == null ? null : $(this).data("PermissionsEditor"); + } + Umbraco.Controls.PermissionsEditor = function() { + /// + /// A class to perform the AJAX callback methods for the PermissionsEditor + /// + return { + //private members + _userID: -1, + _loadingContent: '







', + _pPanel: null, + _tree: null, + _selectedNodes: new Array(), + _chkReplaceSelector: null, + + init: function(tree, uId, pPanel, chkReplaceSelector) { + ///constructor function + this._userID = parseInt(uId); + this._pPanel = pPanel; + this._tree = tree; + this._chkReplaceSelector = chkReplaceSelector; + + //store the api object + tree.data("PermissionsEditor", this); + + //bind the nodeClicked event + var _this = this; + var api = this._tree.UmbracoTreeAPI(); + api.addEventHandler("nodeClicked", function(e, n) { _this.treeNodeChecked(e, n) }); + }, + + //public methods + treeNodeChecked: function(e, data) { + var vals = ""; + var nodeId = $(data).attr("id"); + this._tree.find("li a.checked").each(function() { + var cNodeId = $(this).parent("li").attr("id"); + //if the check box is not the one thats just been checked, add it + if (cNodeId != nodeId && parseInt(cNodeId) > 0) { + vals += cNodeId + ","; + } + }); + this._selectedNodes = vals.split(","); + this._selectedNodes.pop(); //remove the last one as it will be empty + //add the one that was just checked to the end of the array if + if ($(data).children("a").hasClass("checked")) { + this._selectedNodes.push(nodeId); + } + if (this._selectedNodes.length > 0) { + this._beginShowNodePermissions(this._selectedNodes.join(",")); + this._pPanel.show(); + } + else { + this._pPanel.hide(); + } + }, + setReplaceChild: function(doReplace) { + alert(doReplace); + this._replaceChildren = doReplace; + }, + beginSavePermissions: function() { + + //ensure that there are nodes selected to save permissions against + if (this._selectedNodes.length == 0) { + alert("No nodes have been selected"); + return; + } + else if (!confirm("Permissions will be changed for nodes: " + this._selectedNodes.join(",") + ". Are you sure?")) { + return; + } + + //get the list of checked permissions + var checkedPermissions = ""; + this._pPanel.find("input:checked").each(function() { + checkedPermissions += $(this).val(); + }); + + var replaceChildren = $(this._chkReplaceSelector).is(":checked"); + + this._setUpdateProgress(); + var _this = this; + setTimeout(function() { _this._savePermissions(_this._selectedNodes.join(","), checkedPermissions, replaceChildren); }, 10); + }, + + //private methods + _addItemToSelectedCollection: function(chkbox) { + if (chkbox.checked) + this._selectedNodes.push(chkbox.value); + else { + var joined = this._selectedNodes.join(','); + joined = joined.replace(chkbox.value, ''); + this._selectedNodes = joined.split(','); + this._selectedNodes = ContentTreeControl_ReBuildArray(contentTreeControl_selected); + } + + }, + _beginShowNodePermissions: function(selectedIDs) { + this._setUpdateProgress(); + var _this = this; + setTimeout(function() { _this._showNodePermissions(selectedIDs); }, 10); + //setTimeout("PermissionsEditor._showNodePermissions('" + selectedIDs + "');", 10); + }, + _showNodePermissions: function(selectedIDs) { + var _this = this; + umbraco.cms.presentation.user.PermissionsHandler.GetNodePermissions(this._userID, selectedIDs, function(r) { _this._showNodePermissionsCallback(r); }); + }, + _showNodePermissionsCallback: function(result) { + this._pPanel.html(result); + }, + _savePermissions: function(nodeIDs, selectedPermissions, replaceChildren) { + var _this = this; + umbraco.cms.presentation.user.PermissionsHandler.SaveNodePermissions(this._userID, nodeIDs, selectedPermissions, replaceChildren, function(r) { _this._savePermissionsHandler(r) }); + }, + _savePermissionsHandler: function(result) { + if (UmbClientMgr.mainWindow().UmbSpeechBubble != null) + UmbClientMgr.mainWindow().UmbSpeechBubble.ShowMessage("save", "Saved", "Permissions Saved"); + this._pPanel.html(result); + }, + _setUpdateProgress: function() { + this._pPanel.html(this._loadingContent); + } + } + } +})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/users/PermissionsHandler.asmx b/src/Umbraco.Web.UI/umbraco/users/PermissionsHandler.asmx new file mode 100644 index 0000000000..274efb0789 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/users/PermissionsHandler.asmx @@ -0,0 +1 @@ +<%@ WebService Language="C#" CodeBehind="PermissionsHandler.asmx.cs" Class="umbraco.cms.presentation.user.PermissionsHandler" %> diff --git a/src/Umbraco.Web.UI/umbraco/webservices/CMSNode.asmx b/src/Umbraco.Web.UI/umbraco/webservices/CMSNode.asmx deleted file mode 100644 index 2f46849377..0000000000 --- a/src/Umbraco.Web.UI/umbraco/webservices/CMSNode.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="CMSNode.asmx.cs" Class="umbraco.presentation.webservices.CMSNode" %> diff --git a/src/Umbraco.Web.UI/umbraco/webservices/Developer.asmx b/src/Umbraco.Web.UI/umbraco/webservices/Developer.asmx new file mode 100644 index 0000000000..2ca307ed10 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/webservices/Developer.asmx @@ -0,0 +1 @@ +<%@ WebService Language="c#" Codebehind="Developer.asmx.cs" Class="umbraco.webservices.Developer" %> diff --git a/src/Umbraco.Web.UI/umbraco/webservices/MacroContainerService.asmx b/src/Umbraco.Web.UI/umbraco/webservices/MacroContainerService.asmx deleted file mode 100644 index 593d4f5037..0000000000 --- a/src/Umbraco.Web.UI/umbraco/webservices/MacroContainerService.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="MacroContainerService.asmx.cs" Class="umbraco.presentation.webservices.MacroContainerService" %> diff --git a/src/Umbraco.Web.UI/umbraco/webservices/MediaUploader.ashx b/src/Umbraco.Web.UI/umbraco/webservices/MediaUploader.ashx deleted file mode 100644 index 3dab7a3f91..0000000000 --- a/src/Umbraco.Web.UI/umbraco/webservices/MediaUploader.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="MediaUploader.ashx.cs" Class="umbraco.presentation.umbraco.webservices.MediaUploader" %> diff --git a/src/Umbraco.Web.UI/umbraco/webservices/TagsAutoCompleteHandler.ashx b/src/Umbraco.Web.UI/umbraco/webservices/TagsAutoCompleteHandler.ashx deleted file mode 100644 index 240d6ba908..0000000000 --- a/src/Umbraco.Web.UI/umbraco/webservices/TagsAutoCompleteHandler.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="TagsAutoCompleteHandler.ashx.cs" Class="umbraco.presentation.umbraco.webservices.TagsAutoCompleteHandler" %> diff --git a/src/Umbraco.Web.UI/umbraco/webservices/TreeClientService.asmx b/src/Umbraco.Web.UI/umbraco/webservices/TreeClientService.asmx deleted file mode 100644 index 587a2f7db7..0000000000 --- a/src/Umbraco.Web.UI/umbraco/webservices/TreeClientService.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="C#" CodeBehind="TreeClientService.asmx.cs" Class="umbraco.presentation.webservices.TreeClientService" %> diff --git a/src/Umbraco.Web.UI/umbraco/webservices/TreeDataService.ashx b/src/Umbraco.Web.UI/umbraco/webservices/TreeDataService.ashx deleted file mode 100644 index 8c186f8195..0000000000 --- a/src/Umbraco.Web.UI/umbraco/webservices/TreeDataService.ashx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebHandler Language="C#" CodeBehind="TreeDataService.ashx.cs" Class="umbraco.presentation.webservices.TreeDataService" %> diff --git a/src/Umbraco.Web.UI/umbraco/webservices/progressStatus.asmx b/src/Umbraco.Web.UI/umbraco/webservices/progressStatus.asmx deleted file mode 100644 index f18a82d386..0000000000 --- a/src/Umbraco.Web.UI/umbraco/webservices/progressStatus.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="c#" Codebehind="progressStatus.asmx.cs" Class="presentation.umbraco.webservices.progressStatus" %> diff --git a/src/Umbraco.Web.UI/umbraco/webservices/publication.asmx b/src/Umbraco.Web.UI/umbraco/webservices/publication.asmx deleted file mode 100644 index 49626d763a..0000000000 --- a/src/Umbraco.Web.UI/umbraco/webservices/publication.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService Language="c#" Codebehind="publication.asmx.cs" Class="umbraco.webservices.publication" %> diff --git a/src/Umbraco.Web.UI/umbraco/webservices/templates.asmx b/src/Umbraco.Web.UI/umbraco/webservices/templates.asmx new file mode 100644 index 0000000000..318840e4f7 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/webservices/templates.asmx @@ -0,0 +1 @@ +<%@ WebService Language="c#" Codebehind="templates.asmx.cs" Class="umbraco.webservices.templates" %> diff --git a/src/Umbraco.Web.UI/umbraco/webservices/wsdl.xslt b/src/Umbraco.Web.UI/umbraco/webservices/wsdl.xslt deleted file mode 100644 index c79d87b5e4..0000000000 --- a/src/Umbraco.Web.UI/umbraco/webservices/wsdl.xslt +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - - - - - - - - // javascript proxy for SOAP based web services - // by Matthias Hertel - /* */ - - - - - - - proxies. = { - url: "", - ns: "" - } // proxies. - - - - - - - - - - - - - /* inputMessageName='', outputMessageName='' */ - - - /** */ - - proxies.. = function () { return(proxies.callSoap(arguments)); } - proxies...fname = ""; - proxies...service = proxies.; - proxies...action = "\"\""; - proxies...params = [ - - ]; - proxies...rtype = [ - - ]; - - - - - - - - - - - - "" - - - - "" - - - - ":int" - - - - ":float" - - - ":date" - - - - ":bool" - - - - - ":s[]" - - - ":int[]" - - - ":float[]" - - - ":bool[]" - - - - - - ":ds" - - - - - ":x" - - - "" - - - - - - - - - - - - - - , - - - - - - - - - - - - - , - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/HistoryManager.js b/src/Umbraco.Web.UI/umbraco_client/Application/HistoryManager.js deleted file mode 100644 index 5bab77a931..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Application/HistoryManager.js +++ /dev/null @@ -1,60 +0,0 @@ -/// -/// -/// - -Umbraco.Sys.registerNamespace("Umbraco.Controls"); - -(function($) { - - Umbraco.Controls.HistoryManager = function() { - /// This is a wrapper for the bbq plugin history manager, but we could do alot with history mgmt in the future! - - var hashFragmentRegex = new RegExp(/^\w+/); - function getHashFragment(frag) { - //tests for xss and ensures only the first alphanumeric chars are matched - var result = hashFragmentRegex.exec(frag); - if (result != null && result.length > 0) { - return result[0]; - } - return ""; - } - var obj = { - - onNavigate: function(e) { - var fragment = getHashFragment($.param.fragment()); - if (fragment != "") { - $(window.top).trigger("navigating", [fragment]); //raise event! - } - - }, - addHistory: function(name, forceRefresh) { - var fragment = getHashFragment($.param.fragment()); - if (fragment == name && forceRefresh) { - this.onNavigate(); - } - else { - $.bbq.pushState(name, 2); - } - - }, - getCurrent: function () { - return getHashFragment($.param.fragment()); - }, - - addEventHandler: function(fnName, fn) { - /// Adds an event listener to the event name event - if (typeof ($) != "undefined") $(window.top).bind(fnName, fn); //if there's no jQuery, there is no events - }, - removeEventHandler: function(fnName, fn) { - /// Removes an event listener to the event name event - if (typeof ($) != "undefined") $(window.top).unbind(fnName, fn); //if there's no jQuery, there is no events - } - }; - - //wire up the navigate events, wrap method to maintain scope - $(window).bind('hashchange', function(e) { obj.onNavigate.call(obj); }); - - return obj; - }; - -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/VerticalAlign.js b/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/VerticalAlign.js deleted file mode 100644 index 7db9a0636d..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/VerticalAlign.js +++ /dev/null @@ -1,8 +0,0 @@ -(function($) { - $.fn.VerticalAlign = function(opts) { - return this.each(function() { - var top = (($(this).parent().height() - $(this).height()) / 2); - $(this).css('margin-top', top); - }); - }; -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/jquery.autocomplete.js b/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/jquery.autocomplete.js deleted file mode 100644 index c17df18c05..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/jquery.autocomplete.js +++ /dev/null @@ -1,813 +0,0 @@ -/* -* jQuery Autocomplete plugin 1.1 -* -* Copyright (c) 2009 Jörn Zaefferer -* -* Dual licensed under the MIT and GPL licenses: -* http://www.opensource.org/licenses/mit-license.php -* http://www.gnu.org/licenses/gpl.html -* -* Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $ -*/ - -; (function($) { - - $.fn.extend({ - autocomplete: function(urlOrData, options) { - var isUrl = typeof urlOrData == "string"; - options = $.extend({}, $.Autocompleter.defaults, { - url: isUrl ? urlOrData : null, - data: isUrl ? null : urlOrData, - delay: isUrl ? $.Autocompleter.defaults.delay : 10, - max: options && !options.scroll ? 10 : 150 - }, options); - - // if highlight is set to false, replace it with a do-nothing function - options.highlight = options.highlight || function(value) { return value; }; - - // if the formatMatch option is not specified, then use formatItem for backwards compatibility - options.formatMatch = options.formatMatch || options.formatItem; - - return this.each(function() { - new $.Autocompleter(this, options); - }); - }, - result: function(handler) { - return this.bind("result", handler); - }, - search: function(handler) { - return this.trigger("search", [handler]); - }, - flushCache: function() { - return this.trigger("flushCache"); - }, - setOptions: function(options) { - return this.trigger("setOptions", [options]); - }, - unautocomplete: function() { - return this.trigger("unautocomplete"); - } - }); - - $.Autocompleter = function(input, options) { - - var KEY = { - UP: 38, - DOWN: 40, - DEL: 46, - TAB: 9, - RETURN: 13, - ESC: 27, - COMMA: 188, - PAGEUP: 33, - PAGEDOWN: 34, - BACKSPACE: 8 - }; - - // Create $ object for input element - var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); - - var timeout; - var previousValue = ""; - var cache = $.Autocompleter.Cache(options); - var hasFocus = 0; - var lastKeyPressCode; - var config = { - mouseDownOnSelect: false - }; - var select = $.Autocompleter.Select(options, input, selectCurrent, config); - - var blockSubmit; - - // prevent form submit in opera when selecting with return key - $.browser.opera && $(input.form).bind("submit.autocomplete", function() { - if (blockSubmit) { - blockSubmit = false; - return false; - } - }); - - // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all - $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { - // a keypress means the input has focus - // avoids issue where input had focus before the autocomplete was applied - hasFocus = 1; - // track last key pressed - lastKeyPressCode = event.keyCode; - switch (event.keyCode) { - - case KEY.UP: - event.preventDefault(); - if (select.visible()) { - select.prev(); - } else { - onChange(0, true); - } - break; - - case KEY.DOWN: - event.preventDefault(); - if (select.visible()) { - select.next(); - } else { - onChange(0, true); - } - break; - - case KEY.PAGEUP: - event.preventDefault(); - if (select.visible()) { - select.pageUp(); - } else { - onChange(0, true); - } - break; - - case KEY.PAGEDOWN: - event.preventDefault(); - if (select.visible()) { - select.pageDown(); - } else { - onChange(0, true); - } - break; - - // matches also semicolon - case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: - case KEY.TAB: - case KEY.RETURN: - if (selectCurrent()) { - // stop default to prevent a form submit, Opera needs special handling - event.preventDefault(); - blockSubmit = true; - return false; - } - break; - - case KEY.ESC: - select.hide(); - break; - - default: - clearTimeout(timeout); - timeout = setTimeout(onChange, options.delay); - break; - } - }).focus(function() { - // track whether the field has focus, we shouldn't process any - // results if the field no longer has focus - hasFocus++; - }).blur(function() { - hasFocus = 0; - if (!config.mouseDownOnSelect) { - hideResults(); - } - }).click(function() { - // show select when clicking in a focused field - if (hasFocus++ > 1 && !select.visible()) { - onChange(0, true); - } - }).bind("search", function() { - // TODO why not just specifying both arguments? - var fn = (arguments.length > 1) ? arguments[1] : null; - function findValueCallback(q, data) { - var result; - if (data && data.length) { - for (var i = 0; i < data.length; i++) { - if (data[i].result.toLowerCase() == q.toLowerCase()) { - result = data[i]; - break; - } - } - } - if (typeof fn == "function") fn(result); - else $input.trigger("result", result && [result.data, result.value]); - } - $.each(trimWords($input.val()), function(i, value) { - request(value, findValueCallback, findValueCallback); - }); - }).bind("flushCache", function() { - cache.flush(); - }).bind("setOptions", function() { - $.extend(options, arguments[1]); - // if we've updated the data, repopulate - if ("data" in arguments[1]) - cache.populate(); - }).bind("unautocomplete", function() { - select.unbind(); - $input.unbind(); - $(input.form).unbind(".autocomplete"); - }); - - - function selectCurrent() { - var selected = select.selected(); - if (!selected) - return false; - - var v = selected.result; - previousValue = v; - - if (options.multiple) { - var words = trimWords($input.val()); - if (words.length > 1) { - var seperator = options.multipleSeparator.length; - var cursorAt = $(input).selection().start; - var wordAt, progress = 0; - $.each(words, function(i, word) { - progress += word.length; - if (cursorAt <= progress) { - wordAt = i; - return false; - } - progress += seperator; - }); - words[wordAt] = v; - // TODO this should set the cursor to the right position, but it gets overriden somewhere - //$.Autocompleter.Selection(input, progress + seperator, progress + seperator); - v = words.join(options.multipleSeparator); - } - v += options.multipleSeparator; - } - - $input.val(v); - hideResultsNow(); - $input.trigger("result", [selected.data, selected.value]); - return true; - } - - function onChange(crap, skipPrevCheck) { - if (lastKeyPressCode == KEY.DEL) { - select.hide(); - return; - } - - var currentValue = $input.val(); - - if (!skipPrevCheck && currentValue == previousValue) - return; - - previousValue = currentValue; - - currentValue = lastWord(currentValue); - if (currentValue.length >= options.minChars) { - $input.addClass(options.loadingClass); - if (!options.matchCase) - currentValue = currentValue.toLowerCase(); - request(currentValue, receiveData, hideResultsNow); - } else { - stopLoading(); - select.hide(); - } - }; - - function trimWords(value) { - if (!value) - return [""]; - if (!options.multiple) - return [$.trim(value)]; - return $.map(value.split(options.multipleSeparator), function(word) { - return $.trim(value).length ? $.trim(word) : null; - }); - } - - function lastWord(value) { - if (!options.multiple) - return value; - var words = trimWords(value); - if (words.length == 1) - return words[0]; - var cursorAt = $(input).selection().start; - if (cursorAt == value.length) { - words = trimWords(value) - } else { - words = trimWords(value.replace(value.substring(cursorAt), "")); - } - return words[words.length - 1]; - } - - // fills in the input box w/the first match (assumed to be the best match) - // q: the term entered - // sValue: the first matching result - function autoFill(q, sValue) { - // autofill in the complete box w/the first match as long as the user hasn't entered in more data - // if the last user key pressed was backspace, don't autofill - if (options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE) { - // fill in the value (keep the case the user has typed) - $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); - // select the portion of the value not typed by the user (so the next character will erase) - $(input).selection(previousValue.length, previousValue.length + sValue.length); - } - }; - - function hideResults() { - clearTimeout(timeout); - timeout = setTimeout(hideResultsNow, 200); - }; - - function hideResultsNow() { - var wasVisible = select.visible(); - select.hide(); - clearTimeout(timeout); - stopLoading(); - if (options.mustMatch) { - // call search and run callback - $input.search( - function(result) { - // if no value found, clear the input box - if (!result) { - if (options.multiple) { - var words = trimWords($input.val()).slice(0, -1); - $input.val(words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "")); - } - else { - $input.val(""); - $input.trigger("result", null); - } - } - } - ); - } - }; - - function receiveData(q, data) { - if (data && data.length && hasFocus) { - stopLoading(); - select.display(data, q); - autoFill(q, data[0].value); - select.show(); - } else { - hideResultsNow(); - } - }; - - function request(term, success, failure) { - if (!options.matchCase) - term = term.toLowerCase(); - var data = cache.load(term); - // recieve the cached data - if (data && data.length) { - success(term, data); - // if an AJAX url has been supplied, try loading the data now - } else if ((typeof options.url == "string") && (options.url.length > 0)) { - - var extraParams = { - timestamp: +new Date() - }; - $.each(options.extraParams, function(key, param) { - extraParams[key] = typeof param == "function" ? param() : param; - }); - - $.ajax({ - // try to leverage ajaxQueue plugin to abort previous requests - mode: "abort", - // limit abortion to this input - port: "autocomplete" + input.name, - dataType: options.dataType, - url: options.url, - data: $.extend({ - q: lastWord(term), - limit: options.max - }, extraParams), - success: function(data) { - var parsed = options.parse && options.parse(data) || parse(data); - cache.add(term, parsed); - success(term, parsed); - } - }); - } else { - // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match - select.emptyList(); - failure(term); - } - }; - - function parse(data) { - var parsed = []; - var rows = data.split("\n"); - for (var i = 0; i < rows.length; i++) { - var row = $.trim(rows[i]); - if (row) { - row = row.split("|"); - parsed[parsed.length] = { - data: row, - value: row[0], - result: options.formatResult && options.formatResult(row, row[0]) || row[0] - }; - } - } - return parsed; - }; - - function stopLoading() { - $input.removeClass(options.loadingClass); - }; - - }; - - $.Autocompleter.defaults = { - inputClass: "ac_input", - resultsClass: "ac_results", - loadingClass: "ac_loading", - minChars: 1, - delay: 400, - matchCase: false, - matchSubset: true, - matchContains: false, - cacheLength: 10, - max: 100, - mustMatch: false, - extraParams: {}, - selectFirst: true, - formatItem: function(row) { return row[0]; }, - formatMatch: null, - autoFill: false, - width: 0, - multiple: false, - multipleSeparator: ", ", - highlight: function(value, term) { - return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); - }, - scroll: true, - scrollHeight: 180, - focus: function (event, ui) { } - }; - - $.Autocompleter.Cache = function(options) { - - var data = {}; - var length = 0; - - function matchSubset(s, sub) { - s = s.toString(); // FR: This forces the value to be a string, otherwise the indexOf() method fails on anything other but a string - if (!options.matchCase) - s = s.toLowerCase(); - var i = s.indexOf(sub); - if (options.matchContains == "word") { - i = s.toLowerCase().search("\\b" + sub.toLowerCase()); - } - if (i == -1) return false; - return i == 0 || options.matchContains; - }; - - function add(q, value) { - if (length > options.cacheLength) { - flush(); - } - if (!data[q]) { - length++; - } - data[q] = value; - } - - function populate() { - if (!options.data) return false; - // track the matches - var stMatchSets = {}, - nullData = 0; - - // no url was specified, we need to adjust the cache length to make sure it fits the local data store - if (!options.url) options.cacheLength = 1; - - // track all options for minChars = 0 - stMatchSets[""] = []; - - // loop through the array and create a lookup structure - for (var i = 0, ol = options.data.length; i < ol; i++) { - var rawValue = options.data[i]; - // if rawValue is a string, make an array otherwise just reference the array - rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; - - var value = options.formatMatch(rawValue, i + 1, options.data.length); - if (value === false) - continue; - - var firstChar = value.charAt(0).toLowerCase(); - // if no lookup array for this character exists, look it up now - if (!stMatchSets[firstChar]) - stMatchSets[firstChar] = []; - - // if the match is a string - var row = { - value: value, - data: rawValue, - result: options.formatResult && options.formatResult(rawValue) || value - }; - - // push the current match into the set list - stMatchSets[firstChar].push(row); - - // keep track of minChars zero items - if (nullData++ < options.max) { - stMatchSets[""].push(row); - } - }; - - // add the data items to the cache - $.each(stMatchSets, function(i, value) { - // increase the cache size - options.cacheLength++; - // add to the cache - add(i, value); - }); - } - - // populate any existing data - setTimeout(populate, 25); - - function flush() { - data = {}; - length = 0; - } - - return { - flush: flush, - add: add, - populate: populate, - load: function(q) { - if (!options.cacheLength || !length) - return null; - /* - * if dealing w/local data and matchContains than we must make sure - * to loop through all the data collections looking for matches - */ - if (!options.url && options.matchContains) { - // track all matches - var csub = []; - // loop through all the data grids for matches - for (var k in data) { - // don't search through the stMatchSets[""] (minChars: 0) cache - // this prevents duplicates - if (k.length > 0) { - var c = data[k]; - $.each(c, function(i, x) { - // if we've got a match, add it to the array - if (matchSubset(x.value, q)) { - csub.push(x); - } - }); - } - } - return csub; - } else - // if the exact item exists, use it - if (data[q]) { - return data[q]; - } else - if (options.matchSubset) { - for (var i = q.length - 1; i >= options.minChars; i--) { - var c = data[q.substr(0, i)]; - if (c) { - var csub = []; - $.each(c, function(i, x) { - if (matchSubset(x.value, q)) { - csub[csub.length] = x; - } - }); - return csub; - } - } - } - return null; - } - }; - }; - - $.Autocompleter.Select = function(options, input, select, config) { - var CLASSES = { - ACTIVE: "ac_over" - }; - - var listItems, - active = -1, - data, - term = "", - needsInit = true, - element, - list; - - // Create results - function init() { - if (!needsInit) - return; - element = $("
") - .hide() - .addClass(options.resultsClass) - .css("position", "absolute") - .appendTo(document.body); - - list = $("
    ").appendTo(element).mouseover(function(event) { - if (target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { - active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); - $(target(event)).addClass(CLASSES.ACTIVE); - options.focus(event, target(event)); - } - }).click(function(event) { - $(target(event)).addClass(CLASSES.ACTIVE); - select(); - // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus - input.focus(); - return false; - }).mousedown(function() { - config.mouseDownOnSelect = true; - }).mouseup(function() { - config.mouseDownOnSelect = false; - }); - - if (options.width > 0) - element.css("width", options.width); - - needsInit = false; - } - - function target(event) { - var element = event.target; - while (element && element.tagName != "LI") - element = element.parentNode; - // more fun with IE, sometimes event.target is empty, just ignore it then - if (!element) - return []; - return element; - } - - function moveSelect(step) { - listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); - movePosition(step); - var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); - options.focus(null, activeItem); - - if (options.scroll) { - var offset = 0; - listItems.slice(0, active).each(function() { - offset += this.offsetHeight; - }); - if ((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { - list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); - } else if (offset < list.scrollTop()) { - list.scrollTop(offset); - } - } - }; - - function movePosition(step) { - active += step; - if (active < 0) { - active = listItems.size() - 1; - } else if (active >= listItems.size()) { - active = 0; - } - } - - function limitNumberOfItems(available) { - return options.max && options.max < available - ? options.max - : available; - } - - function fillList() { - list.empty(); - var max = limitNumberOfItems(data.length); - for (var i = 0; i < max; i++) { - if (!data[i]) - continue; - var formatted = options.formatItem(data[i].data, i + 1, max, data[i].value, term); - if (formatted === false) - continue; - var li = $("
  • ").html(options.highlight(formatted, term)).addClass(i % 2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; - $.data(li, "ac_data", data[i]); - } - listItems = list.find("li"); - if (options.selectFirst) { - listItems.slice(0, 1).addClass(CLASSES.ACTIVE); - active = 0; - } - // apply bgiframe if available - if ($.fn.bgiframe) - list.bgiframe(); - } - - return { - display: function(d, q) { - init(); - data = d; - term = q; - fillList(); - }, - next: function() { - moveSelect(1); - }, - prev: function() { - moveSelect(-1); - }, - pageUp: function() { - if (active != 0 && active - 8 < 0) { - moveSelect(-active); - } else { - moveSelect(-8); - } - }, - pageDown: function() { - if (active != listItems.size() - 1 && active + 8 > listItems.size()) { - moveSelect(listItems.size() - 1 - active); - } else { - moveSelect(8); - } - }, - hide: function() { - element && element.hide(); - listItems && listItems.removeClass(CLASSES.ACTIVE); - active = -1; - }, - visible: function() { - return element && element.is(":visible"); - }, - current: function() { - return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); - }, - show: function() { - var offset = $(input).offset(); - element.css({ - width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), - top: offset.top + input.offsetHeight, - left: offset.left - }).show(); - if (options.scroll) { - list.scrollTop(0); - list.css({ - maxHeight: options.scrollHeight, - overflow: 'auto' - }); - - if ($.browser.msie && typeof document.body.style.maxHeight === "undefined") { - var listHeight = 0; - listItems.each(function() { - listHeight += this.offsetHeight; - }); - var scrollbarsVisible = listHeight > options.scrollHeight; - list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight); - if (!scrollbarsVisible) { - // IE doesn't recalculate width when scrollbar disappears - listItems.width(list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right"))); - } - } - - } - }, - selected: function() { - var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); - return selected && selected.length && $.data(selected[0], "ac_data"); - }, - emptyList: function() { - list && list.empty(); - }, - unbind: function() { - element && element.remove(); - } - }; - }; - - $.fn.selection = function(start, end) { - if (start !== undefined) { - return this.each(function() { - if (this.createTextRange) { - var selRange = this.createTextRange(); - if (end === undefined || start == end) { - selRange.move("character", start); - selRange.select(); - } else { - selRange.collapse(true); - selRange.moveStart("character", start); - selRange.moveEnd("character", end); - selRange.select(); - } - } else if (this.setSelectionRange) { - this.setSelectionRange(start, end); - } else if (this.selectionStart) { - this.selectionStart = start; - this.selectionEnd = end; - } - }); - } - var field = this[0]; - if (field.createTextRange) { - var range = document.selection.createRange(), - orig = field.value, - teststring = "<->", - textLength = range.text.length; - range.text = teststring; - var caretAt = field.value.indexOf(teststring); - field.value = orig; - this.selection(caretAt, caretAt + textLength); - return { - start: caretAt, - end: caretAt + textLength - } - } else if (field.selectionStart !== undefined) { - return { - start: field.selectionStart, - end: field.selectionEnd - } - } - }; - -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/jquery.ba-bbq.min.js b/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/jquery.ba-bbq.min.js deleted file mode 100644 index a0f5573174..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Application/JQuery/jquery.ba-bbq.min.js +++ /dev/null @@ -1,18 +0,0 @@ -/* -* jQuery BBQ: Back Button & Query Library - v1.3pre - 8/26/2010 -* http://benalman.com/projects/jquery-bbq-plugin/ -* -* Copyright (c) 2010 "Cowboy" Ben Alman -* Dual licensed under the MIT and GPL licenses. -* http://benalman.com/about/license/ -*/ -(function ($, r) { var h, n = Array.prototype.slice, t = decodeURIComponent, a = $.param, j, c, m, y, b = $.bbq = $.bbq || {}, s, x, k, e = $.event.special, d = "hashchange", B = "querystring", F = "fragment", z = "elemUrlAttr", l = "href", w = "src", p = /^.*\?|#.*$/g, u, H, g, i, C, E = {}; function G(I) { return typeof I === "string" } function D(J) { var I = n.call(arguments, 1); return function () { return J.apply(this, I.concat(n.call(arguments))) } } function o(I) { return I.replace(H, "$2") } function q(I) { return I.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/, "$1") } function f(K, P, I, L, J) { var R, O, N, Q, M; if (L !== h) { N = I.match(K ? H : /^([^#?]*)\??([^#]*)(#?.*)/); M = N[3] || ""; if (J === 2 && G(L)) { O = L.replace(K ? u : p, "") } else { Q = m(N[2]); L = G(L) ? m[K ? F : B](L) : L; O = J === 2 ? L : J === 1 ? $.extend({}, L, Q) : $.extend({}, Q, L); O = j(O); if (K) { O = O.replace(g, t) } } R = N[1] + (K ? C : O || !N[1] ? "?" : "") + O + M } else { R = P(I !== h ? I : location.href) } return R } a[B] = D(f, 0, q); a[F] = c = D(f, 1, o); a.sorted = j = function (J, K) { var I = [], L = {}; $.each(a(J, K).split("&"), function (P, M) { var O = M.replace(/(?:%5B|=).*$/, ""), N = L[O]; if (!N) { N = L[O] = []; I.push(O) } N.push(M) }); return $.map(I.sort(), function (M) { return L[M] }).join("&") }; c.noEscape = function (J) { J = J || ""; var I = $.map(J.split(""), encodeURIComponent); g = new RegExp(I.join("|"), "g") }; c.noEscape(",/"); c.ajaxCrawlable = function (I) { if (I !== h) { if (I) { u = /^.*(?:#!|#)/; H = /^([^#]*)(?:#!|#)?(.*)$/; C = "#!" } else { u = /^.*#/; H = /^([^#]*)#?(.*)$/; C = "#" } i = !!I } return i }; c.ajaxCrawlable(0); $.deparam = m = function (L, I) { var K = {}, J = { "true": !0, "false": !1, "null": null }; $.each(L.replace(/\+/g, " ").split("&"), function (O, T) { var N = T.split("="), S = t(N[0]), M, R = K, P = 0, U = S.split("]["), Q = U.length - 1; if (/\[/.test(U[0]) && /\]$/.test(U[Q])) { U[Q] = U[Q].replace(/\]$/, ""); U = U.shift().split("[").concat(U); Q = U.length - 1 } else { Q = 0 } if (N.length === 2) { M = t(N[1]); if (I) { M = M && !isNaN(M) ? +M : M === "undefined" ? h : J[M] !== h ? J[M] : M } if (Q) { for (; P <= Q; P++) { S = U[P] === "" ? R.length : U[P]; R = R[S] = P < Q ? R[S] || (U[P + 1] && isNaN(U[P + 1]) ? {} : []) : M } } else { if ($.isArray(K[S])) { K[S].push(M) } else { if (K[S] !== h) { K[S] = [K[S], M] } else { K[S] = M } } } } else { if (S) { K[S] = I ? h : "" } } }); return K }; function A(K, I, J) { if (I === h || typeof I === "boolean") { J = I; I = a[K ? F : B]() } else { I = G(I) ? I.replace(K ? u : p, "") : I } return m(I, J) } m[B] = D(A, 0); m[F] = y = D(A, 1); $[z] || ($[z] = function (I) { return $.extend(E, I) })({ a: l, base: l, iframe: w, img: w, input: w, form: "action", link: l, script: w }); k = $[z]; function v(L, J, K, I) { if (!G(K) && typeof K !== "object") { I = K; K = J; J = h } return this.each(function () { var O = $(this), M = J || k()[(this.nodeName || "").toLowerCase()] || "", N = M && O.attr(M) || ""; O.attr(M, a[L](N, K, I)) }) } $.fn[B] = D(v, B); $.fn[F] = D(v, F); b.pushState = s = function (L, I) { if (G(L) && /^#/.test(L) && I === h) { I = 2 } var K = L !== h, J = c(location.href, K ? L : {}, K ? I : 2); location.href = J }; b.getState = x = function (I, J) { return I === h || typeof I === "boolean" ? y(I) : y(J)[I] }; b.removeState = function (I) { var J = {}; if (I !== h) { J = x(); $.each($.isArray(I) ? I : arguments, function (L, K) { delete J[K] }) } s(J, 2) }; e[d] = $.extend(e[d], { add: function (I) { var K; function J(M) { var L = M[F] = c(); M.getState = function (N, O) { return N === h || typeof N === "boolean" ? m(L, N) : m(L, O)[N] }; K.apply(this, arguments) } if ($.isFunction(I)) { K = I; return J } else { K = I.handler; I.handler = J } } }) })(jQuery, this); -/* -* jQuery hashchange event - v1.3 - 7/21/2010 -* http://benalman.com/projects/jquery-hashchange-plugin/ -* -* Copyright (c) 2010 "Cowboy" Ben Alman -* Dual licensed under the MIT and GPL licenses. -* http://benalman.com/about/license/ -*/ -(function ($, e, b) { var c = "hashchange", h = document, f, g = $.event.special, i = h.documentMode, d = "on" + c in e && (i === b || i > 7); function a(j) { j = j || location.href; return "#" + j.replace(/^[^#]*#?(.*)$/, "$1") } $.fn[c] = function (j) { return j ? this.bind(c, j) : this.trigger(c) }; $.fn[c].delay = 50; g[c] = $.extend(g[c], { setup: function () { if (d) { return false } $(f.start) }, teardown: function () { if (d) { return false } $(f.stop) } }); f = (function () { var j = {}, p, m = a(), k = function (q) { return q }, l = k, o = k; j.start = function () { p || n() }; j.stop = function () { p && clearTimeout(p); p = b }; function n() { var r = a(), q = o(m); if (r !== m) { l(m = r, q); $(e).trigger(c) } else { if (q !== m) { location.href = location.href.replace(/#.*/, "") + q } } p = setTimeout(n, $.fn[c].delay) } $.browser && $.browser.msie && !d && (function () { var q, r; j.start = function () { if (!q) { r = $.fn[c].src; r = r && r + a(); q = $('').attr({ - id: iframeId, - src: 'about:blank', - style: 'display:none' - }).prependTo(item); - - // Clone the form - var newForm = self.form.clone().attr({ - id: formId, - target: iframeId - }).prependTo(item).show(); - - // Remove any id's / css classes from file inputs - $(":file", newForm).each(function (idx, itm) { - $(itm).removeAttr("id").removeAttr("class"); - }); - - // Hide everything in the form, other than file inputs - newForm.children().each(function () { - var isFile = $(this).is(':file'); - if (!isFile && $(this).find(':file').length === 0) { - $(this).hide(); - } - }); - - // Append everything to the form container - item.prependTo(self.formContainerSelector); - - // Attatch event listener to file input - $(":file", newForm).change(function () { - if (isHtml5) { - self._addFileHtml5(this); - } else { - self._addFile($(this)); - } - }); - }, - - _addFileHtml5: function (input) { - var self = this; - $.each(input.files, function (index, file) { - self._addFile(file); - }); - self._afterAddFile(); - }, - - _addFile: function (input) { - var self = this; - var id = self.itemCount; - var $item = $('#fu-item-' + self.uploaderId + '-' + id); - - // Validate file - var filename = (isHtml5) ? input.name : self._getFileName($(input).val()); - if (self._validateFileName(filename) == -1) { - if ($.isFunction(self.opts.onValidationError)) { - self.opts.onValidationError({ input: input, type: 'format' }); - } else { - alert('Invalid file!'); - } - $item.find("form :file").val(''); - return false; - } - - // Check to see if we have reached the limit - if (self.opts.limit !== false && - self.opts.limit >= $(self.itemContainerSelector + " .fu-item").length) { - if ($.isFunction(self.opts.onValidationError)) { - self.opts.onValidationError({ input: input, type: 'limit' }); - } else { - alert('Maximum limit reached!'); - } - $item.find("form :file").val(''); - return false; - } - - // Create context object - var data = { - uploaderId: self.uploaderId, - itemId: id, - name: filename, - size: (isHtml5) ? self._getFileSize(input) : "Unkown", - status: 'queued', - progress: 0, - input: input, - context: null // Somewhere for users to store cutom data - }; - - //Store data in item - $item.data('data', data); - - // Move item to queue, and hide it - $item.appendTo(self.itemContainerSelector).hide(); - - //Create a new form - self._createNewForm(); - - //Callback on file queued - self.opts.onAdd(data); - - if (!isHtml5) { - self._afterAddFile(); - } - }, - - _afterAddFile: function () { - var self = this; - if (self.opts.autoUpload) { - self._processNextItemInQueue(true); - } - }, - - _processNextItemInQueue: function (autoProceed) { - var self = this; - var totalItems = $(self.itemContainerSelector + " .fu-item"); - if (totalItems.length > 0) { - var $pendingUpload = $(totalItems.get(0)); - var data = $pendingUpload.data('data'); - - //before upload - var response = self.opts.onUpload(data); - if (response === false) { - return false; //TODO: Raise onDone event? - } - - self.inProgressItemId = data.itemId; - - data.status = "inprogress"; - data.progress = 0; - - if (isHtml5) { - //Upload Using Html5 api - self._uploadHtml5($pendingUpload, autoProceed); - } else { - //upload using iframe - self._uploadIFrame($pendingUpload, autoProceed); - } - } - }, - - _uploadHtml5: function ($item, autoProceed) { - var self = this; - var $form = $item.find("form"); - var data = $item.data('data'); - var file = data.input; - if (file) { - var fd = new FormData(); - fd.append($(":file", $form).first().attr('name'), file); - - //get other form input and append to formData - $form.find(':input').each(function () { - if (!$(this).is(':file')) { - fd.append($(this).attr('name'), $(this).val()); - } - }); - - //Upload using jQuery AJAX - self.inProgressJqXhr = $.ajax({ - url: $form.attr('action'), - data: fd, - cache: false, - contentType: false, - processData: false, - type: 'POST', - xhr: function () { - var req = $.ajaxSettings.xhr(); - if (req) { - req.upload.addEventListener('progress', function (ev) { - data.progress = Math.round(ev.loaded * 100 / ev.total); - self.opts.onProgress(data); - }, false); - } - return req; - } - }).success(function (d) { - data.status = 'success'; - self.opts.onDone(data); - }).error(function (jqXhr, textStatus, errorThrown) { - data.status = 'error'; - data.message = errorThrown; - self.opts.onDone(data); - }).complete(function (jqXhr, textStatus) { - self._afterUpload($item, autoProceed); - }); - - } - }, - - _uploadIFrame: function ($item, autoProceed) { - var self = this; - var $form = $item.find("form"); - var $iframe = $item.find("iframe"); - var data = $item.data('data'); - - self._startDummyProgress(data); - - $form.submit(); - - $iframe.load(function () { - self._stopDummyProgress(data, true); - - // Read content returned - var rawResponse; - - if (this.contentDocument) { - rawResponse = this.contentDocument.body.innerHTML; - } else { - rawResponse = this.contentWindow.document.body.innerHTML; - } - - var response = $.parseJSON(rawResponse); - - if (response.success) { - data.status = 'success'; - } else { - data.status = 'error'; - data.message = 'An error occured whilst uploading.'; - } - - self.opts.onDone(data); - - self._afterUpload($item, autoProceed); - }); - }, - - _startDummyProgress: function (data, count) { - var self = this; - - if (count === undefined) { - count = 0; - } - - if ($.fileUploader.percentageInterval[count]) { - data.progress = $.fileUploader.percentageInterval[count] + Math.floor(Math.random() * 5 + 1); - self.opts.onProgress(data); - } - - if ($.fileUploader.timeInterval[count]) { - self.progressTimeout = setTimeout(function () { - self._startDummyProgress(data, ++count); - }, $.fileUploader.timeInterval[count] * 1000); - } - }, - - _stopDummyProgress: function (data, success) { - var self = this; - - clearTimeout(self.progressTimeout); - - if (success) { - // Force a 100% onProgress event - data.progress = 100; - self.opts.onProgress(data); - } - }, - - _afterUpload: function ($item, autoProceed) { - var self = this; - var data = $item.data('data'); - - if (data.itemId == self.inProgressItemId) { - self.inProgressItemId = 0; - self.inProgressJqXhr = null; - } - - $item.remove(); - - if ($(self.itemContainerSelector + " .fu-item").length > 0) { - if (autoProceed) { - self._processNextItemInQueue(autoProceed); - } - } - else { - self.opts.onDoneAll(); - } - }, - - // Public methods - uploadAll: function () { - var self = this; - var result = self.opts.onUploadAll(self); - if (result === false) { - return false; - } - - self._processNextItemInQueue(true); - }, - - cancelAll: function () { - var self = this; - - // Remove current form - $(self.formContainerSelector).empty(); - - var queuedItems = $(self.itemContainerSelector + " .fu-item"); - for (var i = 0; i < queuedItems.length; i++) { - var data = $(queuedItems.get(i)).data('data'); - self.cancelItem(data.itemId); - } - - self.itemCount = 0; - - // Recreate the form - self._createNewForm(); - }, - - cancelItem: function (itemId) { - var self = this; - var $item = $("#fu-item-" + self.uploaderId + "-" + itemId); - var data = $item.data('data'); - - // If the item to cancel is in progress, abort the upload - if (self.inProgressItemId == itemId && self.inProgressJqXhr) { - //NB: The abort event handler will cleanup the aborted item from the queue - self.inProgressJqXhr.abort(); - } - else { - // Update the status - data.status = 'canceled'; - - self.opts.onDone(data); - - self._afterUpload($item, false); - } - } - }; - - $.fileUploader = { - version: '1.0', - count: 0, - timeInterval: [1, 2, 4, 2, 1, 5, 5, 5, 5], - percentageInterval: [10, 20, 30, 40, 60, 80, 85, 90, 95] - }; - - $.fn.fileUploader = function (o) { - var args = arguments; - if (typeof o === 'string') { - var api = this.fileUploaderApi(); - if (api[o]) { - return api[o].apply(api, Array.prototype.slice.call(args, 1)); - } else { - $.error('Method ' + o + ' does not exist on jQuery.fileUploader'); - } - } - else if (typeof o === 'object' || !o) { - return this.each(function () { - var fileUploader = new FileUploader(this, o); - $(this).data("fileuploader_api", fileUploader); - }); - } - }; - - $.fn.fileUploaderApi = function () { - //ensure there's only 1 - if (this.length != 1) { - throw "Requesting the API can only match one element"; - } - - //ensure thsi is a collapse panel - if (this.data("fileuploader_api") === null) { - throw "The matching element had not been bound to a fileUploader"; - } - - return this.data("fileuploader_api"); - }; - -})(jQuery, window, document); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/FolderBrowser/Css/folderbrowser.css b/src/Umbraco.Web.UI/umbraco_client/FolderBrowser/Css/folderbrowser.css deleted file mode 100644 index 02eab9b761..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/FolderBrowser/Css/folderbrowser.css +++ /dev/null @@ -1,274 +0,0 @@ -.umbFolderBrowser { - -} - -.upload-overlay { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: transparent url(../../modal/modalBackground.gif); - z-index: 9999; - display: none; -} - -.upload-panel { - display: block; - position: absolute; - top: 50%; - left: 50%; - text-align: center; - margin: -200px 0 0 -200px; - border: solid 5px #ccc; - background: #fff; - padding: 20px; - width: 400px; - height: 400px; -} - -.upload-panel h1 { - color: #ccc; -} - -.upload-panel p { - color: #aaa; -} - -.upload-panel .queued, -.upload-panel .queued li { - list-style: none; - margin: 0; - padding: 0; - text-align: left; -} - -.upload-panel .queued { - display: block; - margin: 20px 0; - height: 220px; - overflow: auto; - overflow-y: scroll; -} - -.upload-panel .queued li { - display: block; - background: transparent url(../../../umbraco/images/umbraco/mediaFile.gif) left center no-repeat; - padding: 2px 0 2px 20px; -} - -.upload-panel .queued li .label { - display: inline-block; - width: 145px; - margin: 0 5px 0 0; - border: solid 1px #e0e0e0; - height: 1.5em; - vertical-align: middle; -} - -.upload-panel .queued li span.progress { - display: inline-block; - width: 180px; - border: solid 1px #e0e0e0; - overflow: hidden; - height: 1.5em; - vertical-align: middle; -} - -.upload-panel .queued li span.progress span { - display: inline-block; - background: #6c3; - overflow: hidden; - height: 1.5em; -} - -.upload-panel .queued li span.progress span.canceled, -.upload-panel .queued li span.progress span.error { - background: #f33; -} - -.upload-panel .queued li img { - margin-left: 5px; - vertical-align: middle; -} - -.upload-panel .buttons { - text-align: right; -} - -.upload-panel .buttons span { - display: block; - float: left; -} - -.umbFolderBrowser .breadcrumb { - margin: 20px 20px 15px; - padding: 0 0 10px; - background: none !Important; -} - -.umbFolderBrowser .breadcrumb li { - display: inline; - padding: 0 5px 0 0; - margin: 0; -} - -.umbFolderBrowser .breadcrumb li:after { - content: '>'; -} - -.umbFolderBrowser .breadcrumb li:last-child:after, -.umbFolderBrowser .breadcrumb li:first-child:after { - content: ''; -} - -.umbFolderBrowser .breadcrumb li a { - margin-right: 5px; -} - -.umbFolderBrowser .thumb-sizer { - position: absolute; - top: 18px; - right: 270px; - border-right: solid 1px #ccc; - padding-right: 15px; - display: none; -} - -.umbFolderBrowser .thumb-sizer input, -.umbFolderBrowser .thumb-sizer img { - vertical-align: baseline; -} - -.umbFolderBrowser .filter { - position: absolute; - top: 30px; - right: 80px; -} - -.umbFolderBrowser .filter input { - border: solid 1px #ccc; - padding: 0 3px; - width: 194px; -} - -.umbFolderBrowser .throbber { - margin: 0 20px; -} - -.umbFolderBrowser .items { - margin: 5px 15px; - padding: 0; -} - -.umbFolderBrowser .items li { - display: inline-block; - list-style: none; - float: left; -} - -.umbFolderBrowser .items li div { - display: inline-block; - position: relative; - list-style: none; - margin: 5px; - padding: 0; - background: #f5f5f5; - text-align: center; - border: solid 1px #ccc; - width: 120px; - height: 150px; - float: left; - -moz-box-shadow: 2px 2px 2px #e0e0e0; - -webkit-box-shadow: 2px 2px 2px #e0e0e0; - box-shadow: 2px 2px 2px #e0e0e0; - cursor: pointer; -} - -.umbFolderBrowser .items.medium li div { - width: 95px; - height: 125px; -} - -.umbFolderBrowser .items.small li div { - width: 70px; - height: 100px; -} - -.umbFolderBrowser .img { - display: block; - background: #e0e0e0; - text-align: center; - width: 100px; - height: 100px; - margin: 10px; -} - -.umbFolderBrowser .items.medium .img { - width: 75px; - height: 75px; -} - -.umbFolderBrowser .items.small .img { - width: 50px; - height: 50px; -} - -.umbFolderBrowser .img:before { - content: ''; - display: inline-block; - height: 100%; - vertical-align: middle; -} - -.umbFolderBrowser .img img { - display:inline; - display:inline-table; - display:inline-block; - vertical-align: middle; - max-width: 100px; - max-height: 100px; -} - -.umbFolderBrowser .items.medium .img img { - max-width: 75px; - max-height: 75px; -} - -.umbFolderBrowser .items.small .img img { - max-width: 50px; - max-height: 50px; -} - -.umbFolderBrowser li span { - display: block; - white-space: nowrap; - margin: 0 5px; - overflow: hidden; -} - -.umbFolderBrowser .items li.selected div { - background: #D5EFFC; - border-color: #99DEFD; -} - -.umbFolderBrowser .items li.selected .img { - background: #99DEFD; -} - -.umbFolderBrowser .items.ui-sortable-dragging li.selected { - display: none !important; -} - -.umbFolderBrowser .items.ui-sortable-dragging li.ui-sortable-helper, -.umbFolderBrowser .items.ui-sortable-dragging li.ui-sortable-placeholder { - display: inline-block !important; -} - -.umbFolderBrowser .items li.ui-sortable-helper div { - border: dotted 5px #99DEFD; -} - -.umbFolderBrowser .items li.ui-sortable-helper span { - display: none !important; -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/FolderBrowser/Js/folderbrowser.js b/src/Umbraco.Web.UI/umbraco_client/FolderBrowser/Js/folderbrowser.js deleted file mode 100644 index d15c173632..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/FolderBrowser/Js/folderbrowser.js +++ /dev/null @@ -1,468 +0,0 @@ - -Umbraco.Sys.registerNamespace("Umbraco.Controls"); - -(function ($, Base, window, document, undefined) { - - var itemMappingOptions = { - 'create': function (o) { - var item = ko.mapping.fromJS(o.data); - item.selected = ko.observable(false); - item.toggleSelected = function (itm, e) { - - if (this.selected()) { - return; - } - - if (!e.ctrlKey) { - for (var i = 0; i < o.parent().length; i++) { - o.parent()[i].selected(false); - } - } - - this.selected(true); - }; - item.edit = function () { - //TODO: Could do with a better way of getting to the parent control - $(".umbFolderBrowser").folderBrowserApi()._editItem(this.Id()); - }; - return item; - } - }; - - Umbraco.Controls.FolderBrowser = Base.extend({ - // Private - _el: null, - _elId: null, - _parentId: null, - _nodePath: null, - _opts: null, - _viewModel: null, - - _getChildNodes: function () { - var self = this; - - $.ajaxSetup({ cache: false }); - $.getJSON(self._opts.basePath + "/FolderBrowserService/GetChildren/" + self._parentId, function (data) { - if (data != undefined && data.length > 0) { - ko.mapping.fromJS(data, itemMappingOptions, self._viewModel.items); - } else { - self._viewModel.items([]); - $("img.throbber").hide(); - } - }); - }, - - _getItemById: function (id) { - var self = this; - - var results = ko.utils.arrayFilter(self._viewModel.items(), function (item) { - return item.Id() === id; - }); - - return results.length == 1 ? results[0] : undefined; - }, - - _editItem: function (id) { - var self = this; - - var item = self._getItemById(id); - if (item === undefined) { - throw Error("No item found with the id: " + id); - } - - window.location.href = "editMedia.aspx?id=" + item.Id(); - }, - - _downloadItem: function (id) { - var self = this; - - var item = self._getItemById(id); - if (item === undefined) { - throw Error("No item found with the id: " + id); - } - - window.open(item.FileUrl(), "Download"); - }, - - _deleteItems: function (ids) { - var self = this; - - var msg = ids.length + " item" + ((ids.length > 1) ? "s" : ""); - - if (confirm(window.top.uiKeys['defaultdialogs_confirmdelete'] + ' the selected ' + msg + '?\n\n')) { - $(window.top).trigger("nodeDeleting", []); - - $.getJSON(self._opts.basePath + "/FolderBrowserService/Delete/" + ids.join(), function (data) { - if (data != undefined && data.success) { - //raise nodeDeleted event - $(window.top).trigger("nodeDeleted", []); - - //TODO: Reload current open node in tree - - // Reload nodes - self._getChildNodes(); - - } else { - throw Error("There was an error deleting the selected nodes: " + ids.join()); - } - }); - } - }, - - _initViewModel: function () { - var self = this; - - // Setup the viewmode; - self._viewModel = $.extend({}, { - parent: self, - thumbSize: ko.observable('large'), - filterTerm: ko.observable(''), - items: ko.observableArray([]), - queued: ko.observableArray([]) - }); - - self._viewModel.thumbSize.subscribe(function (newValue) { - $(".umbFolderBrowser .items").removeClass("large").removeClass("medium").removeClass("small").addClass(newValue); - }); - - self._viewModel.filtered = ko.computed(function () { - return ko.utils.arrayFilter(this.items(), function (item) { - return item.Name().toLowerCase().indexOf(self._viewModel.filterTerm().toLowerCase()) > -1 || - item.Tags().toLowerCase().indexOf(self._viewModel.filterTerm().toLowerCase()) > -1; - }); - }, self._viewModel); - - self._viewModel.selected = ko.computed(function () { - return ko.utils.arrayFilter(this.items(), function (item) { - return item.selected(); - }); - }, self._viewModel); - - self._viewModel.selectedIds = ko.computed(function () { - var ids = []; - ko.utils.arrayForEach(this.selected(), function (item) { - ids.push(item.Id()); - }); - return ids; - }, self._viewModel); - - self._viewModel.itemIds = ko.computed(function () { - var ids = []; - ko.utils.arrayForEach(this.items(), function (item) { - ids.push(item.Id()); - }); - return ids; - }, self._viewModel); - - }, - - _initToolbar: function () { - var self = this; - - // Inject the upload button into the toolbar - var button = $(""); - button.click(function (e) { - e.preventDefault(); - $(".upload-overlay").show(); - }); - $(".umb-panel-header .umb-btn-toolbar").append(button); - }, - - _initOverlay: function () { - var self = this; - - // Inject the upload overlay - var instructions = 'draggable' in document.createElement('span') ? "

    Drag files here to upload

    \

    Or, click the button below to choose the items to upload

    " : "

    Click the browse button below to choose the items to upload

    "; - - var overlay = $("
    " + - "
    " + - instructions + - "
    " + - "" + - "" + - "" + - "" + - "" + - "
    • " + - "" + - "" + - "" + - "
    " + - "
    " + - "" + - "" + - "" + - " " + - "" + - "Cancel All   " + - "Close" + - "
    " + - "
    " + - "
    "); - - $("body").prepend(overlay); - - // Create uploader - jQuery("#fileupload").fileUploader({ - allowedExtension: '', - dropTarget: ".upload-overlay", - onAdd: function (data) { - - // Create a bindable version of the data object - var file = { - uploaderId: data.uploaderId, - itemId: data.itemId, - name: ko.observable(data.name), - size: data.size, - progress: ko.observable(data.progress), - status: ko.observable(''), - message: ko.observable(''), - cancel: function () { - if (this.progress() < 100) { - jQuery("#fileupload").fileUploader("cancelItem", this.itemId); - } else { - self._viewModel.queued.remove(this); - } - } - }; - - file.name.subscribe(function (newValue) { - $("#fu-item-" + file.uploaderId + "-" + file.itemId + " input[name=name]").val(newValue); - }); - - // Store item back in context for easy access later - data.context = file; - - // Push bindable item into queue - self._viewModel.queued.push(file); - }, - onDone: function (data) { - switch (data.status) { - case 'success': - self._viewModel.queued.remove(data.context); - break; - case 'error': - data.context.message(data.message); - data.context.status(data.status); - //self._viewModel.queued.remove(data.context); - break; - case 'canceled': - data.context.message("Canceled by user"); - data.context.progress(100); - data.context.status(data.status); - //self._viewModel.queued.remove(data.context); - break; - default: - break; - } - }, - onProgress: function (data) { - data.context.progress(data.progress); - }, - onDoneAll: function () { - self._getChildNodes(); - $(".upload-overlay").hide(); - UmbClientMgr.mainTree().syncTree(self._nodePath.toString(), true, false); - } - }); - - // Hook up uploader buttons - $(".upload-overlay .upload").click(function (e) { - e.preventDefault(); - jQuery("#fileupload").fileUploader("uploadAll"); - }); - - $(".upload-overlay #replaceExisting").click(function () { - $("input[name=replaceExisting]").val($(this).is(":checked")); - }); - - $(".upload-overlay .cancel").click(function (e) { - e.preventDefault(); - $("#fileupload").fileUploader("cancelAll"); - }); - - $(".upload-overlay .close").click(function (e) { - e.preventDefault(); - $(".upload-overlay").hide(); - }); - - // Listen for drag events - $(".umbFolderBrowser").on('dragenter dragover', function (e) { - $(".upload-overlay").show(); - }); - - $(".upload-overlay").on('dragleave dragexit', function (e) { - $(this).hide(); - }).click(function () { - $(this).hide(); - }); - - $(".upload-panel").click(function (e) { - e.stopPropagation(); - }); - }, - - _initContextMenu: function () { - var self = this; - - // Setup context menus - $.contextMenu({ - selector: '.umbFolderBrowser .items li', - callback: function (key, options) { - var id = options.$trigger.data("id"); - switch (key) { - case "edit": - self._editItem(id); - break; - case "download": - self._downloadItem(id); - break; - case "delete": - self._deleteItems(self._viewModel.selectedIds()); - break; - default: - break; - } - }, - items: { - "edit": { name: "Edit", icon: "edit" }, - "download": { name: "Download", icon: "download" }, - "separator1": "-----", - "delete": { name: "Delete", icon: "delete" } - }, - animation: { show: "fadeIn", hide: "fadeOut" } - }); - }, - - _initItems: function () { - var self = this; - - $(".umbFolderBrowser .items").sortable({ - helper: "clone", - opacity: 0.6, - start: function (e, ui) { - // Add dragging class to container - $(".umbFolderBrowser .items").addClass("ui-sortable-dragging"); - }, - update: function (e, ui) { - // Can't sort when filtered so just return - if (self._viewModel.filterTerm().length > 0) { - return; - } - - //var oldIndex = self._viewModel.items.indexOf(self._viewModel.tempItem()); - var newIndex = ui.item.index(); - - $(".umbFolderBrowser .items .selected").sort(function (a, b) { - return parseInt($(a).data("order"), 10) > parseInt($(b).data("order"), 10) ? 1 : -1; - }).each(function (idx, itm) { - var id = $(itm).data("id"); - var item = self._getItemById(id); - if (item !== undefined) { - var oldIndex = self._viewModel.items.indexOf(item); - - // Update the index of the current item in the array - self._viewModel.items.splice((newIndex + idx), 0, self._viewModel.items.splice(oldIndex, 1)[0]); - } - }); - - }, - stop: function (e, ui) { - // Remove dragging class from container - $(".umbFolderBrowser .items").removeClass("ui-sortable-dragging"); - - if (self._viewModel.filterTerm().length > 0) { - $(this).sortable("cancel"); - alert("Can't sort items which have been filtered"); - } - else { - - $.ajax({ - url: self._opts.umbracoPath + "/umbracoapi/media/postsort", - type: 'POST', - contentType: "application/json; charset=utf-8", - dataType: 'json', - data: JSON.stringify({ - parentId: self._parentId, - idSortOrder: self._viewModel.itemIds() - }), - processData: false, - success: function (data, textStatus) { - if (textStatus == "error") { - alert("Could not update sort order"); - self._getChildNodes(); - } - }, - error: function(data) { - alert("Could not update sort order. Err: " + data.statusText); - self._getChildNodes(); - } - }); - } - } - }); - }, - - // Constructor - constructor: function (el, opts) { - var self = this; - - // Store el info - self._el = el; - self._elId = el.id; - - // Grab parent id from element - self._parentId = $(el).data("parentid"); - - // Grab node path from element - self._nodePath = $(el).data("nodepath"); - - // Merge options with default - self._opts = $.extend({ - - // Default options go here - }, opts); - - self._initViewModel(); - self._initToolbar(); - self._initOverlay(); - self._initContextMenu(); - self._initItems(); - - // Bind the viewmodel - ko.applyBindings(self._viewModel, el); - ko.applyBindings(self._viewModel, $(".upload-overlay").get(0)); - - // Grab children media items - self._getChildNodes(); - } - - // Public - }); - - $.fn.folderBrowser = function (o) { - if ($(this).length != 1) { - throw "Only one folder browser can exist on the page at any one time"; - } - - return $(this).each(function () { - var folderBrowser = new Umbraco.Controls.FolderBrowser(this, o); - $(this).data("api", folderBrowser); - }); - }; - - $.fn.folderBrowserApi = function () { - //ensure there's only 1 - if ($(this).length != 1) { - throw "Requesting the API can only match one element"; - } - - //ensure thsi is a collapse panel - if ($(this).data("api") === null) { - throw "The matching element had not been bound to a folderBrowser"; - } - - return $(this).data("api"); - }; - -})(jQuery, base2.Base, window, document); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/MacroContainer/macroContainer.css b/src/Umbraco.Web.UI/umbraco_client/MacroContainer/macroContainer.css deleted file mode 100644 index d789de27fc..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/MacroContainer/macroContainer.css +++ /dev/null @@ -1,46 +0,0 @@ -.macroeditor -{ - border: 2px dotted #33cc66; - padding: 8px 5px 10px 25px; - margin: 5px 0; -} -.macroeditor:hover -{ - border: 2px solid #33cc66; - cursor: move; -} - -.macroeditor td -{ - padding: 10px 0; -} - -.macroeditor h4 -{ - font-size: 14px; - font-weight: bold; -} - - -.macroeditor .macroDelete -{ - float: right; - border: 1px solid #f00; - background-color: #fcc; - padding: 3px; - text-decoration: none; -} - -.macroContainerAdd -{ - font-size: 14px; - font-weight: bold; - text-decoration: none; -} - -.macrocontainer -{ - margin: 5px 0; - border: 1px solid #ccc; - padding: 10px; -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/MaskedInput/jquery.maskedinput-1.3.min.js b/src/Umbraco.Web.UI/umbraco_client/MaskedInput/jquery.maskedinput-1.3.min.js deleted file mode 100644 index bfae047791..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/MaskedInput/jquery.maskedinput-1.3.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/* -Masked Input plugin for jQuery -Copyright (c) 2007-2011 Josh Bush (digitalbush.com) -Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license) -Version: 1.3 -*/ -(function (a) { var b = (a.browser.msie ? "paste" : "input") + ".mask", c = window.orientation != undefined; a.mask = { definitions: { 9: "[0-9]", a: "[A-Za-z]", "*": "[A-Za-z0-9]" }, dataName: "rawMaskFn" }, a.fn.extend({ caret: function (a, b) { if (this.length != 0) { if (typeof a == "number") { b = typeof b == "number" ? b : a; return this.each(function () { if (this.setSelectionRange) this.setSelectionRange(a, b); else if (this.createTextRange) { var c = this.createTextRange(); c.collapse(!0), c.moveEnd("character", b), c.moveStart("character", a), c.select() } }) } if (this[0].setSelectionRange) a = this[0].selectionStart, b = this[0].selectionEnd; else if (document.selection && document.selection.createRange) { var c = document.selection.createRange(); a = 0 - c.duplicate().moveStart("character", -1e5), b = a + c.text.length } return { begin: a, end: b} } }, unmask: function () { return this.trigger("unmask") }, mask: function (d, e) { if (!d && this.length > 0) { var f = a(this[0]); return f.data(a.mask.dataName)() } e = a.extend({ placeholder: "_", completed: null }, e); var g = a.mask.definitions, h = [], i = d.length, j = null, k = d.length; a.each(d.split(""), function (a, b) { b == "?" ? (k--, i = a) : g[b] ? (h.push(new RegExp(g[b])), j == null && (j = h.length - 1)) : h.push(null) }); return this.trigger("unmask").each(function () { function v(a) { var b = f.val(), c = -1; for (var d = 0, g = 0; d < k; d++) if (h[d]) { l[d] = e.placeholder; while (g++ < b.length) { var m = b.charAt(g - 1); if (h[d].test(m)) { l[d] = m, c = d; break } } if (g > b.length) break } else l[d] == b.charAt(g) && d != i && (g++, c = d); if (!a && c + 1 < i) f.val(""), t(0, k); else if (a || c + 1 >= i) u(), a || f.val(f.val().substring(0, c + 1)); return i ? d : j } function u() { return f.val(l.join("")).val() } function t(a, b) { for (var c = a; c < b && c < k; c++) h[c] && (l[c] = e.placeholder) } function s(a) { var b = a.which, c = f.caret(); if (a.ctrlKey || a.altKey || a.metaKey || b < 32) return !0; if (b) { c.end - c.begin != 0 && (t(c.begin, c.end), p(c.begin, c.end - 1)); var d = n(c.begin - 1); if (d < k) { var g = String.fromCharCode(b); if (h[d].test(g)) { q(d), l[d] = g, u(); var i = n(d); f.caret(i), e.completed && i >= k && e.completed.call(f) } } return !1 } } function r(a) { var b = a.which; if (b == 8 || b == 46 || c && b == 127) { var d = f.caret(), e = d.begin, g = d.end; g - e == 0 && (e = b != 46 ? o(e) : g = n(e - 1), g = b == 46 ? n(g) : g), t(e, g), p(e, g - 1); return !1 } if (b == 27) { f.val(m), f.caret(0, v()); return !1 } } function q(a) { for (var b = a, c = e.placeholder; b < k; b++) if (h[b]) { var d = n(b), f = l[b]; l[b] = c; if (d < k && h[d].test(f)) c = f; else break } } function p(a, b) { if (!(a < 0)) { for (var c = a, d = n(b); c < k; c++) if (h[c]) { if (d < k && h[c].test(l[d])) l[c] = l[d], l[d] = e.placeholder; else break; d = n(d) } u(), f.caret(Math.max(j, a)) } } function o(a) { while (--a >= 0 && !h[a]); return a } function n(a) { while (++a <= k && !h[a]); return a } var f = a(this), l = a.map(d.split(""), function (a, b) { if (a != "?") return g[a] ? e.placeholder : a }), m = f.val(); f.data(a.mask.dataName, function () { return a.map(l, function (a, b) { return h[b] && a != e.placeholder ? a : null }).join("") }), f.attr("readonly") || f.one("unmask", function () { f.unbind(".mask").removeData(a.mask.dataName) }).bind("focus.mask", function () { m = f.val(); var b = v(); u(); var c = function () { b == d.length ? f.caret(0, b) : f.caret(b) }; (a.browser.msie ? c : function () { setTimeout(c, 0) })() }).bind("blur.mask", function () { v(), f.val() != m && f.change() }).bind("keydown.mask", r).bind("keypress.mask", s).bind(b, function () { setTimeout(function () { f.caret(v(!0)) }, 0) }), v() }) } }) })(jQuery) \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_bg.gif b/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_bg.gif deleted file mode 100644 index de552c543e..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_bg.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxfooter_bg.gif b/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxfooter_bg.gif deleted file mode 100644 index a2a9a75c68..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxfooter_bg.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxfooter_statusBar_bg.gif b/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxfooter_statusBar_bg.gif deleted file mode 100644 index 33edb379a3..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxfooter_statusBar_bg.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxfooter_statusBar_h2_bg.gif b/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxfooter_statusBar_h2_bg.gif deleted file mode 100644 index 44fb09b675..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxfooter_statusBar_h2_bg.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxhead_bg.gif b/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxhead_bg.gif deleted file mode 100644 index 33de9b46f9..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxhead_bg.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxhead_h2_bg.gif b/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxhead_h2_bg.gif deleted file mode 100644 index b6d6208013..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Panel/images/panel_boxhead_h2_bg.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Panel/javascript.js b/src/Umbraco.Web.UI/umbraco_client/Panel/javascript.js deleted file mode 100644 index aca9e28529..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Panel/javascript.js +++ /dev/null @@ -1,9 +0,0 @@ -//OBSOLETE! THIS IS LEGACY CODE AND WILL BE REMOVED IN FUTURE VERSIONS! - -function resizePanel(PanelName, hasMenu, redoOnResize) { - //DO NOTHING -} - -function resizePanelTo(PanelName, hasMenu, pWidth, pHeight) { - //DO NOTHING -} diff --git a/src/Umbraco.Web.UI/umbraco_client/Panel/style.css b/src/Umbraco.Web.UI/umbraco_client/Panel/style.css deleted file mode 100644 index c59ecd925a..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Panel/style.css +++ /dev/null @@ -1,101 +0,0 @@ - .panel { - margin: 0 auto; /* center for now */ - - background: url(images/panel_bg.gif) top repeat-x; - font-size: 100%; - padding: 0px; - display: block; - border: 0px; - position: relative; - } - - .panelstatus { - margin: 0 auto; /* center for now */ - width: 17em; /* ems so it will grow */ - font-size: 100%; - } - - .panel .boxhead { - background: url(images/panel_boxhead_bg.gif) top left no-repeat; - margin: 0px; - padding: 0px; - text-align: center; - display: block; - width: auto; - border: 0px; - position: relative; - } - - .panel .boxhead h2, .panel .boxfooter h2 { - background: url(images/panel_boxhead_h2_bg.gif) top right no-repeat; - margin: 0px; - padding: 3px 0px 1px 10px; - font-weight: bold; - text-align: left; - font-family: "Trebuchet MS", verdana, arial; - font-size: 10px; color: #378080; - display: block; - height: 14px; - width: auto; - } - - .panel .boxbody { - border-top: 1px solid #cac9c9; - border-right: 1px solid #cac9c9; - background: url(images/panel_boxfooter_bg.gif) bottom left repeat-y #fff; - padding: 0px; - margin: 0px; - position: relative; - } - - .panel .boxfooter { - height: 12px; - margin: 0; - padding: 0px; - background: url(images/panel_boxfooter_bg.gif) 11px 11px repeat-x #fff; - } - - .panel .boxfooter .statusBar{height: 12px; margin: 0px; background: url(images/panel_boxfooter_statusBar_bg.gif) top left no-repeat;} - .panel .boxfooter .statusBar h2{display: block; height: 12px; margin: 0px; padding: 0px; background: url(images/panel_boxfooter_statusBar_h2_bg.gif) top right no-repeat;} - - - .panelstatus .boxfooter { - font-size: 9px; - color: #999; - height: 15px; - margin: 0; - padding: 5px 0 8px 15px; - } - - .panel .content { - overflow: auto; - padding: 0px 10px 0px 10px; - margin:0px; - position: relative; - width: auto !Important; - width: 10em; - } - - .panel .content .innerContent{padding: 0px; margin: 0px; width: 100%;} - - #treeWindow .innerContent{height: 100%;} - - .panel .menubar_panel { - background-image: url(../tabView/images/background.gif); - height:32px; - - display: block; - border-bottom:1px solid #CAC9C9; - border-left:1px solid #CAC9C9; - border-right:1px solid #CAC9C9 !Important; - - overflow:hidden; - padding: 0px; - margin: 0px; - } - - - .guiDialogTinyMark { - font-family: "Trebuchet MS", verdana, arial; - font-size: 10px; color: #606057; - } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Preview/previewModeBadge.png b/src/Umbraco.Web.UI/umbraco_client/Preview/previewModeBadge.png deleted file mode 100644 index af6a9789e6..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Preview/previewModeBadge.png and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Preview/umbracoPreview.css b/src/Umbraco.Web.UI/umbraco_client/Preview/umbracoPreview.css deleted file mode 100644 index c54d85cd15..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Preview/umbracoPreview.css +++ /dev/null @@ -1,4 +0,0 @@ -#umbracoPreviewBadge -{ - position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background-image: url(previewModeBadge.png) no-repeat; -} diff --git a/src/Umbraco.Web.UI/umbraco_client/PunyCode/punycode.min.js b/src/Umbraco.Web.UI/umbraco_client/PunyCode/punycode.min.js index e14359fbaf..5b5c120d4d 100644 --- a/src/Umbraco.Web.UI/umbraco_client/PunyCode/punycode.min.js +++ b/src/Umbraco.Web.UI/umbraco_client/PunyCode/punycode.min.js @@ -1,2 +1,2 @@ -/*! https://mths.be/punycode v1.3.2 by @mathias */ -!function(a){function b(a){throw RangeError(E[a])}function c(a,b){for(var c=a.length,d=[];c--;)d[c]=b(a[c]);return d}function d(a,b){var d=a.split("@"),e="";d.length>1&&(e=d[0]+"@",a=d[1]),a=a.replace(D,".");var f=a.split("."),g=c(f,b).join(".");return e+g}function e(a){for(var b,c,d=[],e=0,f=a.length;f>e;)b=a.charCodeAt(e++),b>=55296&&56319>=b&&f>e?(c=a.charCodeAt(e++),56320==(64512&c)?d.push(((1023&b)<<10)+(1023&c)+65536):(d.push(b),e--)):d.push(b);return d}function f(a){return c(a,function(a){var b="";return a>65535&&(a-=65536,b+=H(a>>>10&1023|55296),a=56320|1023&a),b+=H(a)}).join("")}function g(a){return 10>a-48?a-22:26>a-65?a-65:26>a-97?a-97:t}function h(a,b){return a+22+75*(26>a)-((0!=b)<<5)}function i(a,b,c){var d=0;for(a=c?G(a/x):a>>1,a+=G(a/b);a>F*v>>1;d+=t)a=G(a/F);return G(d+(F+1)*a/(a+w))}function j(a){var c,d,e,h,j,k,l,m,n,o,p=[],q=a.length,r=0,w=z,x=y;for(d=a.lastIndexOf(A),0>d&&(d=0),e=0;d>e;++e)a.charCodeAt(e)>=128&&b("not-basic"),p.push(a.charCodeAt(e));for(h=d>0?d+1:0;q>h;){for(j=r,k=1,l=t;h>=q&&b("invalid-input"),m=g(a.charCodeAt(h++)),(m>=t||m>G((s-r)/k))&&b("overflow"),r+=m*k,n=x>=l?u:l>=x+v?v:l-x,!(n>m);l+=t)o=t-n,k>G(s/o)&&b("overflow"),k*=o;c=p.length+1,x=i(r-j,c,0==j),G(r/c)>s-w&&b("overflow"),w+=G(r/c),r%=c,p.splice(r++,0,w)}return f(p)}function k(a){var c,d,f,g,j,k,l,m,n,o,p,q,r,w,x,B=[];for(a=e(a),q=a.length,c=z,d=0,j=y,k=0;q>k;++k)p=a[k],128>p&&B.push(H(p));for(f=g=B.length,g&&B.push(A);q>f;){for(l=s,k=0;q>k;++k)p=a[k],p>=c&&l>p&&(l=p);for(r=f+1,l-c>G((s-d)/r)&&b("overflow"),d+=(l-c)*r,c=l,k=0;q>k;++k)if(p=a[k],c>p&&++d>s&&b("overflow"),p==c){for(m=d,n=t;o=j>=n?u:n>=j+v?v:n-j,!(o>m);n+=t)x=m-o,w=t-o,B.push(H(h(o+x%w,0))),m=G(x/w);B.push(H(h(m,0))),j=i(d,r,f==g),d=0,++f}++d,++c}return B.join("")}function l(a){return d(a,function(a){return B.test(a)?j(a.slice(4).toLowerCase()):a})}function m(a){return d(a,function(a){return C.test(a)?"xn--"+k(a):a})}var n="object"==typeof exports&&exports&&!exports.nodeType&&exports,o="object"==typeof module&&module&&!module.nodeType&&module,p="object"==typeof global&&global;(p.global===p||p.window===p||p.self===p)&&(a=p);var q,r,s=2147483647,t=36,u=1,v=26,w=38,x=700,y=72,z=128,A="-",B=/^xn--/,C=/[^\x20-\x7E]/,D=/[\x2E\u3002\uFF0E\uFF61]/g,E={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},F=t-u,G=Math.floor,H=String.fromCharCode;if(q={version:"1.3.2",ucs2:{decode:e,encode:f},decode:j,encode:k,toASCII:m,toUnicode:l},"function"==typeof define&&"object"==typeof define.amd&&define.amd)define("punycode",function(){return q});else if(n&&o)if(module.exports==n)o.exports=q;else for(r in q)q.hasOwnProperty(r)&&(n[r]=q[r]);else a.punycode=q}(this); \ No newline at end of file +/*! https://mths.be/punycode v1.3.2 by @mathias */ +!function (a) { function b(a) { throw RangeError(E[a]) } function c(a, b) { for (var c = a.length, d = []; c--;) d[c] = b(a[c]); return d } function d(a, b) { var d = a.split("@"), e = ""; d.length > 1 && (e = d[0] + "@", a = d[1]), a = a.replace(D, "."); var f = a.split("."), g = c(f, b).join("."); return e + g } function e(a) { for (var b, c, d = [], e = 0, f = a.length; f > e;) b = a.charCodeAt(e++), b >= 55296 && 56319 >= b && f > e ? (c = a.charCodeAt(e++), 56320 == (64512 & c) ? d.push(((1023 & b) << 10) + (1023 & c) + 65536) : (d.push(b), e--)) : d.push(b); return d } function f(a) { return c(a, function (a) { var b = ""; return a > 65535 && (a -= 65536, b += H(a >>> 10 & 1023 | 55296), a = 56320 | 1023 & a), b += H(a) }).join("") } function g(a) { return 10 > a - 48 ? a - 22 : 26 > a - 65 ? a - 65 : 26 > a - 97 ? a - 97 : t } function h(a, b) { return a + 22 + 75 * (26 > a) - ((0 != b) << 5) } function i(a, b, c) { var d = 0; for (a = c ? G(a / x) : a >> 1, a += G(a / b) ; a > F * v >> 1; d += t) a = G(a / F); return G(d + (F + 1) * a / (a + w)) } function j(a) { var c, d, e, h, j, k, l, m, n, o, p = [], q = a.length, r = 0, w = z, x = y; for (d = a.lastIndexOf(A), 0 > d && (d = 0), e = 0; d > e; ++e) a.charCodeAt(e) >= 128 && b("not-basic"), p.push(a.charCodeAt(e)); for (h = d > 0 ? d + 1 : 0; q > h;) { for (j = r, k = 1, l = t; h >= q && b("invalid-input"), m = g(a.charCodeAt(h++)), (m >= t || m > G((s - r) / k)) && b("overflow"), r += m * k, n = x >= l ? u : l >= x + v ? v : l - x, !(n > m) ; l += t) o = t - n, k > G(s / o) && b("overflow"), k *= o; c = p.length + 1, x = i(r - j, c, 0 == j), G(r / c) > s - w && b("overflow"), w += G(r / c), r %= c, p.splice(r++, 0, w) } return f(p) } function k(a) { var c, d, f, g, j, k, l, m, n, o, p, q, r, w, x, B = []; for (a = e(a), q = a.length, c = z, d = 0, j = y, k = 0; q > k; ++k) p = a[k], 128 > p && B.push(H(p)); for (f = g = B.length, g && B.push(A) ; q > f;) { for (l = s, k = 0; q > k; ++k) p = a[k], p >= c && l > p && (l = p); for (r = f + 1, l - c > G((s - d) / r) && b("overflow"), d += (l - c) * r, c = l, k = 0; q > k; ++k) if (p = a[k], c > p && ++d > s && b("overflow"), p == c) { for (m = d, n = t; o = j >= n ? u : n >= j + v ? v : n - j, !(o > m) ; n += t) x = m - o, w = t - o, B.push(H(h(o + x % w, 0))), m = G(x / w); B.push(H(h(m, 0))), j = i(d, r, f == g), d = 0, ++f } ++d, ++c } return B.join("") } function l(a) { return d(a, function (a) { return B.test(a) ? j(a.slice(4).toLowerCase()) : a }) } function m(a) { return d(a, function (a) { return C.test(a) ? "xn--" + k(a) : a }) } var n = "object" == typeof exports && exports && !exports.nodeType && exports, o = "object" == typeof module && module && !module.nodeType && module, p = "object" == typeof global && global; (p.global === p || p.window === p || p.self === p) && (a = p); var q, r, s = 2147483647, t = 36, u = 1, v = 26, w = 38, x = 700, y = 72, z = 128, A = "-", B = /^xn--/, C = /[^\x20-\x7E]/, D = /[\x2E\u3002\uFF0E\uFF61]/g, E = { overflow: "Overflow: input needs wider integers to process", "not-basic": "Illegal input >= 0x80 (not a basic code point)", "invalid-input": "Invalid input" }, F = t - u, G = Math.floor, H = String.fromCharCode; if (q = { version: "1.3.2", ucs2: { decode: e, encode: f }, decode: j, encode: k, toASCII: m, toUnicode: l }, "function" == typeof define && "object" == typeof define.amd && define.amd) define("punycode", function () { return q }); else if (n && o) if (module.exports == n) o.exports = q; else for (r in q) q.hasOwnProperty(r) && (n[r] = q[r]); else a.punycode = q }(this); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/NodeDefinition.js b/src/Umbraco.Web.UI/umbraco_client/Tree/NodeDefinition.js deleted file mode 100644 index a14e5b25bb..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Tree/NodeDefinition.js +++ /dev/null @@ -1,50 +0,0 @@ -/// -/// - -Umbraco.Sys.registerNamespace("Umbraco.Controls"); - -Umbraco.Controls.NodeDefinition = function() { - /// - /// An object that defines the details of a current node selected in the tree. - /// Contains the logic to modify the html markup of a node such as styles. - /// - return { - jsTree: null, - jsNode: null, - nodeId: null, - nodeName: null, - nodeType: null, - sourceUrl: null, - menu: null, - treeType: null, - _isDebug: false, - - updateDefinition: function(jsTree, jsNode, nodeId, nodeName, nodeType, sourceUrl, menu, treeType) { - /// - /// Updates the current objects properties at one time - /// - /// A reference to the current node's jsTree - /// A reference to the jquery li node - /// The node id of the node - /// The node name of the node - /// The node type/tree type of the node - - this.jsTree = jsTree; - this.jsNode = jsNode; - this.nodeId = nodeId; - this.nodeName = nodeName; - this.nodeType = nodeType; - this.sourceUrl = sourceUrl; - this.menu = menu; - this.treeType = treeType; - - this._debug("updateDefinition: " + nodeId + ", " + nodeName + ", " + nodeType); - }, - - _debug: function(strMsg) { - if (this._isDebug) { - Sys.Debug.trace("NodeDefinition: " + strMsg); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/dot_for_ie.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/dot_for_ie.gif deleted file mode 100644 index c0cc5fda7c..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/dot_for_ie.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/icons.png b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/icons.png deleted file mode 100644 index b12618a80b..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/icons.png and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/style.css b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/style.css deleted file mode 100644 index fab2676a8a..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/style.css +++ /dev/null @@ -1,30 +0,0 @@ -/* LOCKED */ -.tree-default .locked li a { color:gray; } -/* DOTS */ -.tree-default ul { background-position:6px 1px; background-repeat:repeat-y; background-image:url(data:image/gif;base64,R0lGODlhAgACAIAAAB4dGf///yH5BAEAAAEALAAAAAACAAIAAAICRF4AOw==); _background-image:url("dot_for_ie.gif"); *background-image:url("dot_for_ie.gif"); } -.tree-default li { background-position:-64px -16px; background-repeat:no-repeat; background-image:url("icons.png"); } -/* NO DOTS */ -.tree-default .no_dots, .tree-default .no_dots ul { background:transparent; } -.tree-default .no_dots li.leaf { background-image:none; background-color:transparent; } -/* OPEN or CLOSED */ -.tree-default li.open { background:url("icons.png") -32px -48px no-repeat; } -.tree-default li.closed, #jstree-dragged.tree-default li li.open { background:url("icons.png") -48px -32px no-repeat; } -#jstree-marker { background-image:url("icons.png"); } -/* DEFAULT, HOVER, CLICKED, LOADING STATES */ -.tree-default li a, .tree-default li span { border-radius:3px; -moz-border-radius:3px; -webkit-border-radius:3px; } -.tree-default li a:hover, .tree-default li a.hover, .tree-default li span { background: #e7f4f9; border:1px solid #d8f0fa; padding:0px 3px 0px 3px; } -.tree-default li a.clicked, .tree-default li a.clicked:hover, .tree-default li span.clicked { background: #beebff; border:1px solid #99defd; padding:0px 3px 0px 3px; } -/* ICONS */ -.tree-default ins { background-image:url("icons.png"); background-position:0 0; background-repeat:no-repeat; } -.tree-default ul li a.loading ins { background-image:url("throbber.gif") !important; background-position:0 0 !important; } /* UL is added to make selector stronger */ -.tree-default li a ins.forbidden { background-position:-16px -16px; } -.tree-default .locked li a ins { background-position:0 -48px; } -.tree-default li span ins { background-position:-16px 0; } -#jstree-dragged.tree-default ins { background:url("icons.png") -16px -32px no-repeat; } -#jstree-dragged.tree-default ins.forbidden { background:url("icons.png") -16px -16px no-repeat; } - -/* CONTEXT MENU */ -.tree-default-context a ins { background-image:url("icons.png"); background-repeat:no-repeat; background-position:-64px -64px; } -.tree-default-context a ins.create { background-position:0 -16px; } -.tree-default-context a ins.rename { background-position:-16px 0px; } -.tree-default-context a ins.remove { background-position:0 -32px; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/throbber.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/throbber.gif deleted file mode 100644 index 95ff649d41..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/default/throbber.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/arrows.png b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/arrows.png deleted file mode 100644 index f2017bf2ac..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/arrows.png and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/check_0.png b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/check_0.png deleted file mode 100644 index 2fa0ff7e71..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/check_0.png and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/check_1.png b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/check_1.png deleted file mode 100644 index d5483f63a7..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/check_1.png and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/check_2.png b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/check_2.png deleted file mode 100644 index 904c227b64..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/check_2.png and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/contextMenuBg.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/contextMenuBg.gif deleted file mode 100644 index 28ca91665d..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/contextMenuBg.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/icons.png b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/icons.png deleted file mode 100644 index b12618a80b..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/icons.png and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/newVersion_overlay.png b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/newVersion_overlay.png deleted file mode 100644 index 03de50df38..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/newVersion_overlay.png and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/protect_overlay.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/protect_overlay.gif deleted file mode 100644 index 6b5aa32120..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/protect_overlay.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/style.css b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/style.css deleted file mode 100644 index fa3fc3b3e9..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/style.css +++ /dev/null @@ -1,206 +0,0 @@ -/* OVERRIDE TREE_COMPONENT.css */ -.tree.tree-umbraco li -{ - line-height: 20px; - min-height: 20px; - font: icon; - position:relative; - font-family: Arial,Lucida Grande; - font-size: 12px; -} -.tree.tree-umbraco li a -{ - background-image: url(../../../../umbraco/images/umbraco/sprites.png); - /*background-color: #fff;*/ - padding: 0px 0px 2px 18px; - border: 0; - color: #2f2f2f; - background-repeat:no-repeat !important; - line-height: 18px; - height: 18px; - width: 100%; -} -.tree.tree-umbraco ul {margin-left:0px;} -.tree.tree-umbraco ul li ul {padding-left:5px;} -.tree.tree-umbraco li a ins { display:none;} -/* LOADER */ -.tree.tree-umbraco ul li a.loading ins { background-image:url("throbber.gif") !important; background-position:0 0 !important;display:inline-block; float:left;} -.tree.tree-umbraco ul li a.loading {padding:0px !important;background:none;} -/* OPEN or CLOSED */ -.tree.tree-umbraco li { padding-left:0px; } -.tree.tree-umbraco li li { padding-left: 15px; } -.tree.tree-umbraco li.open { background-image:none; } -.tree.tree-umbraco li li.open { background:url("arrows.png") -18px -43px no-repeat; } -.tree.tree-umbraco li li:hover.open { background-position: -2px -43px; } -.tree.tree-umbraco li.closed, #jstree-dragged.tree-default li li.open { background:url("arrows.png") 0px -20px no-repeat; } -.tree.tree-umbraco li:hover.closed, #jstree-dragged.tree-default li li:hover.open { background-position:0px 2px; } -/* Ensure no sprite is loaded */ -.tree.tree-umbraco li a.noSpr, .tree.tree-umbraco li a.noSpr:hover -{ - background-position:0px 0px; - background:none; -} -/* Hover state */ -.tree.tree-umbraco li a:hover -{ - text-decoration: underline; -} -/* Menu text styles */ -.tree.tree-umbraco li div -{ - padding: 1px 3px 1px 3px; - height:16px; - line-height:16px; - cursor:pointer; -} -/* highlight the selected element */ -.tree.tree-umbraco li .clicked div -{ - padding: 0px 2px 0px 2px; - background: #D5EFFC; - border:1px solid #99DEFD; - -moz-border-radius: 2px; - display: inline; - width: auto; -} - - -/* renaming */ -.tree.tree-umbraco li div.renaming -{ - padding: 0px 2px 0px 12px; - background: #D5EFFC; - border:1px solid #99DEFD; - -moz-border-radius: 2px; -} -.tree-umbraco li div.renaming input -{ - border:0px; - font-size: 11px; -} - - - -/* Used for checkbox tree */ - -.tree.tree-umbraco.inheritedcheckbox li a, -.tree.tree-umbraco.inheritedcheckbox li a.unchecked, -.tree.tree-umbraco.checkbox li a, -.tree.tree-umbraco.checkbox li a.unchecked - { background: url("check_0.png") no-repeat 0px 0px ! important; } -.tree.tree-umbraco.inheritedcheckbox li a.undetermined, -.tree.tree-umbraco.checkbox li a.undetermined -{ background:url("check_1.png") no-repeat 0px 0px ! important; } -.tree.tree-umbraco.inheritedcheckbox li a.checked, -.tree.tree-umbraco.checkbox li a.checked -{ background:url("check_2.png") no-repeat 0px 0px ! important; } - -/* CONTEXT MENU (overriding default styles) */ - -#jstree-contextmenu.tree-umbraco-context -{ - background: #f0f0f0 url(contextMenuBg.gif) repeat-y left; - border: 1px solid #979797; - padding: 3px; - padding-top: 2px; - padding-bottom: 0px; - width: 180px; - font-family: Arial,Lucida Grande; - font-size: 11px; - z-index: 1000; - margin: 0px; - display: block; -} - - -#jstree-contextmenu.tree-umbraco-context li -{ - border-left:none; - background:transparent; - height: 26px; -} - -#jstree-contextmenu.tree-umbraco-context li a -{ - border:0px; - padding: 2px; - height: 20px; - line-height:20px; - color: #1a1818; - text-decoration: none; - text-indent: 0px; - margin-top: 1px !Important; - margin-bottom: 3px !Important; -} - -#jstree-contextmenu.tree-umbraco-context li a:hover -{ - border: 1px solid #a8d8eb; - padding: 1px; - background: #dcecf3; - text-indent: 0px; -} -#jstree-contextmenu.tree-umbraco-context .separator -{ - background: #FFFFFF; - border-top: 1px solid #E0E0E0; - font-size: 1px; - height: 1px; - line-height: 1px; - margin: 0 2px 4px 29px; - min-height: 1px; - display: block; -} -#jstree-contextmenu.tree-umbraco-context .separator span {display:none;} -#jstree-contextmenu.tree-umbraco-context img -{ - height: 16px; - width: 16px; - margin: 1px 10px 0 4px; -} - -#jstree-contextmenu.tree-umbraco-context li a ins {display:none;} -#jstree-contextmenu.tree-umbraco-context li a span {margin:0;padding-left:1px;background:none;} -#jstree-contextmenu.tree-umbraco-context li a span div.menuLabel{padding-left: 37px;} - -/* Tree icons sprites */ - -.sprTree, -.tree.tree-umbraco li a.sprTree.clicked:hover -{ - background-image: 0px 0px; -} - - - -/* tree node overlays / custom styles */ - -.tree.tree-umbraco li.dim a -{ - color:#C0C0C0; -} - -.tree.tree-umbraco li div.overlay -{ - position: absolute; - width: 8px; - height: 8px; -} - -.tree.tree-umbraco li div.overlay-new -{ - z-index: 500; - left: 11px; - top: 0px; - background: no-repeat url(newVersion_overlay.png); - _filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='protect_overlay.gif'); -} - -.tree.tree-umbraco li div.overlay-protect -{ - z-index: 501; - left: 22px; - top: 8px; - background: no-repeat url(protect_overlay.gif); - _filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='protect_overlay.gif'); -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/throbber.gif b/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/throbber.gif deleted file mode 100644 index 95ff649d41..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/Themes/umbraco/throbber.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/UmbracoContext.js b/src/Umbraco.Web.UI/umbraco_client/Tree/UmbracoContext.js deleted file mode 100644 index 5cec4237a7..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Tree/UmbracoContext.js +++ /dev/null @@ -1,89 +0,0 @@ -(function ($) { - ///Custom plugin to create the Umbraco context menu, this configures the context menu plugin before it's callbacks eecute - - $.extend($.tree.plugins, { - "UmbracoContext": { - - settings: { - fullMenu: [], - onBeforeContext: false - }, - - _getContextMenu: function (strMenu) { - /// Builds a new context menu object (array) based on the string representation passed in - - var newMenu = new Array(); - var separatorIndexes = new Array(); - for (var i = 0; i < strMenu.length; i++) { - var letter = strMenu.charAt(i); - //get a js menu item by letter - var menuItem = this._getMenuItemByLetter(letter); - if (menuItem == "separator") { separatorIndexes.push(newMenu.length); } - else if (menuItem != null) newMenu.push(menuItem); - } - for (var i = 0; i < separatorIndexes.length; i++) { - if (separatorIndexes[i] > 0) { - newMenu[separatorIndexes[i] - 1].separator_after = true; - } - } - return newMenu; - }, - - _getMenuItemByLetter: function (letter) { - - /// Finds the menu item in our full menu by the letter and returns object - var fullMenu = $.tree.plugins.UmbracoContext.settings.fullMenu; - //insert selector if it's a comma - if (letter == ",") return "separator"; - for (var m in fullMenu) { - if (fullMenu[m].id == letter) { - return fullMenu[m]; - } - } - return null; - }, - - callbacks: { - oninit: function (t) { - $.tree.plugins.UmbracoContext.settings = t.settings.plugins.UmbracoContext; - //need to remove the defaults - $.tree.plugins.contextmenu.defaults.items = {}; - }, - onrgtclk: function (n, t, e) { - ///Need to set the context menu items for the context menu plugin for the current node - var _this = $.tree.plugins.UmbracoContext; - - var menu = _this.settings.onBeforeContext.call(this, n, t, e); - if (menu != "") { - t.settings.plugins.contextmenu.items = _this._getContextMenu(menu); - } - else { - t.settings.plugins.contextmenu.items = []; - } - } - } - } - }); - $(function () { - //add events to auto hide the menu on a delay - var pause = true; - var timer = null; - $("#jstree-contextmenu").bind("mouseenter", function () { - pause = true; - clearTimeout(timer); - }); - $("#jstree-contextmenu").bind("mouseleave", function () { - pause = false; - timer = setTimeout(function () { - if (!pause) { - $.tree.plugins.contextmenu.hide(); - } - }, 500); - }); - //disable right clicking the context menu, this is for IE bug - $("#jstree-contextmenu").bind("contextmenu", function (e) { - e.preventDefault(); - return false; - }); - }); -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/UmbracoTree.js b/src/Umbraco.Web.UI/umbraco_client/Tree/UmbracoTree.js deleted file mode 100644 index 04dd13122d..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Tree/UmbracoTree.js +++ /dev/null @@ -1,1054 +0,0 @@ -/// -/// -/// -/// -/// -/// -/// -/// - -Umbraco.Sys.registerNamespace("Umbraco.Controls"); - -(function($) { - - $.fn.UmbracoTree = function(opts) { - /// jQuery plugin to create an Umbraco tree. See option remarks below. - return this.each(function() { - new Umbraco.Controls.UmbracoTree().init($(this), opts); - }); - }; - $.fn.UmbracoTreeAPI = function() { - /// exposes the Umbraco Tree api for the selected object - if ($(this).length != 1) { - throw "UmbracoTreeAPI selector requires that there be exactly one control selected, this selector returns " + $(this).length; - }; - // check if there's an api stored for the id of the object specified, if there's not - // check if the first child is a div and if that has the api specified - var api = Umbraco.Controls.UmbracoTree.inst[$(this).attr("id")] || null; - if (api == null) - return Umbraco.Controls.UmbracoTree.inst[$(this).children("div").attr("id")] || null; - return api; - }; - - Umbraco.Controls.TreeDefaultOptions = function() { - return { - doNotInit: false, //this is used for the main umbraco tree so that the tree doesn't fully initialize until rebuildTree is explicitly called - jsonFullMenu: {}, //The tree menu, by default is empty - appActions: null, //A reference to a MenuActions object - deletingText: "Deleting...", //the txt to display when a node is deleting - treeMode: "standard", //determines the type of tree: false/null = normal, 'checkbox' = checkboxes enabled, 'inheritedcheckbox' = parent nodes have checks inherited from children - recycleBinId: -20, //the id of the recycle bin for the current tree - serviceUrl: "", //Url path for the tree client service - dataUrl: "", //Url path for the tree data service - - //These are all properties of the ITreeService and are - //used to pass the properties in to the InitAppTreeData service - app: "", //the application name to render - treeType: "", //the active tree application - showContext: true, //boolean indicating whether or not to show a context menu - isDialog: false, - dialogMode: "none", //boolean indicating whether or not the tree is in dialog mode - functionToCall: "", - nodeKey: "" - }; - }; - - Umbraco.Controls.UmbracoTree = function() { - /// - /// The object that manages the Umbraco tree. - /// Has these events: syncNotFound, syncFound, rebuiltTree, newchildNodeFound, nodeMoved, nodeCopied, ajaxError, nodeClicked - /// - return { - _opts: {}, - _cntr: ++Umbraco.Controls.UmbracoTree.cntr, //increments the number of tree instances. - _containerId: null, - _context: null, //the jquery context used to get the container - _actionNode: new Umbraco.Controls.NodeDefinition(), //the most recent node right clicked for context menu - _activeTreeType: "content", //tracks which is the active tree type, this is used in searching and syncing. - _tree: null, //reference to the jsTree object - _isEditMode: false, //not really used YET - _isDebug: false, //set to true to enable alert debugging - _loadedApps: [], //stores the application names that have been loaded to track which JavaScript code has been inserted into the DOM - _treeClass: "umbTree", //used for other libraries to detect which elements are an umbraco tree - _currenAJAXRequest: false, //used to determine if there is currently an ajax request being executed. - _isSyncing: false, - - addEventHandler: function(fnName, fn) { - /// Adds an event listener to the event name event - this._getContainer().bind(fnName, fn); - }, - - removeEventHandler: function(fnName, fn) { - /// Removes an event listener to the event name event - this._getContainer().unbind(fnName, fn); - }, - - _raiseEvent: function(evName, args) { - /// Raises an event and attaches it to the container - this._getContainer().trigger(evName, args); - }, - - init: function(jItem, opts) { - /// Initializes the tree with the options and stores the tree API in the jQuery data object for the current element - this._debug("init: creating new tree with class/id: " + jItem.attr("class") + " / " + jItem.attr("id")); - - this._opts = $.extend(Umbraco.Controls.TreeDefaultOptions(), opts); - - this._context = jItem.get(0).ownerDocument; - - //wire up event handlers - if (this._opts.appActions != null) { - var _this = this; - //wrapped functions maintain scope - this._opts.appActions.addEventHandler("nodeDeleting", function (E) { _this.onNodeDeleting(E); }); - this._opts.appActions.addEventHandler("nodeDeleted", function (E) { _this.onNodeDeleted(E); }); - this._opts.appActions.addEventHandler("nodeRefresh", function (E) { _this.onNodeRefresh(E); }); - this._opts.appActions.addEventHandler("publicError", function (E, err) { _this.onPublicError(E, err); }); - } - - this._containerId = jItem.attr("id"); - - if (!this._opts.doNotInit) { - //initializes the jsTree - this._tree = $.tree.create(); - this._tree.init(this._getContainer(), this._getInitOptions()); - } - - jItem.addClass(this._treeClass); - - //store a reference to this api by the id and the counter - Umbraco.Controls.UmbracoTree.inst[this._cntr] = this; - if (!this._getContainer().attr("id")) this._getContainer().attr("id", "UmbTree_" + this._cntr); - Umbraco.Controls.UmbracoTree.inst[this._getContainer().attr("id")] = Umbraco.Controls.UmbracoTree.inst[this._cntr]; - - }, - - setRecycleBinNodeId: function(id) { - this._opts.recycleBinId = id; - }, - - //TODO: add public method to clear a specific tree cache - clearTreeCache: function() { - // This will remove all stored trees in client side cache so that the next time a tree needs loading it will be refreshed - this._debug("clearTreeCache..."); - - this._loadedApps = []; - }, - - toggleEditMode: function(enable) { - this._debug("Edit mode. Currently: " + this._tree.settings.rules.draggable); - this._isEditMode = enable; - - this.saveTreeState(this._opts.app); - //need to trick the system so it thinks it's a different app, then rebuild with new rules - var app = this._opts.app; - this._opts.app = "temp"; - this.rebuildTree(app); - - this._debug("Edit mode. New Mode: " + this._tree.settings.rules.draggable); - if (this._opts.appActions) - this._opts.appActions.showSpeachBubble("info", "Tree Edit Mode", "The tree is now operating in edit mode"); - }, - - refreshTree: function(treeType) { - /// This wraps the standard jsTree functionality unless a treeType is specified. If one is, then it will just reload that nodes children - this._debug("refreshTree: " + treeType); - if (!treeType) { - this.rebuildTree(); - } - else { - var allRoots = this._getContainer().find("li[rel='rootNode']"); - var _this = this; - var root = allRoots.filter(function() { - return ($(this).attr("umb:type") == _this._activeTreeType); //filter based on custom namespace requires custom function - }); - if (root.length == 1) { - this._debug("refreshTree: reloading tree type: " + treeType); - this._loadChildNodes(root); - } - else { - //couldn't find it, so refresh the whole tree - this.rebuildTree(); - } - } - - }, - rebuildTree: function(app, callback) { - /// This will rebuild the tree structure for the application specified - - this._debug("rebuildTree"); - - //if app is null, then we will rebuild the current app which also means clearing the cache. - if (!app) { - this.clearTreeCache(); - app = this._opts.app; // zb-00011 #29447 : bugfix assignment - } - else if (this._tree && (this._opts.app.toLowerCase() == app.toLowerCase())) { - this._debug("not rebuilding"); - - //don't rebuild if the tree object exists, the app that's being requested to be loaded is - //flagged as already loaded, and the tree actually has nodes in it - - return; - } - else { - this._opts.app = app; - } - //kill the tree - if (this._tree) { - this._tree.destroy(); - } - - var _this = this; - - //check if we should rebuild from a saved tree - var saveData = this._loadedApps["tree_" + app]; - - this.setActiveTreeType(app); - - if (saveData != null) { - this._debug("rebuildTree: rebuilding from cache: app = " + app); - - //create the tree from the saved data. - //this._initNode = saveData.d; - this._tree = $.tree.create(); - this._tree.init(this._getContainer(), this._getInitOptions(saveData.d)); - - //ensure the static data is gone - this._tree.settings.data.opts.static = null; - - this._configureNodes(this._getContainer().find("li"), true); - //select the last node - var lastSelected = saveData.selected != null ? $(saveData.selected[0]).attr("id") : null; - if (lastSelected != null) { - //create an event handler for the tree sync - var _this = this; - var foundHandler = function(EV, node) { - //remove the event handler from firing again - _this.removeEventHandler("syncFound", foundHandler); - _this._debug("rebuildTree: node synced, selecting node..."); - //ensure the node is selected, ensure the event is fired and reselect is true since jsTree thinks this node is already selected by id - _this.selectNode(node, false, true); - }; - this._debug("rebuildTree: syncing to last selected: " + lastSelected); - //add the event handler for the tree sync and sync the tree - this.addEventHandler("syncFound", foundHandler); - this.setActiveTreeType($(saveData.selected[0]).attr("umb:type")); - this.syncTree(lastSelected); - } - - if (typeof callback == "function") callback.apply(this, [lastSelected]); - } - else { - this._debug("rebuildTree: rebuilding from scratch: app = " + app); - - this._currentAJAXRequest = true; - - _this._tree = $.tree.create(); - _this._tree.init(_this._getContainer(), _this._getInitOptions()); - - if (typeof callback == "function") callback.apply(this, []); - } - - }, - - saveTreeState: function(appAlias) { - /// - /// Saves the state of the current application trees so we can restore it next time the user visits the app - /// - - this._debug("saveTreeState: " + appAlias + " : ajax request? " + this._currentAJAXRequest); - - //clear the saved data for the current app before saving - this._loadedApps["tree_" + appAlias] = null; - - //if an ajax request is currently in progress, abort saving the tree state and set the - //data object for the application to null. - if (!this._currentAJAXRequest) { - //only save the data if there are nodes - var nodeCount = this._getContainer().find("li[rel='dataNode']").length; - if (nodeCount > 0) { - this._debug("saveTreeState: node count = " + nodeCount); - var treeData = this._tree.get(); - //need to update the 'state' of the data. jsTree get doesn't return the state of nodes properly! - this._updateJSONNodeState(treeData); - this._debug("saveTreeState: treeData = " + treeData); - - this._loadedApps["tree_" + appAlias] = { selected: this._tree.selected, d: treeData }; - } - } - }, - - _updateJSONNodeState: function(obj) { - /// - /// A recursive function to store the state of the node for the JSON object when using saveTreeState. - /// This is required since jsTree doesn't output the state of the tree nodes with the request to getJSON method. - /// This is also required to save the correct title for each node since we store our title in a div tag, not just the a tag - /// - - var node = $("li[id='" + obj.attributes.id + "']").filter(function() { - return ($(this).attr("umb:type") == obj.attributes["umb:type"]); //filter based on custom namespace requires custom function - }); - - //saves the correct title - obj.data.title = $.trim(node.children("a").children("div").text()); - obj.state = obj.data.state; - //ensures that the style property of the data contains only single quotes since jsTree puts double around the attr - for (var i in obj.data.attributes) { - if (!obj.data.attributes.hasOwnProperty(i)) continue; - if (i == "style" || i == "class") { - obj.data.attributes[i] = obj.data.attributes[i].replace(/\"/g, "'"); - } - } - - //recurse through children - if (obj.children != null) { - for (var x in obj.children) { - this._updateJSONNodeState(obj.children[x]); - } - } - }, - - syncTree: function(path, forceReload, supressChildReload, newId) { - /// - /// Syncronizes the tree with the path supplied and makes that node visible/selected. - /// - /// The path of the node - /// If true, will ensure that the node to be synced is synced with data from the server - /// This parameter is only used when we don't have a real unique ID for a node, for example for a file. If a filename changes we don't know what the new one is since we are syncing the tree to the old original path. Once we retrieve the results the sync the tree we need to find the result by it's new id and update the node. - - this._debug("syncTree: " + path + ", " + forceReload); - - //set the flag so that multiple synces aren't attempted - this._isSyncing = true; - - this._syncTree.call(this, path, forceReload, null, null, supressChildReload, newId); - - }, - - childNodeCreated: function() { - /// - /// Reloads the children of the current action node and selects the node that didn't exist there before. - /// If it cannot determine which node is new, then no node is selected. If the children are not already - /// loaded, then it is impossible for this method to determine which child is new. - /// - - this._debug("childNodeCreated"); - - //store the current child ids so we can determine which one is the new one - var childrenIds = new Array(); - this._actionNode.jsNode.find("ul > li").each(function() { - childrenIds.push($(this).attr("id")); - }); - var _this = this; - var currId = this._actionNode.nodeId; - this.reloadActionNode(true, false, function(success) { - if (success && childrenIds.length > 0) { - var found = false; - var actionNode = _this.findNode(currId); - if (actionNode) { - actionNode.find("ul > li").each(function() { - //if the id of the current child is not found in the original list, then this is the new one, store it - if ($.inArray($(this).attr("id"), childrenIds) == -1) { - found = $(this); - } - }); - } - if (found) { - _this._debug("childNodeCreated: selecting new child node: " + found.attr("id")); - _this.selectNode(found, true, true); - _this._raiseEvent("newChildNodeFound", [found]); - return; - } - } - _this._debug("childNodeCreated: could not select new child!"); - }); - }, - - moveNode: function(nodeId, parentPath) { - /// Moves a node in the tree. This will remove the existing node by id and sync the tree to the new path - - this._debug("moveNode"); - - //remove the old node - var old = this.findNode(nodeId); - if (old) old.remove(); - - //build the path to the new node - var newPath = parentPath + "," + nodeId; - //create an event handler for the tree sync - var _this = this; - var foundHandler = function(EV, node) { - //remove the event handler from firing again - _this.removeEventHandler("syncFound", foundHandler); - //ensure the node is selected, ensure the event is fired and reselect is true since jsTree thinks this node is already selected by id - _this.selectNode(node, false, true); - _this._raiseEvent("nodeMoved", [node]); - }; - //add the event handler for the tree sync and sync the tree - this.addEventHandler("syncFound", foundHandler); - this.syncTree(newPath); - }, - - copyNode: function(nodeId, parentPath) { - /// Copies a node in the tree. This will keep the current node selected but will sync the tree to show the copied node too - - this._debug("copyNode"); - - var originalNode = this.findNode(nodeId); - - //create an event handler for the tree sync - var _this = this; - var foundHandler = function(EV, node) { - //remove the event handler from firing again - _this.removeEventHandler("syncFound", foundHandler); - //now that the new parent node is found, expand it - _this._loadChildNodes(node, null); - //reselect the original node since sync will select the one that was copied - if (originalNode) _this.selectNode(originalNode, true); - _this._raiseEvent("nodeCopied", [node]); - }; - //add the event handler for the tree sync and sync the to the parent path - this.addEventHandler("syncFound", foundHandler); - this.syncTree(parentPath); - }, - - findNode: function(nodeId, findGlobal) { - /// Returns either the found branch or false if not found in the tree - /// Optional. If true, disregards the tree type and searches the entire tree for the id - var _this = this; - var branch = this._getContainer().find("li[id='" + nodeId + "']"); - if (!findGlobal) branch = branch.filter(function() { - return ($(this).attr("umb:type") == _this._activeTreeType); //filter based on custom namespace requires custom function - }); - var found = branch.length > 0 ? branch : false; - this._debug("findNode: " + nodeId + " in '" + this._activeTreeType + "' tree. Found? " + found); - return found; - }, - - selectNode: function(node, supressEvent, reselect) { - /// - /// Makes the selected node the active node, but only if it is not already selected or if reselect is true. - /// - /// If set to true, will select the node but will supress the onSelected event - /// If set to true, will call the select_branch method even if the node is already selected - - //this._debug("selectNode, edit mode? " + this._isEditMode); - - var selectedId = this._tree.selected != null ? $(this._tree.selected[0]).attr("id") : null; - - this._debug("selectNode (" + node.attr("id") + "). supressEvent? " + supressEvent + ", reselect? " + reselect); - - if (reselect || (selectedId == null || selectedId != node.attr("id"))) { - //if we don't wan the event to fire, we'll set the callback to a null method and set it back after we call the select_branch method - if (supressEvent || this._isEditMode) { - this._tree.settings.callback.onselect = function() { }; - } - this._tree.select_branch(node); - //reset the method / maintain scope in callback - var _this = this; - this._tree.settings.callback.onselect = function(N, T) { _this.onSelect(N, T) }; - } - - }, - - reloadActionNode: function(supressSelect, supressChildReload, callback) { - /// - /// Gets the current action node's parent's data source url, then passes this url and the current action node's id - /// to a web service. The webservice will find the JSON data for the current action node and return it. This - /// will parse the returned JSON into html and replace the current action nodes' markup with the refreshed server data. - /// If by chance, the ajax call fails because of inconsistent data (a developer has implemented poor tree design), then - /// this use the build in jsTree reload which works ok. - /// - /// - /// A callback function which will have a boolean parameter passed. True = the reload was succesful, - /// False = the reload failed and the generic _tree.refresh() method was used. - /// - this._debug("reloadActionNode: supressSelect = " + supressSelect + ", supressChildReload = " + supressChildReload); - - if (this._actionNode != null && this._actionNode.jsNode != null) { - var nodeParent = this._actionNode.jsNode.parents("li:first"); - this._debug("reloadActionNode: found " + nodeParent.length + " parent nodes"); - if (nodeParent.length == 1) { - var nodeDef = this.getNodeDef(nodeParent); - this._debug("reloadActionNode: loading ajax for node: " + nodeDef.nodeId); - var _this = this; - //replace the node to refresh with loading and return the new loading element - var toReplace = $("
  • " + (this._tree.settings.lang.loading || "Loading ...") + "
  • ").replaceAll(this._actionNode.jsNode); - $.get(this._getUrl(nodeDef.sourceUrl), null, - function(msg) { - if (!msg || msg.length == 0) { - _this._debug("reloadActionNode: error loading ajax data, performing jsTree refresh"); - _this.rebuildTree(); /*try jsTree refresh as last resort */ - if (callback != null) callback.call(_this, false); - return; - } - //filter the results to find the object corresponding to the one we want refreshed - var oFound = null; - for (var o in msg) { - if (msg[o].attributes != null && msg[o].attributes.id == _this._actionNode.nodeId) { - oFound = $.tree.datastores.json().parse(msg[o], _this._tree); - //ensure the tree type is the same too - if ($(oFound).attr("umb:type") == _this._actionNode.treeType) { break; } - else { oFound = null; } - } - } - if (oFound != null) { - _this._debug("reloadActionNode: node is refreshed! : " + supressSelect); - var reloaded = $(oFound).replaceAll(toReplace); - _this._configureNodes(reloaded, true); - if (!supressSelect) _this.selectNode(reloaded, true, true); - if (!supressChildReload) { - _this._loadChildNodes(reloaded, function() { - if (callback != null) callback.call(_this, true); - }); - } - else { if (callback != null) callback.call(_this, true); } - } - else { - _this._debug("reloadActionNode: error finding child node in ajax data, performing jsTree refresh"); - _this.rebuildTree(); /*try jsTree refresh as last resort */ - if (callback != null) callback.call(_this, false); - } - }, "json"); - return; - } - - this._debug("reloadActionNode: error finding parent node, performing jsTree refresh"); - this.rebuildTree(); /*try jsTree refresh as last resort */ - if (callback != null) callback.call(this, false); - } - }, - - getActionNode: function() { - /// Returns the latest node interacted with - this._debug("getActionNode: " + this._actionNode.nodeId); - return this._actionNode; - }, - - setActiveTreeType: function(treeType) { - /// - /// All interactions with the tree are done so based on the current tree type (i.e. content, media). - /// When sycning, or searching, the operations will be done on the current tree type so developers - /// can explicitly specify on with this method before performing the operations. - /// The active tree type is always updated any time a node interaction takes place. - /// - - this._activeTreeType = treeType; - }, - - onNodeDeleting: function(EV) { - /// Event handler for when a tree node is about to be deleted - - this._debug("onNodeDeleting") - - //first, close the branch - this._tree.close_branch(this._actionNode.jsNode); - //show the deleting text - this._actionNode.jsNode.find("a div") - .html(this._opts.deletingText) - .effect("highlight", {}, 1000); - }, - - onNodeDeleted: function (EV) { - /// Event handler for when a tree node is deleted after ajax call - - this._debug("onNodeDeleted"); - - var tree = this._tree; - var nodeToDel = this._actionNode.jsNode; - var parentNode = this._tree.parent(nodeToDel); - - //ensure the branch is closed - this._tree.close_branch(nodeToDel); - //make the node disapear - nodeToDel.hide("drop", { direction: "down" }, 400, function () { - //remove the node from the DOM, do this after 1 second as IE doesn't like it when you try this right away. - setTimeout(function () { - nodeToDel.remove(); - - if (parentNode != undefined && parentNode != -1) { - tree.open_branch(parentNode); - } - - }, 250); - }); - - this._updateRecycleBin(); - - }, - - onNodeRefresh: function(EV) { - /// Handles the nodeRefresh event of the context menu and does the refreshing - - this._debug("onNodeRefresh"); - - this._loadChildNodes(this._actionNode.jsNode, null); - }, - - onSelect: function(NODE, TREE_OBJ) { - /// Fires the JS associated with the node, if the tree is in edit mode, allows for rename instead - //this._debug("onSelect, edit mode? " + this._isEditMode); - this._debug("onSelect"); - if (this._isEditMode) { - this._tree.rename(NODE); - return false; - } - else { - this.setActiveTreeType($(NODE).attr("umb:type")); - var js = $(NODE).children("a").attr("href").replace("javascript:", ""); - - this._debug("onSelect: js: " + js); - - try { - var func = eval(js); - if (func != null) { - func.call(); - } - } catch (e) { } - - return true; - } - - - }, - - onBeforeOpen: function(NODE, TREE_OBJ) { - /// Before opening child nodes, ensure that the data method and url are set properly - this._currentAJAXRequest = true; - TREE_OBJ.settings.data.opts.url = this._opts.dataUrl; - TREE_OBJ.settings.data.opts.method = "GET"; - }, - - onJSONData: function(DATA, TREE_OBJ) { - this._debug("onJSONData"); - - this._ensureContext(); - - this._currentAJAXRequest = false; - - if (typeof DATA.d != "undefined") { - - var msg = DATA.d; - //recreates the tree - if ($.inArray(msg.app, this._loadedApps) == -1) { - this._debug("loading js for app: " + msg.app); - this._loadedApps.push(msg.app); - //inject the scripts - this._getContainer().after(""); - } - return eval(msg.json); - } - - return DATA; - }, - - onBeforeRequest: function(NODE, TREE_OBJ) { - this._ensureContext(); - - if (TREE_OBJ.settings.data.opts.method == "POST") { - var parameters = "{'app':'" + this._opts.app + "','showContextMenu':'" + this._opts.showContext + "', 'isDialog':'" + this._opts.isDialog + "', 'dialogMode':'" + this._opts.dialogMode + "', 'treeType':'" + this._opts.treeType + "', 'functionToCall':'" + this._opts.functionToCall + "', 'nodeKey':'" + this._opts.nodeKey + "'}" - return parameters; - } - else { - var nodeDef = this.getNodeDef($(NODE)); - return this._getUrlParams(nodeDef.sourceUrl); - } - }, - - onChange: function(NODE, TREE_OBJ) { - //bubble an event! - this._raiseEvent("nodeClicked", [NODE]); - }, - - onBeforeContext: function(NODE, TREE_OBJ, EV) { - - //update the action node's NodeDefinition and set the active tree type - this._actionNode = this.getNodeDef($(NODE)); - this.setActiveTreeType($(NODE).attr("umb:type")); - - this._debug("onBeforeContext: " + this._actionNode.menu); - - return this._actionNode.menu; - }, - - onLoad: function(TREE_OBJ) { - /// When the application first loads, load the child nodes - - this._debug("onLoad"); - - //ensure the static data is gone - this._tree.settings.data.opts.static = null; - var _this = this; - _this._loadChildNodes($(_this._getContainer()).find("li"), null); - }, - - onParse: function(STR, TREE_OBJ) { - this._debug("onParse"); - - this._ensureContext(); - - var obj = $(STR); - this._configureNodes(obj); - //this will return the full html of the configured node - return $('
    ').append($(obj).clone()).remove().html(); - }, - - onDestroy: function(TREE_OBJ) { - /// - /// When the tree is destroyed we need to ensure that all of the events both - /// live and bind are gone. For some reason the jstree unbinding doesn't seem to do it's job - /// so instead we need to clear all of the events ourselves - /// - this._debug("onDestroy: " + TREE_OBJ.container.attr("id")); - - TREE_OBJ.container - .unbind("contextmenu") - .unbind("click") - .unbind("dblclick") - .unbind("mouseover") - .unbind("mousedown") - .unbind("mouseup"); - - $("a", TREE_OBJ.container.get(0)) - .die("contextmenu") - .die("click") - .die("dblclick") - .die("mouseover") - .die("mousedown"); - - //also need to kill the custom selector we've fixed in jstree source - $("#" + TREE_OBJ.container.attr("id") + " li").die("click"); - - $("li", TREE_OBJ.container.get(0)) - .die("click"); - }, - - onError: function(ERR, TREE_OBJ) { - this._debug("ERROR!!!!! " + ERR); - }, - - onPublicError: function(ev, errorObj) { - /// Event handler for when a tree node fails an ajax call - - this._debug("onPublicError"); - - var errorNode = this._actionNode.jsNode; - - // reload parent - this.reloadActionNode(false, true, null); - - if (this._isDebug) { - alert('There was an error processing the request\n' + - '=========================================\n\n' + - 'Error Message:\n ' + - errorObj.get_message() + '\n\n' + - 'Technical information:\n ' + - '=========================================\n\n' + - 'Status Code: ' + errorObj.get_statusCode() + '\n\n' + - 'Exception Type: ' + errorObj.get_exceptionType() + '\n\n' + - 'Timed Out: ' + errorObj.get_timedOut() + '\n\n' + - 'Full Stacktrace:\n' + errorObj.get_stackTrace()); - } else { - this._opts.appActions.showSpeachBubble("error", "Error handling action", errorObj.get_message()); - } - - }, - - _debug: function(strMsg) { - if (this._isDebug && Sys && Sys.Debug) { - Sys.Debug.trace("UmbracoTree: " + strMsg); - } - }, - - _configureNodes: function(nodes, reconfigure) { - /// - /// Ensures the node is configured properly after it's loaded via ajax. - /// This includes setting overlays and ensuring the correct icon paths are used. - /// This also ensures that the correct markup is rendered for the tree (i.e. inserts html nodes for text, etc...) - /// - - var _this = this; - - //don't process the nodes that have already been loaded, unless reconfigure is true - if (!reconfigure) { - nodes = nodes.not("li[class*='loaded']"); - } - - this._debug("_configureNodes: " + nodes.length); - - var rxInput = new RegExp("\\boverlay-\\w+\\b", "gi"); - nodes.each(function() { - //if it is checkbox tree (not standard), don't worry about overlays and remove the default icon. - if (_this._opts.treeMode != "standard") { - $(this).children("a:first").css("background", ""); - return; - } - //remove all overlays if reconfiguring - $(this).children("div").remove(); - var m = $(this).attr("class").match(rxInput); - if (m != null) { - for (i = 0; i < m.length; i++) { - _this._debug("_configureNodes: adding overlay: " + m[i] + " for node: " + $(this).attr("id")); - $(this).children("a:first").before("
    "); - } - } - //create a div for the text - var a = $(this).children("a"); - var ins = a.children("ins"); - ins.remove(); //need to remove before you do a .text() otherwise whitespace is included - var txt = $("
    " + a.text() + "
    "); - //check if it's not a sprite, if not then move the ins node just after the anchor, otherwise remove - if (a.hasClass("noSpr")) { - a.attr("style", ins.attr("style")); - } - else { - - } - a.html(txt); - //add the loaded class to each element so we know not to process it again - $(this).addClass("loaded"); - }); - }, - - getNodeDef: function(NODE) { - /// Converts a jquery node with metadata to a NodeDefinition - - //get our meta data stored with our node - var nodedata = $(NODE).children("a").metadata({ type: 'attr', name: 'umb:nodedata' }); - this._debug("getNodeDef: " + $(NODE).attr("id") + ", " + nodedata.nodeType + ", " + nodedata.source); - var def = new Umbraco.Controls.NodeDefinition(); - def.updateDefinition(this._tree, $(NODE), $(NODE).attr("id"), $(NODE).find("a > div").html(), nodedata.nodeType, nodedata.source, nodedata.menu, $(NODE).attr("umb:type")); - return def; - }, - - _updateRecycleBin: function() { - /// Generally used for when a node is deleted. This will set the actionNode to the recycle bin node and force a refresh of it's children - this._debug("_updateRecycleBin BinId: " + this._opts.recycleBinId); - - var rNode = this.findNode(this._opts.recycleBinId, true); - if (rNode) { - this._actionNode = this.getNodeDef(rNode); - var _this = this; - this.reloadActionNode(true, true, function(success) { - if (success) { - _this.findNode(_this._opts.recycleBinId, true).effect("highlight", {}, 1000); - } - }); - } - }, - _ensureContext: function() { - /// - /// ensure that the tree object always has the correct context. - /// this is a fix for the TinyMCE dialog window, as it tends to lose object context for some wacky reason - /// when ajax calls are made. Works fine in all other instances. - /// - this._tree.container = this._getContainer(); - }, - _loadChildNodes: function(liNode, callback) { - /// jsTree won't allow you to open a node that doesn't explitly have childen, this will force it to try - /// a jquery object for the current li node - - this._debug("_loadChildNodes: " + liNode.attr("id")); - - liNode.removeClass("leaf"); - - var _this = this; - - //close branch will actually cause a select to happen so we'll intercept the select callback and then reset it once complete - //if we don't wan the event to fire, we'll set the callback to a null method and set it back after we call the select_branch method - this._tree.settings.callback.onselect = function() { }; - this._tree.close_branch(liNode, true); - this._tree.settings.callback.onselect = function(N, T) { _this.onSelect(N, T) }; - - liNode.children("ul:eq(0)").remove(); - this._tree.open_branch(liNode, false, callback); - }, - - _syncTree: function(path, forceReload, numPaths, numAsync, supressChildReload, newId) { - /// - /// This is the internal method that will recursively search for the nodes to sync. If an invalid path is - /// passed to this method, it will raise an event which can be handled. - /// - /// The path of the node to find - /// If true, will ensure that the node to be synced is synced with data from the server - /// the number of id's deep to search starting from the end of the path. Used in recursion. - /// the number of async calls made so far to sync. Used in recursion and used to determine if the found node has been loaded by ajax. - /// This parameter is only used when we don't have a real unique ID for a node, for example for a file. If a filename changes we don't know what the new one is since we are syncing the tree to the old original path. Once we retrieve the results the sync the tree we need to find the result by it's new id and update the node. - - this._debug("_syncTree"); - - var paths = path.split(","); - var found = null; - var foundIndex = null; - if (numPaths == null) numPaths = (paths.length - 0); - for (var i = 0; i < numPaths; i++) { - foundIndex = paths.length - (1 + i); - found = this.findNode(paths[foundIndex]); - this._debug("_syncTree: finding... " + paths[foundIndex] + " found? " + found); - if (found) break; - } - - //if no node has been found at all in the entire path, then bubble an error event - if (!found) { - this._debug("no node found in path: " + path + " : " + numPaths); - this._isSyncing = false; //reset flag - this._raiseEvent("syncNotFound", [path]); - return; - } - - //if the found node was not the end of the path, we need to load them in recursively. - if (found.attr("id") != paths[paths.length - 1]) { - var _this = this; - this._loadChildNodes(found, function(NODE, TREE_OBJ) { - //check if the next node to be found is in the children, if it is not, there's a problem bubble an event! - var pathsToSearch = paths.length - (Number(foundIndex) + 1); - if (_this.findNode(paths[foundIndex + 1])) { - _this._syncTree(path, forceReload, pathsToSearch, (numAsync == null ? numAsync == 1 : ++numAsync)); - } - else { - _this._debug("node not found in children: " + path + " : " + numPaths); - this._isSyncing = false; //reset flag - _this._raiseEvent("syncNotFound", [path]); - } - }); - } - else { - //only force the reload of this nodes data if forceReload is specified and the node has not already come from the server - var doReload = (forceReload && (numAsync == null || numAsync < 1)); - this._debug("_syncTree: found! numAsync: " + numAsync + ", forceReload: " + forceReload + ", doReload: " + doReload); - if (doReload) { - this._actionNode = this.getNodeDef(found); - //we need to change the id if the newId parameter is set - if (newId) { - this._actionNode.nodeId = newId; - } - if (supressChildReload === undefined) { - this.reloadActionNode(false, true, null); - } else { - this.reloadActionNode(false, supressChildReload, null); - } - } - else { - //we have found our node, select it but supress the selecting event - if (found.attr("id") != "-1") this.selectNode(found, true); - this._configureNodes(found, doReload); - } - this._isSyncing = false; //reset flag - //bubble event - this._raiseEvent("syncFound", [found]); - } - }, - - - _getUrlParams: function(nodeSource) { - /// This converts Url query string params to json - var p = {}; - if (nodeSource) { - var urlSplit = nodeSource.split("?"); - if (urlSplit.length > 1) { - var sp = urlSplit[1].split("&"); - for (var i = 0; i < sp.length; i++) { - var e = sp[i].split("="); - p[e[0]] = e[1]; - } - p["rnd2"] = Umbraco.Utils.generateRandom(); - } - } - return p; - }, - - _getUrl: function(nodeSource) { - /// Returns the json service url - - if (nodeSource == null || nodeSource == "") { - return this._opts.dataUrl; - } - var params = nodeSource.split("?")[1]; - return this._opts.dataUrl + "?" + params + "&rnd2=" + Umbraco.Utils.generateRandom(); - }, - _getContainer: function() { - return $("#" + this._containerId, this._context); - }, - _getInitOptions: function(initData) { - /// return the initialization objects for the tree - - this._debug("_getInitOptions"); - - var _this = this; - - var options = { - data: { - type: "json", - async: true, - opts: { - static: initData == null ? null : initData, - method: "POST", - url: _this._opts.serviceUrl, - outer_attrib: ["id", "umb:type", "class", "rel"], - inner_attrib: ["umb:nodedata", "href", "class", "style"] - } - }, - ui: { - dots: false, - rtl: false, - animation: false, - hover_mode: true, - theme_path: false, - theme_name: "umbraco" - }, - langs: { - new_node: "New folder", - loading: "
    " + (this._tree.settings.lang.loading || "Loading ...") + "
    " - }, - callback: { - //ensures that the node id isn't appended to the async url - beforedata: function(N, T) { return _this.onBeforeRequest(N, T); }, - //wrapped functions maintain scope in callback - beforeopen: function(N, T) { _this.onBeforeOpen(N, T); }, - onselect: function(N, T) { _this.onSelect(N, T); }, - onchange: function(N, T) { _this.onChange(N, T); }, - ondata: function(D, T) { return _this.onJSONData(D, T); }, - onload: function(T) { if (initData == null) _this.onLoad(T); }, - onparse: function(S, T) { return _this.onParse(S, T); }, - error: function(E, T) { _this.onError(E, T); }, - ondestroy: function(T) { _this.onDestroy(T); } - }, - plugins: { - //UmbracoContext comes before context menu so that the events fire first - UmbracoContext: { - fullMenu: _this._opts.jsonFullMenu, - onBeforeContext: function(N, T, E) { return _this.onBeforeContext(N, T, E); } - }, - contextmenu: {} - } - }; - if (this._opts.treeMode != "standard") { - options.plugins.checkbox = { three_state: false } - } - - //if there's no service URL, then disable ajax requests - if (this._opts.serviceUrl == "" || this._opts.dataUrl == "") { - options.data.async = false; - options.data.opts.static = {}; - } - - //set global ajax settings: - $.ajaxSetup({ - contentType: "application/json; charset=utf-8" - }); - - this._debug("_getInitOptions. Async enabled = " + options.data.async); - - return options; - } - - }; - } - - // instance manager - Umbraco.Controls.UmbracoTree.cntr = 0; - Umbraco.Controls.UmbracoTree.inst = {}; - - -})(jQuery); diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.checkbox.js b/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.checkbox.js deleted file mode 100644 index a31e0d6b9f..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.checkbox.js +++ /dev/null @@ -1,75 +0,0 @@ -(function ($) { - $.extend($.tree.plugins, { - "checkbox" : { - defaults : { - three_state : true - }, - get_checked : function (t) { - if(!t) t = $.tree.focused(); - return t.container.find("a.checked").parent(); - }, - get_undeterminded : function (t) { - if(!t) t = $.tree.focused(); - return t.container.find("a.undetermined").parent(); - }, - get_unchecked : function (t) { - if(!t) t = $.tree.focused(); - return t.container.find("a:not(.checked, .undetermined)").parent(); - }, - - check : function (n) { - if(!n) return false; - var t = $.tree.reference(n); - n = t.get_node(n); - if(n.children("a").hasClass("checked")) return true; - - var opts = $.extend(true, {}, $.tree.plugins.checkbox.defaults, t.settings.plugins.checkbox); - if(opts.three_state) { - n.find("li").andSelf().children("a").removeClass("unchecked undetermined").addClass("checked"); - n.parents("li").each(function () { - if($(this).children("ul").find("a:not(.checked):eq(0)").size() > 0) { - $(this).parents("li").andSelf().children("a").removeClass("unchecked checked").addClass("undetermined"); - return false; - } - else $(this).children("a").removeClass("unchecked undetermined").addClass("checked"); - }); - } - else n.children("a").removeClass("unchecked").addClass("checked"); - return true; - }, - uncheck : function (n) { - if(!n) return false; - var t = $.tree.reference(n); - n = t.get_node(n); - if(n.children("a").hasClass("unchecked")) return true; - - var opts = $.extend(true, {}, $.tree.plugins.checkbox.defaults, t.settings.plugins.checkbox); - if(opts.three_state) { - n.find("li").andSelf().children("a").removeClass("checked undetermined").addClass("unchecked"); - n.parents("li").each(function () { - if($(this).find("a.checked, a.undetermined").size() - 1 > 0) { - $(this).parents("li").andSelf().children("a").removeClass("unchecked checked").addClass("undetermined"); - return false; - } - else $(this).children("a").removeClass("checked undetermined").addClass("unchecked"); - }); - } - else n.children("a").removeClass("checked").addClass("unchecked"); - return true; - }, - toggle : function (n) { - if(!n) return false; - var t = $.tree.reference(n); - n = t.get_node(n); - if(n.children("a").hasClass("checked")) $.tree.plugins.checkbox.uncheck(n); - else $.tree.plugins.checkbox.check(n); - }, - - callbacks : { - onchange : function(n, t) { - $.tree.plugins.checkbox.toggle(n); - } - } - } - }); -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.contextmenu.js b/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.contextmenu.js deleted file mode 100644 index b1b476aa59..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.contextmenu.js +++ /dev/null @@ -1,137 +0,0 @@ -(function ($) { - $.extend($.tree.plugins, { - "contextmenu": { - object: $("
      "), - data: { - t: false, - a: false, - r: false - }, - - defaults: { - class_name: "hover", - items: { - create: { - label: "Create", - icon: "create", - visible: function (NODE, TREE_OBJ) { if (NODE.length != 1) return 0; return TREE_OBJ.check("creatable", NODE); }, - action: function (NODE, TREE_OBJ) { TREE_OBJ.create(false, TREE_OBJ.get_node(NODE[0])); }, - separator_after: true - }, - rename: { - label: "Rename", - icon: "rename", - visible: function (NODE, TREE_OBJ) { if (NODE.length != 1) return false; return TREE_OBJ.check("renameable", NODE); }, - action: function (NODE, TREE_OBJ) { TREE_OBJ.rename(NODE); } - }, - remove: { - label: "Delete", - icon: "remove", - visible: function (NODE, TREE_OBJ) { var ok = true; $.each(NODE, function () { if (TREE_OBJ.check("deletable", this) == false) ok = false; return false; }); return ok; }, - action: function (NODE, TREE_OBJ) { $.each(NODE, function () { TREE_OBJ.remove(this); }); } - } - } - }, - show: function (obj, t) { - var opts = $.extend(true, {}, $.tree.plugins.contextmenu.defaults, t.settings.plugins.contextmenu); - obj = $(obj); - $.tree.plugins.contextmenu.object.empty(); - var str = ""; - var cnt = 0; - for (var i in opts.items) { - if (!opts.items.hasOwnProperty(i)) continue; - if (opts.items[i] === false) continue; - var r = 1; - if (typeof opts.items[i].visible == "function") r = opts.items[i].visible.call(null, $.tree.plugins.contextmenu.data.a, t); - if (r == -1) continue; - else cnt++; - if (opts.items[i].separator_before === true) str += "
    •  
    • "; - str += '
    • '; - - // updated from patch by Matt Brailsford (http://our.umbraco.org/forum/using/ui-questions/6225-Custom-icon-in-Context-menu#comment39514) - str += " "; str += ""; - if (opts.items[i].icon && opts.items[i].icon.indexOf("/") >= 0) { - str += "
      "; - } else { - str += opts.items[i].label; - } - str += '
    • '; - - if (opts.items[i].separator_after === true) str += "
    •  
    • "; - } - var tmp = obj.children("a:visible").offset(); - $.tree.plugins.contextmenu.object.attr("class", "tree-context tree-" + t.settings.ui.theme_name.toString() + "-context").html(str); - var h = $.tree.plugins.contextmenu.object.height(); - var w = $.tree.plugins.contextmenu.object.width(); - var x = tmp.left; - var y = tmp.top + parseInt(obj.children("a:visible").height()) + 2; - var max_y = $(window).height() + $(window).scrollTop(); - var max_x = $(window).width() + $(window).scrollLeft(); - if (y + h > max_y) y = Math.max((max_y - h - 2), 0); - if (x + w > max_x) x = Math.max((max_x - w - 2), 0); - $.tree.plugins.contextmenu.object.css({ "left": (x), "top": (y) }).fadeIn("fast"); - }, - hide: function () { - if (!$.tree.plugins.contextmenu.data.t) return; - var opts = $.extend(true, {}, $.tree.plugins.contextmenu.defaults, $.tree.plugins.contextmenu.data.t.settings.plugins.contextmenu); - if ($.tree.plugins.contextmenu.data.r && $.tree.plugins.contextmenu.data.a) { - $.tree.plugins.contextmenu.data.a.children("a, span").removeClass(opts.class_name); - } - $.tree.plugins.contextmenu.data = { a: false, r: false, t: false }; - $.tree.plugins.contextmenu.object.fadeOut("fast"); - }, - exec: function (cmd) { - if ($.tree.plugins.contextmenu.data.t == false) return; - var opts = $.extend(true, {}, $.tree.plugins.contextmenu.defaults, $.tree.plugins.contextmenu.data.t.settings.plugins.contextmenu); - try { opts.items[cmd].action.apply(null, [$.tree.plugins.contextmenu.data.a, $.tree.plugins.contextmenu.data.t]); } catch (e) { }; - }, - - callbacks: { - oninit: function () { - if (!$.tree.plugins.contextmenu.css) { - var css = '#jstree-contextmenu { display:none; position:absolute; z-index:2000; list-style-type:none; margin:0; padding:0; left:-2000px; top:-2000px; } .tree-context { margin:20px; padding:0; width:180px; border:1px solid #979797; padding:2px; background:#f5f5f5; list-style-type:none; }.tree-context li { height:22px; margin:0 0 0 27px; padding:0; background:#ffffff; border-left:1px solid #e0e0e0; }.tree-context li a { position:relative; display:block; height:22px; line-height:22px; margin:0 0 0 -28px; text-decoration:none; color:black; padding:0; }.tree-context li a ins { text-decoration:none; float:left; width:16px; height:16px; margin:0 0 0 0; background-color:#f0f0f0; border:1px solid #f0f0f0; border-width:3px 5px 3px 6px; line-height:16px; }.tree-context li a span { display:block; background:#f0f0f0; margin:0 0 0 29px; padding-left:5px; }.tree-context li.separator { background:#f0f0f0; height:2px; line-height:2px; font-size:1px; border:0; margin:0; padding:0; }.tree-context li.separator span { display:block; margin:0px 0 0px 27px; height:1px; border-top:1px solid #e0e0e0; border-left:1px solid #e0e0e0; line-height:1px; font-size:1px; background:white; }.tree-context li a:hover { border:1px solid #d8f0fa; height:20px; line-height:20px; }.tree-context li a:hover span { background:#e7f4f9; margin-left:28px; }.tree-context li a:hover ins { background-color:#e7f4f9; border-color:#e7f4f9; border-width:2px 5px 2px 5px; }.tree-context li a.disabled { color:gray; }.tree-context li a.disabled ins { }.tree-context li a.disabled:hover { border:0; height:22px; line-height:22px; }.tree-context li a.disabled:hover span { background:#f0f0f0; margin-left:29px; }.tree-context li a.disabled:hover ins { border-color:#f0f0f0; background-color:#f0f0f0; border-width:3px 5px 3px 6px; }'; - $.tree.plugins.contextmenu.css = this.add_sheet({ str: css }); - } - }, - onrgtclk: function (n, t, e) { - var opts = $.extend(true, {}, $.tree.plugins.contextmenu.defaults, t.settings.plugins.contextmenu); - n = $(n); - if (n.size() == 0) return; - $.tree.plugins.contextmenu.data.t = t; - if (!n.children("a:eq(0)").hasClass("clicked")) { - $.tree.plugins.contextmenu.data.a = n; - $.tree.plugins.contextmenu.data.r = true; - n.children("a").addClass(opts.class_name); - e.target.blur(); - } - else { - $.tree.plugins.contextmenu.data.r = false; - $.tree.plugins.contextmenu.data.a = (t.selected_arr && t.selected_arr.length > 1) ? t.selected_arr : t.selected; - } - $.tree.plugins.contextmenu.show(n, t); - e.preventDefault(); - e.stopPropagation(); - // return false; // commented out because you might want to do something in your own callback - }, - onchange: function () { $.tree.plugins.contextmenu.hide(); }, - beforedata: function () { $.tree.plugins.contextmenu.hide(); }, - ondestroy: function () { $.tree.plugins.contextmenu.hide(); } - } - } - }); - $(function () { - $.tree.plugins.contextmenu.object.hide().appendTo("body"); - $("a", $.tree.plugins.contextmenu.object[0]) - //has to be .live, do not change to .on, it doesn't work - .live("click", function (event) { - if (!$(this).hasClass("disabled")) { - $.tree.plugins.contextmenu.exec.apply(null, [$(this).attr("rel")]); - $.tree.plugins.contextmenu.hide(); - } - event.stopPropagation(); - event.preventDefault(); - return false; - }) - $(document).bind("mousedown", function (event) { if ($(event.target).parents("#jstree-contextmenu").size() == 0) $.tree.plugins.contextmenu.hide(); }); - }); -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.js b/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.js deleted file mode 100644 index 347440de2b..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.js +++ /dev/null @@ -1,2072 +0,0 @@ -/* -* jsTree 0.9.9a -* http://jstree.com/ -* -* Copyright (c) 2009 Ivan Bozhanov (vakata.com) -* -* Dual licensed under the MIT and GPL licenses: -* http://www.opensource.org/licenses/mit-license.php -* http://www.gnu.org/licenses/gpl.html -* -* Date: 2009-10-06 -* -*/ - -(function ($) { - // jQuery plugin - $.tree = { - datastores: {}, - plugins: {}, - defaults: { - data: { - async: false, // Are async requests used to load open_branch contents - type: "html", // One of included datastores - opts: { method: "GET", url: false} // Options passed to datastore - }, - selected: false, // FALSE or STRING or ARRAY - opened: [], // ARRAY OF INITIALLY OPENED NODES - languages: [], // ARRAY of string values (which will be used as CSS classes - so they must be valid) - ui: { - dots: true, // BOOL - dots or no dots - animation: 0, // INT - duration of open/close animations in miliseconds - scroll_spd: 4, - theme_path: false, // Path to the theme CSS file - if set to false and theme_name is not false - will lookup jstree-path-here/themes/theme-name-here/style.css - theme_name: "default", // if set to false no theme will be loaded - selected_parent_close: "select_parent", // false, "deselect", "select_parent" - selected_delete: "select_previous" // false, "select_previous" - }, - types: { - "default": { - clickable: true, // can be function - renameable: true, // can be function - deletable: true, // can be function - creatable: true, // can be function - draggable: true, // can be function - max_children: -1, // -1 - not set, 0 - no children, 1 - one child, etc // can be function - max_depth: -1, // -1 - not set, 0 - no children, 1 - one level of children, etc // can be function - valid_children: "all", // all, none, array of values // can be function - icon: { - image: false, - position: false - } - } - }, - rules: { - multiple: false, // FALSE | CTRL | ON - multiple selection off/ with or without holding Ctrl - multitree: "none", // all, none, array of tree IDs to accept from - type_attr: "rel", // STRING attribute name (where is the type stored as string) - createat: "bottom", // STRING (top or bottom) new nodes get inserted at top or bottom - drag_copy: "ctrl", // FALSE | CTRL | ON - drag to copy off/ with or without holding Ctrl - drag_button: "left", // left, right or both - use_max_children: true, - use_max_depth: true, - - max_children: -1, - max_depth: -1, - valid_children: "all" - }, - lang: { - new_node: "New folder", - loading: "Loading ..." - }, - callback: { - beforechange: function (NODE, TREE_OBJ) { return true }, - beforeopen: function (NODE, TREE_OBJ) { return true }, - beforeclose: function (NODE, TREE_OBJ) { return true }, - beforemove: function (NODE, REF_NODE, TYPE, TREE_OBJ) { return true }, - beforecreate: function (NODE, REF_NODE, TYPE, TREE_OBJ) { return true }, - beforerename: function (NODE, LANG, TREE_OBJ) { return true }, - beforedelete: function (NODE, TREE_OBJ) { return true }, - beforedata: function (NODE, TREE_OBJ) { return { id: $(NODE).attr("id") || 0} }, // PARAMETERS PASSED TO SERVER - ondata: function (DATA, TREE_OBJ) { return DATA; }, // modify data before parsing it - onparse: function (STR, TREE_OBJ) { return STR; }, // modify string before visualizing it - onhover: function (NODE, TREE_OBJ) { }, // node hovered - onselect: function (NODE, TREE_OBJ) { }, // node selected - ondeselect: function (NODE, TREE_OBJ) { }, // node deselected - onchange: function (NODE, TREE_OBJ) { }, // focus changed - onrename: function (NODE, TREE_OBJ, RB) { }, // node renamed - onmove: function (NODE, REF_NODE, TYPE, TREE_OBJ, RB) { }, // move completed - oncopy: function (NODE, REF_NODE, TYPE, TREE_OBJ, RB) { }, // copy completed - oncreate: function (NODE, REF_NODE, TYPE, TREE_OBJ, RB) { }, // node created - ondelete: function (NODE, TREE_OBJ, RB) { }, // node deleted - onopen: function (NODE, TREE_OBJ) { }, // node opened - onopen_all: function (TREE_OBJ) { }, // all nodes opened - onclose_all: function (TREE_OBJ) { }, // all nodes closed - onclose: function (NODE, TREE_OBJ) { }, // node closed - error: function (TEXT, TREE_OBJ) { }, // error occured - ondblclk: function (NODE, TREE_OBJ) { TREE_OBJ.toggle_branch.call(TREE_OBJ, NODE); TREE_OBJ.select_branch.call(TREE_OBJ, NODE); }, - onrgtclk: function (NODE, TREE_OBJ, EV) { }, // right click - to prevent use: EV.preventDefault(); EV.stopPropagation(); return false - onload: function (TREE_OBJ) { }, - oninit: function (TREE_OBJ) { }, - onfocus: function (TREE_OBJ) { }, - ondestroy: function (TREE_OBJ) { }, - onsearch: function (NODES, TREE_OBJ) { NODES.addClass("search"); }, - ondrop: function (NODE, REF_NODE, TYPE, TREE_OBJ) { }, - check: function (RULE, NODE, VALUE, TREE_OBJ) { return VALUE; }, - check_move: function (NODE, REF_NODE, TYPE, TREE_OBJ) { return true; } - }, - plugins: {} - }, - - create: function () { return new tree_component(); }, - focused: function () { return tree_component.inst[tree_component.focused]; }, - reference: function (obj) { - var o = $(obj); - if (!o.size()) o = $("#" + obj); - if (!o.size()) return null; - o = (o.is(".tree")) ? o.attr("id") : o.parents(".tree:eq(0)").attr("id"); - return tree_component.inst[o] || null; - }, - rollback: function (data) { - for (var i in data) { - if (!data.hasOwnProperty(i)) continue; - var tmp = tree_component.inst[i]; - var lock = !tmp.locked; - - // if not locked - lock the tree - if (lock) tmp.lock(true); - // Cancel ongoing rename - tmp.inp = false; - tmp.container.html(data[i].html).find(".dragged").removeClass("dragged").end().find(".hover").removeClass("hover"); - - if (data[i].selected) { - tmp.selected = $("#" + data[i].selected); - tmp.selected_arr = []; - tmp.container - .find("a.clicked").each(function () { - tmp.selected_arr.push(tmp.get_node(this)); - }); - } - // if this function set the lock - unlock - if (lock) tmp.lock(false); - - delete lock; - delete tmp; - } - }, - drop_mode: function (opts) { - opts = $.extend(opts, { show: false, type: "default", str: "Foreign node" }); - tree_component.drag_drop.foreign = true; - tree_component.drag_drop.isdown = true; - tree_component.drag_drop.moving = true; - tree_component.drag_drop.appended = false; - tree_component.drag_drop.f_type = opts.type; - tree_component.drag_drop.f_data = opts; - - - if (!opts.show) { - tree_component.drag_drop.drag_help = false; - tree_component.drag_drop.drag_node = false; - } - else { - tree_component.drag_drop.drag_help = $(""); - tree_component.drag_drop.drag_node = tree_component.drag_drop.drag_help.find("li:eq(0)"); - } - if ($.tree.drag_start !== false) $.tree.drag_start.call(null, false); - }, - drag_start: false, - drag: false, - drag_end: false - }; - $.fn.tree = function (opts) { - return this.each(function () { - var conf = $.extend({}, opts); - if (tree_component.inst && tree_component.inst[$(this).attr('id')]) tree_component.inst[$(this).attr('id')].destroy(); - if (conf !== false) new tree_component().init(this, conf); - }); - }; - - // core - function tree_component() { - return { - cntr: ++tree_component.cntr, - settings: $.extend({}, $.tree.defaults), - - init: function (elem, conf) { - var _this = this; - this.container = $(elem); - if (this.container.size == 0) return false; - tree_component.inst[this.cntr] = this; - if (!this.container.attr("id")) this.container.attr("id", "jstree_" + this.cntr); - tree_component.inst[this.container.attr("id")] = tree_component.inst[this.cntr]; - tree_component.focused = this.cntr; - this.settings = $.extend(true, {}, this.settings, conf); - - // DEAL WITH LANGUAGE VERSIONS - if (this.settings.languages && this.settings.languages.length) { - this.current_lang = this.settings.languages[0]; - var st = false; - var id = "#" + this.container.attr("id"); - for (var ln = 0; ln < this.settings.languages.length; ln++) { - st = tree_component.add_css(id + " ." + this.settings.languages[ln]); - if (st !== false) st.style.display = (this.settings.languages[ln] == this.current_lang) ? "" : "none"; - } - } - else this.current_lang = false; - // THEMES - this.container.addClass("tree"); - if (this.settings.ui.theme_name !== false) { - if (this.settings.ui.theme_path === false) { - $("script").each(function () { - if (this.src.toString().match(/jquery\.tree.*?js$/)) { _this.settings.ui.theme_path = this.src.toString().replace(/jquery\.tree.*?js$/, "") + "themes/" + _this.settings.ui.theme_name + "/style.css"; return false; } - }); - } - if (this.settings.ui.theme_path != "" && $.inArray(this.settings.ui.theme_path, tree_component.themes) == -1) { - tree_component.add_sheet({ url: this.settings.ui.theme_path }); - tree_component.themes.push(this.settings.ui.theme_path); - } - this.container.addClass("tree-" + this.settings.ui.theme_name); - } - // TYPE ICONS - var type_icons = ""; - for (var t in this.settings.types) { - if (!this.settings.types.hasOwnProperty(t)) continue; - if (!this.settings.types[t].icon) continue; - if (this.settings.types[t].icon.image || this.settings.types[t].icon.position) { - if (t == "default") type_icons += "#" + this.container.attr("id") + " li > a ins { "; - else type_icons += "#" + this.container.attr("id") + " li[rel=" + t + "] > a ins { "; - if (this.settings.types[t].icon.image) type_icons += " background-image:url(" + this.settings.types[t].icon.image + "); "; - if (this.settings.types[t].icon.position) type_icons += " background-position:" + this.settings.types[t].icon.position + "; "; - type_icons += "} "; - } - } - if (type_icons != "") tree_component.add_sheet({ str: type_icons }); - - if (this.settings.rules.multiple) this.selected_arr = []; - this.offset = false; - this.hovered = false; - this.locked = false; - - if (tree_component.drag_drop.marker === false) tree_component.drag_drop.marker = $("
      ").attr({ id: "jstree-marker" }).hide().appendTo("body"); - this.callback("oninit", [this]); - this.refresh(); - this.attach_events(); - this.focus(); - }, - refresh: function (obj) { - if (this.locked) return this.error("LOCKED"); - var _this = this; - if (obj && !this.settings.data.async) obj = false; - this.is_partial_refresh = obj ? true : false; - - // SAVE OPENED - this.opened = Array(); - if (this.settings.opened != false) { - $.each(this.settings.opened, function (i, item) { - if (this.replace(/^#/, "").length > 0) { _this.opened.push("#" + this.replace(/^#/, "")); } - }); - this.settings.opened = false; - } - else { - this.container.find("li.open").each(function (i) { if (this.id) { _this.opened.push("#" + this.id); } }); - } - - // SAVE SELECTED - if (this.selected) { - this.settings.selected = Array(); - if (obj) { - $(obj).find("li:has(a.clicked)").each(function () { - if (this.id) _this.settings.selected.push("#" + this.id); - }); - } - else { - if (this.selected_arr) { - $.each(this.selected_arr, function () { - if (this.attr("id")) _this.settings.selected.push("#" + this.attr("id")); - }); - } - else { - if (this.selected.attr("id")) this.settings.selected.push("#" + this.selected.attr("id")); - } - } - } - else if (this.settings.selected !== false) { - var tmp = Array(); - if ((typeof this.settings.selected).toLowerCase() == "object") { - $.each(this.settings.selected, function () { - if (this.replace(/^#/, "").length > 0) tmp.push("#" + this.replace(/^#/, "")); - }); - } - else { - if (this.settings.selected.replace(/^#/, "").length > 0) tmp.push("#" + this.settings.selected.replace(/^#/, "")); - } - this.settings.selected = tmp; - } - - if (obj && this.settings.data.async) { - this.opened = Array(); - obj = this.get_node(obj); - obj.find("li.open").each(function (i) { _this.opened.push("#" + this.id); }); - if (obj.hasClass("open")) obj.removeClass("open").addClass("closed"); - if (obj.hasClass("leaf")) obj.removeClass("leaf"); - obj.children("ul:eq(0)").html(""); - return this.open_branch(obj, true, function () { _this.reselect.apply(_this); }); - } - - var _this = this; - var _datastore = new $.tree.datastores[this.settings.data.type](); - if (this.container.children("ul").size() == 0) { - this.container.html(""); - } - _datastore.load(this.callback("beforedata", [false, this]), this, this.settings.data.opts, function (data) { - data = _this.callback("ondata", [data, _this]); - _datastore.parse(data, _this, _this.settings.data.opts, function (str) { - str = _this.callback("onparse", [str, _this]); - _this.container.empty().append($("
        ").html(str)); - _this.container.find("li:last-child").addClass("last").end().find("li:has(ul)").not(".open").addClass("closed"); - _this.container.find("li").not(".open").not(".closed").addClass("leaf"); - _this.reselect(); - }); - }); - }, - reselect: function (is_callback) { - var _this = this; - - if (!is_callback) this.cl_count = 0; - else this.cl_count--; - // REOPEN BRANCHES - if (this.opened && this.opened.length) { - var opn = false; - for (var j = 0; this.opened && j < this.opened.length; j++) { - if (this.settings.data.async) { - var tmp = this.get_node(this.opened[j]); - if (tmp.size() && tmp.hasClass("closed") > 0) { - opn = true; - var tmp = this.opened[j].toString().replace('/', '\\/'); - delete this.opened[j]; - this.open_branch(tmp, true, function () { _this.reselect.apply(_this, [true]); }); - this.cl_count++; - } - } - else this.open_branch(this.opened[j], true); - } - if (this.settings.data.async && opn) return; - if (this.cl_count > 0) return; - delete this.opened; - } - if (this.cl_count > 0) return; - - // DOTS and RIGHT TO LEFT - this.container.css("direction", "ltr").children("ul:eq(0)").addClass("ltr"); - if (this.settings.ui.dots == false) this.container.children("ul:eq(0)").addClass("no_dots"); - - // REPOSITION SCROLL - if (this.scrtop) { - this.container.scrollTop(_this.scrtop); - delete this.scrtop; - } - // RESELECT PREVIOUSLY SELECTED - if (this.settings.selected !== false) { - $.each(this.settings.selected, function (i) { - if (_this.is_partial_refresh) _this.select_branch($(_this.settings.selected[i].toString().replace('/', '\\/'), _this.container), (_this.settings.rules.multiple !== false)); - else _this.select_branch($(_this.settings.selected[i].toString().replace('/', '\\/'), _this.container), (_this.settings.rules.multiple !== false && i > 0)); - }); - this.settings.selected = false; - } - this.callback("onload", [_this]); - }, - - get: function (obj, format, opts) { - if (!format) format = this.settings.data.type; - if (!opts) opts = this.settings.data.opts; - return new $.tree.datastores[format]().get(obj, this, opts); - }, - - attach_events: function () { - var _this = this; - - this.container - .bind("mousedown.jstree", function (event) { - if (tree_component.drag_drop.isdown) { - tree_component.drag_drop.move_type = false; - event.preventDefault(); - event.stopPropagation(); - event.stopImmediatePropagation(); - return false; - } - }) - .bind("mouseup.jstree", function (event) { - setTimeout(function () { _this.focus.apply(_this); }, 5); - }) - .bind("click.jstree", function (event) { - //event.stopPropagation(); - return true; - }); - //** THIS IS A MODIFICATION TO JSTREE, IN FIREFOX THE ORIGINAL CONTEXT OF THIS SELECTOR IS LOST IN THE TINY MCE OVERLAY - //$("li", this.container.get(0)) - $("#" + this.container.attr("id") + " li") - //has to be .live, do not change to .on, it doesn't work - .live("click", function (event) { // WHEN CLICK IS ON THE ARROW - if (event.target.tagName != "LI") return true; - _this.off_height(); - if (event.pageY - $(event.target).offset().top > _this.li_height) return true; - _this.toggle_branch.apply(_this, [event.target]); - event.stopPropagation(); - return false; - }); - $("a", this.container.get(0)) - //has to be .live, do not change to .on, it doesn't work - .live("click", function (event) { // WHEN CLICK IS ON THE TEXT OR ICON - if (event.which && event.which == 3) return true; - if (_this.locked) { - event.preventDefault(); - event.target.blur(); - return _this.error("LOCKED"); - } - _this.select_branch.apply(_this, [event.target, event.ctrlKey || _this.settings.rules.multiple == "on"]); - if (_this.inp) { _this.inp.blur(); } - event.preventDefault(); - event.target.blur(); - return false; - }) - //has to be .live, do not change to .on, it doesn't work - .live("dblclick", function (event) { // WHEN DOUBLECLICK ON TEXT OR ICON - if (_this.locked) { - event.preventDefault(); - event.stopPropagation(); - event.target.blur(); - return _this.error("LOCKED"); - } - _this.callback("ondblclk", [_this.get_node(event.target).get(0), _this]); - event.preventDefault(); - event.stopPropagation(); - event.target.blur(); - }) - //has to be .live, do not change to .on, it doesn't work - .live("contextmenu", function (event) { - if (_this.locked) { - event.target.blur(); - return _this.error("LOCKED"); - } - return _this.callback("onrgtclk", [_this.get_node(event.target).get(0), _this, event]); - }) - //has to be .live, do not change to .on, it doesn't work - .live("mouseover", function (event) { - if (_this.locked) { - event.preventDefault(); - event.stopPropagation(); - return _this.error("LOCKED"); - } - if (_this.hovered !== false && (event.target.tagName == "A" || event.target.tagName == "INS")) { - _this.hovered.children("a").removeClass("hover"); - _this.hovered = false; - } - _this.callback("onhover", [_this.get_node(event.target).get(0), _this]); - }) - //has to be .live, do not change to .on, it doesn't work - .live("mousedown", function (event) { - if (_this.settings.rules.drag_button == "left" && event.which && event.which != 1) return true; - if (_this.settings.rules.drag_button == "right" && event.which && event.which != 3) return true; - _this.focus.apply(_this); - if (_this.locked) return _this.error("LOCKED"); - // SELECT LIST ITEM NODE - var obj = _this.get_node(event.target); - // IF ITEM IS DRAGGABLE - if (_this.settings.rules.multiple != false && _this.selected_arr.length > 1 && obj.children("a:eq(0)").hasClass("clicked")) { - var counter = 0; - for (var i in _this.selected_arr) { - if (!_this.selected_arr.hasOwnProperty(i)) continue; - if (_this.check("draggable", _this.selected_arr[i])) { - _this.selected_arr[i].addClass("dragged"); - tree_component.drag_drop.origin_tree = _this; - counter++; - } - } - if (counter > 0) { - if (_this.check("draggable", obj)) tree_component.drag_drop.drag_node = obj; - else tree_component.drag_drop.drag_node = _this.container.find("li.dragged:eq(0)"); - tree_component.drag_drop.isdown = true; - tree_component.drag_drop.drag_help = $("
        ").append("
          "); - var tmp = tree_component.drag_drop.drag_node.clone(); - if (_this.settings.languages.length > 0) tmp.find("a").not("." + _this.current_lang).hide(); - tree_component.drag_drop.drag_help.children("ul:eq(0)").append(tmp); - tree_component.drag_drop.drag_help.find("li:eq(0)").removeClass("last").addClass("last").children("a").html(" Multiple selection").end().children("ul").remove(); - - tree_component.drag_drop.dragged = _this.container.find("li.dragged"); - } - } - else { - if (_this.check("draggable", obj)) { - tree_component.drag_drop.drag_node = obj; - tree_component.drag_drop.drag_help = $("
          ").append("
            "); - var tmp = obj.clone(); - if (_this.settings.languages.length > 0) tmp.find("a").not("." + _this.current_lang).hide(); - tree_component.drag_drop.drag_help.children("ul:eq(0)").append(tmp); - tree_component.drag_drop.drag_help.find("li:eq(0)").removeClass("last").addClass("last"); - tree_component.drag_drop.isdown = true; - tree_component.drag_drop.foreign = false; - tree_component.drag_drop.origin_tree = _this; - obj.addClass("dragged"); - - tree_component.drag_drop.dragged = _this.container.find("li.dragged"); - } - } - tree_component.drag_drop.init_x = event.pageX; - tree_component.drag_drop.init_y = event.pageY; - obj.blur(); - event.preventDefault(); - event.stopPropagation(); - return false; - }); - }, - focus: function () { - if (this.locked) return false; - if (tree_component.focused != this.cntr) { - tree_component.focused = this.cntr; - this.callback("onfocus", [this]); - } - }, - - off_height: function () { - if (this.offset === false) { - this.container.css({ position: "relative" }); - this.offset = this.container.offset(); - var tmp = 0; - tmp = parseInt($.curCSS(this.container.get(0), "paddingTop", true), 10); - if (tmp) this.offset.top += tmp; - tmp = parseInt($.curCSS(this.container.get(0), "borderTopWidth", true), 10); - if (tmp) this.offset.top += tmp; - this.container.css({ position: "" }); - } - if (!this.li_height) { - var tmp = this.container.find("ul li.closed, ul li.leaf").eq(0); - this.li_height = tmp.height(); - if (tmp.children("ul:eq(0)").size()) this.li_height -= tmp.children("ul:eq(0)").height(); - if (!this.li_height) this.li_height = 18; - } - }, - scroll_check: function (x, y) { - var _this = this; - var cnt = _this.container; - var off = _this.container.offset(); - - var st = cnt.scrollTop(); - var sl = cnt.scrollLeft(); - // DETECT HORIZONTAL SCROLL - var h_cor = (cnt.get(0).scrollWidth > cnt.width()) ? 40 : 20; - - if (y - off.top < 20) cnt.scrollTop(Math.max((st - _this.settings.ui.scroll_spd), 0)); // NEAR TOP - if (cnt.height() - (y - off.top) < h_cor) cnt.scrollTop(st + _this.settings.ui.scroll_spd); // NEAR BOTTOM - if (x - off.left < 20) cnt.scrollLeft(Math.max((sl - _this.settings.ui.scroll_spd), 0)); // NEAR LEFT - if (cnt.width() - (x - off.left) < 40) cnt.scrollLeft(sl + _this.settings.ui.scroll_spd); // NEAR RIGHT - - if (cnt.scrollLeft() != sl || cnt.scrollTop() != st) { - tree_component.drag_drop.move_type = false; - tree_component.drag_drop.ref_node = false; - tree_component.drag_drop.marker.hide(); - } - tree_component.drag_drop.scroll_time = setTimeout(function () { _this.scroll_check(x, y); }, 50); - }, - scroll_into_view: function (obj) { - obj = obj ? this.get_node(obj) : this.selected; - if (!obj) return false; - var off_t = obj.offset().top; - var beg_t = this.container.offset().top; - var end_t = beg_t + this.container.height(); - var h_cor = (this.container.get(0).scrollWidth > this.container.width()) ? 40 : 20; - if (off_t + 5 < beg_t) this.container.scrollTop(this.container.scrollTop() - (beg_t - off_t + 5)); - if (off_t + h_cor > end_t) this.container.scrollTop(this.container.scrollTop() + (off_t + h_cor - end_t)); - }, - - get_node: function (obj) { - return $(obj).closest("li"); - }, - get_type: function (obj) { - obj = !obj ? this.selected : this.get_node(obj); - if (!obj) return; - var tmp = obj.attr(this.settings.rules.type_attr); - return tmp || "default"; - }, - set_type: function (str, obj) { - obj = !obj ? this.selected : this.get_node(obj); - if (!obj || !str) return; - obj.attr(this.settings.rules.type_attr, str); - }, - get_text: function (obj, lang) { - obj = this.get_node(obj); - if (!obj || obj.size() == 0) return ""; - if (this.settings.languages && this.settings.languages.length) { - lang = lang ? lang : this.current_lang; - obj = obj.children("a." + lang); - } - else obj = obj.children("a:visible"); - var val = ""; - obj.contents().each(function () { - if (this.nodeType == 3) { val = this.data; return false; } - }); - return val; - }, - - check: function (rule, obj) { - if (this.locked) return false; - var v = false; - // if root node - if (obj === -1) { if (typeof this.settings.rules[rule] != "undefined") v = this.settings.rules[rule]; } - else { - obj = !obj ? this.selected : this.get_node(obj); - if (!obj) return; - var t = this.get_type(obj); - if (typeof this.settings.types[t] != "undefined" && typeof this.settings.types[t][rule] != "undefined") v = this.settings.types[t][rule]; - else if (typeof this.settings.types["default"] != "undefined" && typeof this.settings.types["default"][rule] != "undefined") v = this.settings.types["default"][rule]; - } - if (typeof v == "function") v = v.call(null, obj, this); - v = this.callback("check", [rule, obj, v, this]); - return v; - }, - check_move: function (nod, ref_node, how) { - if (this.locked) return false; - if ($(ref_node).closest("li.dragged").size()) return false; - - var tree1 = nod.parents(".tree:eq(0)").get(0); - var tree2 = ref_node.parents(".tree:eq(0)").get(0); - // if different trees - if (tree1 && tree1 != tree2) { - var m = $.tree.reference(tree2.id).settings.rules.multitree; - if (m == "none" || ($.isArray(m) && $.inArray(tree1.id, m) == -1)) return false; - } - - var p = (how != "inside") ? this.parent(ref_node) : this.get_node(ref_node); - nod = this.get_node(nod); - if (p == false) return false; - var r = { - max_depth: this.settings.rules.use_max_depth ? this.check("max_depth", p) : -1, - max_children: this.settings.rules.use_max_children ? this.check("max_children", p) : -1, - valid_children: this.check("valid_children", p) - }; - var nod_type = (typeof nod == "string") ? nod : this.get_type(nod); - if (typeof r.valid_children != "undefined" && (r.valid_children == "none" || (typeof r.valid_children == "object" && $.inArray(nod_type, $.makeArray(r.valid_children)) == -1))) return false; - - if (this.settings.rules.use_max_children) { - if (typeof r.max_children != "undefined" && r.max_children != -1) { - if (r.max_children == 0) return false; - var c_count = 1; - if (tree_component.drag_drop.moving == true && tree_component.drag_drop.foreign == false) { - c_count = tree_component.drag_drop.dragged.size(); - c_count = c_count - p.find('> ul > li.dragged').size(); - } - if (r.max_children < p.find('> ul > li').size() + c_count) return false; - } - } - - if (this.settings.rules.use_max_depth) { - if (typeof r.max_depth != "undefined" && r.max_depth === 0) return this.error("MOVE: MAX-DEPTH REACHED"); - // check for max_depth up the chain - var mx = (r.max_depth > 0) ? r.max_depth : false; - var i = 0; - var t = p; - while (t !== -1) { - t = this.parent(t); - i++; - var m = this.check("max_depth", t); - if (m >= 0) { - mx = (mx === false) ? (m - i) : Math.min(mx, m - i); - } - if (mx !== false && mx <= 0) return this.error("MOVE: MAX-DEPTH REACHED"); - } - if (mx !== false && mx <= 0) return this.error("MOVE: MAX-DEPTH REACHED"); - if (mx !== false) { - var incr = 1; - if (typeof nod != "string") { - var t = nod; - // possible async problem - when nodes are not all loaded down the chain - while (t.size() > 0) { - if (mx - incr < 0) return this.error("MOVE: MAX-DEPTH REACHED"); - t = t.children("ul").children("li"); - incr++; - } - } - } - } - if (this.callback("check_move", [nod, ref_node, how, this]) == false) return false; - return true; - }, - - hover_branch: function (obj) { - if (this.locked) return this.error("LOCKED"); - var _this = this; - var obj = _this.get_node(obj); - if (!obj.size()) return this.error("HOVER: NOT A VALID NODE"); - if (!_this.check("clickable", obj)) return this.error("SELECT: NODE NOT SELECTABLE"); - if (this.hovered) this.hovered.children("A").removeClass("hover"); - this.hovered = obj; - this.hovered.children("a").addClass("hover"); - this.scroll_into_view(this.hovered); - }, - select_branch: function (obj, multiple) { - if (this.locked) return this.error("LOCKED"); - if (!obj && this.hovered !== false) obj = this.hovered; - var _this = this; - obj = _this.get_node(obj); - if (!obj.size()) return this.error("SELECT: NOT A VALID NODE"); - obj.children("a").removeClass("hover"); - // CHECK AGAINST RULES FOR SELECTABLE NODES - if (!_this.check("clickable", obj)) return this.error("SELECT: NODE NOT SELECTABLE"); - if (_this.callback("beforechange", [obj.get(0), _this]) === false) return this.error("SELECT: STOPPED BY USER"); - // IF multiple AND obj IS ALREADY SELECTED - DESELECT IT - if (this.settings.rules.multiple != false && multiple && obj.children("a.clicked").size() > 0) { - return this.deselect_branch(obj); - } - if (this.settings.rules.multiple != false && multiple) { - this.selected_arr.push(obj); - } - if (this.settings.rules.multiple != false && !multiple) { - for (var i in this.selected_arr) { - if (!this.selected_arr.hasOwnProperty(i)) continue; - this.selected_arr[i].children("A").removeClass("clicked"); - this.callback("ondeselect", [this.selected_arr[i].get(0), _this]); - } - this.selected_arr = []; - this.selected_arr.push(obj); - if (this.selected && this.selected.children("A").hasClass("clicked")) { - this.selected.children("A").removeClass("clicked"); - this.callback("ondeselect", [this.selected.get(0), _this]); - } - } - if (!this.settings.rules.multiple) { - if (this.selected) { - this.selected.children("A").removeClass("clicked"); - this.callback("ondeselect", [this.selected.get(0), _this]); - } - } - // SAVE NEWLY SELECTED - this.selected = obj; - if (this.hovered !== false) { - this.hovered.children("A").removeClass("hover"); - this.hovered = obj; - } - - // FOCUS NEW NODE AND OPEN ALL PARENT NODES IF CLOSED - this.selected.children("a").addClass("clicked").end().parents("li.closed").each(function () { _this.open_branch(this, true); }); - - // SCROLL SELECTED NODE INTO VIEW - this.scroll_into_view(this.selected); - - this.callback("onselect", [this.selected.get(0), _this]); - this.callback("onchange", [this.selected.get(0), _this]); - }, - deselect_branch: function (obj) { - if (this.locked) return this.error("LOCKED"); - var _this = this; - var obj = this.get_node(obj); - if (obj.children("a.clicked").size() == 0) return this.error("DESELECT: NODE NOT SELECTED"); - - obj.children("a").removeClass("clicked"); - this.callback("ondeselect", [obj.get(0), _this]); - if (this.settings.rules.multiple != false && this.selected_arr.length > 1) { - this.selected_arr = []; - this.container.find("a.clicked").filter(":first-child").parent().each(function () { - _this.selected_arr.push($(this)); - }); - if (obj.get(0) == this.selected.get(0)) { - this.selected = this.selected_arr[0]; - } - } - else { - if (this.settings.rules.multiple != false) this.selected_arr = []; - this.selected = false; - } - this.callback("onchange", [obj.get(0), _this]); - }, - toggle_branch: function (obj) { - if (this.locked) return this.error("LOCKED"); - var obj = this.get_node(obj); - if (obj.hasClass("closed")) return this.open_branch(obj); - if (obj.hasClass("open")) return this.close_branch(obj); - }, - open_branch: function (obj, disable_animation, callback) { - var _this = this; - - if (this.locked) return this.error("LOCKED"); - var obj = this.get_node(obj); - if (!obj.size()) return this.error("OPEN: NO SUCH NODE"); - if (obj.hasClass("leaf")) return this.error("OPEN: OPENING LEAF NODE"); - if (this.settings.data.async && obj.find("li").size() == 0) { - - if (this.callback("beforeopen", [obj.get(0), this]) === false) return this.error("OPEN: STOPPED BY USER"); - - obj.children("ul:eq(0)").remove().end().append(""); - obj.removeClass("closed").addClass("open"); - - var _datastore = new $.tree.datastores[this.settings.data.type](); - _datastore.load(this.callback("beforedata", [obj, this]), this, this.settings.data.opts, function (data) { - data = _this.callback("ondata", [data, _this]); - if (!data || data.length == 0) { - obj.removeClass("closed").removeClass("open").addClass("leaf").children("ul").remove(); - if (callback) callback.call(); - return; - } - _datastore.parse(data, _this, _this.settings.data.opts, function (str) { - str = _this.callback("onparse", [str, _this]); - // if(obj.children('ul:eq(0)').children('li').size() > 1) obj.children("ul").find('.loaading').parent().replaceWith(str); else - obj.children("ul:eq(0)").replaceWith($("
              ").html(str)); - obj.find("li:last-child").addClass("last").end().find("li:has(ul)").not(".open").addClass("closed"); - obj.find("li").not(".open").not(".closed").addClass("leaf"); - _this.open_branch.apply(_this, [obj]); - if (callback) callback.call(); - }); - }); - return true; - } - else { - if (!this.settings.data.async) { - if (this.callback("beforeopen", [obj.get(0), this]) === false) return this.error("OPEN: STOPPED BY USER"); - } - if (parseInt(this.settings.ui.animation) > 0 && !disable_animation) { - obj.children("ul:eq(0)").css("display", "none"); - obj.removeClass("closed").addClass("open"); - obj.children("ul:eq(0)").slideDown(parseInt(this.settings.ui.animation), function () { - $(this).css("display", ""); - if (callback) callback.call(); - }); - } else { - obj.removeClass("closed").addClass("open"); - if (callback) callback.call(); - } - this.callback("onopen", [obj.get(0), this]); - return true; - } - }, - close_branch: function (obj, disable_animation) { - if (this.locked) return this.error("LOCKED"); - var _this = this; - var obj = this.get_node(obj); - if (!obj.size()) return this.error("CLOSE: NO SUCH NODE"); - if (_this.callback("beforeclose", [obj.get(0), _this]) === false) return this.error("CLOSE: STOPPED BY USER"); - if (parseInt(this.settings.ui.animation) > 0 && !disable_animation && obj.children("ul:eq(0)").size() == 1) { - obj.children("ul:eq(0)").slideUp(parseInt(this.settings.ui.animation), function () { - if (obj.hasClass("open")) obj.removeClass("open").addClass("closed"); - $(this).css("display", ""); - }); - } - else { - if (obj.hasClass("open")) obj.removeClass("open").addClass("closed"); - } - if (this.selected && this.settings.ui.selected_parent_close !== false && obj.children("ul:eq(0)").find("a.clicked").size() > 0) { - obj.find("li:has(a.clicked)").each(function () { - _this.deselect_branch(this); - }); - if (this.settings.ui.selected_parent_close == "select_parent" && obj.children("a.clicked").size() == 0) this.select_branch(obj, (this.settings.rules.multiple != false && this.selected_arr.length > 0)); - } - this.callback("onclose", [obj.get(0), this]); - }, - open_all: function (obj, callback) { - if (this.locked) return this.error("LOCKED"); - var _this = this; - obj = obj ? this.get_node(obj) : this.container; - - var s = obj.find("li.closed").size(); - if (!callback) this.cl_count = 0; - else this.cl_count--; - if (s > 0) { - this.cl_count += s; - // maybe add .andSelf() - obj.find("li.closed").each(function () { var __this = this; _this.open_branch.apply(_this, [this, true, function () { _this.open_all.apply(_this, [__this, true]); } ]); }); - } - else if (this.cl_count == 0) this.callback("onopen_all", [this]); - }, - close_all: function (obj) { - if (this.locked) return this.error("LOCKED"); - var _this = this; - obj = obj ? this.get_node(obj) : this.container; - // maybe add .andSelf() - obj.find("li.open").each(function () { _this.close_branch(this, true); }); - this.callback("onclose_all", [this]); - }, - - set_lang: function (i) { - if (!$.isArray(this.settings.languages) || this.settings.languages.length == 0) return false; - if (this.locked) return this.error("LOCKED"); - if (!$.inArray(i, this.settings.languages) && typeof this.settings.languages[i] != "undefined") i = this.settings.languages[i]; - if (typeof i == "undefined") return false; - if (i == this.current_lang) return true; - var st = false; - var id = "#" + this.container.attr("id"); - st = tree_component.get_css(id + " ." + this.current_lang); - if (st !== false) st.style.display = "none"; - st = tree_component.get_css(id + " ." + i); - if (st !== false) st.style.display = ""; - this.current_lang = i; - return true; - }, - get_lang: function () { - if (!$.isArray(this.settings.languages) || this.settings.languages.length == 0) return false; - return this.current_lang; - }, - - create: function (obj, ref_node, position) { - if (this.locked) return this.error("LOCKED"); - - var root = false; - if (ref_node == -1) { root = true; ref_node = this.container; } - else ref_node = ref_node ? this.get_node(ref_node) : this.selected; - - if (!root && (!ref_node || !ref_node.size())) return this.error("CREATE: NO NODE SELECTED"); - - var pos = position; - - var tmp = ref_node; // for type calculation - if (position == "before") { - position = ref_node.parent().children().index(ref_node); - ref_node = ref_node.parents("li:eq(0)"); - } - if (position == "after") { - position = ref_node.parent().children().index(ref_node) + 1; - ref_node = ref_node.parents("li:eq(0)"); - } - if (!root && ref_node.size() == 0) { root = true; ref_node = this.container; } - - if (!root) { - if (!this.check("creatable", ref_node)) return this.error("CREATE: CANNOT CREATE IN NODE"); - if (ref_node.hasClass("closed")) { - if (this.settings.data.async && ref_node.children("ul").size() == 0) { - var _this = this; - return this.open_branch(ref_node, true, function () { _this.create.apply(_this, [obj, ref_node, position]); }); - } - else this.open_branch(ref_node, true); - } - } - - // creating new object to pass to parseJSON - var torename = false; - if (!obj) obj = {}; - else obj = $.extend(true, {}, obj); - if (!obj.attributes) obj.attributes = {}; - if (!obj.attributes[this.settings.rules.type_attr]) obj.attributes[this.settings.rules.type_attr] = this.get_type(tmp) || "default"; - if (this.settings.languages.length) { - if (!obj.data) { obj.data = {}; torename = true; } - for (var i = 0; i < this.settings.languages.length; i++) { - if (!obj.data[this.settings.languages[i]]) obj.data[this.settings.languages[i]] = ((typeof this.settings.lang.new_node).toLowerCase() != "string" && this.settings.lang.new_node[i]) ? this.settings.lang.new_node[i] : this.settings.lang.new_node; - } - } - else { - if (!obj.data) { obj.data = this.settings.lang.new_node; torename = true; } - } - - obj = this.callback("ondata", [obj, this]); - var obj_s = $.tree.datastores.json().parse(obj, this); - obj_s = this.callback("onparse", [obj_s, this]); - var $li = $(obj_s); - - if ($li.children("ul").size()) { - if (!$li.is(".open")) $li.addClass("closed"); - } - else $li.addClass("leaf"); - $li.find("li:last-child").addClass("last").end().find("li:has(ul)").not(".open").addClass("closed"); - $li.find("li").not(".open").not(".closed").addClass("leaf"); - - var r = { - max_depth: this.settings.rules.use_max_depth ? this.check("max_depth", (root ? -1 : ref_node)) : -1, - max_children: this.settings.rules.use_max_children ? this.check("max_children", (root ? -1 : ref_node)) : -1, - valid_children: this.check("valid_children", (root ? -1 : ref_node)) - }; - var nod_type = this.get_type($li); - if (typeof r.valid_children != "undefined" && (r.valid_children == "none" || ($.isArray(r.valid_children) && $.inArray(nod_type, r.valid_children) == -1))) return this.error("CREATE: NODE NOT A VALID CHILD"); - - if (this.settings.rules.use_max_children) { - if (typeof r.max_children != "undefined" && r.max_children != -1 && r.max_children >= this.children(ref_node).size()) return this.error("CREATE: MAX_CHILDREN REACHED"); - } - - if (this.settings.rules.use_max_depth) { - if (typeof r.max_depth != "undefined" && r.max_depth === 0) return this.error("CREATE: MAX-DEPTH REACHED"); - // check for max_depth up the chain - var mx = (r.max_depth > 0) ? r.max_depth : false; - var i = 0; - var t = ref_node; - - while (t !== -1 && !root) { - t = this.parent(t); - i++; - var m = this.check("max_depth", t); - if (m >= 0) { - mx = (mx === false) ? (m - i) : Math.min(mx, m - i); - } - if (mx !== false && mx <= 0) return this.error("CREATE: MAX-DEPTH REACHED"); - } - if (mx !== false && mx <= 0) return this.error("CREATE: MAX-DEPTH REACHED"); - if (mx !== false) { - var incr = 1; - var t = $li; - while (t.size() > 0) { - if (mx - incr < 0) return this.error("CREATE: MAX-DEPTH REACHED"); - t = t.children("ul").children("li"); - incr++; - } - } - } - - if ((typeof position).toLowerCase() == "undefined" || position == "inside") - position = (this.settings.rules.createat == "top") ? 0 : ref_node.children("ul:eq(0)").children("li").size(); - if (ref_node.children("ul").size() == 0 || (root == true && ref_node.children("ul").children("li").size() == 0)) { - if (!root) var a = this.moved($li, ref_node.children("a:eq(0)"), "inside", true); - else var a = this.moved($li, this.container.children("ul:eq(0)"), "inside", true); - } - else if (pos == "before" && ref_node.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").size()) - var a = this.moved($li, ref_node.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").children("a:eq(0)"), "before", true); - else if (pos == "after" && ref_node.children("ul:eq(0)").children("li:nth-child(" + (position) + ")").size()) - var a = this.moved($li, ref_node.children("ul:eq(0)").children("li:nth-child(" + (position) + ")").children("a:eq(0)"), "after", true); - else if (ref_node.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").size()) - var a = this.moved($li, ref_node.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").children("a:eq(0)"), "before", true); - else - var a = this.moved($li, ref_node.children("ul:eq(0)").children("li:last").children("a:eq(0)"), "after", true); - - if (a === false) return this.error("CREATE: ABORTED"); - - if (torename) { - this.select_branch($li.children("a:eq(0)")); - this.rename(); - } - return $li; - }, - rename: function (obj, new_name) { - if (this.locked) return this.error("LOCKED"); - obj = obj ? this.get_node(obj) : this.selected; - var _this = this; - if (!obj || !obj.size()) return this.error("RENAME: NO NODE SELECTED"); - if (!this.check("renameable", obj)) return this.error("RENAME: NODE NOT RENAMABLE"); - if (!this.callback("beforerename", [obj.get(0), _this.current_lang, _this])) return this.error("RENAME: STOPPED BY USER"); - - obj.parents("li.closed").each(function () { _this.open_branch(this) }); - if (this.current_lang) obj = obj.find("a." + this.current_lang); - else obj = obj.find("a:first"); - - // Rollback - var rb = {}; - rb[this.container.attr("id")] = this.get_rollback(); - - var icn = obj.children("ins").clone(); - if ((typeof new_name).toLowerCase() == "string") { - obj.text(new_name).prepend(icn); - _this.callback("onrename", [_this.get_node(obj).get(0), _this, rb]); - } - else { - var last_value = ""; - obj.contents().each(function () { - if (this.nodeType == 3) { last_value = this.data; return false; } - }); - _this.inp = $(""); - _this.inp - .val(last_value.replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<")) - .bind("mousedown", function (event) { event.stopPropagation(); }) - .bind("mouseup", function (event) { event.stopPropagation(); }) - .bind("click", function (event) { event.stopPropagation(); }) - .bind("keyup", function (event) { - var key = event.keyCode || event.which; - if (key == 27) { this.value = last_value; this.blur(); return } - if (key == 13) { this.blur(); return; } - }); - _this.inp.blur(function (event) { - if (this.value == "") this.value = last_value; - obj.text(this.value).prepend(icn); - obj.get(0).style.display = ""; - obj.prevAll("span").remove(); - _this.inp = false; - _this.callback("onrename", [_this.get_node(obj).get(0), _this, rb]); - }); - - var spn = $("").addClass(obj.attr("class")).append(icn).append(_this.inp); - obj.get(0).style.display = "none"; - obj.parent().prepend(spn); - _this.inp.get(0).focus(); - _this.inp.get(0).select(); - } - }, - remove: function (obj) { - if (this.locked) return this.error("LOCKED"); - var _this = this; - - // Rollback - var rb = {}; - rb[this.container.attr("id")] = this.get_rollback(); - - if (obj && (!this.selected || this.get_node(obj).get(0) != this.selected.get(0))) { - obj = this.get_node(obj); - if (obj.size()) { - if (!this.check("deletable", obj)) return this.error("DELETE: NODE NOT DELETABLE"); - if (!this.callback("beforedelete", [obj.get(0), _this])) return this.error("DELETE: STOPPED BY USER"); - $parent = obj.parent(); - if (obj.find("a.clicked").size()) { - var reset_selected = false; - _this.selected_arr = []; - this.container.find("a.clicked").filter(":first-child").parent().each(function () { - if (!reset_selected && this == _this.selected.get(0)) reset_selected = true; - if ($(this).parents().index(obj) != -1) return true; - _this.selected_arr.push($(this)); - }); - if (reset_selected) this.selected = this.selected_arr[0] || false; - } - obj = obj.remove(); - $parent.children("li:last").addClass("last"); - if ($parent.children("li").size() == 0) { - $li = $parent.parents("li:eq(0)"); - $li.removeClass("open").removeClass("closed").addClass("leaf").children("ul").remove(); - } - this.callback("ondelete", [obj.get(0), this, rb]); - } - } - else if (this.selected) { - if (!this.check("deletable", this.selected)) return this.error("DELETE: NODE NOT DELETABLE"); - if (!this.callback("beforedelete", [this.selected.get(0), _this])) return this.error("DELETE: STOPPED BY USER"); - $parent = this.selected.parent(); - var obj = this.selected; - if (this.settings.rules.multiple == false || this.selected_arr.length == 1) { - var stop = true; - var tmp = this.settings.ui.selected_delete == "select_previous" ? this.prev(this.selected) : false; - } - obj = obj.remove(); - $parent.children("li:last").addClass("last"); - if ($parent.children("li").size() == 0) { - $li = $parent.parents("li:eq(0)"); - $li.removeClass("open").removeClass("closed").addClass("leaf").children("ul").remove(); - } - if (!stop && this.settings.rules.multiple != false) { - var _this = this; - this.selected_arr = []; - this.container.find("a.clicked").filter(":first-child").parent().each(function () { - _this.selected_arr.push($(this)); - }); - if (this.selected_arr.length > 0) { - this.selected = this.selected_arr[0]; - this.remove(); - } - } - if (stop && tmp) this.select_branch(tmp); - this.callback("ondelete", [obj.get(0), this, rb]); - } - else return this.error("DELETE: NO NODE SELECTED"); - }, - - next: function (obj, strict) { - obj = this.get_node(obj); - if (!obj.size()) return false; - if (strict) return (obj.nextAll("li").size() > 0) ? obj.nextAll("li:eq(0)") : false; - - if (obj.hasClass("open")) return obj.find("li:eq(0)"); - else if (obj.nextAll("li").size() > 0) return obj.nextAll("li:eq(0)"); - else return obj.parents("li").next("li").eq(0); - }, - prev: function (obj, strict) { - obj = this.get_node(obj); - if (!obj.size()) return false; - if (strict) return (obj.prevAll("li").size() > 0) ? obj.prevAll("li:eq(0)") : false; - - if (obj.prev("li").size()) { - var obj = obj.prev("li").eq(0); - while (obj.hasClass("open")) obj = obj.children("ul:eq(0)").children("li:last"); - return obj; - } - else return obj.parents("li:eq(0)").size() ? obj.parents("li:eq(0)") : false; - }, - parent: function (obj) { - obj = this.get_node(obj); - if (!obj.size()) return false; - return obj.parents("li:eq(0)").size() ? obj.parents("li:eq(0)") : -1; - }, - children: function (obj) { - if (obj === -1) return this.container.children("ul:eq(0)").children("li"); - - obj = this.get_node(obj); - if (!obj.size()) return false; - return obj.children("ul:eq(0)").children("li"); - }, - - toggle_dots: function () { - if (this.settings.ui.dots) { - this.settings.ui.dots = false; - this.container.children("ul:eq(0)").addClass("no_dots"); - } - else { - this.settings.ui.dots = true; - this.container.children("ul:eq(0)").removeClass("no_dots"); - } - }, - - callback: function (cb, args) { - var p = false; - var r = null; - for (var i in this.settings.plugins) { - if (typeof $.tree.plugins[i] != "object") continue; - p = $.tree.plugins[i]; - if (p.callbacks && typeof p.callbacks[cb] == "function") r = p.callbacks[cb].apply(this, args); - if (typeof r !== "undefined" && r !== null) { - if (cb == "ondata" || cb == "onparse") args[0] = r; // keep the chain if data or parse - else return r; - } - } - p = this.settings.callback[cb]; - if (typeof p == "function") return p.apply(null, args); - }, - get_rollback: function () { - var rb = {}; - rb.html = this.container.html(); - rb.selected = this.selected ? this.selected.attr("id") : false; - return rb; - }, - moved: function (what, where, how, is_new, is_copy, rb) { - var what = $(what); - var $parent = $(what).parents("ul:eq(0)"); - var $where = $(where); - if ($where.is("ins")) $where = $where.parent(); - - // Rollback - if (!rb) { - var rb = {}; - rb[this.container.attr("id")] = this.get_rollback(); - if (!is_new) { - var tmp = what.size() > 1 ? what.eq(0).parents(".tree:eq(0)") : what.parents(".tree:eq(0)"); - if (tmp.get(0) != this.container.get(0)) { - tmp = tree_component.inst[tmp.attr("id")]; - rb[tmp.container.attr("id")] = tmp.get_rollback(); - } - delete tmp; - } - } - - if (how == "inside" && this.settings.data.async) { - var _this = this; - if (this.get_node($where).hasClass("closed")) { - return this.open_branch(this.get_node($where), true, function () { _this.moved.apply(_this, [what, where, how, is_new, is_copy, rb]); }); - } - if (this.get_node($where).find("> ul > li > a.loading").size() == 1) { - setTimeout(function () { _this.moved.apply(_this, [what, where, how, is_new, is_copy]); }, 200); - return; - } - } - - - // IF MULTIPLE - if (what.size() > 1) { - var _this = this; - var tmp = this.moved(what.eq(0), where, how, false, is_copy, rb); - what.each(function (i) { - if (i == 0) return; - if (tmp) { // if tmp is false - the previous move was a no-go - tmp = _this.moved(this, tmp.children("a:eq(0)"), "after", false, is_copy, rb); - } - }); - return what; - } - - if (is_copy) { - _what = what.clone(); - _what.each(function (i) { - this.id = this.id + "_copy"; - $(this).find("li").each(function () { - this.id = this.id + "_copy"; - }); - $(this).removeClass("dragged").find("a.clicked").removeClass("clicked").end().find("li.dragged").removeClass("dragged"); - }); - } - else _what = what; - if (is_new) { - if (!this.callback("beforecreate", [this.get_node(what).get(0), this.get_node(where).get(0), how, this])) return false; - } - else { - if (!this.callback("beforemove", [this.get_node(what).get(0), this.get_node(where).get(0), how, this])) return false; - } - - if (!is_new) { - var tmp = what.parents(".tree:eq(0)"); - // if different trees - if (tmp.get(0) != this.container.get(0)) { - tmp = tree_component.inst[tmp.attr("id")]; - - // if there are languages - otherwise - no cleanup needed - if (tmp.settings.languages.length) { - var res = []; - // if new tree has no languages - use current visible - if (this.settings.languages.length == 0) res.push("." + tmp.current_lang); - else { - for (var i in this.settings.languages) { - if (!this.settings.languages.hasOwnProperty(i)) continue; - for (var j in tmp.settings.languages) { - if (!tmp.settings.languages.hasOwnProperty(j)) continue; - if (this.settings.languages[i] == tmp.settings.languages[j]) res.push("." + this.settings.languages[i]); - } - } - } - if (res.length == 0) return this.error("MOVE: NO COMMON LANGUAGES"); - _what.find("a").not(res.join(",")).remove(); - } - _what.find("a.clicked").removeClass("clicked"); - } - } - what = _what; - - // ADD NODE TO NEW PLACE - switch (how) { - case "before": - $where.parents("ul:eq(0)").children("li.last").removeClass("last"); - $where.parent().before(what.removeClass("last")); - $where.parents("ul:eq(0)").children("li:last").addClass("last"); - break; - case "after": - $where.parents("ul:eq(0)").children("li.last").removeClass("last"); - $where.parent().after(what.removeClass("last")); - $where.parents("ul:eq(0)").children("li:last").addClass("last"); - break; - case "inside": - if ($where.parent().children("ul:first").size()) { - if (this.settings.rules.createat == "top") { - $where.parent().children("ul:first").prepend(what.removeClass("last")).children("li:last").addClass("last"); - - // restored this section - var tmp_node = $where.parent().children("ul:first").children("li:first"); - if (tmp_node.size()) { - how = "before"; - where = tmp_node; - } - } - else { - // restored this section - var tmp_node = $where.parent().children("ul:first").children(".last"); - if (tmp_node.size()) { - how = "after"; - where = tmp_node; - } - - $where.parent().children("ul:first").children(".last").removeClass("last").end().append(what.removeClass("last")).children("li:last").addClass("last"); - } - } - else { - what.addClass("last"); - $where.parent().removeClass("leaf").append("
                "); - if (!$where.parent().hasClass("open")) $where.parent().addClass("closed"); - $where.parent().children("ul:first").prepend(what); - } - if ($where.parent().hasClass("closed")) { this.open_branch($where); } - break; - default: - break; - } - // CLEANUP OLD PARENT - if ($parent.find("li").size() == 0) { - var $li = $parent.parent(); - $li.removeClass("open").removeClass("closed").addClass("leaf"); - if (!$li.is(".tree")) $li.children("ul").remove(); - $li.parents("ul:eq(0)").children("li.last").removeClass("last").end().children("li:last").addClass("last"); - } - else { - $parent.children("li.last").removeClass("last"); - $parent.children("li:last").addClass("last"); - } - - // NO LONGER CORRECT WITH position PARAM - if(is_new && how != "inside") where = this.get_node(where).parents("li:eq(0)"); - if (is_copy) this.callback("oncopy", [this.get_node(what).get(0), this.get_node(where).get(0), how, this, rb]); - else if (is_new) this.callback("oncreate", [this.get_node(what).get(0), ($where.is("ul") ? -1 : this.get_node(where).get(0)), how, this, rb]); - else this.callback("onmove", [this.get_node(what).get(0), this.get_node(where).get(0), how, this, rb]); - return what; - }, - error: function (code) { - this.callback("error", [code, this]); - return false; - }, - lock: function (state) { - this.locked = state; - if (this.locked) this.container.children("ul:eq(0)").addClass("locked"); - else this.container.children("ul:eq(0)").removeClass("locked"); - }, - cut: function (obj) { - if (this.locked) return this.error("LOCKED"); - obj = obj ? this.get_node(obj) : this.container.find("a.clicked").filter(":first-child").parent(); - if (!obj || !obj.size()) return this.error("CUT: NO NODE SELECTED"); - tree_component.cut_copy.copy_nodes = false; - tree_component.cut_copy.cut_nodes = obj; - }, - copy: function (obj) { - if (this.locked) return this.error("LOCKED"); - obj = obj ? this.get_node(obj) : this.container.find("a.clicked").filter(":first-child").parent(); - if (!obj || !obj.size()) return this.error("COPY: NO NODE SELECTED"); - tree_component.cut_copy.copy_nodes = obj; - tree_component.cut_copy.cut_nodes = false; - }, - paste: function (obj, position) { - if (this.locked) return this.error("LOCKED"); - - var root = false; - if (obj == -1) { root = true; obj = this.container; } - else obj = obj ? this.get_node(obj) : this.selected; - - if (!root && (!obj || !obj.size())) return this.error("PASTE: NO NODE SELECTED"); - if (!tree_component.cut_copy.copy_nodes && !tree_component.cut_copy.cut_nodes) return this.error("PASTE: NOTHING TO DO"); - - var _this = this; - - var pos = position; - - if (position == "before") { - position = obj.parent().children().index(obj); - obj = obj.parents("li:eq(0)"); - } - else if (position == "after") { - position = obj.parent().children().index(obj) + 1; - obj = obj.parents("li:eq(0)"); - } - else if ((typeof position).toLowerCase() == "undefined" || position == "inside") { - position = (this.settings.rules.createat == "top") ? 0 : obj.children("ul:eq(0)").children("li").size(); - } - if (!root && obj.size() == 0) { root = true; obj = this.container; } - - if (tree_component.cut_copy.copy_nodes && tree_component.cut_copy.copy_nodes.size()) { - var ok = true; - if (!root && !this.check_move(tree_component.cut_copy.copy_nodes, obj.children("a:eq(0)"), "inside")) return false; - - if (obj.children("ul").size() == 0 || (root == true && obj.children("ul").children("li").size() == 0)) { - if (!root) var a = this.moved(tree_component.cut_copy.copy_nodes, obj.children("a:eq(0)"), "inside", false, true); - else var a = this.moved(tree_component.cut_copy.copy_nodes, this.container.children("ul:eq(0)"), "inside", false, true); - } - else if (pos == "before" && obj.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").size()) - var a = this.moved(tree_component.cut_copy.copy_nodes, obj.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").children("a:eq(0)"), "before", false, true); - else if (pos == "after" && obj.children("ul:eq(0)").children("li:nth-child(" + (position) + ")").size()) - var a = this.moved(tree_component.cut_copy.copy_nodes, obj.children("ul:eq(0)").children("li:nth-child(" + (position) + ")").children("a:eq(0)"), "after", false, true); - else if (obj.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").size()) - var a = this.moved(tree_component.cut_copy.copy_nodes, obj.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").children("a:eq(0)"), "before", false, true); - else - var a = this.moved(tree_component.cut_copy.copy_nodes, obj.children("ul:eq(0)").children("li:last").children("a:eq(0)"), "after", false, true); - tree_component.cut_copy.copy_nodes = false; - } - if (tree_component.cut_copy.cut_nodes && tree_component.cut_copy.cut_nodes.size()) { - var ok = true; - obj.parents().andSelf().each(function () { - if (tree_component.cut_copy.cut_nodes.index(this) != -1) { - ok = false; - return false; - } - }); - if (!ok) return this.error("Invalid paste"); - if (!root && !this.check_move(tree_component.cut_copy.cut_nodes, obj.children("a:eq(0)"), "inside")) return false; - - if (obj.children("ul").size() == 0 || (root == true && obj.children("ul").children("li").size() == 0)) { - if (!root) var a = this.moved(tree_component.cut_copy.cut_nodes, obj.children("a:eq(0)"), "inside"); - else var a = this.moved(tree_component.cut_copy.cut_nodes, this.container.children("ul:eq(0)"), "inside"); - } - else if (pos == "before" && obj.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").size()) - var a = this.moved(tree_component.cut_copy.cut_nodes, obj.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").children("a:eq(0)"), "before"); - else if (pos == "after" && obj.children("ul:eq(0)").children("li:nth-child(" + (position) + ")").size()) - var a = this.moved(tree_component.cut_copy.cut_nodes, obj.children("ul:eq(0)").children("li:nth-child(" + (position) + ")").children("a:eq(0)"), "after"); - else if (obj.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").size()) - var a = this.moved(tree_component.cut_copy.cut_nodes, obj.children("ul:eq(0)").children("li:nth-child(" + (position + 1) + ")").children("a:eq(0)"), "before"); - else - var a = this.moved(tree_component.cut_copy.cut_nodes, obj.children("ul:eq(0)").children("li:last").children("a:eq(0)"), "after"); - tree_component.cut_copy.cut_nodes = false; - } - }, - search: function (str, func) { - var _this = this; - if (!str || (this.srch && str != this.srch)) { - this.srch = ""; - this.srch_opn = false; - this.container.find("a.search").removeClass("search"); - } - this.srch = str; - if (!str) return; - - if (!func) func = "contains"; - if (this.settings.data.async) { - if (!this.srch_opn) { - var dd = $.extend({ "search": str }, this.callback("beforedata", [false, this])); - $.ajax({ - type: this.settings.data.opts.method, - url: this.settings.data.opts.url, - data: dd, - dataType: "text", - success: function (data) { - _this.srch_opn = $.unique(data.split(",")); - _this.search.apply(_this, [str, func]); - } - }); - } - else if (this.srch_opn.length) { - if (this.srch_opn && this.srch_opn.length) { - var opn = false; - for (var j = 0; j < this.srch_opn.length; j++) { - if (this.get_node("#" + this.srch_opn[j]).size() > 0) { - opn = true; - var tmp = "#" + this.srch_opn[j]; - delete this.srch_opn[j]; - this.open_branch(tmp, true, function () { _this.search.apply(_this, [str, func]); }); - } - } - if (!opn) { - this.srch_opn = []; - _this.search.apply(_this, [str, func]); - } - } - } - else { - this.srch_opn = false; - var selector = "a"; - // IF LANGUAGE VERSIONS - if (this.settings.languages.length) selector += "." + this.current_lang; - this.callback("onsearch", [this.container.find(selector + ":" + func + "('" + str + "')"), this]); - } - } - else { - var selector = "a"; - // IF LANGUAGE VERSIONS - if (this.settings.languages.length) selector += "." + this.current_lang; - var nn = this.container.find(selector + ":" + func + "('" + str + "')"); - nn.parents("li.closed").each(function () { _this.open_branch(this, true); }); - this.callback("onsearch", [nn, this]); - } - }, - add_sheet: tree_component.add_sheet, - - destroy: function () { - this.callback("ondestroy", [this]); - - this.container.unbind(".jstree"); - $("#" + this.container.attr("id")).die("click.jstree").die("dblclick.jstree").die("mouseover.jstree").die("mouseout.jstree").die("mousedown.jstree"); - this.container.removeClass("tree ui-widget ui-widget-content tree-default tree-" + this.settings.ui.theme_name).children("ul").removeClass("no_dots ltr locked").find("li").removeClass("leaf").removeClass("open").removeClass("closed").removeClass("last").children("a").removeClass("clicked hover search"); - - if (this.cntr == tree_component.focused) { - for (var i in tree_component.inst) { - if (i != this.cntr && i != this.container.attr("id")) { - tree_component.inst[i].focus(); - break; - } - } - } - - tree_component.inst[this.cntr] = false; - tree_component.inst[this.container.attr("id")] = false; - delete tree_component.inst[this.cntr]; - delete tree_component.inst[this.container.attr("id")]; - tree_component.cntr--; - } - } - }; - - // instance manager - tree_component.cntr = 0; - tree_component.inst = {}; - - // themes - tree_component.themes = []; - - // drag'n'drop stuff - tree_component.drag_drop = { - isdown: false, // Is there a drag - drag_node: false, // The actual node - drag_help: false, // The helper - dragged: false, - - init_x: false, - init_y: false, - moving: false, - - origin_tree: false, - marker: false, - - move_type: false, // before, after or inside - ref_node: false, // reference node - appended: false, // is helper appended - - foreign: false, // Is the dragged node a foreign one - droppable: [], // Array of classes that can be dropped onto the tree - - open_time: false, // Timeout for opening nodes - scroll_time: false // Timeout for scrolling - }; - tree_component.mouseup = function (event) { - var tmp = tree_component.drag_drop; - if (tmp.open_time) clearTimeout(tmp.open_time); - if (tmp.scroll_time) clearTimeout(tmp.scroll_time); - - if (tmp.moving && $.tree.drag_end !== false) $.tree.drag_end.call(null, event, tmp); - - if (tmp.foreign === false && tmp.drag_node && tmp.drag_node.size()) { - tmp.drag_help.remove(); - if (tmp.move_type) { - var tree1 = tree_component.inst[tmp.ref_node.parents(".tree:eq(0)").attr("id")]; - if (tree1) tree1.moved(tmp.dragged, tmp.ref_node, tmp.move_type, false, (tmp.origin_tree.settings.rules.drag_copy == "on" || (tmp.origin_tree.settings.rules.drag_copy == "ctrl" && event.ctrlKey))); - } - tmp.move_type = false; - tmp.ref_node = false; - } - if (tmp.foreign !== false) { - if (tmp.drag_help) tmp.drag_help.remove(); - if (tmp.move_type) { - var tree1 = tree_component.inst[tmp.ref_node.parents(".tree:eq(0)").attr("id")]; - if (tree1) tree1.callback("ondrop", [tmp.f_data, tree1.get_node(tmp.ref_node).get(0), tmp.move_type, tree1]); - } - tmp.foreign = false; - tmp.move_type = false; - tmp.ref_node = false; - } - // RESET EVERYTHING - if (tree_component.drag_drop.marker) tree_component.drag_drop.marker.hide(); - if (tmp.dragged && tmp.dragged.size()) tmp.dragged.removeClass("dragged"); - tmp.dragged = false; - tmp.drag_help = false; - tmp.drag_node = false; - tmp.f_type = false; - tmp.f_data = false; - tmp.init_x = false; - tmp.init_y = false; - tmp.moving = false; - tmp.appended = false; - tmp.origin_tree = false; - if (tmp.isdown) { - tmp.isdown = false; - event.preventDefault(); - event.stopPropagation(); - return false; - } - }; - tree_component.mousemove = function (event) { - var tmp = tree_component.drag_drop; - var is_start = false; - - if (tmp.isdown) { - if (!tmp.moving && Math.abs(tmp.init_x - event.pageX) < 5 && Math.abs(tmp.init_y - event.pageY) < 5) { - event.preventDefault(); - event.stopPropagation(); - return false; - } - else { - if (!tmp.moving) { - tree_component.drag_drop.moving = true; - is_start = true; - } - } - - if (tmp.open_time) clearTimeout(tmp.open_time); - - if (tmp.drag_help !== false) { - if (!tmp.appended) { - if (tmp.foreign !== false) tmp.origin_tree = $.tree.focused(); - $("body").append(tmp.drag_help); - tmp.w = tmp.drag_help.width(); - tmp.appended = true; - } - tmp.drag_help.css({ "left": (event.pageX + 5), "top": (event.pageY + 15) }); - } - - if (is_start && $.tree.drag_start !== false) $.tree.drag_start.call(null, event, tmp); - if ($.tree.drag !== false) $.tree.drag.call(null, event, tmp); - - if (event.target.tagName == "DIV" && event.target.id == "jstree-marker") return false; - - var et = $(event.target); - if (et.is("ins")) et = et.parent(); - var cnt = et.is(".tree") ? et : et.parents(".tree:eq(0)"); - - // if not moving over a tree - if (cnt.size() == 0 || !tree_component.inst[cnt.attr("id")]) { - if (tmp.scroll_time) clearTimeout(tmp.scroll_time); - if (tmp.drag_help !== false) tmp.drag_help.find("li:eq(0) ins").addClass("forbidden"); - tmp.move_type = false; - tmp.ref_node = false; - tree_component.drag_drop.marker.hide(); - return false; - } - - var tree2 = tree_component.inst[cnt.attr("id")]; - tree2.off_height(); - - if (tmp.scroll_time) clearTimeout(tmp.scroll_time); - tmp.scroll_time = setTimeout(function () { tree2.scroll_check(event.pageX, event.pageY); }, 50); - - var mov = false; - var st = cnt.scrollTop(); - - if (event.target.tagName == "A" || event.target.tagName == "INS") { - // just in case if hover is over the draggable - if (et.is("#jstree-dragged")) return false; - if (tree2.get_node(event.target).hasClass("closed")) { - tmp.open_time = setTimeout(function () { tree2.open_branch(et); }, 500); - } - - var et_off = et.offset(); - var goTo = { - x: (et_off.left - 1), - y: (event.pageY - et_off.top) - }; - - var arr = []; - if (goTo.y < tree2.li_height / 3 + 1) arr = ["before", "inside", "after"]; - else if (goTo.y > tree2.li_height * 2 / 3 - 1) arr = ["after", "inside", "before"]; - else { - if (goTo.y < tree2.li_height / 2) arr = ["inside", "before", "after"]; - else arr = ["inside", "after", "before"]; - } - var ok = false; - var nn = (tmp.foreign == false) ? tmp.origin_tree.container.find("li.dragged") : tmp.f_type; - $.each(arr, function (i, val) { - if (tree2.check_move(nn, et, val)) { - mov = val; - ok = true; - return false; - } - }); - if (ok) { - switch (mov) { - case "before": - goTo.y = et_off.top - 2; - tree_component.drag_drop.marker.attr("class", "marker"); - break; - case "after": - goTo.y = et_off.top - 2 + tree2.li_height; - tree_component.drag_drop.marker.attr("class", "marker"); - break; - case "inside": - goTo.x -= 2; - goTo.y = et_off.top - 2 + tree2.li_height / 2; - tree_component.drag_drop.marker.attr("class", "marker_plus"); - break; - } - tmp.move_type = mov; - tmp.ref_node = $(event.target); - if (tmp.drag_help !== false) tmp.drag_help.find(".forbidden").removeClass("forbidden"); - tree_component.drag_drop.marker.css({ "left": goTo.x, "top": goTo.y }).show(); - } - } - - if ((et.is(".tree") || et.is("ul")) && et.find("li:eq(0)").size() == 0) { - var et_off = et.offset(); - tmp.move_type = "inside"; - tmp.ref_node = cnt.children("ul:eq(0)"); - if (tmp.drag_help !== false) tmp.drag_help.find(".forbidden").removeClass("forbidden"); - tree_component.drag_drop.marker.attr("class", "marker_plus"); - tree_component.drag_drop.marker.css({ "left": (et_off.left + 10), "top": et_off.top + 15 }).show(); - } - else if ((event.target.tagName != "A" && event.target.tagName != "INS") || !ok) { - if (tmp.drag_help !== false) tmp.drag_help.find("li:eq(0) ins").addClass("forbidden"); - tmp.move_type = false; - tmp.ref_node = false; - tree_component.drag_drop.marker.hide(); - } - event.preventDefault(); - event.stopPropagation(); - return false; - } - return true; - }; - $(function () { - $(document).bind("mousemove.jstree", tree_component.mousemove); - $(document).bind("mouseup.jstree", tree_component.mouseup); - }); - - // cut, copy, paste stuff - tree_component.cut_copy = { - copy_nodes: false, - cut_nodes: false - }; - - // css stuff - tree_component.css = false; - tree_component.get_css = function (rule_name, delete_flag) { - rule_name = rule_name.toLowerCase(); - var css_rules = tree_component.css.cssRules || tree_component.css.rules; - var j = 0; - do { - if (css_rules.length && j > css_rules.length + 5) return false; - if (css_rules[j].selectorText && css_rules[j].selectorText.toLowerCase() == rule_name) { - if (delete_flag == true) { - if (tree_component.css.removeRule) document.styleSheets[i].removeRule(j); - if (tree_component.css.deleteRule) document.styleSheets[i].deleteRule(j); - return true; - } - else return css_rules[j]; - } - } - while (css_rules[++j]); - return false; - }; - tree_component.add_css = function (rule_name) { - if (tree_component.get_css(rule_name)) return false; - (tree_component.css.insertRule) ? tree_component.css.insertRule(rule_name + ' { }', 0) : tree_component.css.addRule(rule_name, null, 0); - return tree_component.get_css(rule_name); - }; - tree_component.remove_css = function (rule_name) { - return tree_component.get_css(rule_name, true); - }; - tree_component.add_sheet = function (opts) { - if (opts.str) { - var tmp = document.createElement("style"); - tmp.setAttribute('type', "text/css"); - if (tmp.styleSheet) { - document.getElementsByTagName("head")[0].appendChild(tmp); - tmp.styleSheet.cssText = opts.str; - } - else { - tmp.appendChild(document.createTextNode(opts.str)); - document.getElementsByTagName("head")[0].appendChild(tmp); - } - return tmp.sheet || tmp.styleSheet; - } - if (opts.url) { - if (document.createStyleSheet) { - try { document.createStyleSheet(opts.url); } catch (e) { }; - } - else { - var newSS = document.createElement('link'); - newSS.rel = 'stylesheet'; - newSS.type = 'text/css'; - newSS.media = "all"; - newSS.href = opts.url; - // var styles = "@import url(' " + url + " ');"; - // newSS.href ='data:text/css,'+escape(styles); - document.getElementsByTagName("head")[0].appendChild(newSS); - return newSS.styleSheet; - } - } - }; - $(function () { - var u = navigator.userAgent.toLowerCase(); - var v = (u.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [0, '0'])[1]; - - var css = '/* TREE LAYOUT */ .tree ul { margin:0 0 0 5px; padding:0 0 0 0; list-style-type:none; } .tree li { display:block; min-height:18px; line-height:18px; padding:0 0 0 15px; margin:0 0 0 0; /* Background fix */ clear:both; } .tree li ul { display:none; } .tree li a, .tree li span { display:inline-block;line-height:16px;height:16px;color:black;white-space:nowrap;text-decoration:none;padding:1px 4px 1px 4px;margin:0; } .tree li a:focus { outline: none; } .tree li a input, .tree li span input { margin:0;padding:0 0;display:inline-block;height:12px !important;border:1px solid white;background:white;font-size:10px;font-family:Verdana; } .tree li a input:not([class="xxx"]), .tree li span input:not([class="xxx"]) { padding:1px 0; } /* FOR DOTS */ .tree .ltr li.last { float:left; } .tree > ul li.last { overflow:visible; } /* OPEN OR CLOSE */ .tree li.open ul { display:block; } .tree li.closed ul { display:none !important; } /* FOR DRAGGING */ #jstree-dragged { position:absolute; top:-10px; left:-10px; margin:0; padding:0; } #jstree-dragged ul ul ul { display:none; } #jstree-marker { padding:0; margin:0; line-height:5px; font-size:1px; overflow:hidden; height:5px; position:absolute; left:-45px; top:-30px; z-index:1000; background-color:transparent; background-repeat:no-repeat; display:none; } #jstree-marker.marker { width:45px; background-position:-32px top; } #jstree-marker.marker_plus { width:5px; background-position:right top; } /* BACKGROUND DOTS */ .tree li li { overflow:hidden; } .tree > .ltr > li { display:table; } /* ICONS */ .tree ul ins { display:inline-block; text-decoration:none; width:16px; height:16px; } .tree .ltr ins { margin:0 4px 0 0px; } '; - if ($.browser.msie) { - if ($.browser.version == 6) css += '.tree li { height:18px; zoom:1; } .tree li li { overflow:visible; } .tree .ltr li.last { margin-top: expression( (this.previousSibling && /open/.test(this.previousSibling.className) ) ? "-2px" : "0"); } .marker { width:45px; background-position:-32px top; } .marker_plus { width:5px; background-position:right top; }'; - if ($.browser.version == 7) css += '.tree li li { overflow:visible; } .tree .ltr li.last { margin-top: expression( (this.previousSibling && /open/.test(this.previousSibling.className) ) ? "-2px" : "0"); }'; - } - if ($.browser.opera) css += '.tree > ul > li.last:after { content:"."; display: block; height:1px; clear:both; visibility:hidden; }'; - if ($.browser.mozilla && $.browser.version.indexOf("1.8") == 0) css += '.tree .ltr li a { display:inline; float:left; } .tree li ul { clear:both; }'; - tree_component.css = tree_component.add_sheet({ str: css }); - }); -})(jQuery); - -// Datastores -// HTML and JSON are included here by default -(function($) { - $.extend($.tree.datastores, { - "html": function() { - return { - get: function(obj, tree, opts) { - return obj && $(obj).size() ? $('
                ').append(tree.get_node(obj).clone()).html() : tree.container.children("ul:eq(0)").html(); - }, - parse: function(data, tree, opts, callback) { - if (callback) callback.call(null, data); - return data; - }, - load: function(data, tree, opts, callback) { - if (opts.url) { - $.ajax({ - 'type': opts.method, - 'url': opts.url, - 'data': data, - 'dataType': "html", - 'success': function(d, textStatus) { - callback.call(null, d); - }, - 'error': function(xhttp, textStatus, errorThrown) { - callback.call(null, false); - tree.error(errorThrown + " " + textStatus); - } - }); - } - else { - callback.call(null, opts.static || tree.container.children("ul:eq(0)").html()); - } - } - }; - }, - "json": function() { - return { - get: function(obj, tree, opts) { - var _this = this; - if (!obj || $(obj).size() == 0) obj = tree.container.children("ul").children("li"); - else obj = $(obj); - - if (!opts) opts = {}; - if (!opts.outer_attrib) opts.outer_attrib = ["id", "rel", "class"]; - if (!opts.inner_attrib) opts.inner_attrib = []; - - if (obj.size() > 1) { - var arr = []; - obj.each(function() { - arr.push(_this.get(this, tree, opts)); - }); - return arr; - } - if (obj.size() == 0) return []; - - var json = { attributes: {}, data: {} }; - if (obj.hasClass("open")) json.data.state = "open"; - if (obj.hasClass("closed")) json.data.state = "closed"; - - for (var i in opts.outer_attrib) { - if (!opts.outer_attrib.hasOwnProperty(i)) continue; - var val = (opts.outer_attrib[i] == "class") ? obj.attr(opts.outer_attrib[i]).replace(/(^| )last( |$)/ig, " ").replace(/(^| )(leaf|closed|open)( |$)/ig, " ") : obj.attr(opts.outer_attrib[i]); - if (typeof val != "undefined" && val.toString().replace(" ", "").length > 0) json.attributes[opts.outer_attrib[i]] = val; - delete val; - } - - if (tree.settings.languages.length) { - for (var i in tree.settings.languages) { - if (!tree.settings.languages.hasOwnProperty(i)) continue; - var a = obj.children("a." + tree.settings.languages[i]); - if (opts.force || opts.inner_attrib.length || a.children("ins").get(0).style.backgroundImage.toString().length || a.children("ins").get(0).className.length) { - json.data[tree.settings.languages[i]] = {}; - json.data[tree.settings.languages[i]].title = tree.get_text(obj, tree.settings.languages[i]); - if (a.children("ins").get(0).style.className.length) { - json.data[tree.settings.languages[i]].icon = a.children("ins").get(0).style.className; - } - if (a.children("ins").get(0).style.backgroundImage.length) { - json.data[tree.settings.languages[i]].icon = a.children("ins").get(0).style.backgroundImage.replace("url(", "").replace(")", ""); - } - if (opts.inner_attrib.length) { - json.data[tree.settings.languages[i]].attributes = {}; - for (var j in opts.inner_attrib) { - if (!opts.inner_attrib.hasOwnProperty(j)) continue; - var val = a.attr(opts.inner_attrib[j]); - if (typeof val != "undefined" && val.toString().replace(" ", "").length > 0) json.data[tree.settings.languages[i]].attributes[opts.inner_attrib[j]] = val; - delete val; - } - } - } - else { - json.data[tree.settings.languages[i]] = tree.get_text(obj, tree.settings.languages[i]); - } - } - } - else { - var a = obj.children("a"); - json.data.title = tree.get_text(obj); - - if (a.children("ins").size() && a.children("ins").get(0).className.length) { - json.data.icon = a.children("ins").get(0).className; - } - if (a.children("ins").size() && a.children("ins").get(0).style.backgroundImage.length) { - json.data.icon = a.children("ins").get(0).style.backgroundImage.replace("url(", "").replace(")", ""); - } - - if (opts.inner_attrib.length) { - json.data.attributes = {}; - for (var j in opts.inner_attrib) { - if (!opts.inner_attrib.hasOwnProperty(j)) continue; - var val = a.attr(opts.inner_attrib[j]); - if (typeof val != "undefined" && val.toString().replace(" ", "").length > 0) json.data.attributes[opts.inner_attrib[j]] = val; - delete val; - } - } - } - - if (obj.children("ul").size() > 0) { - json.children = []; - obj.children("ul").children("li").each(function() { - json.children.push(_this.get(this, tree, opts)); - }); - } - return json; - }, - parse: function(data, tree, opts, callback) { - if (Object.prototype.toString.apply(data) === "[object Array]") { - var str = ''; - for (var i = 0; i < data.length; i++) { - if (typeof data[i] == "function") continue; - str += this.parse(data[i], tree, opts); - } - if (callback) callback.call(null, str); - return str; - } - - if (!data || !data.data) { - if (callback) callback.call(null, false); - return ""; - } - - var str = ''; - str += "
              •  "; - } - else str += " "; - str += ((typeof data.data[tree.settings.languages[i]].title).toLowerCase() != "undefined" ? data.data[tree.settings.languages[i]].title : data.data[tree.settings.languages[i]]) + ""; - } - } - else { - var attr = {}; - attr["href"] = ""; - attr["style"] = ""; - attr["class"] = ""; - if ((typeof data.data.attributes).toLowerCase() != "undefined") { - for (var i in data.data.attributes) { - if (!data.data.attributes.hasOwnProperty(i)) continue; - if (i == "style" || i == "class") attr[i] += " " + data.data.attributes[i]; - else attr[i] = data.data.attributes[i]; - } - } - str += " "; - } - else str += " "; - str += ((typeof data.data.title).toLowerCase() != "undefined" ? data.data.title : data.data) + ""; - } - if (data.children && data.children.length) { - str += '
                  '; - for (var i = 0; i < data.children.length; i++) { - str += this.parse(data.children[i], tree, opts); - } - str += '
                '; - } - str += "
              • "; - if (callback) callback.call(null, str); - return str; - }, - load: function(data, tree, opts, callback) { - if (opts.static) { - callback.call(null, opts.static); - } - else { - $.ajax({ - 'type': opts.method, - 'url': opts.url, - 'data': data, - 'dataType': "json", - 'success': function(d, textStatus) { - callback.call(null, d); - }, - 'error': function(xhttp, textStatus, errorThrown) { - callback.call(null, false); - tree.error(errorThrown + " " + textStatus); - } - }); - } - } - } - } - }); -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.metadata.js b/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.metadata.js deleted file mode 100644 index 8c12a7bdf5..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Tree/jquery.tree.metadata.js +++ /dev/null @@ -1,17 +0,0 @@ -(function ($) { - if(typeof $.metadata == "undefined") throw "jsTree metadata: jQuery metadata plugin not included."; - - $.extend($.tree.plugins, { - "metadata" : { - defaults : { - attribute : "data" - }, - callbacks : { - check : function(rule, obj, value, tree) { - var opts = $.extend(true, {}, $.tree.plugins.metadata.defaults, this.settings.plugins.metadata); - if(typeof $(obj).metadata({ type : "attr", name : opts.attribute })[rule] != "undefined") return $(obj).metadata()[rule]; - } - } - } - }); -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/menuIcons.css b/src/Umbraco.Web.UI/umbraco_client/Tree/menuIcons.css deleted file mode 100644 index 9cbe7c96d5..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Tree/menuIcons.css +++ /dev/null @@ -1,115 +0,0 @@ -.menuSpr -{ - float: left; - background-image: url(sprites.png); - background-position: -7px -8px; - margin-top: 1px; - width: 17px; - height: 16px; - margin: 1px 13px 1px 0px; -} - -.sprNew -{ - background-position: -7px -339px; -} -.sprCreateFolder -{ - background-position: -7px -108px; -} -.sprSendToTranslate -{ - background-position: -7px -636px; -} -.sprBinEmpty -{ - background-position: -7px -42px; -} -.sprTranslate -{ - background-position: -7px -636px; -} -.sprSave -{ - background-position: -7px -603px; -} -.sprImportDocumentType -{ - background-position: -7px -273px; -} -.sprExportDocumentType -{ - background-position: -7px -240px; -} -.sprAudit -{ - background-position: -7px -6px; -} -.sprPackage2 -{ - background-position: -7px -405px; -} -.sprDelete -{ - background-position: -7px -174px; -} -.sprMove -{ - background-position: -7px -141px; -} -.sprCopy -{ - background-position: -7px -75px; -} -.sprSort -{ - background-position: -7px -670px; -} -.sprPermission -{ - background-position: -7px -438px; -} -.sprProtect -{ - background-position: -7px -471px; -} -.sprRollback -{ - background-position: -7px -570px; -} -.sprRefresh -{ - background-position: -7px -537px; -} -.sprNotify -{ - background-position: -7px -372px; -} -.sprUpdate -{ - background-position: -7px -603px; -} -.sprPublish -{ - background-position: -7px -504px; -} -.sprToPublish -{ - background-position: -7px -504px; -} -.sprLogout -{ - background-position: -7px -306px; -} -.sprDomain -{ - background-position: -7px -207px; -} -.sprLiveEdit -{ - background-position: -7px -706px; -} -.sprChangeDocType -{ - background-position: -12px -741px; -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/sprites.png b/src/Umbraco.Web.UI/umbraco_client/Tree/sprites.png deleted file mode 100644 index 820942c963..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/Tree/sprites.png and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/Tree/treeIcons.css b/src/Umbraco.Web.UI/umbraco_client/Tree/treeIcons.css deleted file mode 100644 index 41a0c0ae7f..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/Tree/treeIcons.css +++ /dev/null @@ -1,38 +0,0 @@ -.sprTreeDeveloperCacheItem {background-position: -6px -8px ! important;} -.sprTreeDeveloperCacheTypes {background-position: -6px -40px ! important;} -.sprTreeDeveloperMacro {background-position: -6px -72px ! important;} -.sprTreeDeveloperPython {background-position: -6px -104px ! important; width: 15px; height: 15px} -.sprTreeDeveloperRegistry {background-position: -6px -135px ! important;} -.sprTreeDoc {background-position: -6px -199px ! important;} -.sprTreeDoc2 {background-position: -6px -231px ! important;} -.sprTreeDoc3 {background-position: -6px -263px ! important;} -.sprTreeDoc4 {background-position: -6px -295px ! important;} -.sprTreeDoc5 {background-position: -6px -327px ! important;} -.sprTreeDocPic {background-position: -6px -359px ! important;} -.sprTreeFolder {background-position: -6px -391px ! important;} -.sprTreeFolder_o {background-position: -6px -423px ! important;} -.sprTreeMediaFile {background-position: -6px -455px ! important;} -.sprTreeMediaMovie {background-position: -6px -487px ! important;} -.sprTreemediaFile {background-position: -6px -519px ! important;} -.sprTreeMediaPhoto {background-position: -6px -551px ! important;} -.sprTreeMember {background-position: -6px -583px ! important;} -.sprTreeMemberGroup {background-position: -6px -615px ! important;} -.sprTreeMemberType {background-position: -6px -647px ! important;} -.sprTreeNewsletter {background-position: -6px -679px ! important;} -.sprTreePackage {background-position: -6px -711px ! important;} -.sprTreeRepository {background-position: -6px -743px ! important;} -.sprTreeSettingAgent {background-position: -6px -775px ! important;} -.sprTreeSettingCss {background-position: -6px -807px ! important;} -.sprTreeSettingCssItem {background-position: -6px -839px ! important;} -.sprTreeSettingDataType {background-position: -6px -871px ! important;} -.sprTreeSettingDataTypeChild {background-position: -6px -903px ! important;} -.sprTreeSettingDomain {background-position: -6px -935px ! important;} -.sprTreeSettingLanguage {background-position: -6px -967px ! important;} -.sprTreeSettingScript {background-position: -6px -999px ! important;} -.sprTreeSettingTemplate {background-position: -6px -1031px ! important;} -.sprTreeSettingXml {background-position: -6px -1063px ! important;} -.sprTreeStatistik {background-position: -6px -1095px ! important;} -.sprTreeUser {background-position: -6px -1127px ! important;} -.sprTreeUserGroup {background-position: -6px -1159px ! important;} -.sprTreeUserType {background-position: -6px -1191px ! important;} -.sprNew{background-position: -6px -339px;} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/theme.css b/src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/theme.css deleted file mode 100644 index 19755e4c08..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/datepicker/aqua/theme.css +++ /dev/null @@ -1,221 +0,0 @@ -/* Distributed as part of The Coolest DHTML Calendar - Author: Mihai Bazon, www.bazon.net/mishoo - Copyright Dynarch.com 2005, www.dynarch.com -*/ - -/* The main calendar widget. DIV containing a table. */ - -div.calendar { position: relative; border: 2px solid #CAC9C9;} - -.calendar, .calendar table { - - font-size: 11px; - color: #000; - cursor: default; - font-family: "trebuchet ms",verdana,tahoma,sans-serif; - background: #fff url(../../propertyPane/images/propertyBackground.gif); -} - -.calendar table { - border: none; -} - -/* Header part -- contains navigation buttons and day names. */ - -.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ - text-align: center; /* They are the navigation buttons */ - padding: 2px; /* Make the buttons seem like they're pressing */ - font-weight: bold; -} - -.calendar .nav { - font-family: verdana,tahoma,sans-serif; -} - -.calendar .nav div { -} - -.calendar thead tr { color: #000; } - -.calendar thead .title { /* This holds the current "month, year" */ - font-weight: bold; /* Pressing it will take you to the current date */ - text-align: center; - padding: 2px; -} - -.calendar thead .headrow { /* Row
containing navigation buttons */ - -} - -.calendar thead .name { /* Cells containing the day names */ -} - -/* The body part -- contains all the days in month. */ - -.calendar tbody .day { /* Cells in footer (only one right now) */ - text-align: center; - color: #fff; -} - -.calendar tfoot .ttip { /* Tooltip (status bar) cell '); - $("tr:first td", table.tBodies[0]).each(function() { colgroup.append($('').css('width', $(this).width())); }); - $(table).prepend(colgroup); - } - ; - } - - function updateHeaderSortCount(table, sortList) { - var c = table.config, l = sortList.length; - for (var i = 0; i < l; i++) { - var s = sortList[i], o = c.headerList[s[0]]; - o.count = s[1]; - o.count++; - } - } - - function multisort(table, sortList, cache) { - if (table.config.debug) { - var sortTime = new Date(); - } - var dynamicExp = "var sortWrapper = function(a,b) {", l = sortList.length; - for (var i = 0; i < l; i++) { - var c = sortList[i][0]; - var order = sortList[i][1]; - var s = (getCachedSortType(table.config.parsers, c) == "text") ? ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ? "sortNumeric" : "sortNumericDesc"); - var e = "e" + i; - dynamicExp += "var " + e + " = " + s + "(a[" + c + "],b[" + c + "]); "; - dynamicExp += "if(" + e + ") { return " + e + "; } "; - dynamicExp += "else { "; - } - var orgOrderCol = cache.normalized[0].length - 1; - dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];"; - for (var i = 0; i < l; i++) { - dynamicExp += "}; "; - } - dynamicExp += "return 0; "; - dynamicExp += "}; "; - eval(dynamicExp); - cache.normalized.sort(sortWrapper); - if (table.config.debug) { - benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime); - } - return cache; - } - - ; - - function sortText(a, b) { return ((a < b) ? -1 : ((a > b) ? 1 : 0)); } - - ; - - function sortTextDesc(a, b) { return ((b < a) ? -1 : ((b > a) ? 1 : 0)); } - - ; - - function sortNumeric(a, b) { return a - b; } - - ; - - function sortNumericDesc(a, b) { return b - a; } - - ; - - function getCachedSortType(parsers, i) { return parsers[i].type; } - - ; - this.construct = function(settings) { - return this.each(function() { - if (!this.tHead || !this.tBodies) return; - var $this, $document, $headers, cache, config, shiftDown = 0, sortOrder; - this.config = {}; - config = $.extend(this.config, $.tablesorter.defaults, settings); - $this = $(this); - $headers = buildHeaders(this); - this.config.parsers = buildParserCache(this, $headers); - cache = buildCache(this); - var sortCSS = [config.cssDesc, config.cssAsc]; - fixColumnWidth(this); - $headers.click(function(e) { - $this.trigger("sortStart"); - var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0; - if (!this.sortDisabled && totalRows > 0) { - var $cell = $(this); - var i = this.column; - this.order = this.count++ % 2; - if (!e[config.sortMultiSortKey]) { - config.sortList = []; - if (config.sortForce != null) { - var a = config.sortForce; - for (var j = 0; j < a.length; j++) { - if (a[j][0] != i) { - config.sortList.push(a[j]); - } - } - } - config.sortList.push([i, this.order]); - } - else { - if (isValueInArray(i, config.sortList)) { - for (var j = 0; j < config.sortList.length; j++) { - var s = config.sortList[j], o = config.headerList[s[0]]; - if (s[0] == i) { - o.count = s[1]; - o.count++; - s[1] = o.count % 2; - } - } - } - else { - config.sortList.push([i, this.order]); - } - } - ; - setTimeout(function() { - setHeadersCss($this[0], $headers, config.sortList, sortCSS); - appendToTable($this[0], multisort($this[0], config.sortList, cache)); - }, 1); - return false; - } - }).mousedown(function() { - if (config.cancelSelection) { - this.onselectstart = function() { return false }; - return false; - } - }); - $this.bind("update", function() { - this.config.parsers = buildParserCache(this, $headers); - cache = buildCache(this); - }).bind("sorton", function(e, list) { - $(this).trigger("sortStart"); - config.sortList = list; - var sortList = config.sortList; - updateHeaderSortCount(this, sortList); - setHeadersCss(this, $headers, sortList, sortCSS); - appendToTable(this, multisort(this, sortList, cache)); - }).bind("appendCache", function() { appendToTable(this, cache); }).bind("applyWidgetId", function(e, id) { getWidgetById(id).format(this); }).bind("applyWidgets", function() { applyWidget(this); }); - if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) { - config.sortList = $(this).metadata().sortlist; - } - if (config.sortList.length > 0) { - $this.trigger("sorton", [config.sortList]); - } - applyWidget(this); - }); - }; - this.addParser = function(parser) { - var l = parsers.length, a = true; - for (var i = 0; i < l; i++) { - if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) { - a = false; - } - } - if (a) { - parsers.push(parser); - } - ; - }; - this.addWidget = function(widget) { widgets.push(widget); }; - this.formatFloat = function(s) { - var i = parseFloat(s); - return (isNaN(i)) ? 0 : i; - }; - this.formatInt = function(s) { - var i = parseInt(s); - return (isNaN(i)) ? 0 : i; - }; - this.isDigit = function(s, config) { - var DECIMAL = '\\' + config.decimal; - var exp = '/(^[+]?0(' + DECIMAL + '0+)?$)|(^([-+]?[1-9][0-9]*)$)|(^([-+]?((0?|[1-9][0-9]*)' + DECIMAL + '(0*[1-9][0-9]*)))$)|(^[-+]?[1-9]+[0-9]*' + DECIMAL + '0+$)/'; - return RegExp(exp).test($.trim(s)); - }; - this.clearTableBody = function(table) { - if ($.browser.msie) { - - function empty() { while (this.firstChild) this.removeChild(this.firstChild); } - - empty.apply(table.tBodies[0]); - } - else { - table.tBodies[0].innerHTML = ""; - } - }; - } - }); - $.fn.extend({ tablesorter: $.tablesorter.construct }); - var ts = $.tablesorter; - ts.addParser({ id: "text", is: function(s) { return true; }, format: function(s) { return $.trim(s.toLowerCase()); }, type: "text" }); - ts.addParser({ - id: "digit", - is: function(s, table) { - var c = table.config; - return $.tablesorter.isDigit(s, c); - }, - format: function(s) { return $.tablesorter.formatFloat(s); }, - type: "numeric" - }); - ts.addParser({ id: "currency", is: function(s) { return /^[£$€?.]/.test(s); }, format: function(s) { return $.tablesorter.formatFloat(s.replace(new RegExp(/[^0-9.]/g), "")); }, type: "numeric" }); - ts.addParser({ - id: "ipAddress", is: function(s) { return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s); }, - format: function(s) { - var a = s.split("."), r = "", l = a.length; - for (var i = 0; i < l; i++) { - var item = a[i]; - if (item.length == 2) { - r += "0" + item; - } - else { - r += item; - } - } - return $.tablesorter.formatFloat(r); - }, - type: "numeric" - }); - ts.addParser({ id: "url", is: function(s) { return /^(https?|ftp|file):\/\/$/.test(s); }, format: function(s) { return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), '')); }, type: "text" }); - ts.addParser({ id: "isoDate", is: function(s) { return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s); }, format: function(s) { return $.tablesorter.formatFloat((s != "") ? new Date(s.replace(new RegExp(/-/g), "/")).getTime() : "0"); }, type: "numeric" }); - ts.addParser({ id: "percent", is: function(s) { return /\%$/.test($.trim(s)); }, format: function(s) { return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), "")); }, type: "numeric" }); - ts.addParser({ id: "usLongDate", is: function(s) { return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/)); }, format: function(s) { return $.tablesorter.formatFloat(new Date(s).getTime()); }, type: "numeric" }); - ts.addParser({ - id: "shortDate", is: function(s) { return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s); }, - format: function(s, table) { - var c = table.config; - s = s.replace(/\-/g, "/"); - if (c.dateFormat == "us") { - s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2"); - } - else if (c.dateFormat == "uk") { - s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1"); - } - else if (c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") { - s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3"); - } - return $.tablesorter.formatFloat(new Date(s).getTime()); - }, - type: "numeric" - }); - ts.addParser({ id: "time", is: function(s) { return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s); }, format: function(s) { return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime()); }, type: "numeric" }); - ts.addParser({ - id: "metadata", is: function(s) { return false; }, - format: function(s, table, cell) { - var c = table.config, p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName; - return $(cell).metadata()[p]; - }, - type: "numeric" - }); - ts.addWidget({ - id: "zebra", - format: function(table) { - if (table.config.debug) { - var time = new Date(); - } - $("tr:visible", table.tBodies[0]).filter(':even').removeClass(table.config.widgetZebra.css[1]).addClass(table.config.widgetZebra.css[0]).end().filter(':odd').removeClass(table.config.widgetZebra.css[0]).addClass(table.config.widgetZebra.css[1]); - if (table.config.debug) { - $.tablesorter.benchmark("Applying Zebra widget", time); - } - } - }); -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tablesorting/tableFilterUtill.js b/src/Umbraco.Web.UI/umbraco_client/tablesorting/tableFilterUtill.js deleted file mode 100644 index e844921b5b..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/tablesorting/tableFilterUtill.js +++ /dev/null @@ -1,353 +0,0 @@ -// -// @author daemach -// - -daemach = function() { - this.name = "Daemach's Toolbox"; - this.version = "2.0"; - this.debug = false; - - // load extensions - this.cw = new colorWeasel(this); - this.cm = new cssMonkey(this); -}; - -jQuery.extend(daemach, { - prototype: { - log: function() { - if (!top.window.console || !top.window.console.log || !this.debug) { - return; - } else { - top.window.console.log([].join.call(arguments, '')); - }; - }, - time: function() { - if (!top.window.console || !top.window.console.time || !this.debug) { - return; - } else { - top.window.console.time([].join.call(arguments, '')); - }; - }, - timeEnd: function() { - if (!top.window.console || !top.window.console.timeEnd || !this.debug) { - return; - } else { - top.window.console.timeEnd([].join.call(arguments, '')); - }; - }, - profile: function() { - if (!top.window.console || !top.window.console.profile || !this.debug) { - return; - } else { - top.window.console.profile([].join.call(arguments, '')); - }; - }, - profileEnd: function() { - if (!top.window.console || !top.window.console.profileEnd || !this.debug) { - return; - } else { - top.window.console.profileEnd([].join.call(arguments, '')); - }; - }, - delay: function(condition, callback, scope, interval, done, i) { - interval = interval || 15; - if (typeof done === "undefined") { - var done = false; - i = setInterval(function() { $d.delay(condition, callback, scope, interval, done, i); }, interval) - } else { - var con = condition.apply(scope); - console.log(con); - if (con) { - clearInterval(i); - callback.call(scope); - } - } - } - - } -}); - -// -// ============================================================================================== -// ColorWeasel v1.4 -// ============================================================================================== -// - -colorWeasel = function(root) { - this.name = "colorWeasel"; - this.version = "1.4"; - this.root = root; -}; -jQuery.extend(colorWeasel, { - prototype: { - rgb2hsl: function(rgb) { - rgb = this.rgb2hex(rgb); - var r = parseInt(rgb.substr(0, 2), 16) / 255; - var g = parseInt(rgb.substr(2, 2), 16) / 255; - var b = parseInt(rgb.substr(4, 2), 16) / 255; - var max = Math.max(r, g, b), - min = Math.min(r, g, b), - delta = (max - min), - l = (max + min) / 2, - h = 0, - s = 0, - dR, dG, dB; - if (max != min) { - s = (l < .5) ? (max - min) / (max + min) : (max - min) / (2 - max - min); - dR = (((max - r) / 6) + (delta / 2)) / delta; - dG = (((max - g) / 6) + (delta / 2)) / delta; - dB = (((max - b) / 6) + (delta / 2)) / delta; - h = (max != r) ? (max != g) ? ((2 / 3) + dG - dR) : ((1 / 3) + dR - dB) : (dB - dG); - }; - if (h < 0) { h += 1; }; - if (h > 1) { h -= 1; }; - h *= 360; - return [h, s, l]; - }, - hsl2rgb: function(h, s, l) { // H as degrees 0..360, S L as decimals, 0..1. - if (typeof h == "object" && h.constructor == Array) { - l = h[2]; - s = h[1]; - h = h[0]; - }; - h /= 360; - var y = (l > .5) ? (l + s) - (l * s) : l * (s + 1), - x = l * 2 - y, - r = Math.round(255 * _hue2Rgb(x, y, h + (1 / 3))), - g = Math.round(255 * _hue2Rgb(x, y, h)), - b = Math.round(255 * _hue2Rgb(x, y, h - (1 / 3))); - - function _hue2Rgb(x, y, h) { - if (h < 0) { - h += 1; - } else if (h > 1) { - h -= 1; - }; - - return ((h * 6) < 1) ? (x + (y - x) * h * 6) : ((h * 2) < 1) ? y : ((h * 3) < 2) ? (x + (y - x) * ((2 / 3) - h) * 6) : x; - } - return this.zeroPad(r.toString(16)).toUpperCase() + this.zeroPad(g.toString(16)).toUpperCase() + this.zeroPad(b.toString(16)).toUpperCase(); - }, - zeroPad: function(num) { - var str = '0' + num; - return str.substring(str.length - 2); - }, - rgb2hex: function(rgb) { - if (!rgb.match(/(rgb\()[^\)]+(\))/)) { - return rgb; - }; - var t = /(rgb\()([^\)]+)(\))/.exec(rgb); - t = t[2].replace(/\s+/g, "").split(","); - var r = this.zeroPad(parseInt(t[0]).toString(16).toUpperCase()); - var g = this.zeroPad(parseInt(t[1]).toString(16).toUpperCase()); - var b = this.zeroPad(parseInt(t[2]).toString(16).toUpperCase()); - - return r + g + b; - }, - ccLighter: function(rgb, perc) { - var hsl = this.rgb2hsl(rgb); - hsl[2] += (hsl[2] *= perc); - hsl[2] = (hsl[2] >= 1) ? 1 : hsl[2]; - rgb = this.hsl2rgb(hsl); - return rgb; - }, - ccDarker: function(rgb, perc) { - var hsl = this.rgb2hsl(rgb); - hsl[2] -= (hsl[2] *= perc); - hsl[2] = (hsl[2] <= 0) ? 0 : hsl[2]; - rgb = this.hsl2rgb(hsl); - return rgb; - }, - ccComplementary: function(rgb) { - var hsl = this.rgb2hsl(rgb); - hsl[0] += 180; - hsl[0] = (hsl[0] > 360) ? hsl[0] - 360 : hsl[0]; - rgb = this.hsl2rgb(hsl); - return rgb; - } - } -}); - - - -// -// ============================================================================================== -// CSSMonkey v1.2 -// ============================================================================================== -// - -// -// @author daemach -// - -cssMonkey = function(root) { - this.name = "cssMonkey"; - this.version = "1.2"; - this.root = root; - this.sheets = []; - if (document.styleSheets) { - this.parseStyles(); - } else { - return false; - } -}; - -jQuery.extend(cssMonkey, { - prototype: { - parseStyles: function() { - var media, mediaType, styleSheet; - if (document.styleSheets.length > 0) { - for (var i = 0; i < document.styleSheets.length; i++) { - if (document.styleSheets[i].disabled) { - continue; - } - media = document.styleSheets[i].media; - mediaType = typeof media; - - if (mediaType == "string") { - if (media == "" || media.indexOf("screen") != -1) { - styleSheet = document.styleSheets[i]; - } - } else if (mediaType == "object") { - if (media.mediaText == "" || media.mediaText.indexOf("screen") != -1) { - styleSheet = document.styleSheets[i]; - } - } - if (typeof styleSheet != "undefined") { - this.sheets.push(styleSheet); - } - } - } - }, - toggleSheet: function(disable, id) { - var s = this.sheets; - id = (typeof id === "undefined") ? null : id; - disable = (typeof disable === "undefined") ? null : disable; - - for (var i = 0; i < s.length; i++) { - if (id === null || i == id || s[i].href.indexOf(id) >= 0) { - s[i].disabled = (disable || (disable === null && !s[i].disabled)) ? true : false; - } - } - }, - getRule: function(s, a) { - var rules, matches = [], sObj = []; - for (var i = this.sheets.length - 1; i >= 0; i--) { - rules = (this.sheets[i].cssRules) ? this.sheets[i].cssRules : this.sheets[i].rules; - if (!rules.length) { - return matches; - } - s = s.toLowerCase(); - for (var r = rules.length - 1; r >= 0; r--) { - if (typeof rules[r].selectorText != "undefined" && rules[r].selectorText.toLowerCase() == s) { - a = (typeof a == "undefined") ? null : rules[r].style[this.camelCase(a)]; - matches.push([rules[r], i, r, a]); - } - } - } - return matches; - }, - _findStyle: function(cssText, attr) { - var n = (attr.indexOf(":") >= 0) ? attr.split(":")[0].trim() : attr; - if (n.length) { - for (var i = 0; i < cssText.length; i++) { - if (cssText[i].indexOf(n) >= 0) { return i; } - } - } - return -1; - }, - camelCase: function(s) { - if (s == "float") { return "cssFloat"; }; - for (var str = /-([a-z])/; str.test(s); s = s.replace(str, RegExp.$1.toUpperCase())); - return s; - }, - getComputedStyle: function(ele, attr) { - attr = this.camelCase(attr); - if (ele.currentStyle) { - return ele.currentStyle[attr]; - } else if (window.getComputedStyle) { - return window.getComputedStyle(ele, null)[attr]; - } - return null; - }, - getRootStyle: function(ele, attr) { - attr = this.camelCase(attr); - var tmpAttr = this.getComputedStyle(ele, attr), tmpEle = ele; - if (attr.indexOf("Color") >= 0) { - tmpAttr = this.root.cw.rgb2hex(tmpAttr); - while (tmpEle !== "body" && tmpAttr == "transparent") { - tmpEle = tmpEle.parentNode; - tmpAttr = this.root.cw.rgb2hex(this.getComputedStyle(tmpEle, attr)); - } - tmpAttr = (tmpAttr == "transparent") ? "000000" : tmpAttr; - } - return tmpAttr; - }, - setRule: function(selector, styles, index) { - - var rules = this.getRule(selector), oCSS, nCSS, o, s, rCSS; - if (rules.length) { - nCSS = styles.split(";"); - if (!nCSS[nCSS.length - 1].trim().length) { nCSS.pop(); } - for (var i = 0; i < rules.length; i++) { - oCSS = rules[i][0].style.cssText.split(";"); - for (var x = 0; x < nCSS.length; x++) { - s = this._findStyle(oCSS, nCSS[x]); - if (s >= 0) { - oCSS[s] = nCSS[x]; - } else { - oCSS.push(nCSS[x]); - } - } - rules[i][0].style.cssText = oCSS.join(";"); - } - } else { - var sheet = this.sheets[this.sheets.length - 1]; - var rules = sheet.cssRules ? sheet.cssRules : sheet.rules; - if (index == undefined) { - index = rules.length; - } - this._insertRule(sheet, selector, styles, index) - } - }, - _insertRule: function(sheet, selector, styles, index) { - - if (sheet.insertRule) { - sheet.insertRule(selector + "{" + styles + "}", index); - } else if (sheet.addRule) { - sheet.addRule(selector, styles, index); - } - }, - deleteRule: function(s) { - var rules = this.getRule(s), sheet; - for (var i = rules.length - 1; i >= 0; i--) { - sheet = this.sheets[rules[i][1]]; - if (sheet.deleteRule) { - sheet.deleteRule(rules[i][2]); - } else if (sheet.removeRule) { - sheet.removeRule(rules[i][2]); - } - } - } - } -}); - - - - - - - - - -// -// ============================================================================================== -// Light it up... -// ============================================================================================== -// -if (typeof $daemach == "undefined") { - var $daemach = new daemach(); -} -var $d = $daemach; - - diff --git a/src/Umbraco.Web.UI/umbraco_client/tabview/images/background.gif b/src/Umbraco.Web.UI/umbraco_client/tabview/images/background.gif deleted file mode 100644 index 659b9b705a..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/tabview/images/background.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/tabview/images/bg.gif b/src/Umbraco.Web.UI/umbraco_client/tabview/images/bg.gif deleted file mode 100644 index 98792ee38e..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/tabview/images/bg.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/tabview/images/footer_bg.gif b/src/Umbraco.Web.UI/umbraco_client/tabview/images/footer_bg.gif deleted file mode 100644 index a2a9a75c68..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/tabview/images/footer_bg.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/tabview/images/footer_statusBar_bg.gif b/src/Umbraco.Web.UI/umbraco_client/tabview/images/footer_statusBar_bg.gif deleted file mode 100644 index 33edb379a3..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/tabview/images/footer_statusBar_bg.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/tabview/images/footer_statusBar_h2_bg.gif b/src/Umbraco.Web.UI/umbraco_client/tabview/images/footer_statusBar_h2_bg.gif deleted file mode 100644 index 44fb09b675..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/tabview/images/footer_statusBar_h2_bg.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/tabview/images/left_both.gif b/src/Umbraco.Web.UI/umbraco_client/tabview/images/left_both.gif deleted file mode 100644 index 74d7361f4d..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/tabview/images/left_both.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/tabview/images/right_both.gif b/src/Umbraco.Web.UI/umbraco_client/tabview/images/right_both.gif deleted file mode 100644 index af9ef6e59b..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/tabview/images/right_both.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/tabview/javascript.js b/src/Umbraco.Web.UI/umbraco_client/tabview/javascript.js deleted file mode 100644 index eacb1525d5..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/tabview/javascript.js +++ /dev/null @@ -1,102 +0,0 @@ -/// - -Umbraco.Sys.registerNamespace('Umbraco.Controls'); - -Umbraco.Controls.TabView = (function () { - var onChangeEvents = []; - - var obj = { - setActiveTab: function (tabviewid, tabid, tabs) { - for (var i = 0; i < tabs.length; i++) { - jQuery("#" + tabs[i]).attr("class", "tabOff"); - jQuery("#" + tabs[i] + "layer").hide(); - } - - var activeTab = jQuery("#" + tabid).attr("class", "tabOn"); - jQuery("#" + tabid + "layer").show(); - jQuery("#" + tabviewid + '_activetab').val(tabid); - - // show first tinymce toolbar - jQuery(".tinymceMenuBar").hide(); - jQuery(document).ready(function () { - jQuery("#" + tabid + "layer .tinymceMenuBar:first").show(); - }); - for (var i = 0; i < onChangeEvents.length; i++) { - var fn = onChangeEvents[i]; - fn.apply(activeTab, [tabviewid, tabid, tabid]); - } - }, - - onActiveTabChange: function (fn) { - onChangeEvents.push(fn); - }, - - resizeTabViewTo: function (TabPageArr, TabViewName, tvHeight, tvWidth) { - if (!tvHeight) { - tvHeight = jQuery(window).height(); //getViewportHeight(); - if (document.location) { - tvHeight = tvHeight - 10; - } - } - if (!tvWidth) { - tvWidth = jQuery(window).width(); // getViewportWidth(); - if (document.location) { - tvWidth = tvWidth - 10; - } - } - - var tabviewHeight = tvHeight - 12; - - jQuery("#" + TabViewName).width(tvWidth); - jQuery("#" + TabViewName).height(tabviewHeight); - - - for (i = 0; i < TabPageArr.length; i++) { - scrollwidth = tvWidth - 30; - jQuery("#" + TabPageArr[i] + "layer_contentlayer").height((tabviewHeight - 67)); - - //document.getElementById(TabPageArr[i] +"layer_contentlayer").style.border = "2px solid #fff"; - - jQuery("#" + TabPageArr[i] + "layer_contentlayer").width((tvWidth - 2)); - jQuery("#" + TabPageArr[i] + "layer_menu").width((scrollwidth)); - jQuery("#" + TabPageArr[i] + "layer_menu_slh").width((scrollwidth)); - } - }, - - tabSwitch: function (direction) { - var preFix = "TabView1"; - var currentTab = jQuery("#" + preFix + "_activetab").val(); - - currentTab = currentTab.substr(preFix.length + 4, currentTab.length - preFix.length - 4); - var nextTab = Math.round(currentTab) + direction; - - if (nextTab < 10) - nextTab = "0" + nextTab; - - // Try to grab the next one! - if (nextTab != "00") { - if (jQuery("#" + preFix + '_tab' + nextTab) != null) { - setActiveTab(preFix, preFix + '_tab' + nextTab, eval(preFix + '_tabs')); - } - } - } - }; - - return obj; -})(); - -function setActiveTab(tabviewid, tabid, tabs) { - Umbraco.Controls.TabView.setActiveTab(tabviewid, tabid, tabs); -} - -function resizeTabView(TabPageArr, TabViewName) { - Umbraco.Controls.TabView.resizeTabViewTo(TabPageArr, TabViewName); -} - -function resizeTabViewTo(TabPageArr, TabViewName, tvHeight, tvWidth) { - Umbraco.Controls.TabView.resizeTabViewTo(TabPageArr, TabViewName, tvHeight, tvWidth); -} - -function tabSwitch(direction) { - Umbraco.Controls.TabView.tabSwitch(direction); -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tabview/oldstyle.css b/src/Umbraco.Web.UI/umbraco_client/tabview/oldstyle.css deleted file mode 100644 index 99c50f9542..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/tabview/oldstyle.css +++ /dev/null @@ -1,76 +0,0 @@ - .header { - float:left; - width:100%; - background:#DAE0D2 url("bg.gif") repeat-x bottom; - font-size:93%; - line-height:normal; - } - .header ul { - margin:0; - padding:10px 10px 0; - list-style:none; - } - - li.tabOff, li.tabOn { - float:left;background:url("left_both.gif") no-repeat left top; - margin:0; - padding:0 0 0 9px; - border-bottom:1px solid #765; - color:#000; - } - - a.tabOff, a.tabOn { - float:left; - display:block; - width:.1em; - background:url("right_both.gif") no-repeat right top; - padding:5px 15px 4px 6px; - text-decoration:none; - font-weight:bold; - color:#765; - } - - .header > ul a {width:auto;} - - #header a:hover { - - color:#333; - } - - .header li:hover, .header li:hover a { - background-position:0% -150px; - color:#333; - } - - .header li:hover a { - background-position:100% -150px; - } - - li.tabOn { - background-position:0 -150px; - border-width:0; - } - - li.tabOn a { - background-position:100% -150px; - padding-bottom:5px; - color:#333; - } - .layer { - display:none; - } - - .menubar { - background:#F5F5F5; - height:32px; - width:100%; - float:left; - border-bottom:1px solid #919B9C; - } - -.tabpagecontainer { - border-bottom:1px solid #919B9C; - border-left:1px solid #919B9C; - border-right:1px solid #919B9C; - height:99%; - } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tabview/style.css b/src/Umbraco.Web.UI/umbraco_client/tabview/style.css deleted file mode 100644 index da63f2019f..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/tabview/style.css +++ /dev/null @@ -1,130 +0,0 @@ -.header { - float:left; - width:100%; - background: #fff url(images/bg.gif) repeat-x bottom; - font-size:11px; - line-height:normal; - font-family: "Trebuchet MS", verdana, arial; - font-size: 11px; - font-weight: normal; - -} -.header ul { - margin:0; - padding:0px 0px 0; - list-style:none; -} -.header li { - display:inline; - - margin:0; - padding:0; -} - -.header a { - float:left; - background:url(images/left_both.gif) no-repeat left top; - margin:0; - padding:0 0 0 9px; - border-bottom:1px solid #CAC9C9; - text-decoration:none; - outline: none; -} -.header a span { - float:left; - display:block; - background:url(images/right_both.gif) no-repeat right top; - padding:4px 15px 0px 6px; - font-weight:bold; - color:#65787A; -} - -/* Commented Backslash Hack hides rule from IE5-Mac \*/ -.header a span { - float:none; - -} -/* End IE5-Mac hack */ - -.header a:hover span { - color:#333; -} - -.header a:hover { - background-position:0% -150px; - -} - -.header a:hover span { - background-position:100% -150px; -} - -.header ul li.tabOn a { - background-position:0 -150px; - border-width:0; -} - -.header ul li.tabOn a span { - background-position:100% -150px; - padding-bottom:1px; - color:#333; -} - -.tabpage -{ - display: none; - background:#ffffff; - padding: 0px; margin: 0px; - clear:both; - z-index: 1; -} - - -.tabpagescrollinglayer -{ - clear:both; - overflow: auto; - padding: 0px; margin: 0px; - position: relative; -} - - -.tabpagescrollinglayer .tabpageContent{margin: 0px; padding: 0px 10px 0px 10px;} - -.menubar { - background-image: url(../tabView/images/background.gif); - height:32px; - width: auto; - display: block; - border-bottom:1px solid #CAC9C9; - padding: 0px; margin: 0px; -} - -.tabpagecontainer { - border-left:1px solid #CAC9C9; - border-right:1px solid #CAC9C9; - } - -.tabOff .mceEditorContainer{display: none !Important;} - - .footer { - height: 12px; - margin: 0; - padding: 0px; - background: url(images/footer_bg.gif) 11px 11px repeat-x #fff; - overflow: hidden; - clear: both; - position: relative; - } - - .footer .status h2 { - margin: 0px; - font-weight: bold; - text-align: left; - font-family: "Trebuchet MS", verdana, arial; - font-size: 10px; color: #378080; - display: block; - } - - .footer .status{height: 12px; margin: 0px; overflow: hidden; background: url(images/footer_statusBar_bg.gif) top left no-repeat;} - .footer .status h2{display: block; height: 12px; overflow: hidden; margin: 0px; padding: 0px; background: url(images/footer_statusBar_h2_bg.gif) top right no-repeat;} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/ui/dd.css b/src/Umbraco.Web.UI/umbraco_client/ui/dd.css deleted file mode 100644 index ebd818d515..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/ui/dd.css +++ /dev/null @@ -1,172 +0,0 @@ -/************** Skin 1 *********************/ -.dd { - /*display:inline-block !important;*/ - text-align:left; - background-color:#fff; - font-family:Arial, Helvetica, sans-serif; - font-size:12px; - position:relative; -} -.dd .ddTitle { - background:#f2f2f2; - border:1px solid #c3c3c3; - padding:3px; - text-indent:0; - cursor:default; - overflow:hidden; - height:16px; -} -.dd .ddTitle span.arrow { - background:url(dd_arrow.gif) no-repeat 0 0; float:right; display:inline-block;width:16px; height:16px; cursor:pointer; -} - -.dd .ddTitle span.ddTitleText {text-indent:1px; overflow:hidden; line-height:16px;} -.dd .ddTitle span.ddTitleText img{text-align:left; padding:0 2px 0 0} -.dd .ddTitle img.selected { - padding:0 3px 0 0; - vertical-align:top; -} -.dd .ddChild { - position:absolute; - border:1px solid #c3c3c3; - border-top:none; - display:none; - margin:0; - width:auto; - overflow:auto; - overflow-x:hidden !important; - background-color:#ffffff; -} -.dd .ddChild .opta a, .dd .ddChild .opta a:visited {padding-left:10px} -.dd .ddChild a { - display:block; - padding:2px 0 2px 3px; - text-decoration:none; - color:#000; - overflow:hidden; - white-space:nowrap; - cursor:pointer; -} -.dd .ddChild a:hover { - background-color:#66CCFF; -} -.dd .ddChild a img { - border:0; - padding:0 2px 0 0; - vertical-align:middle; -} -.dd .ddChild a.selected { - background-color:#66CCFF; - -} -.hidden {display:none;} - -.dd .borderTop{border-top:1px solid #c3c3c3 !important;} -.dd .noBorderTop{border-top:none 0 !important} - -/************** Skin 2 *********************/ -.dd2 { - /*display:inline-block !important;*/ - text-align:left; - background-color:#fff; - font-family:Arial, Helvetica, sans-serif; - font-size:12px; - position:relative; -} -.dd2 .ddTitle { - background:transparent url(../images/msDropDown.gif) no-repeat; - padding:0 3px; - text-indent:0; - cursor:default; - overflow:hidden; - height:36px; -} -.dd2 .ddTitle span.arrow { - background:transparent url(../images/icon-arrow.gif) no-repeat 0 0; float:right; display:inline-block;width:27px; height:27px; cursor:pointer; top:5px; position:relative; right:2px; -} - -.dd2 .ddTitle span.ddTitleText {text-indent:1px; overflow:hidden; line-height:33px; font-family:Georgia, "Times New Roman", Times, serif; font-size:16px; font-weight:bold; color:#fff;} -.dd2 .ddTitle span.ddTitleText img{text-align:left; padding:0 2px 0 0;} -.dd2 .ddTitle img.selected { - padding:0 2px 0 0; - vertical-align:top; -} -.dd2 .ddChild { - position:absolute; - border:1px solid #c3c3c3; - border-top:none; - display:none; - margin:0; - width:auto; - overflow:auto; - overflow-x:hidden !important; - background-color:#ffffff; - font-size:14px; -} -.dd2 .ddChild .opta a, .dd2 .ddChild .opta a:visited {padding-left:10px} -.dd2 .ddChild a { - display:block; - padding:3px 0 3px 3px; - text-decoration:none; - color:#000; - overflow:hidden; - white-space:nowrap; - cursor:pointer; -} -.dd2 .ddChild a:hover { - background-color:#66CCFF; -} -.dd2 .ddChild a img { - border:0; - padding:0 2px 0 0; - vertical-align:middle; -} -.dd2 .ddChild a.selected { - background-color:#66CCFF; -} - -.dd2 .borderTop{border-top:1px solid #c3c3c3 !important;} -.dd2 .noBorderTop{border-top:none 0 !important} - -/************* use sprite *****************/ -.dd .ddChild a.sprite, .dd .ddChild a.sprite:visited { - background-image:url(../icons/sprite.gif); - background-repeat:no-repeat; - padding-left:24px; -} - -.dd .ddChild a.calendar, .dd .ddChild a.calendar:visited { - background-position:0 -404px; -} -.dd .ddChild a.shoppingcart, .dd .ddChild a.shoppingcart:visited { - background-position:0 -330px; -} -.dd .ddChild a.cd, .dd .ddChild a.cd:visited { - background-position:0 -439px; -} -.dd .ddChild a.email, .dd .ddChild a.email:visited { - background-position:0 -256px; -} -.dd .ddChild a.faq, .dd .ddChild a.faq:visited { - background-position:0 -183px; -} -.dd .ddChild a.games, -.dd .ddChild a.games:visited { - background-position:0 -365px; -} -.dd .ddChild a.music, .dd .ddChild a.music:visited { - background-position:0 -146px; -} -.dd .ddChild a.phone, .dd .ddChild a.phone:visited { - background-position:0 -109px; -} -.dd .ddChild a.graph, .dd .ddChild a.graph:visited { - background-position:0 -73px; -} -.dd .ddChild a.secured, .dd .ddChild a.secured:visited { - background-position:0 -37px; -} -.dd .ddChild a.video, .dd .ddChild a.video:visited { - background-position:0 0; -} -/*******************************/ diff --git a/src/Umbraco.Web.UI/umbraco_client/ui/dd_arrow.gif b/src/Umbraco.Web.UI/umbraco_client/ui/dd_arrow.gif deleted file mode 100644 index 5b29df710b..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/ui/dd_arrow.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/ui/jQueryWresize.js b/src/Umbraco.Web.UI/umbraco_client/ui/jQueryWresize.js deleted file mode 100644 index ea0569964b..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/ui/jQueryWresize.js +++ /dev/null @@ -1,60 +0,0 @@ -// -// =============================================================================== -// WResize is the jQuery plugin for fixing the IE window resize bug -// ............................................................................... -// Copyright 2007 / Andrea Ercolino -// ------------------------------------------------------------------------------- -// LICENSE: http://www.opensource.org/licenses/mit-license.php -// WEBSITE: http://noteslog.com/ -// =============================================================================== -// - -(function($) { - $.fn.wresize = function(f) { - version = '1.1'; - wresize = { fired: false, width: 0 }; - - function resizeOnce() { - if ($.browser.msie) { - if (!wresize.fired) { - wresize.fired = true; - } - else { - var version = parseInt($.browser.version, 10); - wresize.fired = false; - if (version < 7) { - return false; - } - else { - //a vertical resize is fired once, an horizontal resize twice - var width = $(window).width(); - if (width != wresize.width) { - wresize.width = width; - return false; - } - } - } - } - - return true; - } - - function handleWResize(e) { - if (resizeOnce()) { - return f.apply(this, [e]); - } - } - - this.each(function() { - if (this == window) { - $(this).resize(handleWResize); - } - else { - $(this).resize(f); - } - }); - - return this; - }; - -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/ui/jquery.alphanumeric.js b/src/Umbraco.Web.UI/umbraco_client/ui/jquery.alphanumeric.js deleted file mode 100644 index e51a611896..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/ui/jquery.alphanumeric.js +++ /dev/null @@ -1,73 +0,0 @@ -(function ($) { - $.fn.alphanumeric = function (p) { - - p = $.extend({ - ichars: "!@#$%^&*()+=[]\\\';,/{}|\":<>?~`.- ", - nchars: "", - allow: "" - }, p); - - return this.each - ( - function () { - - if (p.nocaps) p.nchars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - if (p.allcaps) p.nchars += "abcdefghijklmnopqrstuvwxyz"; - - s = p.allow.split(''); - for (i = 0; i < s.length; i++) if (p.ichars.indexOf(s[i]) != -1) s[i] = "\\" + s[i]; - p.allow = s.join('|'); - - var reg = new RegExp(p.allow, 'gi'); - var ch = p.ichars + p.nchars; - ch = ch.replace(reg, ''); - - $(this).keypress - ( - function (e) { - - if (!e.charCode) k = String.fromCharCode(e.which); - else k = String.fromCharCode(e.charCode); - - if (ch.indexOf(k) != -1) e.preventDefault(); - if (e.ctrlKey && k == 'v') e.preventDefault(); - - } - ); - - $(this).bind('contextmenu', function () { return false }); - } - ); - - }; - - $.fn.numeric = function (p) { - - var az = "abcdefghijklmnopqrstuvwxyz"; - az += az.toUpperCase(); - - p = $.extend({ - nchars: az - }, p); - - return this.each(function () { - $(this).alphanumeric(p); - }); - }; - - $.fn.alpha = function (p) { - - var nm = "1234567890"; - - p = $.extend({ - nchars: nm - }, p); - - return this.each(function () { - $(this).alphanumeric(p); - } - ); - - }; - -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/ui/jquery.dd.js b/src/Umbraco.Web.UI/umbraco_client/ui/jquery.dd.js deleted file mode 100644 index 01e5f6112f..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/ui/jquery.dd.js +++ /dev/null @@ -1,1112 +0,0 @@ -// MSDropDown - uncompressed.jquery.dd -// author: Marghoob Suleman - Search me on google -// Date: 12th Aug, 2009 -// Version: 2.38.4 -// Revision: 38 -// web: www.giftlelo.com | www.marghoobsuleman.com -/* -// msDropDown is free jQuery Plugin: you can redistribute it and/or modify -// it under the terms of the either the MIT License or the Gnu General Public License (GPL) Version 2 -*/ -; (function ($) { - - var msOldDiv = ""; - var dd = function (element, options) { - var sElement = element; - var $this = this; //parent this - var options = $.extend({ - height: 120, - visibleRows: 7, - rowHeight: 23, - showIcon: true, - zIndex: 9999, - mainCSS: 'dd', - useSprite: false, - animStyle: 'slideDown', - onInit: '', - jsonTitle: true, - style: '' - }, options); - this.ddProp = new Object(); //storing propeties; - var oldSelectedValue = ""; - var actionSettings = {}; - actionSettings.insideWindow = true; - actionSettings.keyboardAction = false; - actionSettings.currentKey = null; - var ddList = false; - var config = { postElementHolder: '_msddHolder', postID: '_msdd', postTitleID: '_title', postTitleTextID: '_titletext', postChildID: '_child', postAID: '_msa', postOPTAID: '_msopta', postInputID: '_msinput', postArrowID: '_arrow', postInputhidden: '_inp' }; - var styles = { dd: options.mainCSS, ddTitle: 'ddTitle', arrow: 'arrow', ddChild: 'ddChild', ddTitleText: 'ddTitleText', disabled: .30, ddOutOfVision: 'ddOutOfVision', borderTop: 'borderTop', noBorderTop: 'noBorderTop', selected: 'selected' }; - var attributes = { actions: "focus,blur,change,click,dblclick,mousedown,mouseup,mouseover,mousemove,mouseout,keypress,keydown,keyup", prop: "size,multiple,disabled,tabindex" }; - this.onActions = new Object(); - var elementid = $(sElement).prop("id"); - if (typeof (elementid) == "undefined" || elementid.length <= 0) { - //assign and id; - elementid = "msdrpdd" + $.msDropDown.counter++; //I guess it makes unique for the page. - $(sElement).attr("id", elementid); - }; - var inlineCSS = $(sElement).prop("style"); - options.style += (inlineCSS == undefined) ? "" : inlineCSS; - var allOptions = $(sElement).children(); - ddList = ($(sElement).prop("size") > 1 || $(sElement).prop("multiple") == true) ? true : false; - if (ddList) { options.visibleRows = $(sElement).prop("size"); }; - var a_array = {}; //stores id, html & value etc - var currentP = 0; - var isFilter = false; - var oldHeight; - var isClosing = false; - var cacheElement = {}; - var inputText = ""; - var selectedItem; - - var getElement = function (ele) { - if (typeof (cacheElement[ele]) == "undefined") { - cacheElement[ele] = document.getElementById(ele); - } - return cacheElement[ele]; - }; - var getPostID = function (id) { - return elementid + config[id]; - }; - var getOptionsProperties = function (option) { - var currentOption = option; - var styles = $(currentOption).prop("style"); - return (typeof styles == "undefined") ? "" : styles.cssText; - }; - var matchIndex = function (index) { - if (typeof this.selectedItem === 'undefined') - this.selectedItem = $("#" + elementid + " option:selected"); - if (this.selectedItem.length > 1) { - for (var i = 0; i < this.selectedItem.length; i++) { - if (index == this.selectedItem[i].index) { - return true; - }; - }; - } else if (this.selectedItem.length == 1) { - if (this.selectedItem[0].index == index) { - return true; - }; - }; - return false; - }; - var createA = function (currentOptOption, current, currentopt, tp) { - var aTag = ""; - //var aidfix = getPostID("postAID"); - var aidoptfix = (tp == "opt") ? getPostID("postOPTAID") : getPostID("postAID"); - var aid = (tp == "opt") ? aidoptfix + "_" + (current) + "_" + (currentopt) : aidoptfix + "_" + (current); - var arrow = ""; - var t = ""; - var clsName = ""; - var pH = ""; //addition html - - clsName = ' ' + options.useSprite + ' ' + currentOptOption.className; - arrow = $(currentOptOption).prop("title"); - var reg = new RegExp(/^\{.*\}$/); - var isJson = reg.test(arrow); - if (options.jsonTitle == true && isJson == true) { - if (arrow.length != 0) { - var obj = eval("[" + arrow + "]"); - img = (typeof obj[0].image == "undefined") ? "" : obj[0].image; - t = (typeof obj[0].title == "undefined") ? "" : obj[0].title; - pH = (typeof obj[0].postHTML == "undefined") ? "" : obj[0].postHTML; - arrow = (img.length == 0) ? "" : ' '; - }; - } else { - arrow = (arrow.length == 0) ? "" : ' '; - }; - - var sText = $(currentOptOption).text(); - var sValue = $(currentOptOption).val(); - var sEnabledClass = ($(currentOptOption).prop("disabled") == true) ? "disabled" : "enabled"; - a_array[aid] = { html: arrow + sText, value: sValue, text: sText, index: currentOptOption.index, id: aid, title: t }; - var innerStyle = getOptionsProperties(currentOptOption); - if (matchIndex(currentOptOption.index) == true) { - aTag += ''; - aTag += arrow + '' + sText + ''; - if (pH !== "") { - aTag += pH; - }; - aTag += ''; - return aTag; - }; - var in_array = function (t) { - var sText = t.toLowerCase(); - if (sText.length == 0) return -1; - var a = ""; - for (var i in a_array) { - var a_text = a_array[i].text.toLowerCase(); - if (a_text.substr(0, sText.length) == sText) { - a += "#" + a_array[i].id + ", "; - }; - }; - return (a == "") ? -1 : a; - }; - var createATags = function () { - var childnodes = allOptions; - if (childnodes.length == 0) return ""; - var aTag = ""; - var aidfix = getPostID("postAID"); - var aidoptfix = getPostID("postOPTAID"); - childnodes.each(function (current) { - var currentOption = childnodes[current]; - //OPTGROUP - if (currentOption.nodeName.toString().toLowerCase() == "optgroup") { - aTag += "
"; - aTag += "" + $(currentOption).prop("label") + ""; - var optChild = $(currentOption).children(); - optChild.each(function (currentopt) { - var currentOptOption = optChild[currentopt]; - aTag += createA(currentOptOption, current, currentopt, "opt"); - }); - aTag += "
"; - - } else { - aTag += createA(currentOption, current, "", ""); - }; - }); - return aTag; - }; - var createChildDiv = function () { - var id = getPostID("postID"); - var childid = getPostID("postChildID"); - var sStyle = options.style; - sDiv = ""; - sDiv += '
0) { - sText = $("#" + elementid + " option:selected").text(); - arrow = $("#" + elementid + " option:selected").prop("title"); - }; - var img = ""; - var t = ""; - var reg = new RegExp(/^\{.*\}$/); - var isJson = reg.test(arrow); - if (options.jsonTitle == true && isJson == true) { - if (arrow.length != 0) { - var obj = eval("[" + arrow + "]"); - img = (typeof obj[0].image == "undefined") ? "" : obj[0].image; - t = (typeof obj[0].title == "undefined") ? "" : obj[0].title; - arrow = (img.length == 0 || options.showIcon == false || options.useSprite != false) ? "" : ' '; - }; - } else { - arrow = (arrow.length == 0 || arrow == undefined || options.showIcon == false || options.useSprite != false) ? "" : ' '; - }; - var sDiv = '
' + arrow + '' + sText + '
'; - return sDiv; - }; - var applyEventsOnA = function () { - var childid = getPostID("postChildID"); - $("#" + childid + " a.enabled").unbind("click"); //remove old one - $("#" + childid + " a.enabled").bind("click", function (event) { - event.preventDefault(); - manageSelection(this); - setValue(); - if (!ddList) { - $("#" + childid).unbind("mouseover"); - setInsideWindow(false); - var sText = (options.showIcon == false) ? $(this).text() : $(this).html(); - setTitleText(sText); - //$this.data("dd").close(); - $this.close(); - }; - //actionSettings.oldIndex = a_array[$($this).prop("id")].index; - }); - }; - var createDropDown = function () { - var changeInsertionPoint = false; - var id = getPostID("postID"); - var titleid = getPostID("postTitleID"); - var titletextid = getPostID("postTitleTextID"); - var childid = getPostID("postChildID"); - var arrowid = getPostID("postArrowID"); - var iWidth = $("#" + elementid).outerWidth(); - var sStyle = options.style; - if ($("#" + id).length > 0) { - $("#" + id).remove(); - changeInsertionPoint = true; - }; - var sDiv = '
options.visibleRows) { - var margin = parseInt($("#" + childid + " a:first").css("padding-bottom")) + parseInt($("#" + childid + " a:first").css("padding-top")); - var iHeight = ((options.rowHeight) * options.visibleRows) - margin; - $("#" + childid).css("height", iHeight + "px"); - } else if (ddList) { - var iHeight = $("#" + elementid).height(); - $("#" + childid).css("height", iHeight + "px"); - }; - //set out of vision - if (changeInsertionPoint == false) { - setOutOfVision(); - addRefreshMethods(elementid); - }; - if ($("#" + elementid).prop("disabled") == true) { - $("#" + id).css("opacity", styles.disabled); - }; - applyEvents(); - //add events - //arrow hightlight - $("#" + titleid).bind("mouseover", function (event) { - hightlightArrow(1); - }); - $("#" + titleid).bind("mouseout", function (event) { - hightlightArrow(0); - }); - //open close events - applyEventsOnA(); - $("#" + childid + " a.disabled").css("opacity", styles.disabled); - //alert("ddList "+ddList) - if (ddList) { - $("#" + childid).bind("mouseover", function (event) { - if (!actionSettings.keyboardAction) { - actionSettings.keyboardAction = true; - $(document).bind("keydown", function (event) { - var keyCode = event.keyCode; - actionSettings.currentKey = keyCode; - if (keyCode == 39 || keyCode == 40) { - //move to next - event.preventDefault(); event.stopPropagation(); - next(); - setValue(); - }; - if (keyCode == 37 || keyCode == 38) { - event.preventDefault(); event.stopPropagation(); - //move to previous - previous(); - setValue(); - }; - }); - - } - }); - }; - $("#" + childid).bind("mouseout", function (event) { setInsideWindow(false); $(document).unbind("keydown", d_onkeydown); actionSettings.keyboardAction = false; actionSettings.currentKey = null; }); - $("#" + titleid).bind("click", function (event) { - setInsideWindow(false); - if ($("#" + childid + ":visible").length == 1) { - $("#" + childid).unbind("mouseover"); - } else { - $("#" + childid).bind("mouseover", function (event) { setInsideWindow(true); }); - //alert("open "+elementid + $this); - //$this.data("dd").openMe(); - $this.open(); - }; - }); - $("#" + titleid).bind("mouseout", function (evt) { - setInsideWindow(false); - }); - if (options.showIcon && options.useSprite != false) { - setTitleImageSprite(); - }; - }; - var getByIndex = function (index) { - for (var i in a_array) { - if (a_array[i].index == index) { - return a_array[i]; - }; - }; - return -1; - }; - var manageSelection = function (obj) { - var childid = getPostID("postChildID"); - if ($("#" + childid + " a." + styles.selected).length == 1) { //check if there is any selected - oldSelectedValue = $("#" + childid + " a." + styles.selected).text(); //i should have value here. but sometime value is missing - //alert("oldSelectedValue "+oldSelectedValue); - }; - if (!ddList) { - $("#" + childid + " a." + styles.selected).removeClass(styles.selected); - }; - var selectedA = $("#" + childid + " a." + styles.selected).prop("id"); - if (selectedA != undefined) { - var oldIndex = (actionSettings.oldIndex == undefined || actionSettings.oldIndex == null) ? a_array[selectedA].index : actionSettings.oldIndex; - }; - if (obj && !ddList) { - $(obj).addClass(styles.selected); - }; - if (ddList) { - var keyCode = actionSettings.currentKey; - if ($("#" + elementid).prop("multiple") == true) { - if (keyCode == 17) { - //control - actionSettings.oldIndex = a_array[$(obj).prop("id")].index; - $(obj).toggleClass(styles.selected); - //multiple - } else if (keyCode == 16) { - $("#" + childid + " a." + styles.selected).removeClass(styles.selected); - $(obj).addClass(styles.selected); - //shift - var currentSelected = $(obj).prop("id"); - var currentIndex = a_array[currentSelected].index; - for (var i = Math.min(oldIndex, currentIndex) ; i <= Math.max(oldIndex, currentIndex) ; i++) { - $("#" + getByIndex(i).id).addClass(styles.selected); - }; - } else { - $("#" + childid + " a." + styles.selected).removeClass(styles.selected); - $(obj).addClass(styles.selected); - actionSettings.oldIndex = a_array[$(obj).prop("id")].index; - }; - } else { - $("#" + childid + " a." + styles.selected).removeClass(styles.selected); - $(obj).addClass(styles.selected); - actionSettings.oldIndex = a_array[$(obj).prop("id")].index; - }; - //isSingle - }; - }; - var addRefreshMethods = function (id) { - //deprecated - var objid = id; - getElement(objid).refresh = function (e) { - $("#" + objid).msDropDown(options); - }; - }; - var setInsideWindow = function (val) { - actionSettings.insideWindow = val; - }; - var getInsideWindow = function () { - return actionSettings.insideWindow; - //will work on this - /* - var childid = getPostID("postChildID"); - return ($("#"+childid + ":visible").length == 0) ? false : true; - */ - }; - var applyEvents = function () { - var mainid = getPostID("postID"); - var actions_array = attributes.actions.split(","); - for (var iCount = 0; iCount < actions_array.length; iCount++) { - var action = actions_array[iCount]; - //var actionFound = $("#"+elementid).prop(action); - var actionFound = has_handler(action); - if (actionFound == true) { - switch (action) { - case "focus": - $("#" + mainid).bind("mouseenter", function (event) { - getElement(elementid).focus(); - //$("#"+elementid).focus(); - }); - break; - case "click": - $("#" + mainid).bind("click", function (event) { - //getElement(elementid).onclick(); - $("#" + elementid).trigger("click"); - }); - break; - case "dblclick": - $("#" + mainid).bind("dblclick", function (event) { - //getElement(elementid).ondblclick(); - $("#" + elementid).trigger("dblclick"); - }); - break; - case "mousedown": - $("#" + mainid).bind("mousedown", function (event) { - //getElement(elementid).onmousedown(); - $("#" + elementid).trigger("mousedown"); - }); - break; - case "mouseup": - //has in close mthod - $("#" + mainid).bind("mouseup", function (event) { - //getElement(elementid).onmouseup(); - $("#" + elementid).trigger("mouseup"); - //setValue(); - }); - break; - case "mouseover": - $("#" + mainid).bind("mouseover", function (event) { - //getElement(elementid).onmouseover(); - $("#" + elementid).trigger("mouseover"); - }); - break; - case "mousemove": - $("#" + mainid).bind("mousemove", function (event) { - //getElement(elementid).onmousemove(); - $("#" + elementid).trigger("mousemove"); - }); - break; - case "mouseout": - $("#" + mainid).bind("mouseout", function (event) { - //getElement(elementid).onmouseout(); - $("#" + elementid).trigger("mouseout"); - }); - break; - }; - }; - }; - - }; - var setOutOfVision = function () { - var sId = getPostID("postElementHolder"); - $("#" + elementid).after("
"); - $("#" + elementid).appendTo($("#" + sId)); - }; - var setTitleText = function (sText) { - var titletextid = getPostID("postTitleTextID"); - $("#" + titletextid).html(sText); - }; - var navigateA = function (w) { - var where = w; - var childid = getPostID("postChildID"); - var visibleA = $("#" + childid + " a:visible"); - var totalA = visibleA.length; - var currentP = $("#" + childid + " a:visible").index($("#" + childid + " a.selected:visible")); - var nextA; - switch (where) { - case "next": - if (currentP < totalA - 1) { - currentP++; - nextA = visibleA[currentP]; - }; - break; - case "previous": - if (currentP < totalA && currentP > 0) { - currentP--; - nextA = visibleA[currentP]; - }; - break; - }; - if (typeof (nextA) == "undefined") { - return false; - }; - $("#" + childid + " a." + styles.selected).removeClass(styles.selected); - $(nextA).addClass(styles.selected); - var selectedA = nextA.id; - if (!ddList) { - var sText = (options.showIcon == false) ? a_array[selectedA].text : $("#" + selectedA).html(); - setTitleText(sText); - setTitleImageSprite(a_array[selectedA].index); - }; - if (where == "next") { - if (parseInt(($("#" + selectedA).position().top + $("#" + selectedA).height())) >= parseInt($("#" + childid).height())) { - $("#" + childid).scrollTop(($("#" + childid).scrollTop()) + $("#" + selectedA).height() + $("#" + selectedA).height()); - }; - } else { - if (parseInt(($("#" + selectedA).position().top + $("#" + selectedA).height())) <= 0) { - $("#" + childid).scrollTop(($("#" + childid).scrollTop() - $("#" + childid).height()) - $("#" + selectedA).height()); - }; - }; - }; - var next = function () { - navigateA("next"); - }; - var previous = function () { - navigateA("previous"); - }; - var setTitleImageSprite = function (i) { - if (options.useSprite != false) { - var titletextid = getPostID("postTitleTextID"); - var index = (typeof (i) == "undefined") ? getElement(elementid).selectedIndex : i; - var sClassName = getElement(elementid).options[index].className; - if (sClassName.length > 0) { - var childid = getPostID("postChildID"); - var id = $("#" + childid + " a." + sClassName).prop("id"); - var backgroundImg = $("#" + id).css("background-image"); - var backgroundPosition = $("#" + id).css("background-position"); - if (backgroundPosition == undefined) { - backgroundPosition = $("#" + id).css("background-position-x") + " " + $("#" + id).css("background-position-y"); - }; - var paddingLeft = $("#" + id).css("padding-left"); - if (backgroundImg != undefined) { - $("#" + titletextid).find("." + styles.ddTitleText).attr('style', "background:" + backgroundImg); - }; - if (backgroundPosition != undefined) { - $("#" + titletextid).find("." + styles.ddTitleText).css('background-position', backgroundPosition); - }; - if (paddingLeft != undefined) { - $("#" + titletextid).find("." + styles.ddTitleText).css('padding-left', paddingLeft); - }; - $("#" + titletextid).find("." + styles.ddTitleText).css('background-repeat', 'no-repeat'); - $("#" + titletextid).find("." + styles.ddTitleText).css('padding-bottom', '2px'); - }; - }; - }; - var setValue = function () { - //alert("setValue "+elementid); - var childid = getPostID("postChildID"); - var allSelected = $("#" + childid + " a." + styles.selected); - if (allSelected.length == 1) { - var sText = $("#" + childid + " a." + styles.selected).text(); - var selectedA = $("#" + childid + " a." + styles.selected).prop("id"); - if (selectedA != undefined) { - var sValue = a_array[selectedA].value; - getElement(elementid).selectedIndex = a_array[selectedA].index; - }; - //set image on title if using sprite - - if (options.showIcon && options.useSprite != false) - setTitleImageSprite(); - } else if (allSelected.length > 1) { - //var alls = $("#"+elementid +" > option:selected").removeprop("selected"); - for (var i = 0; i < allSelected.length; i++) { - var selectedA = $(allSelected[i]).prop("id"); - var index = a_array[selectedA].index; - getElement(elementid).options[index].selected = "selected"; - }; - }; - //alert(getElement(elementid).selectedIndex); - var sIndex = getElement(elementid).selectedIndex; - $this.ddProp["selectedIndex"] = sIndex; - //alert("selectedIndex "+ $this.ddProp["selectedIndex"] + " sIndex "+sIndex); - }; - var has_handler = function (name) { - // True if a handler has been added in the html. - if ($("#" + elementid).prop("on" + name) != undefined) { - return true; - }; - // True if a handler has been added using jQuery. - var evs = $("#" + elementid).data("events"); - if (evs && evs[name]) { - return true; - }; - return false; - }; - var blur_m = function (evt) { - $("#" + elementid).focus(); - $("#" + elementid)[0].blur(); - setValue(); - $(document).unbind("mouseup", d_onmouseup); - $(document).unbind("mouseup", blur_m); - }; - var checkMethodAndApply = function () { - //console.log("calling checkMethodAndApply"); - var childid = getPostID("postChildID"); - if (has_handler('change') == true) { - //alert(1); - var currentSelected = a_array[$("#" + childid + " a.selected").prop("id")]; - if (currentSelected != undefined) { - var currentSelectedValue = currentSelected.text; - if ($.trim(oldSelectedValue) !== $.trim(currentSelectedValue) && oldSelectedValue !== "") { - $("#" + elementid).trigger("change"); - }; - } - }; - if (has_handler('mouseup') == true) { - $("#" + elementid).trigger("mouseup"); - }; - if (has_handler('blur') == true) { - $(document).bind("mouseup", blur_m); - }; - return false; - }; - var hightlightArrow = function (ison) { - var arrowid = getPostID("postArrowID"); - if (ison == 1) - $("#" + arrowid).css({ backgroundPosition: '0 100%' }); - else - $("#" + arrowid).css({ backgroundPosition: '0 0' }); - }; - var setOriginalProperties = function () { - //properties = {}; - for (var i in getElement(elementid)) { - if (typeof (getElement(elementid)[i]) !== 'function' && typeof (getElement(elementid)[i]) !== "undefined" && typeof (getElement(elementid)[i]) !== "null") { - $this.set(i, getElement(elementid)[i], true); //true = setting local properties - }; - }; - }; - var setValueByIndex = function (prop, val) { - if (getByIndex(val) != -1) { - getElement(elementid)[prop] = val; - var childid = getPostID("postChildID"); - $("#" + childid + " a." + styles.selected).removeClass(styles.selected); - $("#" + getByIndex(val).id).addClass(styles.selected); - var sText = getByIndex(getElement(elementid).selectedIndex).html; - setTitleText(sText); - }; - }; - var addRemoveFromIndex = function (i, action) { - if (action == 'd') { - for (var key in a_array) { - if (a_array[key].index == i) { - delete a_array[key]; - break; - }; - }; - }; - //update index - var count = 0; - for (var key in a_array) { - a_array[key].index = count; - count++; - }; - }; - var shouldOpenOpposite = function () { - var childid = getPostID("postChildID"); - var main = getPostID("postID"); - var pos = $("#" + main).offset(); - var mH = $("#" + main).height(); - var wH = $(window).height(); - var st = $(window).scrollTop(); - var cH = $("#" + childid).height(); - var css = { zIndex: options.zIndex, top: (mH) + "px", display: "none" }; - var ani = options.animStyle; - var opp = false; - var borderTop = styles.noBorderTop; - $("#" + childid).removeClass(styles.noBorderTop); - $("#" + childid).removeClass(styles.borderTop); - if ((wH + st) < Math.floor(cH + mH + pos.top)) { - var tp = cH; - css = { zIndex: options.zIndex, top: "-" + tp + "px", display: "none" }; - ani = "show"; - opp = true; - borderTop = styles.borderTop; - }; - return { opp: opp, ani: ani, css: css, border: borderTop }; - }; - var fireOpenEvent = function () { - if ($this.onActions["onOpen"] != null) { - eval($this.onActions["onOpen"])($this); - }; - }; - var fireCloseEvent = function () { - checkMethodAndApply(); - if ($this.onActions["onClose"] != null) { - eval($this.onActions["onClose"])($this); - }; - }; - var d_onkeydown = function (event) { - var childid = getPostID("postChildID"); - var keyCode = event.keyCode; - //alert("keyCode "+keyCode); - if (keyCode == 8) { - event.preventDefault(); event.stopPropagation(); - //remove char - inputText = (inputText.length == 0) ? "" : inputText.substr(0, inputText.length - 1); - }; - switch (keyCode) { - case 39: - case 40: - //move to next - event.preventDefault(); event.stopPropagation(); - next(); - break; - case 37: - case 38: - //move to previous - event.preventDefault(); event.stopPropagation(); - previous(); - break; - case 27: - case 13: - $this.close(); - setValue(); - break; - default: - if (keyCode > 46) { - inputText += String.fromCharCode(keyCode); - }; - var ind = in_array(inputText); - if (ind != -1) { - $("#" + childid).css({ height: 'auto' }); - $("#" + childid + " a").hide(); - $(ind).show(); - var wf = shouldOpenOpposite(); - $("#" + childid).css(wf.css); - $("#" + childid).css({ display: 'block' }); - } else { - $("#" + childid + " a").show(); - $("#" + childid).css({ height: oldHeight + 'px' }); - }; - break; - }; - if (has_handler("keydown") == true) { - getElement(elementid).onkeydown(); - }; - return false; - }; - var d_onmouseup = function (event) { - if (getInsideWindow() == false) { - //alert("evt.target: "+event.target); - //$this.data("dd").close(); - $this.close(); - }; - return false; - }; - var d_onkeyup = function (event) { - if ($("#" + elementid).prop("onkeyup") != undefined) { - //$("#"+elementid).keyup(); - getElement(elementid).onkeyup(); - }; - return false; - }; - /************* public methods *********************/ - this.open = function () { - if (($this.get("disabled", true) == true) || ($this.get("options", true).length == 0)) return; - var childid = getPostID("postChildID"); - if (msOldDiv != "" && childid != msOldDiv) { - $("#" + msOldDiv).slideUp("fast"); - $("#" + msOldDiv).css({ zIndex: '0' }); - }; - if ($("#" + childid).css("display") == "none") { - var oldSelected = a_array[$("#" + childid + " a.selected").prop("id")]; - if (oldSelected != undefined) - oldSelectedValue = oldSelected.text; - - //keyboard action - inputText = ""; - oldHeight = $("#" + childid).height(); - $("#" + childid + " a").show(); - $(document).bind("keydown", d_onkeydown); - $(document).bind("keyup", d_onkeyup); - //end keyboard action - - //close onmouseup - $(document).bind("mouseup", d_onmouseup); - - //check open - var wf = shouldOpenOpposite(); - $("#" + childid).css(wf.css); - if (wf.opp == true) { - $("#" + childid).css({ display: 'block' }); - $("#" + childid).addClass(wf.border); - fireOpenEvent(); - } else { - $("#" + childid)[wf.ani]("fast", function () { - $("#" + childid).addClass(wf.border); - fireOpenEvent(); - }); - }; - if (childid != msOldDiv) { - msOldDiv = childid; - }; - }; - }; - this.close = function () { - var childid = getPostID("postChildID"); - if (!$("#" + childid).is(":visible") || isClosing) return; - isClosing = true; - //console.log("calling close " + $("#"+childid).css("display")); - if ($("#" + childid).css("display") == "none") { return false; }; - var top = $("#" + getPostID("postTitleID")).position().top; - var wf = shouldOpenOpposite(); - //var oldHeight = $("#"+childid).height(); - isFilter = false; - if (wf.opp == true) { - $("#" + childid).animate({ - height: 0, - top: top - }, function () { - $("#" + childid).css({ height: oldHeight + 'px', display: 'none' }); - fireCloseEvent(); - isClosing = false; - }); - } - else { - $("#" + childid).slideUp("fast", function (event) { - fireCloseEvent(); - $("#" + childid).css({ zIndex: '0' }); - $("#" + childid).css({ height: oldHeight + 'px' }); - isClosing = false; - }); - }; - setTitleImageSprite(); - $(document).unbind("keydown", d_onkeydown); - $(document).unbind("keyup", d_onkeyup); - $(document).unbind("mouseup", d_onmouseup); - }; - this.selectedIndex = function (i) { - if (typeof (i) == "undefined") { - return $this.get("selectedIndex"); - } else { - $this.set("selectedIndex", i); - }; - }; - this.debug = function (is) { - if (typeof (is) == "undefined" || is == true) { - $("." + styles.ddOutOfVision).removeAttr("style"); - } else { - $("." + styles.ddOutOfVision).attr("style", "height:0px;overflow:hidden;position:absolute"); - }; - }; - //update properties - this.set = function (prop, val, isLocal) { - //alert("- set " + prop + " : "+val); - if (typeof prop == "undefined" || typeof val == "undefined") return false; - $this.ddProp[prop] = val; - if (isLocal != true) { - switch (prop) { - case "selectedIndex": - setValueByIndex(prop, val); - break; - case "disabled": - $this.disabled(val, true); - break; - case "multiple": - getElement(elementid)[prop] = val; - ddList = ($(sElement).prop("size") > 0 || $(sElement).prop("multiple") == true) ? true : false; - if (ddList) { - //do something - var iHeight = $("#" + elementid).height(); - var childid = getPostID("postChildID"); - $("#" + childid).css("height", iHeight + "px"); - //hide titlebar - var titleid = getPostID("postTitleID"); - $("#" + titleid).hide(); - var childid = getPostID("postChildID"); - $("#" + childid).css({ display: 'block', position: 'relative' }); - applyEventsOnA(); - }; - break; - case "size": - getElement(elementid)[prop] = val; - if (val == 0) { - getElement(elementid).multiple = false; - }; - ddList = ($(sElement).prop("size") > 0 || $(sElement).prop("multiple") == true) ? true : false; - if (val == 0) { - //show titlebar - var titleid = getPostID("postTitleID"); - $("#" + titleid).show(); - var childid = getPostID("postChildID"); - $("#" + childid).css({ display: 'none', position: 'absolute' }); - var sText = ""; - if (getElement(elementid).selectedIndex >= 0) { - var aObj = getByIndex(getElement(elementid).selectedIndex); - sText = aObj.html; - manageSelection($("#" + aObj.id)); - }; - setTitleText(sText); - } else { - //hide titlebar - var titleid = getPostID("postTitleID"); - $("#" + titleid).hide(); - var childid = getPostID("postChildID"); - $("#" + childid).css({ display: 'block', position: 'relative' }); - }; - break; - default: - try { - //check if this is not a readonly properties - getElement(elementid)[prop] = val; - } catch (e) { - //silent - }; - break; - }; - }; - //alert("get " + prop + " : "+$this.ddProp[prop]); - //$this.set("selectedIndex", 0); - }; - this.get = function (prop, forceRefresh) { - if (prop == undefined && forceRefresh == undefined) { - //alert("c1 : " +$this.ddProp); - return $this.ddProp; - }; - if (prop != undefined && forceRefresh == undefined) { - //alert("c2 : " +$this.ddProp[prop]); - return ($this.ddProp[prop] != undefined) ? $this.ddProp[prop] : null; - }; - if (prop != undefined && forceRefresh != undefined) { - //alert("c3 : " +getElement(elementid)[prop]); - return getElement(elementid)[prop]; - }; - }; - this.visible = function (val) { - var id = getPostID("postID"); - if (val == true) { - $("#" + id).show(); - } else if (val == false) { - $("#" + id).hide(); - } else { - return $("#" + id).css("display"); - }; - }; - this.add = function (opt, index) { - var objOpt = opt; - var sText = objOpt.text; - var sValue = (objOpt.value == undefined || objOpt.value == null) ? sText : objOpt.value; - var img = (objOpt["title"] == undefined || objOpt["title"] == null) ? '' : objOpt["title"]; - var i = (index == undefined || index == null) ? getElement(elementid).options.length : index; - getElement(elementid).options[i] = new Option(sText, sValue); - if (img != '') getElement(elementid).options[i]["title"] = img; - //check if exist - var ifA = getByIndex(i); - if (ifA != -1) { - //replace - var aTag = createA(getElement(elementid).options[i], i, "", ""); - $("#" + ifA.id).html(aTag); - //a_array[key] - } else { - var aTag = createA(getElement(elementid).options[i], i, "", ""); - //add - var childid = getPostID("postChildID"); - $("#" + childid).append(aTag); - applyEventsOnA(); - }; - }; - this.remove = function (i) { - getElement(elementid).remove(i); - if ((getByIndex(i)) != -1) { $("#" + getByIndex(i).id).remove(); addRemoveFromIndex(i, 'd'); }; - //alert("a" +a); - if (getElement(elementid).length == 0) { - setTitleText(""); - } else { - var sText = getByIndex(getElement(elementid).selectedIndex).html; - setTitleText(sText); - }; - $this.set("selectedIndex", getElement(elementid).selectedIndex); - }; - this.disabled = function (dis, isLocal) { - getElement(elementid).disabled = dis; - //alert(getElement(elementid).disabled); - var id = getPostID("postID"); - if (dis == true) { - $("#" + id).css("opacity", styles.disabled); - $this.close(); - } else if (dis == false) { - $("#" + id).css("opacity", 1); - }; - if (isLocal != true) { - $this.set("disabled", dis); - }; - }; - //return form element - this.form = function () { - return (getElement(elementid).form == undefined) ? null : getElement(elementid).form; - }; - this.item = function () { - //index, subindex - use arguments.length - if (arguments.length == 1) { - return getElement(elementid).item(arguments[0]); - } else if (arguments.length == 2) { - return getElement(elementid).item(arguments[0], arguments[1]); - } else { - throw { message: "An index is required!" }; - }; - }; - this.namedItem = function (nm) { - return getElement(elementid).namedItem(nm); - }; - this.multiple = function (is) { - if (typeof (is) == "undefined") { - return $this.get("multiple"); - } else { - $this.set("multiple", is); - }; - - }; - this.size = function (sz) { - if (typeof (sz) == "undefined") { - return $this.get("size"); - } else { - $this.set("size", sz); - }; - }; - this.addMyEvent = function (nm, fn) { - $this.onActions[nm] = fn; - }; - this.fireEvent = function (nm) { - eval($this.onActions[nm])($this); - }; - this.showRows = function (r) { - if (typeof r == "undefined" || r == 0) { return false }; - var childid = getPostID("postChildID"); - var fc = $("#" + childid + " a:first").height(); - var dh = (fc == 0) ? options.rowHeight : fc; - var iHeight = r * dh; - $("#" + childid).css("height", iHeight + "px"); - }; - //end - var updateCommonVars = function () { - $this.set("version", $.msDropDown.version); - $this.set("author", $.msDropDown.author); - }; - var init = function () { - //create wrapper - createDropDown(); - //update propties - //alert("init"); - setOriginalProperties(); - updateCommonVars(); - if (options.onInit != '') { - eval(options.onInit)($this); - }; - }; - init(); - }; - //static - $.msDropDown = { - version: '2.38.4', - author: "Marghoob Suleman", - counter: 20, - debug: function (v) { - if (v == true) { - $(".ddOutOfVision").css({ height: '20px', position: 'relative' }); - } else { - $(".ddOutOfVision").css({ height: '0px', position: 'absolute' }); - }; - }, - create: function (id, opt) { - return $(id).msDropDown(opt).data("dd"); - } - }; - $.fn.extend({ - msDropDown: function (options) { - return this.each(function () { - //if ($(this).data('dd')) return; // need to comment when using refresh method - will remove in next version - var mydropdown = new dd(this, options); - $(this).data('dd', mydropdown); - }); - } - }); - //fixed for prop - if (typeof ($.fn.prop) == 'undefined') { - $.fn.prop = function (w, v) { - if (typeof v == "undefined") { - return $(this).attr(w); - }; - try { - $(this).attr(w, v); - } catch (e) { - //some properties are read only. - }; - }; - }; - -})(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/ui/jquery.tooltip.min.js b/src/Umbraco.Web.UI/umbraco_client/ui/jquery.tooltip.min.js deleted file mode 100644 index 5bbef215d8..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/ui/jquery.tooltip.min.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * jQuery Tools 1.2.4 - The missing UI library for the Web - * - * [tooltip] - * - * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE. - * - * http://flowplayer.org/tools/ - * - * File generated: Mon Aug 30 08:49:23 GMT 2010 - */ -(function(f){function p(a,b,c){var h=c.relative?a.position().top:a.offset().top,e=c.relative?a.position().left:a.offset().left,i=c.position[0];h-=b.outerHeight()-c.offset[0];e+=a.outerWidth()+c.offset[1];var j=b.outerHeight()+a.outerHeight();if(i=="center")h+=j/2;if(i=="bottom")h+=j;i=c.position[1];a=b.outerWidth()+a.outerWidth();if(i=="center")e-=a/2;if(i=="left")e-=a;return{top:h,left:e}}function u(a,b){var c=this,h=a.add(c),e,i=0,j=0,m=a.attr("title"),q=a.attr("data-tooltip"),r=n[b.effect],l,s= -a.is(":input"),v=s&&a.is(":checkbox, :radio, select, :button, :submit"),t=a.attr("type"),k=b.events[t]||b.events[s?v?"widget":"input":"def"];if(!r)throw'Nonexistent effect "'+b.effect+'"';k=k.split(/,\s*/);if(k.length!=2)throw"Tooltip: bad events configuration for "+t;a.bind(k[0],function(d){clearTimeout(i);if(b.predelay)j=setTimeout(function(){c.show(d)},b.predelay);else c.show(d)}).bind(k[1],function(d){clearTimeout(j);if(b.delay)i=setTimeout(function(){c.hide(d)},b.delay);else c.hide(d)});if(m&& -b.cancelDefault){a.removeAttr("title");a.data("title",m)}f.extend(c,{show:function(d){if(!e){if(q)e=f(q);else if(m)e=f(b.layout).addClass(b.tipClass).appendTo(document.body).hide().append(m);else if(b.tip)e=f(b.tip).eq(0);else{e=a.next();e.length||(e=a.parent().next())}if(!e.length)throw"Cannot find tooltip for "+a;}if(c.isShown())return c;e.stop(true,true);var g=p(a,e,b);d=d||f.Event();d.type="onBeforeShow";h.trigger(d,[g]);if(d.isDefaultPrevented())return c;g=p(a,e,b);e.css({position:"absolute", -top:g.top,left:g.left});l=true;r[0].call(c,function(){d.type="onShow";l="full";h.trigger(d)});g=b.events.tooltip.split(/,\s*/);e.bind(g[0],function(){clearTimeout(i);clearTimeout(j)});g[1]&&!a.is("input:not(:checkbox, :radio), textarea")&&e.bind(g[1],function(o){o.relatedTarget!=a[0]&&a.trigger(k[1].split(" ")[0])});return c},hide:function(d){if(!e||!c.isShown())return c;d=d||f.Event();d.type="onBeforeHide";h.trigger(d);if(!d.isDefaultPrevented()){l=false;n[b.effect][1].call(c,function(){d.type="onHide"; -h.trigger(d)});return c}},isShown:function(d){return d?l=="full":l},getConf:function(){return b},getTip:function(){return e},getTrigger:function(){return a}});f.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","),function(d,g){f.isFunction(b[g])&&f(c).bind(g,b[g]);c[g]=function(o){f(c).bind(g,o);return c}})}f.tools=f.tools||{version:"1.2.4"};f.tools.tooltip={conf:{effect:"toggle",fadeOutSpeed:"fast",predelay:0,delay:30,opacity:1,tip:0,position:["top","center"],offset:[0,0],relative:false,cancelDefault:true, -events:{def:"mouseenter,mouseleave",input:"focus,blur",widget:"focus mouseenter,blur mouseleave",tooltip:"mouseenter,mouseleave"},layout:"
",tipClass:"tooltip"},addEffect:function(a,b,c){n[a]=[b,c]}};var n={toggle:[function(a){var b=this.getConf(),c=this.getTip();b=b.opacity;b<1&&c.css({opacity:b});c.show();a.call()},function(a){this.getTip().hide();a.call()}],fade:[function(a){var b=this.getConf();this.getTip().fadeTo(b.fadeInSpeed,b.opacity,a)},function(a){this.getTip().fadeOut(this.getConf().fadeOutSpeed, -a)}]};f.fn.tooltip=function(a){var b=this.data("tooltip");if(b)return b;a=f.extend(true,{},f.tools.tooltip.conf,a);if(typeof a.position=="string")a.position=a.position.split(/,?\s/);this.each(function(){b=new u(f(this),a);f(this).data("tooltip",b)});return a.api?b:this}})(jQuery); diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index e5a1d3f4f9..f9c62eeb0e 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -1,51 +1,55 @@ - + + + + + + + - + - - + - + - + - + - + - + - - + - + - + - + - - + - - + - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - \ No newline at end of file +--> + diff --git a/src/Umbraco.Web.UI/web.Template.Release.config b/src/Umbraco.Web.UI/web.Template.Release.config index c483af69c4..c1cb26e747 100644 --- a/src/Umbraco.Web.UI/web.Template.Release.config +++ b/src/Umbraco.Web.UI/web.Template.Release.config @@ -1,12 +1,10 @@ - - @@ -15,5 +13,4 @@ - \ No newline at end of file diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index ee8d98cae3..5e4894cbdf 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -1,17 +1,23 @@ - -
-
-
-
-
- -
-
-
-
+ + + +
+
+
+
+
+ + +
+
+
@@ -22,236 +28,121 @@ - - - - - + + + + - + - - - - - + + + + + - - - - - - - - - - - + + + + + + + + + + - - - - + + + + - - + + - + + - - - - - - + + + + + - - - - - - - - + + + + + + + + - - + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + @@ -259,198 +150,452 @@ + - - - - - + - - + + - - + + + + + + + - - + + + + + + + - - - - - + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - + - + - + + + + + + + + diff --git a/src/Umbraco.Web/AppBuilderExtensions.cs b/src/Umbraco.Web/AppBuilderExtensions.cs new file mode 100644 index 0000000000..04226d81f2 --- /dev/null +++ b/src/Umbraco.Web/AppBuilderExtensions.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Microsoft.AspNet.SignalR; +using Microsoft.Owin.Logging; +using Owin; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Web.SignalR; + +namespace Umbraco.Web +{ + /// + /// Provides general extension methods to IAppBuilder. + /// + public static class AppBuilderExtensions + { + /// + /// Called at the end of configuring middleware + /// + /// The app builder. + /// + /// This could be used for something else in the future - maybe to inform Umbraco that middleware is done/ready, but for + /// now this is used to raise the custom event + /// + /// This is an extension method in case developer entirely replace the UmbracoDefaultOwinStartup class, in which case they will + /// need to ensure they call this extension method in their startup class. + /// + public static void FinalizeMiddlewareConfiguration(this IAppBuilder app) + { + UmbracoDefaultOwinStartup.OnMiddlewareConfigured(new OwinMiddlewareConfiguredEventArgs(app)); + } + + /// + /// Sets the OWIN logger to use Umbraco's logging system. + /// + /// The app builder. + public static void SetUmbracoLoggerFactory(this IAppBuilder app) + { + app.SetLoggerFactory(new OwinLoggerFactory()); + } + + /// + /// Configures SignalR. + /// + /// The app builder. + /// + public static IAppBuilder UseSignalR(this IAppBuilder app, IGlobalSettings globalSettings) + { + var umbracoPath = globalSettings.GetUmbracoMvcArea(); + var signalrPath = HttpRuntime.AppDomainAppVirtualPath + umbracoPath + "/BackOffice/signalr"; + return app.MapSignalR(signalrPath, new HubConfiguration { EnableDetailedErrors = true }); + } + } +} diff --git a/src/Umbraco.Web/ApplicationContextExtensions.cs b/src/Umbraco.Web/ApplicationContextExtensions.cs deleted file mode 100644 index b91e73df0d..0000000000 --- a/src/Umbraco.Web/ApplicationContextExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Threading; -using System.Web; -using Umbraco.Core; - -namespace Umbraco.Web -{ - public static class ApplicationContextExtensions - { - /// - /// Restarts the application pool by unloading the application domain. - /// - /// - /// - public static void RestartApplicationPool(this ApplicationContext appContext, HttpContextBase http) - { - // we're going to put an application wide flag to show that the application is about to restart. - // we're doing this because if there is a script checking if the app pool is fully restarted, then - // it can check if this flag exists... if it does it means the app pool isn't restarted yet. - http.Application.Add("AppPoolRestarting", true); - - // unload app domain - we must null out all identities otherwise we get serialization errors - // http://www.zpqrtbnk.net/posts/custom-iidentity-serialization-issue - http.User = null; - if (HttpContext.Current != null) - HttpContext.Current.User = null; - Thread.CurrentPrincipal = null; - HttpRuntime.UnloadAppDomain(); - } - } -} diff --git a/src/Umbraco.Web/AreaRegistrationContextExtensions.cs b/src/Umbraco.Web/AreaRegistrationContextExtensions.cs index a3b4c53e36..1321d3722c 100644 --- a/src/Umbraco.Web/AreaRegistrationContextExtensions.cs +++ b/src/Umbraco.Web/AreaRegistrationContextExtensions.cs @@ -1,4 +1,4 @@ -using System.Web.Http; +using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; @@ -25,4 +25,4 @@ namespace Umbraco.Web return apiRoute; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/AspNetHttpContextAccessor.cs b/src/Umbraco.Web/AspNetHttpContextAccessor.cs new file mode 100644 index 0000000000..babd8dfcfa --- /dev/null +++ b/src/Umbraco.Web/AspNetHttpContextAccessor.cs @@ -0,0 +1,20 @@ +using System; +using System.Web; + +namespace Umbraco.Web +{ + internal class AspNetHttpContextAccessor : IHttpContextAccessor + { + public HttpContext HttpContext + { + get + { + return HttpContext.Current; + } + set + { + throw new NotSupportedException(); + } + } + } +} diff --git a/src/Umbraco.Web/BaseRest/BaseRestHandler.cs b/src/Umbraco.Web/BaseRest/BaseRestHandler.cs deleted file mode 100644 index 6f899463d7..0000000000 --- a/src/Umbraco.Web/BaseRest/BaseRestHandler.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Web; -using System.Web.SessionState; -using System.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.BaseRest; - -namespace Umbraco.Web.BaseRest -{ - [Obsolete("Umbraco /base is obsoleted, use WebApi (UmbracoApiController) instead for all REST based logic")] - internal class BaseRestHandler : IHttpHandler, IRequiresSessionState - { - static readonly string BaseUrl; - - static BaseRestHandler() - { - BaseUrl = UriUtility.ToAbsolute(Core.IO.SystemDirectories.Base).ToLower(); - if (!BaseUrl.EndsWith("/")) - BaseUrl += "/"; - } - - public bool IsReusable - { - get { return true; } - } - - /// - /// Returns a value indicating whether a specified Uri should be routed to the BaseRestHandler. - /// - /// The specified Uri. - /// A value indicating whether the specified Uri should be routed to the BaseRestHandler. - public static bool IsBaseRestRequest(Uri uri) - { - if (uri == null) return false; - return UmbracoConfig.For.BaseRestExtensions().Enabled - && uri.AbsolutePath.ToLowerInvariant().StartsWith(BaseUrl); - } - - public void ProcessRequest(HttpContext context) - { - string url = context.Request.RawUrl; - - // sanitize and split the url - url = url.Substring(BaseUrl.Length); - if (url.ToLower().Contains(".aspx")) - url = url.Substring(0, url.IndexOf(".aspx", StringComparison.OrdinalIgnoreCase)); - if (url.ToLower().Contains("?")) - url = url.Substring(0, url.IndexOf("?", StringComparison.OrdinalIgnoreCase)); - var urlParts = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - - // by default, return xml content - context.Response.ContentType = "text/xml"; - - // ensure that we have a valid request ie /base/library/method/[parameters].aspx - if (urlParts.Length < 2) - { - context.Response.Write("Invalid request, missing parts."); - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - return; - } - - var extensionAlias = urlParts[0]; - var methodName = urlParts[1]; - var paramsCount = urlParts.Length - 2; - - var method = RestExtensionMethodInfo.GetMethod(extensionAlias, methodName, paramsCount); - - if (!method.Exists) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; - context.Response.Output.Write("Extension or method not found."); - } - else if (!method.CanBeInvokedByCurrentMember) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; - context.Response.Output.Write("Permission denied."); - } - else - { - if (!method.ReturnXml) - context.Response.ContentType = "text/html"; - - TrySetCulture(); - - var result = method.Invoke(urlParts.Skip(2).ToArray()); - if (result.Length >= 7 && result.Substring(0, 7) == "") - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; - } - context.Response.Output.Write(result); - } - - context.Response.End(); - } - - #region from baseHttpModule.cs - - // note - is this ok? - - private static void TrySetCulture() - { - var domain = HttpContext.Current.Request.Url.Host; // host only - if (TrySetCulture(domain)) return; - - domain = HttpContext.Current.Request.Url.Authority; // host with port - TrySetCulture(domain); - } - - private static bool TrySetCulture(string domain) - { - var uDomain = global::umbraco.cms.businesslogic.web.Domain.GetDomain(domain); - if (uDomain == null) return false; - System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(uDomain.Language.CultureAlias); - return true; - } - - #endregion - } -} diff --git a/src/Umbraco.Web/BaseRest/MemberRest.cs b/src/Umbraco.Web/BaseRest/MemberRest.cs deleted file mode 100644 index eca49d0fd2..0000000000 --- a/src/Umbraco.Web/BaseRest/MemberRest.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; -using System.Xml.XPath; - -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.member; -using umbraco.cms.businesslogic.property; - -namespace Umbraco.Web.BaseRest -{ - [Obsolete("Umbraco /base is obsoleted, use WebApi (UmbracoApiController) instead for all REST based logic")] - public static class MemberRest - { - public static int GetCurrentMemberId() - { - return Member.CurrentMemberId(); - } - - public static Member GetCurrentMember() - { - return Member.GetCurrentMember(); - } - - public static int Login(string login, string password) - { - Member m = Member.GetMemberFromLoginNameAndPassword(login, password); - var id = GetCurrentMemberId(); - if (id == 0) - { - if (m == null) - { - return 0; - } - else - { - Member.AddMemberToCache(m); - return m.Id; - } - } - else - { - return id; - } - } - - public static int Logout() - { - var currentId = GetCurrentMemberId(); - if (currentId > 0) - { - Member.RemoveMemberFromCache(currentId); - Member.ClearMemberFromClient(currentId); - return currentId; - } - else - { - return 0; - } - } - - public static XPathNodeIterator GetCurrentMemberAsXml() - { - var m = GetCurrentMember(); - if (m != null) - { - XmlDocument doc = new XmlDocument(); - doc.LoadXml(m.ToXml(doc, false).OuterXml); - XPathNavigator nav = doc.CreateNavigator(); - return nav.Select("/node"); - } - else - { - return null; - } - } - - public static string SetProperty(string alias, object value) - { - string ret = "False"; - - try - { - var m = GetCurrentMember(); - - if (m != null) - { - var prop = m.getProperty(alias); - - if (m.ContentType != null && MemberType.GetByAlias(m.ContentType.Alias).MemberCanEdit(prop.PropertyType)) - { - - if (prop.PropertyType.ValidationRegExp.Trim() != "") - { - Regex regex = new Regex(m.getProperty(alias).PropertyType.ValidationRegExp); - - if (regex.IsMatch(value.ToString())) - { - prop.Value = value; - ret = "True"; - m.Save(); - } - } - else - { - prop.Value = value; - ret = "True"; - m.Save(); - } - - } - } - } - catch (Exception e) - { - ret = e.Message; - } - - return ret; - } - } -} diff --git a/src/Umbraco.Web/BaseRest/RestExtensionAttribute.cs b/src/Umbraco.Web/BaseRest/RestExtensionAttribute.cs deleted file mode 100644 index 821fc40129..0000000000 --- a/src/Umbraco.Web/BaseRest/RestExtensionAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Web.BaseRest -{ - [Obsolete("Umbraco /base is obsoleted, use WebApi (UmbracoApiController) instead for all REST based logic")] - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public sealed class RestExtensionAttribute : Attribute - { - public string Alias { get; private set; } - - public RestExtensionAttribute(string alias) - { - this.Alias = alias; - } - } -} diff --git a/src/Umbraco.Web/BaseRest/RestExtensionMethodAttribute.cs b/src/Umbraco.Web/BaseRest/RestExtensionMethodAttribute.cs deleted file mode 100644 index bd994439dd..0000000000 --- a/src/Umbraco.Web/BaseRest/RestExtensionMethodAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Web.BaseRest -{ - [Obsolete("Umbraco /base is obsoleted, use WebApi (UmbracoApiController) instead for all REST based logic")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class RestExtensionMethodAttribute : Attribute - { - public bool AllowAll { get; set; } - public string AllowGroup { get; set; } - public string AllowType { get; set; } - public string AllowMember { get; set; } - public bool ReturnXml { get; set; } - - public RestExtensionMethodAttribute() - { - this.AllowAll = true; - this.ReturnXml = true; - } - } -} diff --git a/src/Umbraco.Web/BaseRest/RestExtensionMethodInfo.cs b/src/Umbraco.Web/BaseRest/RestExtensionMethodInfo.cs deleted file mode 100644 index 2ee974a0fe..0000000000 --- a/src/Umbraco.Web/BaseRest/RestExtensionMethodInfo.cs +++ /dev/null @@ -1,334 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Xml; -using System.IO; - -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.BaseRest; -using Umbraco.Core.IO; -using umbraco.cms.businesslogic.member; - -namespace Umbraco.Web.BaseRest -{ - [Obsolete("Umbraco /base is obsoleted, use WebApi (UmbracoApiController) instead for all REST based logic")] - class RestExtensionMethodInfo - { - #region Utilities - - static readonly char[] Split = new[] { ',' }; - - static string[] SplitString(string s) - { - return string.IsNullOrWhiteSpace(s) - ? new string[] { } - : s.ToLower().Split(Split, StringSplitOptions.RemoveEmptyEntries); - } - - static string GetAttribute(XmlNode node, string name) - { - if (node == null) - throw new ArgumentNullException("node"); - var attributes = node.Attributes; - if (attributes == null) - throw new ArgumentException(@"Node has no Attributes collection.", "node"); - var attribute = attributes[name]; - return attribute == null ? null : attribute.Value; - } - - #endregion - - private RestExtensionMethodInfo() - { - Exists = false; - } - - private RestExtensionMethodInfo(bool allowAll, string allowGroup, string allowType, string allowMember, bool returnXml, MethodInfo method) - { - Exists = true; - _allowAll = allowAll; - _allowGroups = SplitString(allowGroup); - _allowTypes = SplitString(allowType); - _allowMembers = SplitString(allowMember); - ReturnXml = returnXml; - _method = method; - } - - static readonly RestExtensionMethodInfo MissingMethod = new RestExtensionMethodInfo(); - static readonly Dictionary Cache = new Dictionary(); - - readonly bool _allowAll; - readonly string[] _allowGroups; - readonly string[] _allowTypes; - readonly string[] _allowMembers; - readonly MethodInfo _method; - - public bool Exists { get; private set; } - public bool ReturnXml { get; private set; } - - #region Discovery - - // gets a RestExtensionMethodInfo matching extensionAlias and methodName - // by looking everywhere (configuration, attributes, legacy attributes) - // returns MissingMethod (ie .Exists == false) if not found - // - public static RestExtensionMethodInfo GetMethod(string extensionAlias, string methodName, int paramsCount) - { - // note - legacy does not support paramsCount - - return GetFromConfiguration(extensionAlias, methodName, paramsCount) - ?? GetFromAttribute(extensionAlias, methodName, paramsCount) - ?? MissingMethod; - } - - // gets a RestExtensionMethodInfo matching extensionAlias and methodName - // by looking at the configuration file - // returns null if not found - // - static RestExtensionMethodInfo GetFromConfiguration(string extensionAlias, string methodName, int paramsCount) - { - var config = UmbracoConfig.For.BaseRestExtensions(); - - var configExtension = config.Items[extensionAlias]; - if (configExtension == null) - return null; // does not exist - - var configMethod = configExtension[methodName]; - if (configMethod == null) - return null; // does not exist - - MethodInfo method = null; - try - { - var parts = configExtension.Type.Split(','); - if (parts.Length > 2) - throw new Exception(string.Format("Failed to load extension '{0}', invalid type.", configExtension.Type)); - - var assembly = parts.Length == 1 ? Assembly.GetExecutingAssembly() : Assembly.Load(parts[1]); - var type = assembly.GetType(parts[0]); - - if (type == null) - throw new Exception(string.Format("Could not get type \"{0}\".", parts[0])); - - var methods = type.GetMethods() - .Where(m => m.Name == methodName) - .Where(m => m.GetParameters().Count() == paramsCount) - .ToArray(); - - if (methods.Length > 1) - throw new Exception(string.Format("Method \"{0}\" has many overloads with same number of parameters.", methodName)); - - if (methods.Length > 0) - { - method = methods[0]; - if (!method.IsPublic || !method.IsStatic) - throw new Exception(string.Format("Method \"{0}\" has to be public and static.", methodName)); - } - } - catch (Exception e) - { - throw new Exception(string.Format("Failed to load extension '{0}', see inner exception.", configExtension.Type), e); - } - - if (method == null) - return null; // does not exist - - var info = new RestExtensionMethodInfo(configMethod.AllowAll, - configMethod.AllowGroup, configMethod.AllowType, configMethod.AllowMember, - configMethod.ReturnXml, - method); - - return info; - } - - // gets a RestExtensionMethodInfo matching extensionAlias and methodName - // by looking for the attributes - // returns null if not found - // - static RestExtensionMethodInfo GetFromAttribute(string extensionAlias, string methodName, int paramsCount) - { - // here we can cache because any change would trigger an app restart - - var cacheKey = string.Format("{0}.{1}[{2}]", extensionAlias, methodName, paramsCount); - lock (Cache) - { - // if it's in the cache, return - if (Cache.ContainsKey(cacheKey)) - return Cache[cacheKey]; - } - - // find an extension with that alias, then find a method with that name, - // which has been properly marked with the attribute, and use the attribute - // properties to setup a RestExtensionMethodInfo - // - // note: the extension may be implemented by more than one class - - var extensions = PluginManager.Current.ResolveRestExtensions() - .Where(type => type.GetCustomAttribute(false).Alias == extensionAlias); - - RestExtensionMethodInfo info = null; - - foreach (var extension in extensions) // foreach classes with extension alias - { - var methods = extension.GetMethods() - .Where(m => m.Name == methodName) - .Where(m => m.GetParameters().Count() == paramsCount) - .ToArray(); - - if (methods.Length == 0) continue; // not implementing the method = ignore - - if (methods.Length > 1) - throw new Exception(string.Format("Method \"{0}\" has many overloads with same number of parameters.", methodName)); - - var method = methods[0]; - if (!method.IsPublic || !method.IsStatic) - throw new Exception(string.Format("Method \"{0}\" has to be public and static.", methodName)); - - var attribute = method.GetCustomAttributes(typeof(RestExtensionMethodAttribute), false).Cast().SingleOrDefault(); - if (attribute == null) continue; // method has not attribute = ignore - - // got it! - info = new RestExtensionMethodInfo(attribute.AllowAll, - attribute.AllowGroup, attribute.AllowType, attribute.AllowMember, - attribute.ReturnXml, - method); - - // cache - lock (Cache) - { - Cache[cacheKey] = info; - } - - // got it, no need to look any further - break; - } - - return info; - } - - #endregion - - #region Invoke - - public bool CanBeInvokedByCurrentMember - { - get - { - if (_allowAll) - return true; - - var member = Member.GetCurrentMember(); - - if (member == null) - return false; - - var allowed = false; - - if (_allowGroups.Length > 0) - { - // note - assuming these are equivalent - //var groups = member.Groups.Values.Cast().Select(group => group.Text); - var groups = System.Web.Security.Roles.GetRolesForUser(member.LoginName); - allowed = groups.Select(s => s.ToLower()).Intersect(_allowGroups).Any(); - } - - if (!allowed && _allowTypes.Length > 0) - { - allowed = _allowTypes.Contains(member.ContentType.Alias); - } - - if (!allowed && _allowMembers.Length > 0) - { - allowed = _allowMembers.Contains(member.Id.ToString(CultureInfo.InvariantCulture)); - } - - return allowed; - } - } - - public string Invoke(string[] parameters) - { - try - { - if (!_method.IsPublic || !_method.IsStatic) - { - // ensure that method is static public - return "Method has to be public and static"; - } - - // ensure we have the right number of parameters - if (_method.GetParameters().Length != parameters.Length) - { - return "Not Enough parameters in url"; - } - - // invoke - - object response; - - if (_method.GetParameters().Length == 0) - { - response = _method.Invoke(null, null); // invoke with null as parameters as there are none - } - else - { - var methodParams = new object[parameters.Length]; - - var i = 0; - - foreach (var pInfo in _method.GetParameters()) - { - var myType = Type.GetType(pInfo.ParameterType.ToString()); - if (myType == null) throw new Exception("Failed to get type."); - methodParams[(i)] = Convert.ChangeType(parameters[i], myType); - i++; - } - - response = _method.Invoke(null, methodParams); - } - - // this is legacy and could probably be improved - if (response != null) - { - switch (_method.ReturnType.ToString()) - { - case "System.Xml.XPath.XPathNodeIterator": - return ((System.Xml.XPath.XPathNodeIterator)response).Current.OuterXml; - case "System.Xml.Linq.XDocument": - return response.ToString(); - case "System.Xml.XmlDocument": - var xmlDoc = (XmlDocument)response; - var sw = new StringWriter(); - var xw = new XmlTextWriter(sw); - xmlDoc.WriteTo(xw); - return sw.ToString(); - default: - var strResponse = response.ToString(); - - if (ReturnXml) - { - // do a quick "is this html?" check... if it is add CDATA... - if (strResponse.Contains("<") || strResponse.Contains(">")) - strResponse = ""; - return "" + strResponse + ""; - } - - return strResponse; - } - } - - return ReturnXml ? "Null value returned" : string.Empty; - } - catch (Exception ex) - { - //Overall exception handling... - return ""; - } - } - - #endregion - } -} diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index 973ba341f2..7659ea95d1 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -1,15 +1,17 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Web; using Newtonsoft.Json; -using umbraco.interfaces; using Umbraco.Core; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; using Umbraco.Core.Sync; using Umbraco.Web.Routing; using Umbraco.Core.Logging; -using Umbraco.Web.Scheduling; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Scoping; namespace Umbraco.Web { @@ -21,34 +23,16 @@ namespace Umbraco.Web /// public class BatchedDatabaseServerMessenger : DatabaseServerMessenger { - public BatchedDatabaseServerMessenger(ApplicationContext appContext, bool enableDistCalls, DatabaseServerMessengerOptions options) - : base(appContext, enableDistCalls, options) + private readonly IUmbracoDatabaseFactory _databaseFactory; + + public BatchedDatabaseServerMessenger( + IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, ProfilingLogger proflog, IGlobalSettings globalSettings, + bool enableDistCalls, DatabaseServerMessengerOptions options) + : base(runtime, scopeProvider, sqlContext, proflog, globalSettings, enableDistCalls, options) { - Scheduler.Initializing += Scheduler_Initializing; + _databaseFactory = databaseFactory; } - /// - /// Occurs when the scheduler initializes all scheduling activity when the app is ready - /// - /// - /// - private void Scheduler_Initializing(object sender, List e) - { - //if the current resolver is 'this' then we will start the scheduling - var isMessenger = ServerMessengerResolver.HasCurrent && ReferenceEquals(ServerMessengerResolver.Current.Messenger, this); - - if (isMessenger) - { - //start the background task runner for processing instructions - const int delayMilliseconds = 60000; - var instructionProcessingRunner = new BackgroundTaskRunner("InstructionProcessing", ApplicationContext.ProfilingLogger.Logger); - var instructionProcessingTask = new InstructionProcessing(instructionProcessingRunner, this, delayMilliseconds, Options.ThrottleSeconds * 1000); - instructionProcessingRunner.TryAdd(instructionProcessingTask); - e.Add(instructionProcessingTask); - } - } - - // invoked by BatchedDatabaseServerMessengerStartup which is an ApplicationEventHandler // with default "ShouldExecute", so that method will run if app IsConfigured and database // context IsDatabaseConfigured - we still want to check CanConnect though to be safe @@ -56,9 +40,9 @@ namespace Umbraco.Web { UmbracoModule.EndRequest += UmbracoModule_EndRequest; - if (ApplicationContext.DatabaseContext.CanConnect == false) + if (_databaseFactory.CanConnect == false) { - ApplicationContext.ProfilingLogger.Logger.Warn( + Logger.Warn( "Cannot connect to the database, distributed calls will not be enabled for this server."); } else @@ -67,34 +51,6 @@ namespace Umbraco.Web } } - /// - /// This will process cache instructions on a background thread and will run every 5 seconds (or whatever is defined in the ) - /// - private class InstructionProcessing : RecurringTaskBase - { - private readonly DatabaseServerMessenger _messenger; - - public InstructionProcessing(IBackgroundTaskRunner runner, - DatabaseServerMessenger messenger, - int delayMilliseconds, int periodMilliseconds) - : base(runner, delayMilliseconds, periodMilliseconds) - { - _messenger = messenger; - } - - public override bool PerformRun() - { - _messenger.Sync(); - //return true to repeat - return true; - } - - public override bool IsAsync - { - get { return false; } - } - } - private void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e) { // will clear the batch - will remain in HttpContext though - that's ok @@ -103,11 +59,11 @@ namespace Umbraco.Web protected override void DeliverRemote(IEnumerable servers, ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) { - var idsA = ids == null ? null : ids.ToArray(); + var idsA = ids?.ToArray(); Type arrayType; if (GetArrayType(idsA, out arrayType) == false) - throw new ArgumentException("All items must be of the same type, either int or Guid.", "ids"); + throw new ArgumentException("All items must be of the same type, either int or Guid.", nameof(ids)); BatchMessage(servers, refresher, messageType, idsA, arrayType, json); } @@ -125,7 +81,7 @@ namespace Umbraco.Web { WriteInstructions(instructionsBatch); } - + } private void WriteInstructions(IEnumerable instructions) @@ -138,7 +94,11 @@ namespace Umbraco.Web InstructionCount = instructions.Sum(x => x.JsonIdCount) }; - ApplicationContext.DatabaseContext.Database.Insert(dto); + using (var scope = ScopeProvider.CreateScope()) + { + scope.Database.Insert(dto); + scope.Complete(); + } } protected ICollection GetBatch(bool create) @@ -148,7 +108,7 @@ namespace Umbraco.Web // can get the http context from it var httpContext = (UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext) // if this is null, it could be that an async thread is calling this method that we weren't aware of and the UmbracoContext - // wasn't ensured at the beginning of the thread. We can try to see if the HttpContext.Current is available which might be + // wasn't ensured at the beginning of the thread. We can try to see if the HttpContext.Current is available which might be // the case if the asp.net synchronization context has kicked in ?? (HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current)); @@ -188,7 +148,7 @@ namespace Umbraco.Web { batch.Add(new RefreshInstructionEnvelope(servers, refresher, instructions)); } - - } + + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessengerStartup.cs b/src/Umbraco.Web/BatchedDatabaseServerMessengerStartup.cs deleted file mode 100644 index a5b88a08ec..0000000000 --- a/src/Umbraco.Web/BatchedDatabaseServerMessengerStartup.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Sync; - -namespace Umbraco.Web -{ - /// - /// Used to boot up the server messenger once the application succesfully starts - /// - internal class BatchedDatabaseServerMessengerStartup : ApplicationEventHandler - { - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - var messenger = ServerMessengerResolver.HasCurrent - ? ServerMessengerResolver.Current.Messenger as BatchedDatabaseServerMessenger - : null; - - if (messenger != null) - messenger.Startup(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs b/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs index 805c2393a8..66a850e5c5 100644 --- a/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs +++ b/src/Umbraco.Web/BatchedWebServiceServerMessenger.cs @@ -20,13 +20,13 @@ namespace Umbraco.Web UmbracoModule.EndRequest += UmbracoModule_EndRequest; } - internal BatchedWebServiceServerMessenger(string login, string password) + internal BatchedWebServiceServerMessenger(string login, string password) : base(login, password) { UmbracoModule.EndRequest += UmbracoModule_EndRequest; } - internal BatchedWebServiceServerMessenger(string login, string password, bool useDistributedCalls) + internal BatchedWebServiceServerMessenger(string login, string password, bool useDistributedCalls) : base(login, password, useDistributedCalls) { UmbracoModule.EndRequest += UmbracoModule_EndRequest; @@ -45,7 +45,7 @@ namespace Umbraco.Web // can get the http context from it var httpContext = (UmbracoContext.Current == null ? null : UmbracoContext.Current.HttpContext) //if this is null, it could be that an async thread is calling this method that we weren't aware of and the UmbracoContext - // wasn't ensured at the beginning of the thread. We can try to see if the HttpContext.Current is available which might be + // wasn't ensured at the beginning of the thread. We can try to see if the HttpContext.Current is available which might be // the case if the asp.net synchronization context has kicked in ?? (HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current)); @@ -65,7 +65,7 @@ namespace Umbraco.Web return batch; } - void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e) + private void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e) { FlushBatch(); } diff --git a/src/Umbraco.Web/Cache/ApplicationCacheRefresher.cs b/src/Umbraco.Web/Cache/ApplicationCacheRefresher.cs index 5c1ec9e1d4..b3fc1bc9f6 100644 --- a/src/Umbraco.Web/Cache/ApplicationCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ApplicationCacheRefresher.cs @@ -1,32 +1,31 @@ using System; -using Umbraco.Core; using Umbraco.Core.Cache; namespace Umbraco.Web.Cache { - /// - /// Handles Application cache invalidation/refreshing - /// public sealed class ApplicationCacheRefresher : CacheRefresherBase { - protected override ApplicationCacheRefresher Instance - { - get { return this; } - } + public ApplicationCacheRefresher(CacheHelper cacheHelper) + : base(cacheHelper) + { } - public override Guid UniqueIdentifier - { - get { return Guid.Parse(DistributedCache.ApplicationCacheRefresherId); } - } + #region Define - public override string Name - { - get { return "Applications cache refresher"; } - } + protected override ApplicationCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("B15F34A1-BC1D-4F8B-8369-3222728AB4C8"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Application Cache Refresher"; + + #endregion + + #region Refresher public override void RefreshAll() { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); + CacheHelper.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); base.RefreshAll(); } @@ -38,9 +37,10 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); + CacheHelper.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); base.Remove(id); } + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/ApplicationTreeCacheRefresher.cs b/src/Umbraco.Web/Cache/ApplicationTreeCacheRefresher.cs index 2f1ab4b891..0346e6bd3e 100644 --- a/src/Umbraco.Web/Cache/ApplicationTreeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ApplicationTreeCacheRefresher.cs @@ -1,32 +1,31 @@ using System; -using Umbraco.Core; using Umbraco.Core.Cache; namespace Umbraco.Web.Cache { - /// - /// Handles Application tree cache invalidation/refreshing - /// public sealed class ApplicationTreeCacheRefresher : CacheRefresherBase { - protected override ApplicationTreeCacheRefresher Instance - { - get { return this; } - } + public ApplicationTreeCacheRefresher(CacheHelper cacheHelper) + : base(cacheHelper) + { } - public override Guid UniqueIdentifier - { - get { return Guid.Parse(DistributedCache.ApplicationTreeCacheRefresherId); } - } + #region Define - public override string Name - { - get { return "Applications tree cache refresher"; } - } + protected override ApplicationTreeCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("0AC6C028-9860-4EA4-958D-14D39F45886E"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Application Tree Cache Refresher"; + + #endregion + + #region Refresher public override void RefreshAll() { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); + CacheHelper.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); base.RefreshAll(); } @@ -38,9 +37,10 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); + CacheHelper.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); base.Remove(id); } + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/CacheRefresherComponent.cs b/src/Umbraco.Web/Cache/CacheRefresherComponent.cs new file mode 100644 index 0000000000..15030db207 --- /dev/null +++ b/src/Umbraco.Web/Cache/CacheRefresherComponent.cs @@ -0,0 +1,611 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Services; +using System.Linq; +using System.Reflection; +using System.Web; +using System.Web.Hosting; +using Umbraco.Core.Components; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Services.Changes; +using Umbraco.Core.Services.Implement; +using Umbraco.Web.Composing; +using Umbraco.Web.Security; +using Umbraco.Web.Services; +using Content = Umbraco.Core.Models.Content; +using ApplicationTree = Umbraco.Core.Models.ApplicationTree; + +namespace Umbraco.Web.Cache +{ + /// + /// Installs listeners on service events in order to refresh our caches. + /// + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + [RequiredComponent(typeof(IUmbracoCoreComponent))] // runs before every other IUmbracoCoreComponent! + public class CacheRefresherComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + private static readonly ConcurrentDictionary FoundHandlers = new ConcurrentDictionary(); + private DistributedCache _distributedCache; + private List _unbinders; + + public CacheRefresherComponent() + { } + + // for tests + public CacheRefresherComponent(bool supportUnbinding) + { + if (supportUnbinding) + _unbinders = new List(); + } + + public void Initialize(DistributedCache distributedCache) + { + Current.Logger.Info("Initializing Umbraco internal event handlers for cache refreshing."); + + _distributedCache = distributedCache; + + // bind to application tree events + Bind(() => ApplicationTreeService.Deleted += ApplicationTreeService_Deleted, + () => ApplicationTreeService.Deleted -= ApplicationTreeService_Deleted); + Bind(() => ApplicationTreeService.Updated += ApplicationTreeService_Updated, + () => ApplicationTreeService.Updated -= ApplicationTreeService_Updated); + Bind(() => ApplicationTreeService.New += ApplicationTreeService_New, + () => ApplicationTreeService.New -= ApplicationTreeService_New); + + // bind to application events + Bind(() => SectionService.Deleted += SectionService_Deleted, + () => SectionService.Deleted -= SectionService_Deleted); + Bind(() => SectionService.New += SectionService_New, + () => SectionService.New -= SectionService_New); + + // bind to user and user group events + Bind(() => UserService.SavedUserGroup += UserService_SavedUserGroup, + () => UserService.SavedUserGroup -= UserService_SavedUserGroup); + Bind(() => UserService.DeletedUserGroup += UserService_DeletedUserGroup, + () => UserService.DeletedUserGroup -= UserService_DeletedUserGroup); + Bind(() => UserService.SavedUser += UserService_SavedUser, + () => UserService.SavedUser -= UserService_SavedUser); + Bind(() => UserService.DeletedUser += UserService_DeletedUser, + () => UserService.DeletedUser -= UserService_DeletedUser); + Bind(() => UserService.UserGroupPermissionsAssigned += UserService_UserGroupPermissionsAssigned, + () => UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned); + + // bind to dictionary events + Bind(() => LocalizationService.DeletedDictionaryItem += LocalizationService_DeletedDictionaryItem, + () => LocalizationService.DeletedDictionaryItem -= LocalizationService_DeletedDictionaryItem); + Bind(() => LocalizationService.SavedDictionaryItem += LocalizationService_SavedDictionaryItem, + () => LocalizationService.SavedDictionaryItem -= LocalizationService_SavedDictionaryItem); + + // bind to data type events + Bind(() => DataTypeService.Deleted += DataTypeService_Deleted, + () => DataTypeService.Deleted -= DataTypeService_Deleted); + Bind(() => DataTypeService.Saved += DataTypeService_Saved, + () => DataTypeService.Saved -= DataTypeService_Saved); + + // bind to stylesheet events + // fixme why not in v8? + Bind(() => FileService.SavedStylesheet += FileService_SavedStylesheet, + () => FileService.SavedStylesheet -= FileService_SavedStylesheet); + Bind(() => FileService.DeletedStylesheet += FileService_DeletedStylesheet, + () => FileService.DeletedStylesheet -= FileService_DeletedStylesheet); + + // bind to domain events + Bind(() => DomainService.Saved += DomainService_Saved, + () => DomainService.Saved -= DomainService_Saved); + Bind(() => DomainService.Deleted += DomainService_Deleted, + () => DomainService.Deleted -= DomainService_Deleted); + + // bind to language events + Bind(() => LocalizationService.SavedLanguage += LocalizationService_SavedLanguage, + () => LocalizationService.SavedLanguage -= LocalizationService_SavedLanguage); + Bind(() => LocalizationService.DeletedLanguage += LocalizationService_DeletedLanguage, + () => LocalizationService.DeletedLanguage -= LocalizationService_DeletedLanguage); + + // bind to content type events + Bind(() => ContentTypeService.Changed += ContentTypeService_Changed, + () => ContentTypeService.Changed -= ContentTypeService_Changed); + Bind(() => MediaTypeService.Changed += MediaTypeService_Changed, + () => MediaTypeService.Changed -= MediaTypeService_Changed); + Bind(() => MemberTypeService.Changed += MemberTypeService_Changed, + () => MemberTypeService.Changed -= MemberTypeService_Changed); + + // bind to template events + Bind(() => FileService.SavedTemplate += FileService_SavedTemplate, + () => FileService.SavedTemplate -= FileService_SavedTemplate); + Bind(() => FileService.DeletedTemplate += FileService_DeletedTemplate, + () => FileService.DeletedTemplate -= FileService_DeletedTemplate); + + // bind to macro events + Bind(() => MacroService.Saved += MacroService_Saved, + () => MacroService.Saved -= MacroService_Saved); + Bind(() => MacroService.Deleted += MacroService_Deleted, + () => MacroService.Deleted -= MacroService_Deleted); + + // bind to member events + Bind(() => MemberService.Saved += MemberService_Saved, + () => MemberService.Saved -= MemberService_Saved); + Bind(() => MemberService.Deleted += MemberService_Deleted, + () => MemberService.Deleted -= MemberService_Deleted); + Bind(() => MemberGroupService.Saved += MemberGroupService_Saved, + () => MemberGroupService.Saved -= MemberGroupService_Saved); + Bind(() => MemberGroupService.Deleted += MemberGroupService_Deleted, + () => MemberGroupService.Deleted -= MemberGroupService_Deleted); + + // bind to media events - handles all media changes + Bind(() => MediaService.TreeChanged += MediaService_Changed, + () => MediaService.TreeChanged -= MediaService_Changed); + + // bind to content events + Bind(() => ContentService.Saved += ContentService_Saved, // needed for permissions + () => ContentService.Saved -= ContentService_Saved); + Bind(() => ContentService.Copied += ContentService_Copied, // needed for permissions + () => ContentService.Copied -= ContentService_Copied); + Bind(() => ContentService.TreeChanged += ContentService_Changed,// handles all content changes + () => ContentService.TreeChanged -= ContentService_Changed); + + // TreeChanged should also deal with this + //Bind(() => ContentService.SavedBlueprint += ContentService_SavedBlueprint, + // () => ContentService.SavedBlueprint -= ContentService_SavedBlueprint); + //Bind(() => ContentService.DeletedBlueprint += ContentService_DeletedBlueprint, + // () => ContentService.DeletedBlueprint -= ContentService_DeletedBlueprint); + + // bind to public access events + Bind(() => PublicAccessService.Saved += PublicAccessService_Saved, + () => PublicAccessService.Saved -= PublicAccessService_Saved); + Bind(() => PublicAccessService.Deleted += PublicAccessService_Deleted, + () => PublicAccessService.Deleted -= PublicAccessService_Deleted); + + // bind to relation type events + Bind(() => RelationService.SavedRelationType += RelationService_SavedRelationType, + () => RelationService.SavedRelationType -= RelationService_SavedRelationType); + Bind(() => RelationService.DeletedRelationType += RelationService_DeletedRelationType, + () => RelationService.DeletedRelationType -= RelationService_DeletedRelationType); + } + + #region Events binding and handling + + private void Bind(Action binder, Action unbinder) + { + // bind now + binder(); + + // and register unbinder for later, if needed + _unbinders?.Add(unbinder); + } + + // for tests + internal void Unbind() + { + if (_unbinders == null) + throw new NotSupportedException(); + foreach (var unbinder in _unbinders) + unbinder(); + _unbinders = null; + } + + internal static MethodInfo FindHandler(IEventDefinition eventDefinition) + { + var name = eventDefinition.Sender.GetType().Name + "_" + eventDefinition.EventName; + + return FoundHandlers.GetOrAdd(name, n => CandidateHandlers.Value.FirstOrDefault(x => x.Name == n)); + } + + private static readonly Lazy CandidateHandlers = new Lazy(() => + { + var underscore = new[] { '_' }; + + return typeof(CacheRefresherComponent) + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Select(x => + { + if (x.Name.Contains("_") == false) return null; + + var parts = x.Name.Split(underscore, StringSplitOptions.RemoveEmptyEntries).Length; + if (parts != 2) return null; + + var parameters = x.GetParameters(); + if (parameters.Length != 2) return null; + if (typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType) == false) return null; + return x; + }) + .WhereNotNull() + .ToArray(); + }); + + internal static void HandleEvents(IEnumerable events) + { + // fixme remove this in v8, this is a backwards compat hack and is needed because when we are using Deploy, the events will be raised on a background + //thread which means that cache refreshers will also execute on a background thread and in many cases developers may be using UmbracoContext.Current in their + //cache refresher handlers, so before we execute all of the events, we'll ensure a context + UmbracoContext tempContext = null; + if (UmbracoContext.Current == null) + { + var httpContext = new HttpContextWrapper(HttpContext.Current ?? new HttpContext(new SimpleWorkerRequest("temp.aspx", "", new StringWriter()))); + tempContext = UmbracoContext.EnsureContext( + Current.UmbracoContextAccessor, + httpContext, + null, + new WebSecurity(httpContext, Current.Services.UserService, UmbracoConfig.For.GlobalSettings()), + UmbracoConfig.For.UmbracoSettings(), + Current.UrlProviders, + UmbracoConfig.For.GlobalSettings(), + true); + } + + try + { + foreach (var e in events) + { + var handler = FindHandler(e); + if (handler == null) continue; + + handler.Invoke(null, new[] { e.Sender, e.Args }); + } + } + finally + { + tempContext?.Dispose(); + } + } + + #endregion + + #region PublicAccessService + + private void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs e) + { + _distributedCache.RefreshPublicAccess(); + } + + private void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs e) + { + _distributedCache.RefreshPublicAccess(); + } + + #endregion + + #region ContentService + + /// + /// Handles cache refreshing for when content is copied + /// + /// + /// + /// + /// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the + /// case then we need to clear all user permissions cache. + /// + private void ContentService_Copied(IContentService sender, CopyEventArgs e) + { } + + /// + /// Handles cache refreshing for when content is saved (not published) + /// + /// + /// + /// + /// When an entity is saved we need to notify other servers about the change in order for the Examine indexes to + /// stay up-to-date for unpublished content. + /// + private void ContentService_Saved(IContentService sender, SaveEventArgs e) + { } + + private void ContentService_Changed(IContentService sender, TreeChange.EventArgs args) + { + _distributedCache.RefreshContentCache(args.Changes.ToArray()); + } + + // fixme our weird events handling wants this for now + private void ContentService_Deleted(IContentService sender, DeleteEventArgs e) { } + private void ContentService_Moved(IContentService sender, MoveEventArgs e) { } + private void ContentService_Trashed(IContentService sender, MoveEventArgs e) { } + private void ContentService_EmptiedRecycleBin(IContentService sender, RecycleBinEventArgs e) { } + private void ContentService_Published(IContentService sender, PublishEventArgs e) { } + private void ContentService_UnPublished(IContentService sender, PublishEventArgs e) { } + + //private void ContentService_SavedBlueprint(IContentService sender, SaveEventArgs e) + //{ + // _distributedCache.RefreshUnpublishedPageCache(e.SavedEntities.ToArray()); + //} + + //private void ContentService_DeletedBlueprint(IContentService sender, DeleteEventArgs e) + //{ + // _distributedCache.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray()); + //} + + #endregion + + #region ApplicationTreeService + + private void ApplicationTreeService_New(ApplicationTree sender, EventArgs e) + { + _distributedCache.RefreshAllApplicationTreeCache(); + } + + private void ApplicationTreeService_Updated(ApplicationTree sender, EventArgs e) + { + _distributedCache.RefreshAllApplicationTreeCache(); + } + + private void ApplicationTreeService_Deleted(ApplicationTree sender, EventArgs e) + { + _distributedCache.RefreshAllApplicationTreeCache(); + } + + #endregion + + #region Application event handlers + + private void SectionService_New(Section sender, EventArgs e) + { + _distributedCache.RefreshAllApplicationCache(); + } + + private void SectionService_Deleted(Section sender, EventArgs e) + { + _distributedCache.RefreshAllApplicationCache(); + } + + #endregion + + #region LocalizationService / Dictionary + + private void LocalizationService_SavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) + { + foreach (var entity in e.SavedEntities) + _distributedCache.RefreshDictionaryCache(entity.Id); + } + + private void LocalizationService_DeletedDictionaryItem(ILocalizationService sender, DeleteEventArgs e) + { + foreach (var entity in e.DeletedEntities) + _distributedCache.RemoveDictionaryCache(entity.Id); + } + + #endregion + + #region DataTypeService + + private void DataTypeService_Saved(IDataTypeService sender, SaveEventArgs e) + { + foreach (var entity in e.SavedEntities) + _distributedCache.RefreshDataTypeCache(entity); + } + + private void DataTypeService_Deleted(IDataTypeService sender, DeleteEventArgs e) + { + foreach (var entity in e.DeletedEntities) + _distributedCache.RemoveDataTypeCache(entity); + } + + #endregion + + #region DomainService + + private void DomainService_Saved(IDomainService sender, SaveEventArgs e) + { + foreach (var entity in e.SavedEntities) + _distributedCache.RefreshDomainCache(entity); + } + + private void DomainService_Deleted(IDomainService sender, DeleteEventArgs e) + { + foreach (var entity in e.DeletedEntities) + _distributedCache.RemoveDomainCache(entity); + } + + #endregion + + #region LocalizationService / Language + + /// + /// Fires when a langauge is deleted + /// + /// + /// + private void LocalizationService_DeletedLanguage(ILocalizationService sender, DeleteEventArgs e) + { + foreach (var entity in e.DeletedEntities) + _distributedCache.RemoveLanguageCache(entity); + } + + /// + /// Fires when a langauge is saved + /// + /// + /// + private void LocalizationService_SavedLanguage(ILocalizationService sender, SaveEventArgs e) + { + foreach (var entity in e.SavedEntities) + _distributedCache.RefreshLanguageCache(entity); + } + + #endregion + + #region Content|Media|MemberTypeService + + private void ContentTypeService_Changed(IContentTypeService sender, ContentTypeChange.EventArgs args) + { + _distributedCache.RefreshContentTypeCache(args.Changes.ToArray()); + } + + private void MediaTypeService_Changed(IMediaTypeService sender, ContentTypeChange.EventArgs args) + { + _distributedCache.RefreshContentTypeCache(args.Changes.ToArray()); + } + + private void MemberTypeService_Changed(IMemberTypeService sender, ContentTypeChange.EventArgs args) + { + _distributedCache.RefreshContentTypeCache(args.Changes.ToArray()); + } + + // fixme our weird events handling wants this for now + private void ContentTypeService_Saved(IContentTypeService sender, SaveEventArgs args) { } + private void MediaTypeService_Saved(IMediaTypeService sender, SaveEventArgs args) { } + private void MemberTypeService_Saved(IMemberTypeService sender, SaveEventArgs args) { } + private void ContentTypeService_Deleted(IContentTypeService sender, DeleteEventArgs args) { } + private void MediaTypeService_Deleted(IMediaTypeService sender, DeleteEventArgs args) { } + private void MemberTypeService_Deleted(IMemberTypeService sender, DeleteEventArgs args) { } + + #endregion + + #region UserService + + static void UserService_UserGroupPermissionsAssigned(IUserService sender, SaveEventArgs e) + { + //TODO: Not sure if we need this yet depends if we start caching permissions + //var groupIds = e.SavedEntities.Select(x => x.UserGroupId).Distinct(); + //foreach (var groupId in groupIds) + //{ + // DistributedCache.Instance.RefreshUserGroupPermissionsCache(groupId); + //} + } + + private void UserService_SavedUser(IUserService sender, SaveEventArgs e) + { + foreach (var entity in e.SavedEntities) + _distributedCache.RefreshUserCache(entity.Id); + } + + private void UserService_DeletedUser(IUserService sender, DeleteEventArgs e) + { + foreach (var entity in e.DeletedEntities) + _distributedCache.RemoveUserCache(entity.Id); + } + + private void UserService_SavedUserGroup(IUserService sender, SaveEventArgs e) + { + foreach (var entity in e.SavedEntities) + _distributedCache.RefreshUserGroupCache(entity.UserGroup.Id); + } + + private void UserService_DeletedUserGroup(IUserService sender, DeleteEventArgs e) + { + foreach (var entity in e.DeletedEntities) + _distributedCache.RemoveUserGroupCache(entity.Id); + } + + #endregion + + #region FileService + + /// + /// Removes cache for template + /// + /// + /// + private void FileService_DeletedTemplate(IFileService sender, DeleteEventArgs e) + { + foreach (var entity in e.DeletedEntities) + _distributedCache.RemoveTemplateCache(entity.Id); + } + + /// + /// Refresh cache for template + /// + /// + /// + private void FileService_SavedTemplate(IFileService sender, SaveEventArgs e) + { + foreach (var entity in e.SavedEntities) + _distributedCache.RefreshTemplateCache(entity.Id); + } + + // fixme our weird events handling wants this for now + private void FileService_DeletedStylesheet(IFileService sender, DeleteEventArgs e) { } + private void FileService_SavedStylesheet(IFileService sender, SaveEventArgs e) { } + + #endregion + + #region MacroService + + private void MacroService_Deleted(IMacroService sender, DeleteEventArgs e) + { + foreach (var entity in e.DeletedEntities) + _distributedCache.RemoveMacroCache(entity); + } + + private void MacroService_Saved(IMacroService sender, SaveEventArgs e) + { + foreach (var entity in e.SavedEntities) + _distributedCache.RefreshMacroCache(entity); + } + + #endregion + + #region MediaService + + private void MediaService_Changed(IMediaService sender, TreeChange.EventArgs args) + { + _distributedCache.RefreshMediaCache(args.Changes.ToArray()); + } + + // fixme our weird events handling wants this for now + private void MediaService_Saved(IMediaService sender, SaveEventArgs e) { } + private void MediaService_Deleted(IMediaService sender, DeleteEventArgs e) { } + private void MediaService_Moved(IMediaService sender, MoveEventArgs e) { } + private void MediaService_Trashed(IMediaService sender, MoveEventArgs e) { } + private void MediaService_EmptiedRecycleBin(IMediaService sender, RecycleBinEventArgs e) { } + + #endregion + + #region MemberService + + private void MemberService_Deleted(IMemberService sender, DeleteEventArgs e) + { + _distributedCache.RemoveMemberCache(e.DeletedEntities.ToArray()); + } + + private void MemberService_Saved(IMemberService sender, SaveEventArgs e) + { + _distributedCache.RefreshMemberCache(e.SavedEntities.ToArray()); + } + + #endregion + + #region MemberGroupService + + private void MemberGroupService_Deleted(IMemberGroupService sender, DeleteEventArgs e) + { + foreach (var m in e.DeletedEntities.ToArray()) + { + _distributedCache.RemoveMemberGroupCache(m.Id); + } + } + + private void MemberGroupService_Saved(IMemberGroupService sender, SaveEventArgs e) + { + foreach (var m in e.SavedEntities.ToArray()) + { + _distributedCache.RemoveMemberGroupCache(m.Id); + } + } + #endregion + + #region RelationType + + private void RelationService_SavedRelationType(IRelationService sender, SaveEventArgs args) + { + var dc = _distributedCache; + foreach (var e in args.SavedEntities) + dc.RefreshRelationTypeCache(e.Id); + } + + private void RelationService_DeletedRelationType(IRelationService sender, DeleteEventArgs args) + { + var dc = _distributedCache; + foreach (var e in args.DeletedEntities) + dc.RemoveRelationTypeCache(e.Id); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs deleted file mode 100644 index b1b013c974..0000000000 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ /dev/null @@ -1,817 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using Umbraco.Core; -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Services; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic; -using System.Linq; -using System.Reflection; -using System.Web; -using System.Web.Hosting; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Publishing; -using Umbraco.Web.Routing; -using Umbraco.Web.Security; -using Content = Umbraco.Core.Models.Content; -using ApplicationTree = Umbraco.Core.Models.ApplicationTree; -using DeleteEventArgs = umbraco.cms.businesslogic.DeleteEventArgs; - -namespace Umbraco.Web.Cache -{ - /// - /// Class which listens to events on business level objects in order to invalidate the cache amongst servers when data changes - /// - [Weight(int.MinValue)] - public class CacheRefresherEventHandler : ApplicationEventHandler - { - public CacheRefresherEventHandler() - { } - - public CacheRefresherEventHandler(bool supportUnbinding) - { - if (supportUnbinding) - _unbinders = new List(); - } - - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - LogHelper.Info("Initializing Umbraco internal event handlers for cache refreshing"); - - // bind to application tree events - Bind(() => ApplicationTreeService.Deleted += ApplicationTreeService_Deleted, - () => ApplicationTreeService.Deleted -= ApplicationTreeService_Deleted); - Bind(() => ApplicationTreeService.Updated += ApplicationTreeService_Updated, - () => ApplicationTreeService.Updated -= ApplicationTreeService_Updated); - Bind(() => ApplicationTreeService.New += ApplicationTreeService_New, - () => ApplicationTreeService.New -= ApplicationTreeService_New); - - // bind to application events - Bind(() => SectionService.Deleted += SectionService_Deleted, - () => SectionService.Deleted -= SectionService_Deleted); - Bind(() => SectionService.New += SectionService_New, - () => SectionService.New -= SectionService_New); - - // bind to user and user / user group events - Bind(() => UserService.SavedUserGroup += UserService_SavedUserGroup, - () => UserService.SavedUserGroup -= UserService_SavedUserGroup); - Bind(() => UserService.DeletedUserGroup += UserService_DeletedUserGroup, - () => UserService.DeletedUserGroup -= UserService_DeletedUserGroup); - Bind(() => UserService.SavedUser += UserService_SavedUser, - () => UserService.SavedUser -= UserService_SavedUser); - Bind(() => UserService.DeletedUser += UserService_DeletedUser, - () => UserService.DeletedUser -= UserService_DeletedUser); - Bind(() => UserService.UserGroupPermissionsAssigned += UserService_UserGroupPermissionsAssigned, - () => UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned); - - // bind to dictionary events - Bind(() => LocalizationService.DeletedDictionaryItem += LocalizationService_DeletedDictionaryItem, - () => LocalizationService.DeletedDictionaryItem -= LocalizationService_DeletedDictionaryItem); - Bind(() => LocalizationService.SavedDictionaryItem += LocalizationService_SavedDictionaryItem, - () => LocalizationService.SavedDictionaryItem -= LocalizationService_SavedDictionaryItem); - - // bind to data type events - Bind(() => DataTypeService.Deleted += DataTypeService_Deleted, - () => DataTypeService.Deleted -= DataTypeService_Deleted); - Bind(() => DataTypeService.Saved += DataTypeService_Saved, - () => DataTypeService.Saved -= DataTypeService_Saved); - - // bind to stylesheet events - Bind(() => FileService.SavedStylesheet += FileService_SavedStylesheet, - () => FileService.SavedStylesheet -= FileService_SavedStylesheet); - Bind(() => FileService.DeletedStylesheet += FileService_DeletedStylesheet, - () => FileService.DeletedStylesheet -= FileService_DeletedStylesheet); - - // bind to domain events - Bind(() => DomainService.Saved += DomainService_Saved, - () => DomainService.Saved -= DomainService_Saved); - Bind(() => DomainService.Deleted += DomainService_Deleted, - () => DomainService.Deleted -= DomainService_Deleted); - - // bind to language events - Bind(() => LocalizationService.SavedLanguage += LocalizationService_SavedLanguage, - () => LocalizationService.SavedLanguage -= LocalizationService_SavedLanguage); - Bind(() => LocalizationService.DeletedLanguage += LocalizationService_DeletedLanguage, - () => LocalizationService.DeletedLanguage -= LocalizationService_DeletedLanguage); - - // bind to content type events - Bind(() => ContentTypeService.SavedContentType += ContentTypeService_SavedContentType, - () => ContentTypeService.SavedContentType -= ContentTypeService_SavedContentType); - Bind(() => ContentTypeService.SavedMediaType += ContentTypeService_SavedMediaType, - () => ContentTypeService.SavedMediaType -= ContentTypeService_SavedMediaType); - Bind(() => ContentTypeService.DeletedContentType += ContentTypeService_DeletedContentType, - () => ContentTypeService.DeletedContentType -= ContentTypeService_DeletedContentType); - Bind(() => ContentTypeService.DeletedMediaType += ContentTypeService_DeletedMediaType, - () => ContentTypeService.DeletedMediaType -= ContentTypeService_DeletedMediaType); - Bind(() => MemberTypeService.Saved += MemberTypeService_Saved, - () => MemberTypeService.Saved -= MemberTypeService_Saved); - Bind(() => MemberTypeService.Deleted += MemberTypeService_Deleted, - () => MemberTypeService.Deleted -= MemberTypeService_Deleted); - - - - // bind to template events - Bind(() => FileService.SavedTemplate += FileService_SavedTemplate, - () => FileService.SavedTemplate -= FileService_SavedTemplate); - Bind(() => FileService.DeletedTemplate += FileService_DeletedTemplate, - () => FileService.DeletedTemplate -= FileService_DeletedTemplate); - - // bind to macro events - Bind(() => MacroService.Saved += MacroService_Saved, - () => MacroService.Saved -= MacroService_Saved); - Bind(() => MacroService.Deleted += MacroService_Deleted, - () => MacroService.Deleted -= MacroService_Deleted); - - // bind to member events - Bind(() => MemberService.Saved += MemberService_Saved, - () => MemberService.Saved -= MemberService_Saved); - Bind(() => MemberService.Deleted += MemberService_Deleted, - () => MemberService.Deleted -= MemberService_Deleted); - Bind(() => MemberGroupService.Saved += MemberGroupService_Saved, - () => MemberGroupService.Saved -= MemberGroupService_Saved); - Bind(() => MemberGroupService.Deleted += MemberGroupService_Deleted, - () => MemberGroupService.Deleted -= MemberGroupService_Deleted); - - // bind to media events - Bind(() => MediaService.Saved += MediaService_Saved, - () => MediaService.Saved -= MediaService_Saved); - Bind(() => MediaService.Deleted += MediaService_Deleted, - () => MediaService.Deleted -= MediaService_Deleted); - Bind(() => MediaService.Moved += MediaService_Moved, - () => MediaService.Moved -= MediaService_Moved); - Bind(() => MediaService.Trashed += MediaService_Trashed, - () => MediaService.Trashed -= MediaService_Trashed); - Bind(() => MediaService.EmptiedRecycleBin += MediaService_EmptiedRecycleBin, - () => MediaService.EmptiedRecycleBin -= MediaService_EmptiedRecycleBin); - - // bind to content events - // this is for unpublished content syncing across servers (primarily for examine) - Bind(() => ContentService.Saved += ContentService_Saved, - () => ContentService.Saved -= ContentService_Saved); - Bind(() => ContentService.Deleted += ContentService_Deleted, - () => ContentService.Deleted -= ContentService_Deleted); - Bind(() => ContentService.Copied += ContentService_Copied, - () => ContentService.Copied -= ContentService_Copied); - // the Move method of the content service fires Saved/Published events during its - // execution so we don't need to listen to moved - this will probably change in due time - //Bind(() => ContentService.Moved += ContentServiceMoved, - // () => ContentService.Moved -= ContentServiceMoved); - Bind(() => ContentService.Trashed += ContentService_Trashed, - () => ContentService.Trashed -= ContentService_Trashed); - Bind(() => ContentService.EmptiedRecycleBin += ContentService_EmptiedRecycleBin, - () => ContentService.EmptiedRecycleBin -= ContentService_EmptiedRecycleBin); - Bind(() => ContentService.Published += ContentService_Published, - () => ContentService.Published -= ContentService_Published); - Bind(() => ContentService.UnPublished += ContentService_UnPublished, - () => ContentService.UnPublished -= ContentService_UnPublished); - - Bind(() => ContentService.SavedBlueprint += ContentService_SavedBlueprint, - () => ContentService.SavedBlueprint -= ContentService_SavedBlueprint); - Bind(() => ContentService.DeletedBlueprint += ContentService_DeletedBlueprint, - () => ContentService.DeletedBlueprint -= ContentService_DeletedBlueprint); - - // bind to public access events - Bind(() => PublicAccessService.Saved += PublicAccessService_Saved, - () => PublicAccessService.Saved -= PublicAccessService_Saved); - Bind(() => PublicAccessService.Deleted += PublicAccessService_Deleted, - () => PublicAccessService.Deleted -= PublicAccessService_Deleted); - - // bind to relation type events - Bind(() => RelationService.SavedRelationType += RelationService_SavedRelationType, - () => RelationService.SavedRelationType -= RelationService_SavedRelationType); - Bind(() => RelationService.DeletedRelationType += RelationService_DeletedRelationType, - () => RelationService.DeletedRelationType -= RelationService_DeletedRelationType); - } - - private List _unbinders; - - private void Bind(Action binder, Action unbinder) - { - // bind now - binder(); - - // abd register unbinder for later, if needed - if (_unbinders == null) return; - _unbinders.Add(unbinder); - } - - // for tests - internal void Unbind() - { - if (_unbinders == null) - throw new NotSupportedException(); - foreach (var unbinder in _unbinders) - unbinder(); - _unbinders = null; - } - - #region Publishing - - // IPublishingStrategy (obsolete) events are proxied into ContentService, which works fine when - // events are actually raised, but not when they are handled by HandleEvents, so we have to have - // these proxy methods that are *not* registered against any event *but* used by HandleEvents. - - // ReSharper disable once UnusedMember.Local - static void PublishingStrategy_UnPublished(IPublishingStrategy sender, PublishEventArgs e) - { - ContentService_UnPublished(sender, e); - } - - // ReSharper disable once UnusedMember.Local - static void PublishingStrategy_Published(IPublishingStrategy sender, PublishEventArgs e) - { - ContentService_Published(sender, e); - } - - static void ContentService_UnPublished(IPublishingStrategy sender, PublishEventArgs e) - { - if (e.PublishedEntities.Any()) - { - if (e.PublishedEntities.Count() > 1) - { - foreach (var c in e.PublishedEntities) - { - UnPublishSingle(c); - } - } - else - { - var content = e.PublishedEntities.FirstOrDefault(); - UnPublishSingle(content); - } - } - } - - /// - /// Refreshes the xml cache for a single node by removing it - /// - private static void UnPublishSingle(IContent content) - { - DistributedCache.Instance.RemovePageCache(content); - } - - static void ContentService_Published(IPublishingStrategy sender, PublishEventArgs e) - { - if (e.PublishedEntities.Any()) - { - if (e.IsAllRepublished) - { - UpdateEntireCache(); - return; - } - - if (e.PublishedEntities.Count() > 1) - { - UpdateMultipleContentCache(e.PublishedEntities); - } - else - { - var content = e.PublishedEntities.FirstOrDefault(); - UpdateSingleContentCache(content); - } - } - } - - /// - /// Refreshes the xml cache for all nodes - /// - private static void UpdateEntireCache() - { - DistributedCache.Instance.RefreshAllPageCache(); - } - - /// - /// Refreshes the xml cache for nodes in list - /// - private static void UpdateMultipleContentCache(IEnumerable content) - { - DistributedCache.Instance.RefreshPageCache(content.ToArray()); - } - - /// - /// Refreshes the xml cache for a single node - /// - private static void UpdateSingleContentCache(IContent content) - { - DistributedCache.Instance.RefreshPageCache(content); - } - - #endregion - - #region Public access event handlers - - static void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs e) - { - DistributedCache.Instance.RefreshPublicAccess(); - } - - static void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs e) - { - DistributedCache.Instance.RefreshPublicAccess(); - } - - #endregion - - #region Content service event handlers - - static void ContentService_EmptiedRecycleBin(IContentService sender, RecycleBinEventArgs e) - { - if (e.RecycleBinEmptiedSuccessfully && e.IsContentRecycleBin) - { - DistributedCache.Instance.RemoveUnpublishedCachePermanently(e.Ids.ToArray()); - } - } - - /// - /// Handles cache refreshing for when content is trashed - /// - /// - /// - /// - /// This is for the unpublished page refresher - the entity will be unpublished before being moved to the trash - /// and the unpublished event will take care of remove it from any published caches - /// - static void ContentService_Trashed(IContentService sender, MoveEventArgs e) - { - DistributedCache.Instance.RefreshUnpublishedPageCache( - e.MoveInfoCollection.Select(x => x.Entity).ToArray()); - } - - /// - /// Handles cache refreshing for when content is copied - /// - /// - /// - /// - /// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the - /// case then we need to clear all user permissions cache. - /// - static void ContentService_Copied(IContentService sender, CopyEventArgs e) - { - //run the un-published cache refresher since copied content is not published - DistributedCache.Instance.RefreshUnpublishedPageCache(e.Copy); - } - - /// - /// Handles cache refreshing for when content is deleted (not unpublished) - /// - /// - /// - static void ContentService_Deleted(IContentService sender, DeleteEventArgs e) - { - DistributedCache.Instance.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray()); - } - - static void ContentService_DeletedBlueprint(IContentService sender, DeleteEventArgs e) - { - DistributedCache.Instance.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray()); - } - - /// - /// Handles cache refreshing for when content is saved (not published) - /// - /// - /// - /// - /// When an entity is saved we need to notify other servers about the change in order for the Examine indexes to - /// stay up-to-date for unpublished content. - /// - static void ContentService_Saved(IContentService sender, SaveEventArgs e) - { - //filter out the entities that have only been saved (not newly published) since - // newly published ones will be synced with the published page cache refresher - var unpublished = e.SavedEntities.Where(x => x.JustPublished() == false); - //run the un-published cache refresher - DistributedCache.Instance.RefreshUnpublishedPageCache(unpublished.ToArray()); - } - - static void ContentService_SavedBlueprint(IContentService sender, SaveEventArgs e) - { - DistributedCache.Instance.RefreshUnpublishedPageCache(e.SavedEntities.ToArray()); - } - - #endregion - - #region ApplicationTree event handlers - static void ApplicationTreeService_New(ApplicationTree sender, EventArgs e) - { - DistributedCache.Instance.RefreshAllApplicationTreeCache(); - } - - static void ApplicationTreeService_Updated(ApplicationTree sender, EventArgs e) - { - DistributedCache.Instance.RefreshAllApplicationTreeCache(); - } - - static void ApplicationTreeService_Deleted(ApplicationTree sender, EventArgs e) - { - DistributedCache.Instance.RefreshAllApplicationTreeCache(); - } - #endregion - - #region Application event handlers - static void SectionService_New(Section sender, EventArgs e) - { - DistributedCache.Instance.RefreshAllApplicationCache(); - } - - static void SectionService_Deleted(Section sender, EventArgs e) - { - DistributedCache.Instance.RefreshAllApplicationCache(); - } - #endregion - - #region Dictionary event handlers - - static void LocalizationService_SavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDictionaryCache(x.Id)); - } - - static void LocalizationService_DeletedDictionaryItem(ILocalizationService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDictionaryCache(x.Id)); - } - - #endregion - - #region DataType event handlers - static void DataTypeService_Saved(IDataTypeService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDataTypeCache(x)); - } - - static void DataTypeService_Deleted(IDataTypeService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDataTypeCache(x)); - } - - - #endregion - - #region Stylesheet and stylesheet property event handlers - - static void FileService_DeletedStylesheet(IFileService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveStylesheetCache(x)); - } - - static void FileService_SavedStylesheet(IFileService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshStylesheetCache(x)); - } - - #endregion - - #region Domain event handlers - - static void DomainService_Saved(IDomainService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDomainCache(x)); - } - - static void DomainService_Deleted(IDomainService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDomainCache(x)); - } - - #endregion - - #region Language event handlers - /// - /// Fires when a langauge is deleted - /// - /// - /// - static void LocalizationService_DeletedLanguage(ILocalizationService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveLanguageCache(x)); - } - - /// - /// Fires when a langauge is saved - /// - /// - /// - static void LocalizationService_SavedLanguage(ILocalizationService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshLanguageCache(x)); - } - - #endregion - - #region Content/media/member Type event handlers - /// - /// Fires when a media type is deleted - /// - /// - /// - static void ContentTypeService_DeletedMediaType(IContentTypeService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveMediaTypeCache(x)); - } - - /// - /// Fires when a content type is deleted - /// - /// - /// - static void ContentTypeService_DeletedContentType(IContentTypeService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(contentType => DistributedCache.Instance.RemoveContentTypeCache(contentType)); - } - - /// - /// Fires when a member type is deleted - /// - /// - /// - static void MemberTypeService_Deleted(IMemberTypeService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(contentType => DistributedCache.Instance.RemoveMemberTypeCache(contentType)); - } - - /// - /// Fires when a media type is saved - /// - /// - /// - static void ContentTypeService_SavedMediaType(IContentTypeService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshMediaTypeCache(x)); - } - - /// - /// Fires when a content type is saved - /// - /// - /// - static void ContentTypeService_SavedContentType(IContentTypeService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(contentType => DistributedCache.Instance.RefreshContentTypeCache(contentType)); - } - - /// - /// Fires when a member type is saved - /// - /// - /// - static void MemberTypeService_Saved(IMemberTypeService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshMemberTypeCache(x)); - } - - - #endregion - - #region User/permissions event handlers - - static void UserService_UserGroupPermissionsAssigned(IUserService sender, SaveEventArgs e) - { - //TODO: Not sure if we need this yet depends if we start caching permissions - //var groupIds = e.SavedEntities.Select(x => x.UserGroupId).Distinct(); - //foreach (var groupId in groupIds) - //{ - // DistributedCache.Instance.RefreshUserGroupPermissionsCache(groupId); - //} - } - - static void UserService_SavedUser(IUserService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserCache(x.Id)); - } - - static void UserService_DeletedUser(IUserService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserCache(x.Id)); - } - - static void UserService_SavedUserGroup(IUserService sender, SaveEventArgs e) - { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserGroupCache(x.Id)); - } - - static void UserService_DeletedUserGroup(IUserService sender, DeleteEventArgs e) - { - e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserGroupCache(x.Id)); - } - - - #endregion - - #region Template event handlers - - /// - /// Removes cache for template - /// - /// - /// - static void FileService_DeletedTemplate(IFileService sender, DeleteEventArgs e) - { - foreach (var x in e.DeletedEntities) - { - DistributedCache.Instance.RemoveTemplateCache(x.Id); - } - } - - /// - /// Refresh cache for template - /// - /// - /// - static void FileService_SavedTemplate(IFileService sender, SaveEventArgs e) - { - foreach (var x in e.SavedEntities) - { - DistributedCache.Instance.RefreshTemplateCache(x.Id); - } - } - - #endregion - - #region Macro event handlers - - static void MacroService_Deleted(IMacroService sender, DeleteEventArgs e) - { - foreach (var entity in e.DeletedEntities) - { - DistributedCache.Instance.RemoveMacroCache(entity); - } - } - - static void MacroService_Saved(IMacroService sender, SaveEventArgs e) - { - foreach (var entity in e.SavedEntities) - { - DistributedCache.Instance.RefreshMacroCache(entity); - } - } - - #endregion - - #region Media event handlers - - static void MediaService_EmptiedRecycleBin(IMediaService sender, RecycleBinEventArgs e) - { - if (e.RecycleBinEmptiedSuccessfully && e.IsMediaRecycleBin) - { - DistributedCache.Instance.RemoveMediaCachePermanently(e.Ids.ToArray()); - } - } - - static void MediaService_Trashed(IMediaService sender, MoveEventArgs e) - { - DistributedCache.Instance.RemoveMediaCacheAfterRecycling(e.MoveInfoCollection.ToArray()); - } - - static void MediaService_Moved(IMediaService sender, MoveEventArgs e) - { - DistributedCache.Instance.RefreshMediaCacheAfterMoving(e.MoveInfoCollection.ToArray()); - } - - static void MediaService_Deleted(IMediaService sender, DeleteEventArgs e) - { - DistributedCache.Instance.RemoveMediaCachePermanently(e.DeletedEntities.Select(x => x.Id).ToArray()); - } - - static void MediaService_Saved(IMediaService sender, SaveEventArgs e) - { - DistributedCache.Instance.RefreshMediaCache(e.SavedEntities.ToArray()); - } - #endregion - - #region Member event handlers - - static void MemberService_Deleted(IMemberService sender, DeleteEventArgs e) - { - DistributedCache.Instance.RemoveMemberCache(e.DeletedEntities.ToArray()); - } - - static void MemberService_Saved(IMemberService sender, SaveEventArgs e) - { - DistributedCache.Instance.RefreshMemberCache(e.SavedEntities.ToArray()); - } - - #endregion - - #region Member group event handlers - - static void MemberGroupService_Deleted(IMemberGroupService sender, DeleteEventArgs e) - { - foreach (var m in e.DeletedEntities.ToArray()) - { - DistributedCache.Instance.RemoveMemberGroupCache(m.Id); - } - } - - static void MemberGroupService_Saved(IMemberGroupService sender, SaveEventArgs e) - { - foreach (var m in e.SavedEntities.ToArray()) - { - DistributedCache.Instance.RemoveMemberGroupCache(m.Id); - } - } - #endregion - - #region Relation type event handlers - - static void RelationService_SavedRelationType(IRelationService sender, SaveEventArgs args) - { - var dc = DistributedCache.Instance; - foreach (var e in args.SavedEntities) - dc.RefreshRelationTypeCache(e.Id); - } - - static void RelationService_DeletedRelationType(IRelationService sender, DeleteEventArgs args) - { - var dc = DistributedCache.Instance; - foreach (var e in args.DeletedEntities) - dc.RemoveRelationTypeCache(e.Id); - } - - #endregion - - - /// - /// This will inspect the event metadata and execute it's affiliated handler if one is found - /// - /// - internal static void HandleEvents(IEnumerable events) - { - //TODO: We should remove this in v8, this is a backwards compat hack and is needed because when we are using Deploy, the events will be raised on a background - //thread which means that cache refreshers will also execute on a background thread and in many cases developers may be using UmbracoContext.Current in their - //cache refresher handlers, so before we execute all of the events, we'll ensure a context - UmbracoContext tempContext = null; - if (UmbracoContext.Current == null) - { - var httpContext = new HttpContextWrapper(HttpContext.Current ?? new HttpContext(new SimpleWorkerRequest("temp.aspx", "", new StringWriter()))); - tempContext = UmbracoContext.EnsureContext( - httpContext, - ApplicationContext.Current, - new WebSecurity(httpContext, ApplicationContext.Current), - UmbracoConfig.For.UmbracoSettings(), - UrlProviderResolver.Current.Providers, - true); - } - - try - { - foreach (var e in events) - { - var handler = FindHandler(e); - if (handler == null) continue; - handler.Invoke(null, new[] { e.Sender, e.Args }); - } - } - finally - { - if (tempContext != null) - tempContext.Dispose(); // nulls the ThreadStatic context - } - } - - /// - /// Used to cache all candidate handlers - /// - private static readonly Lazy CandidateHandlers = new Lazy(() => - { - var underscore = new[] { '_' }; - - return typeof (CacheRefresherEventHandler) - .GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - .Select(x => - { - if (x.Name.Contains("_") == false) return null; - - var parts = x.Name.Split(underscore, StringSplitOptions.RemoveEmptyEntries).Length; - if (parts != 2) return null; - - var parameters = x.GetParameters(); - if (parameters.Length != 2) return null; - if (typeof (EventArgs).IsAssignableFrom(parameters[1].ParameterType) == false) return null; - return x; - }) - .WhereNotNull() - .ToArray(); - }); - - /// - /// Used to cache all found event handlers - /// - private static readonly ConcurrentDictionary FoundHandlers = new ConcurrentDictionary(); - - internal static MethodInfo FindHandler(IEventDefinition eventDefinition) - { - var name = eventDefinition.Sender.GetType().Name + "_" + eventDefinition.EventName; - - return FoundHandlers.GetOrAdd(name, n => CandidateHandlers.Value.FirstOrDefault(x => x.Name == n)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs new file mode 100644 index 0000000000..257e0c9731 --- /dev/null +++ b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs @@ -0,0 +1,167 @@ +using System; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; +using Umbraco.Web.Composing; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Web.Cache +{ + public sealed class ContentCacheRefresher : PayloadCacheRefresherBase + { + private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly IdkMap _idkMap; + + public ContentCacheRefresher(CacheHelper cacheHelper, IPublishedSnapshotService publishedSnapshotService, IdkMap idkMap) + : base(cacheHelper) + { + _publishedSnapshotService = publishedSnapshotService; + _idkMap = idkMap; + } + + #region Define + + protected override ContentCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("900A4FBE-DF3C-41E6-BB77-BE896CD158EA"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "ContentCacheRefresher"; + + #endregion + + #region Refresher + + public override void Refresh(JsonPayload[] payloads) + { + var runtimeCache = Current.ApplicationCache.RuntimeCache; + + runtimeCache.ClearCacheObjectTypes(); + + // fixme - this is for entity service, not sure why we do it here and nowhere else? + runtimeCache.ClearCacheByKeySearch(CacheKeys.IdToKeyCacheKey); + runtimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey); + + foreach (var payload in payloads) + { + // remove that one + runtimeCache.ClearCacheItem(RepositoryCacheKeys.GetKey(payload.Id)); + + _idkMap.ClearCache(payload.Id); + + // remove those that are in the branch + if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove)) + { + var pathid = "," + payload.Id + ","; + runtimeCache.ClearCacheObjectTypes((k, v) => v.Path.Contains(pathid)); + } + } + + // note: must do what's above FIRST else the repositories still have the old cached + // content and when the PublishedCachesService is notified of changes it does not see + // the new content... + + // fixme - what about this? + // should rename it, and then, this is only for Deploy, and then, ??? + //if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) + // ... + + _publishedSnapshotService.Notify(payloads, out _, out var publishedChanged); + + if (payloads.Any(x => x.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) || publishedChanged) + { + // when a public version changes + Current.ApplicationCache.ClearPartialViewCache(); + MacroCacheRefresher.ClearMacroContentCache(CacheHelper); // just the content + ClearXsltCache(); + + Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.IdToKeyCacheKey); + Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey); + } + + base.Refresh(payloads); + } + + // these events should never trigger + // everything should be PAYLOAD/JSON + + public override void RefreshAll() + { + throw new NotSupportedException(); + } + + public override void Refresh(int id) + { + throw new NotSupportedException(); + } + + public override void Refresh(Guid id) + { + throw new NotSupportedException(); + } + + public override void Remove(int id) + { + throw new NotSupportedException(); + } + + #endregion + + #region Json + + public class JsonPayload + { + public JsonPayload(int id, TreeChangeTypes changeTypes) + { + Id = id; + ChangeTypes = changeTypes; + } + + public int Id { get; } + + public TreeChangeTypes ChangeTypes { get; } + } + + #endregion + + #region Events + + #endregion + + #region Indirect + + public static void RefreshContentTypes(CacheHelper cacheHelper) + { + // we could try to have a mechanism to notify the PublishedCachesService + // and figure out whether published items were modified or not... keep it + // simple for now, just clear the whole thing + + cacheHelper.ClearPartialViewCache(); + MacroCacheRefresher.ClearMacroContentCache(cacheHelper); // just the content + ClearXsltCache(); + + cacheHelper.IsolatedRuntimeCache.ClearCache(); + cacheHelper.IsolatedRuntimeCache.ClearCache(); + } + + #endregion + + #region Helpers + + private static void ClearXsltCache() + { + // todo: document where this is coming from + if (UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration <= 0) return; + Current.ApplicationCache.ClearCacheObjectTypes("MS.Internal.Xml.XPath.XPathSelectionIterator"); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 5e527cdfcb..210845e419 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -1,300 +1,152 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Web.Script.Serialization; -using Newtonsoft.Json; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; using Umbraco.Web.PublishedCache; -using Umbraco.Web.PublishedCache.XmlPublishedCache; namespace Umbraco.Web.Cache { - - /// - /// A cache refresher to ensure content type cache is updated when content types change - this is applicable to content, media and member types - /// - /// - /// This is not intended to be used directly in your code - /// - public sealed class ContentTypeCacheRefresher : JsonCacheRefresherBase + public sealed class ContentTypeCacheRefresher : PayloadCacheRefresherBase { + private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly IdkMap _idkMap; - #region Static helpers - - /// - /// Converts the json to a JsonPayload object - /// - /// - /// - internal static JsonPayload[] DeserializeFromJsonPayload(string json) + public ContentTypeCacheRefresher(CacheHelper cacheHelper, IPublishedSnapshotService publishedSnapshotService, IdkMap idkMap) + : base(cacheHelper) { - var jsonObject = JsonConvert.DeserializeObject(json); - return jsonObject; + _publishedSnapshotService = publishedSnapshotService; + _idkMap = idkMap; } - /// - /// Converts a content type to a jsonPayload object - /// - /// - /// if the item was deleted - /// - internal static JsonPayload FromContentType(IContentTypeBase contentType, bool isDeleted = false) - { - var contentTypeService = (ContentTypeService) ApplicationContext.Current.Services.ContentTypeService; - var payload = new JsonPayload - { - Alias = contentType.Alias, - Id = contentType.Id, - PropertyTypeIds = contentType.PropertyTypes.Select(x => x.Id).ToArray(), - //either IContentType or IMediaType or IMemberType - Type = (contentType is IContentType) - ? typeof(IContentType).Name - : (contentType is IMediaType) - ? typeof(IMediaType).Name - : typeof(IMemberType).Name, - DescendantPayloads = contentTypeService.GetDescendants(contentType).Select(x => FromContentType(x)).ToArray(), - WasDeleted = isDeleted, - PropertyRemoved = contentType.WasPropertyDirty("HasPropertyTypeBeenRemoved"), - AliasChanged = contentType.WasPropertyDirty("Alias"), - PropertyTypeAliasChanged = contentType.PropertyTypes.Any(x => x.WasPropertyDirty("Alias")), - IsNew = contentType.WasPropertyDirty("HasIdentity") - }; + #region Define + protected override ContentTypeCacheRefresher This => this; - return payload; - } + public static readonly Guid UniqueId = Guid.Parse("6902E22C-9C10-483C-91F3-66B7CAE9E2F5"); - /// - /// Creates the custom Json payload used to refresh cache amongst the servers - /// - /// specify false if this is an update, otherwise true if it is a deletion - /// - /// - internal static string SerializeToJsonPayload(bool isDeleted, params IContentTypeBase[] contentTypes) - { - var items = contentTypes.Select(x => FromContentType(x, isDeleted)).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Content Type Cache Refresher"; #endregion - #region Sub classes + #region Refresher - internal class JsonPayload + public override void Refresh(JsonPayload[] payloads) { - public JsonPayload() + // TODO: refactor + // we should NOT directly clear caches here, but instead ask whatever class + // is managing the cache to please clear that cache properly + + if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) { - WasDeleted = false; - IsNew = false; + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); } - public string Alias { get; set; } - public int Id { get; set; } - public int[] PropertyTypeIds { get; set; } - public string Type { get; set; } - public bool AliasChanged { get; set; } - public bool PropertyRemoved { get; set; } - public bool PropertyTypeAliasChanged { get; set; } - public JsonPayload[] DescendantPayloads { get; set; } - public bool WasDeleted { get; set; } - public bool IsNew { get; set; } - } - #endregion + if (payloads.Any(x => x.ItemType == typeof(IMediaType).Name)) + { + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + } - protected override ContentTypeCacheRefresher Instance - { - get { return this; } - } + if (payloads.Any(x => x.ItemType == typeof(IMemberType).Name)) + { + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + } - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.ContentTypeCacheRefresherId); } - } - public override string Name - { - get { return "ContentTypeCacheRefresher"; } + foreach (var id in payloads.Select(x => x.Id)) + { + _idkMap.ClearCache(id); + ClearLegacyCaches(id); + } + + if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) + // don't try to be clever - refresh all + ContentCacheRefresher.RefreshContentTypes(CacheHelper); + + if (payloads.Any(x => x.ItemType == typeof(IMediaType).Name)) + // don't try to be clever - refresh all + MediaCacheRefresher.RefreshMediaTypes(CacheHelper); + + if (payloads.Any(x => x.ItemType == typeof(IMemberType).Name)) + // don't try to be clever - refresh all + MemberCacheRefresher.RefreshMemberTypes(CacheHelper); + + // notify + _publishedSnapshotService.Notify(payloads); + + // now we can trigger the event + base.Refresh(payloads); } public override void RefreshAll() { - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - - //all content type property cache - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.ContentTypePropertiesCacheKey); - //all content type cache - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.ContentTypeCacheKey); - //clear static object cache - global::umbraco.cms.businesslogic.ContentType.RemoveAllDataTypeCache(); - - PublishedContentType.ClearAll(); - - base.RefreshAll(); + throw new NotSupportedException(); } public override void Refresh(int id) { - ClearContentTypeCache(false, id); - base.Refresh(id); + throw new NotSupportedException(); + } + + public override void Refresh(Guid id) + { + throw new NotSupportedException(); } public override void Remove(int id) { - ClearContentTypeCache(true, id); - base.Remove(id); + throw new NotSupportedException(); } - /// - /// Refreshes the cache using the custom jsonPayload provided - /// - /// - public override void Refresh(string jsonPayload) + private void ClearLegacyCaches(int contentTypeId /*, string contentTypeAlias, IEnumerable propertyTypeIds*/) { - var payload = DeserializeFromJsonPayload(jsonPayload); - ClearContentTypeCache(payload); - base.Refresh(jsonPayload); + // legacy umbraco.cms.businesslogic.ContentType + + // TODO - get rid of all this mess + + // clears the cache for each property type associated with the content type + // see src/umbraco.cms/businesslogic/propertytype/propertytype.cs + // that cache is disabled because we could not clear it properly + //foreach (var pid in propertyTypeIds) + // ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.PropertyTypeCacheKey + pid); + + // clears the cache associated with the content type itself + CacheHelper.RuntimeCache.ClearCacheItem(CacheKeys.ContentTypeCacheKey + contentTypeId); + + // clears the cache associated with the content type properties collection + CacheHelper.RuntimeCache.ClearCacheItem(CacheKeys.ContentTypePropertiesCacheKey + contentTypeId); + + // clears the dictionary object cache of the legacy ContentType + // see src/umbraco.cms/businesslogic/ContentType.cs + // that cache is disabled because we could not clear it properly + //global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(contentTypeAlias); } - /// - /// This clears out all cache associated with a content type - /// - /// - /// - /// The cache that is required to be cleared when a content type is updated is as follows: - /// - ApplicationCache (keys to clear): - /// -- CacheKeys.PropertyTypeCacheKey + propertyType.Id (each property type assigned) - /// -- CacheKeys.ContentTypePropertiesCacheKey + contentType.Id - /// - ContentType.RemoveFromDataTypeCache (clears static object/dictionary cache) - /// - InMemoryCacheProvider.Current.Clear(); - /// - RoutesCache.Clear(); - /// - private void ClearContentTypeCache(JsonPayload[] payloads) - { - var needsContentRefresh = false; + #endregion - foreach (var payload in payloads) + #region Json + + public class JsonPayload + { + public JsonPayload(string itemType, int id, ContentTypeChangeTypes changeTypes) { - ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id); - - //clear the cache for each item - ClearContentTypeCache(payload); - - //we only need to do this for IContentType NOT for IMediaType, we don't want to refresh the whole cache. - //if the item was deleted or the alias changed or property removed then we need to refresh the content. - //and, don't refresh the cache if it is new. - if (payload.Type == typeof(IContentType).Name - && payload.IsNew == false - && (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved || payload.PropertyTypeAliasChanged)) - { - needsContentRefresh = true; - } + ItemType = itemType; + Id = id; + ChangeTypes = changeTypes; } - //need to refresh the xml content cache if required - if (needsContentRefresh) - { - var pageRefresher = CacheRefreshersResolver.Current.GetById(new Guid(DistributedCache.PageCacheRefresherId)); - pageRefresher.RefreshAll(); - } + public string ItemType { get; } - //clear the cache providers if there were any content types to clear - if (payloads.Any()) - { - if (payloads.Any(x => x.Type == typeof (IContentType).Name)) - { - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - } - if (payloads.Any(x => x.Type == typeof(IMediaType).Name)) - { - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - } - if (payloads.Any(x => x.Type == typeof(IMemberType).Name)) - { - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - } - + public int Id { get; } - //we only need to do this for IContentType NOT for IMediaType, we don't want to refresh the whole routes - //cache if only a media type has changed. - //we don't want to update the routes cache if all of the content types here are new. - if (payloads.Any(x => x.Type == typeof(IContentType).Name) - && payloads.All(x => x.IsNew) == false) //if they are all new then don't proceed - { - // SD: we need to clear the routes cache here! - // - // zpqrtbnk: no, not here, in fact the caches should subsribe to refresh events else we - // are creating a nasty dependency - but keep it like that for the time being while - // SD is cleaning cache refreshers up. - if (UmbracoContext.Current != null) - { - var contentCache = UmbracoContext.Current.ContentCache.InnerCache as PublishedContentCache; - if (contentCache != null) - contentCache.RoutesCache.Clear(); - } - } - } - } - - /// - /// Clears cache for an individual IContentTypeBase object - /// - /// - /// - /// See notes for the other overloaded ClearContentTypeCache for - /// full details on clearing cache. - /// - /// - /// Return true if the alias of the content type changed - /// - private static void ClearContentTypeCache(JsonPayload payload) - { - //clears the cache associated with the Content type itself - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(string.Format("{0}{1}", CacheKeys.ContentTypeCacheKey, payload.Id)); - //clears the cache associated with the content type properties collection - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.ContentTypePropertiesCacheKey + payload.Id); - - //clears the dictionary object cache of the legacy ContentType - global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(payload.Alias); - - PublishedContentType.ClearContentType(payload.Id); - - //need to recursively clear the cache for each child content type - foreach (var descendant in payload.DescendantPayloads) - { - ClearContentTypeCache(descendant); - } - } - - /// - /// Clears the cache for any content type with the specified Ids - /// - /// true if the entity was deleted, false if it is just an update - /// - private void ClearContentTypeCache(bool isDeleted, params int[] ids) - { - ClearContentTypeCache( - ids.Select( - x => - ApplicationContext.Current.Services.ContentTypeService.GetContentType(x) as IContentTypeBase) - .WhereNotNull() - .Select(x => FromContentType(x, isDeleted)) - .ToArray()); + public ContentTypeChangeTypes ChangeTypes { get; } } + #endregion } } diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index 65d366e2ad..8d1a65f296 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -1,95 +1,43 @@ using System; -using System.Web.Script.Serialization; -using Umbraco.Core; using Umbraco.Core.Cache; -using System.Linq; -using Newtonsoft.Json; using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Services; using Umbraco.Web.PropertyEditors; using Umbraco.Web.PropertyEditors.ValueConverters; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.Cache { - /// - /// A cache refresher to ensure member cache is updated when members change - /// - public sealed class DataTypeCacheRefresher : JsonCacheRefresherBase + public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase { + private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly IdkMap _idkMap; - #region Static helpers - - /// - /// Converts the json to a JsonPayload object - /// - /// - /// - private static JsonPayload[] DeserializeFromJsonPayload(string json) + public DataTypeCacheRefresher(CacheHelper cacheHelper, IPublishedSnapshotService publishedSnapshotService, IdkMap idkMap) + : base(cacheHelper) { - var jsonObject = JsonConvert.DeserializeObject(json); - return jsonObject; + _publishedSnapshotService = publishedSnapshotService; + _idkMap = idkMap; } - /// - /// Creates the custom Json payload used to refresh cache amongst the servers - /// - /// - /// - internal static string SerializeToJsonPayload(params IDataTypeDefinition[] dataTypes) - { - var items = dataTypes.Select(FromDataTypeDefinition).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } + #region Define - /// - /// Converts a macro to a jsonPayload object - /// - /// - /// - private static JsonPayload FromDataTypeDefinition(IDataTypeDefinition dataType) - { - var payload = new JsonPayload - { - UniqueId = dataType.Key, - Id = dataType.Id - }; - return payload; - } + protected override DataTypeCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("35B16C25-A17E-45D7-BC8F-EDAB1DCC28D2"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Data Type Cache Refresher"; #endregion - #region Sub classes + #region Refresher - private class JsonPayload + public override void Refresh(JsonPayload[] payloads) { - public Guid UniqueId { get; set; } - public int Id { get; set; } - } - - #endregion - - protected override DataTypeCacheRefresher Instance - { - get { return this; } - } - - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.DataTypeCacheRefresherId); } - } - - public override string Name - { - get { return "Clears data type cache"; } - } - - public override void Refresh(string jsonPayload) - { - var payloads = DeserializeFromJsonPayload(jsonPayload); - //we need to clear the ContentType runtime cache since that is what caches the // db data type to store the value against and anytime a datatype changes, this also might change // we basically need to clear all sorts of runtime caches here because so many things depend upon a data type @@ -101,25 +49,69 @@ namespace Umbraco.Web.Cache ClearAllIsolatedCacheByEntityType(); ClearAllIsolatedCacheByEntityType(); - var dataTypeCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + var dataTypeCache = CacheHelper.IsolatedRuntimeCache.GetCache(); + foreach (var payload in payloads) { - //clears the prevalue cache if (dataTypeCache) - dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}_{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); + dataTypeCache.Result.ClearCacheByKeySearch(CacheKeys.DataTypePreValuesCacheKey + "_" + payload.Id); - ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id); - PublishedContentType.ClearDataType(payload.Id); - NestedContentHelper.ClearCache(payload.Id); + _idkMap.ClearCache(payload.Id); } + // fixme - not sure I like these? TagsValueConverter.ClearCaches(); - LegacyMediaPickerPropertyConverter.ClearCaches(); SliderValueConverter.ClearCaches(); - MediaPickerPropertyConverter.ClearCaches(); + // notify + _publishedSnapshotService.Notify(payloads); - base.Refresh(jsonPayload); + base.Refresh(payloads); } + + // these events should never trigger + // everything should be PAYLOAD/JSON + + public override void RefreshAll() + { + throw new NotSupportedException(); + } + + public override void Refresh(int id) + { + throw new NotSupportedException(); + } + + public override void Refresh(Guid id) + { + throw new NotSupportedException(); + } + + public override void Remove(int id) + { + throw new NotSupportedException(); + } + + #endregion + + #region Json + + public class JsonPayload + { + public JsonPayload(int id, Guid key, bool removed) + { + Id = id; + Key = key; + Removed = removed; + } + + public int Id { get; } + + public Guid Key { get; } + + public bool Removed { get; } + } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs b/src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs index ae36be33df..13e1e35a83 100644 --- a/src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs @@ -1,30 +1,28 @@ using System; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; - namespace Umbraco.Web.Cache { - /// - /// A cache refresher to ensure the dictionary cache is refreshed when dictionary change - /// public sealed class DictionaryCacheRefresher : CacheRefresherBase { - protected override DictionaryCacheRefresher Instance - { - get { return this; } - } + public DictionaryCacheRefresher(CacheHelper cacheHelper) + : base(cacheHelper) + { } - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.DictionaryCacheRefresherId); } - } + #region Define - public override string Name - { - get { return "Dictionary cache refresher"; } - } + protected override DictionaryCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("D1D7E227-F817-4816-BFE9-6C39B6152884"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Dictionary Cache Refresher"; + + #endregion + + #region Refresher public override void Refresh(int id) { @@ -37,5 +35,7 @@ namespace Umbraco.Web.Cache ClearAllIsolatedCacheByEntityType(); base.Remove(id); } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/DistributedCache.cs b/src/Umbraco.Web/Cache/DistributedCache.cs index b019a21990..23d1067282 100644 --- a/src/Umbraco.Web/Cache/DistributedCache.cs +++ b/src/Umbraco.Web/Cache/DistributedCache.cs @@ -1,9 +1,9 @@ using System; -using System.ComponentModel; +using System.Collections.Generic; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Sync; -using umbraco.interfaces; +using Umbraco.Core.Cache; +using Umbraco.Web.Composing; namespace Umbraco.Web.Cache { @@ -21,102 +21,25 @@ namespace Umbraco.Web.Cache /// public sealed class DistributedCache { - #region Public constants/Ids - - public const string ApplicationTreeCacheRefresherId = "0AC6C028-9860-4EA4-958D-14D39F45886E"; - public const string ApplicationCacheRefresherId = "B15F34A1-BC1D-4F8B-8369-3222728AB4C8"; - public const string TemplateRefresherId = "DD12B6A0-14B9-46e8-8800-C154F74047C8"; - public const string PageCacheRefresherId = "27AB3022-3DFA-47b6-9119-5945BC88FD66"; - public const string UnpublishedPageCacheRefresherId = "55698352-DFC5-4DBE-96BD-A4A0F6F77145"; - public const string MemberCacheRefresherId = "E285DF34-ACDC-4226-AE32-C0CB5CF388DA"; - public const string MemberGroupCacheRefresherId = "187F236B-BD21-4C85-8A7C-29FBA3D6C00C"; - public const string MediaCacheRefresherId = "B29286DD-2D40-4DDB-B325-681226589FEC"; - public const string MacroCacheRefresherId = "7B1E683C-5F34-43dd-803D-9699EA1E98CA"; - public const string UserCacheRefresherId = "E057AF6D-2EE6-41F4-8045-3694010F0AA6"; - public const string UserGroupCacheRefresherId = "45178038-B232-4FE8-AA1A-F2B949C44762"; - public const string UserGroupPermissionsCacheRefresherId = "840AB9C5-5C0B-48DB-A77E-29FE4B80CD3A"; - public const string ContentTypeCacheRefresherId = "6902E22C-9C10-483C-91F3-66B7CAE9E2F5"; - public const string LanguageCacheRefresherId = "3E0F95D8-0BE5-44B8-8394-2B8750B62654"; - public const string DomainCacheRefresherId = "11290A79-4B57-4C99-AD72-7748A3CF38AF"; - public const string RelationTypeCacheRefresherId = "D8375ABA-4FB3-4F86-B505-92FBA1B6F7C9"; - - [Obsolete("This is no longer used and will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - public const string StylesheetCacheRefresherId = "E0633648-0DEB-44AE-9A48-75C3A55CB670"; - public const string StylesheetPropertyCacheRefresherId = "2BC7A3A4-6EB1-4FBC-BAA3-C9E7B6D36D38"; - public const string DataTypeCacheRefresherId = "35B16C25-A17E-45D7-BC8F-EDAB1DCC28D2"; - public const string DictionaryCacheRefresherId = "D1D7E227-F817-4816-BFE9-6C39B6152884"; - public const string PublicAccessCacheRefresherId = "1DB08769-B104-4F8B-850E-169CAC1DF2EC"; - - public static readonly Guid ApplicationTreeCacheRefresherGuid = new Guid(ApplicationTreeCacheRefresherId); - public static readonly Guid ApplicationCacheRefresherGuid = new Guid(ApplicationCacheRefresherId); - public static readonly Guid TemplateRefresherGuid = new Guid(TemplateRefresherId); - public static readonly Guid PageCacheRefresherGuid = new Guid(PageCacheRefresherId); - public static readonly Guid UnpublishedPageCacheRefresherGuid = new Guid(UnpublishedPageCacheRefresherId); - public static readonly Guid MemberCacheRefresherGuid = new Guid(MemberCacheRefresherId); - public static readonly Guid MemberGroupCacheRefresherGuid = new Guid(MemberGroupCacheRefresherId); - public static readonly Guid MediaCacheRefresherGuid = new Guid(MediaCacheRefresherId); - public static readonly Guid MacroCacheRefresherGuid = new Guid(MacroCacheRefresherId); - public static readonly Guid UserCacheRefresherGuid = new Guid(UserCacheRefresherId); - public static readonly Guid UserGroupCacheRefresherGuid = new Guid(UserGroupCacheRefresherId); - public static readonly Guid UserGroupPermissionsCacheRefresherGuid = new Guid(UserGroupPermissionsCacheRefresherId); - public static readonly Guid ContentTypeCacheRefresherGuid = new Guid(ContentTypeCacheRefresherId); - public static readonly Guid LanguageCacheRefresherGuid = new Guid(LanguageCacheRefresherId); - public static readonly Guid DomainCacheRefresherGuid = new Guid(DomainCacheRefresherId); - public static readonly Guid StylesheetCacheRefresherGuid = new Guid(StylesheetCacheRefresherId); - public static readonly Guid StylesheetPropertyCacheRefresherGuid = new Guid(StylesheetPropertyCacheRefresherId); - public static readonly Guid DataTypeCacheRefresherGuid = new Guid(DataTypeCacheRefresherId); - public static readonly Guid DictionaryCacheRefresherGuid = new Guid(DictionaryCacheRefresherId); - public static readonly Guid PublicAccessCacheRefresherGuid = new Guid(PublicAccessCacheRefresherId); - public static readonly Guid RelationTypeCacheRefresherGuid = new Guid(RelationTypeCacheRefresherId); - - #endregion - - #region Constructor & Singleton - - // note - should inject into the application instead of using a singleton - private static readonly DistributedCache InstanceObject = new DistributedCache(); - - /// - /// Initializes a new instance of the class. - /// - private DistributedCache() - { } - - /// - /// Gets the static unique instance of the class. - /// - /// The static unique instance of the class. - /// Exists so that extension methods can be added to the distributed cache. - public static DistributedCache Instance - { - get - { - return InstanceObject; - } - } - - #endregion - #region Core notification methods /// /// Notifies the distributed cache of specifieds item invalidation, for a specified . /// /// The type of the invalidated items. - /// The unique identifier of the ICacheRefresher. + /// The unique identifier of the ICacheRefresher. /// A function returning the unique identifier of items. /// The invalidated items. /// /// This method is much better for performance because it does not need to re-lookup object instances. /// - public void Refresh(Guid factoryGuid, Func getNumericId, params T[] instances) + public void Refresh(Guid refresherGuid, Func getNumericId, params T[] instances) { - if (factoryGuid == Guid.Empty || instances.Length == 0 || getNumericId == null) return; + if (refresherGuid == Guid.Empty || instances.Length == 0 || getNumericId == null) return; - ServerMessengerResolver.Current.Messenger.PerformRefresh( - ServerRegistrarResolver.Current.Registrar.Registrations, - GetRefresherById(factoryGuid), + Current.ServerMessenger.PerformRefresh( + Current.ServerRegistrar.Registrations, + GetRefresherById(refresherGuid), getNumericId, instances); } @@ -124,55 +47,68 @@ namespace Umbraco.Web.Cache /// /// Notifies the distributed cache of a specified item invalidation, for a specified . /// - /// The unique identifier of the ICacheRefresher. + /// The unique identifier of the ICacheRefresher. /// The unique identifier of the invalidated item. - public void Refresh(Guid factoryGuid, int id) + public void Refresh(Guid refresherGuid, int id) { - if (factoryGuid == Guid.Empty || id == default(int)) return; + if (refresherGuid == Guid.Empty || id == default(int)) return; - ServerMessengerResolver.Current.Messenger.PerformRefresh( - ServerRegistrarResolver.Current.Registrar.Registrations, - GetRefresherById(factoryGuid), + Current.ServerMessenger.PerformRefresh( + Current.ServerRegistrar.Registrations, + GetRefresherById(refresherGuid), id); } /// /// Notifies the distributed cache of a specified item invalidation, for a specified . /// - /// The unique identifier of the ICacheRefresher. + /// The unique identifier of the ICacheRefresher. /// The unique identifier of the invalidated item. - public void Refresh(Guid factoryGuid, Guid id) + public void Refresh(Guid refresherGuid, Guid id) { - if (factoryGuid == Guid.Empty || id == Guid.Empty) return; + if (refresherGuid == Guid.Empty || id == Guid.Empty) return; - ServerMessengerResolver.Current.Messenger.PerformRefresh( - ServerRegistrarResolver.Current.Registrar.Registrations, - GetRefresherById(factoryGuid), + Current.ServerMessenger.PerformRefresh( + Current.ServerRegistrar.Registrations, + GetRefresherById(refresherGuid), id); } - public void RefreshByPayload(Guid factoryGuid, object payload) + // payload should be an object, or array of objects, NOT a + // Linq enumerable of some sort (IEnumerable, query...) + public void RefreshByPayload(Guid refresherGuid, TPayload[] payload) { - if (factoryGuid == Guid.Empty || payload == null) return; + if (refresherGuid == Guid.Empty || payload == null) return; - ServerMessengerResolver.Current.Messenger.PerformRefresh( - ServerRegistrarResolver.Current.Registrar.Registrations, - GetRefresherById(factoryGuid), + Current.ServerMessenger.PerformRefresh( + Current.ServerRegistrar.Registrations, + GetRefresherById(refresherGuid), payload); } + // so deal with IEnumerable + public void RefreshByPayload(Guid refresherGuid, IEnumerable payloads) + where TPayload : class + { + if (refresherGuid == Guid.Empty || payloads == null) return; + + Current.ServerMessenger.PerformRefresh( + Current.ServerRegistrar.Registrations, + GetRefresherById(refresherGuid), + payloads.ToArray()); + } /// /// Notifies the distributed cache, for a specified . /// - /// The unique identifier of the ICacheRefresher. + /// The unique identifier of the ICacheRefresher. /// The notification content. - public void RefreshByJson(Guid factoryGuid, string jsonPayload) + public void RefreshByJson(Guid refresherGuid, string jsonPayload) { - if (factoryGuid == Guid.Empty || jsonPayload.IsNullOrWhiteSpace()) return; + if (refresherGuid == Guid.Empty || jsonPayload.IsNullOrWhiteSpace()) return; - ServerMessengerResolver.Current.Messenger.PerformRefresh( - ServerRegistrarResolver.Current.Registrar.Registrations, - GetRefresherById(factoryGuid), + Current.ServerMessenger.PerformRefresh( + Current.ServerRegistrar.Registrations, + GetRefresherById(refresherGuid), jsonPayload); } @@ -185,8 +121,8 @@ namespace Umbraco.Web.Cache //{ // if (refresherId == Guid.Empty || payload == null) return; - // ServerMessengerResolver.Current.Messenger.Notify( - // ServerRegistrarResolver.Current.Registrar.Registrations, + // Current.ServerMessenger.Notify( + // Current.ServerRegistrar.Registrations, // GetRefresherById(refresherId), // json); //} @@ -194,45 +130,28 @@ namespace Umbraco.Web.Cache /// /// Notifies the distributed cache of a global invalidation for a specified . /// - /// The unique identifier of the ICacheRefresher. - public void RefreshAll(Guid factoryGuid) + /// The unique identifier of the ICacheRefresher. + public void RefreshAll(Guid refresherGuid) { - if (factoryGuid == Guid.Empty) return; + if (refresherGuid == Guid.Empty) return; - ServerMessengerResolver.Current.Messenger.PerformRefreshAll( - ServerRegistrarResolver.Current.Registrar.Registrations, - GetRefresherById(factoryGuid)); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This method is no longer in use and does not work as advertised, the allServers parameter doesnt have any affect for database server messengers, do not use!")] - public void RefreshAll(Guid factoryGuid, bool allServers) - { - if (factoryGuid == Guid.Empty) return; - if (allServers) - { - RefreshAll(factoryGuid); - } - else - { - ServerMessengerResolver.Current.Messenger.PerformRefreshAll( - Enumerable.Empty(), - GetRefresherById(factoryGuid)); - } + Current.ServerMessenger.PerformRefreshAll( + Current.ServerRegistrar.Registrations, + GetRefresherById(refresherGuid)); } /// /// Notifies the distributed cache of a specified item removal, for a specified . /// - /// The unique identifier of the ICacheRefresher. + /// The unique identifier of the ICacheRefresher. /// The unique identifier of the removed item. - public void Remove(Guid factoryGuid, int id) + public void Remove(Guid refresherGuid, int id) { - if (factoryGuid == Guid.Empty || id == default(int)) return; + if (refresherGuid == Guid.Empty || id == default(int)) return; - ServerMessengerResolver.Current.Messenger.PerformRemove( - ServerRegistrarResolver.Current.Registrar.Registrations, - GetRefresherById(factoryGuid), + Current.ServerMessenger.PerformRemove( + Current.ServerRegistrar.Registrations, + GetRefresherById(refresherGuid), id); } @@ -240,17 +159,17 @@ namespace Umbraco.Web.Cache /// Notifies the distributed cache of specifieds item removal, for a specified . /// /// The type of the removed items. - /// The unique identifier of the ICacheRefresher. + /// The unique identifier of the ICacheRefresher. /// A function returning the unique identifier of items. /// The removed items. /// /// This method is much better for performance because it does not need to re-lookup object instances. /// - public void Remove(Guid factoryGuid, Func getNumericId, params T[] instances) + public void Remove(Guid refresherGuid, Func getNumericId, params T[] instances) { - ServerMessengerResolver.Current.Messenger.PerformRemove( - ServerRegistrarResolver.Current.Registrar.Registrations, - GetRefresherById(factoryGuid), + Current.ServerMessenger.PerformRemove( + Current.ServerRegistrar.Registrations, + GetRefresherById(refresherGuid), getNumericId, instances); } @@ -258,9 +177,9 @@ namespace Umbraco.Web.Cache #endregion // helper method to get an ICacheRefresher by its unique identifier - private static ICacheRefresher GetRefresherById(Guid uniqueIdentifier) + private static ICacheRefresher GetRefresherById(Guid refresherGuid) { - return CacheRefreshersResolver.Current.GetById(uniqueIdentifier); + return Current.CacheRefreshers[refresherGuid]; } } } diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index 849cd6c81a..c9d800e10d 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -1,42 +1,38 @@ -using System; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Events; +using System; +using System.Linq; using Umbraco.Core.Models; -using umbraco; -using umbraco.cms.businesslogic.web; -using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Services.Changes; namespace Umbraco.Web.Cache { /// - /// Extension methods for + /// Extension methods for . /// internal static class DistributedCacheExtensions { - #region Public access + #region PublicAccessCache public static void RefreshPublicAccess(this DistributedCache dc) { - dc.RefreshAll(DistributedCache.PublicAccessCacheRefresherGuid); + dc.RefreshAll(PublicAccessCacheRefresher.UniqueId); } #endregion - #region Application tree cache + #region ApplicationTreeCache public static void RefreshAllApplicationTreeCache(this DistributedCache dc) { - dc.RefreshAll(DistributedCache.ApplicationTreeCacheRefresherGuid); + dc.RefreshAll(ApplicationTreeCacheRefresher.UniqueId); } #endregion - #region Application cache + #region ApplicationCache public static void RefreshAllApplicationCache(this DistributedCache dc) { - dc.RefreshAll(DistributedCache.ApplicationCacheRefresherGuid); + dc.RefreshAll(ApplicationCacheRefresher.UniqueId); } #endregion @@ -45,17 +41,17 @@ namespace Umbraco.Web.Cache public static void RemoveUserCache(this DistributedCache dc, int userId) { - dc.Remove(DistributedCache.UserCacheRefresherGuid, userId); + dc.Remove(UserCacheRefresher.UniqueId, userId); } public static void RefreshUserCache(this DistributedCache dc, int userId) { - dc.Refresh(DistributedCache.UserCacheRefresherGuid, userId); + dc.Refresh(UserCacheRefresher.UniqueId, userId); } public static void RefreshAllUserCache(this DistributedCache dc) { - dc.RefreshAll(DistributedCache.UserCacheRefresherGuid); + dc.RefreshAll(UserCacheRefresher.UniqueId); } #endregion @@ -64,17 +60,17 @@ namespace Umbraco.Web.Cache public static void RemoveUserGroupCache(this DistributedCache dc, int userId) { - dc.Remove(DistributedCache.UserGroupCacheRefresherGuid, userId); + dc.Remove(UserGroupCacheRefresher.UniqueId, userId); } public static void RefreshUserGroupCache(this DistributedCache dc, int userId) { - dc.Refresh(DistributedCache.UserGroupCacheRefresherGuid, userId); + dc.Refresh(UserGroupCacheRefresher.UniqueId, userId); } public static void RefreshAllUserGroupCache(this DistributedCache dc) { - dc.RefreshAll(DistributedCache.UserGroupCacheRefresherGuid); + dc.RefreshAll(UserGroupCacheRefresher.UniqueId); } #endregion @@ -83,307 +79,210 @@ namespace Umbraco.Web.Cache public static void RemoveUserGroupPermissionsCache(this DistributedCache dc, int groupId) { - dc.Remove(DistributedCache.UserGroupPermissionsCacheRefresherGuid, groupId); + dc.Remove(UserGroupPermissionsCacheRefresher.UniqueId, groupId); } public static void RefreshUserGroupPermissionsCache(this DistributedCache dc, int groupId) { //TODO: Not sure if we need this yet depends if we start caching permissions - //dc.Refresh(DistributedCache.UserGroupPermissionsCacheRefresherGuid, groupId); + //dc.Refresh(UserGroupPermissionsCacheRefresher.UniqueId, groupId); } public static void RefreshAllUserGroupPermissionsCache(this DistributedCache dc) { - dc.RefreshAll(DistributedCache.UserGroupPermissionsCacheRefresherGuid); + dc.RefreshAll(UserGroupPermissionsCacheRefresher.UniqueId); } #endregion - #region Template cache + #region TemplateCache public static void RefreshTemplateCache(this DistributedCache dc, int templateId) { - dc.Refresh(DistributedCache.TemplateRefresherGuid, templateId); + dc.Refresh(TemplateCacheRefresher.UniqueId, templateId); } public static void RemoveTemplateCache(this DistributedCache dc, int templateId) { - dc.Remove(DistributedCache.TemplateRefresherGuid, templateId); - } + dc.Remove(TemplateCacheRefresher.UniqueId, templateId); + } #endregion - #region Dictionary cache + #region DictionaryCache public static void RefreshDictionaryCache(this DistributedCache dc, int dictionaryItemId) { - dc.Refresh(DistributedCache.DictionaryCacheRefresherGuid, dictionaryItemId); + dc.Refresh(DictionaryCacheRefresher.UniqueId, dictionaryItemId); } public static void RemoveDictionaryCache(this DistributedCache dc, int dictionaryItemId) { - dc.Remove(DistributedCache.DictionaryCacheRefresherGuid, dictionaryItemId); + dc.Remove(DictionaryCacheRefresher.UniqueId, dictionaryItemId); } #endregion - - #region Data type cache - public static void RefreshDataTypeCache(this DistributedCache dc, IDataTypeDefinition dataType) + #region DataTypeCache + + public static void RefreshDataTypeCache(this DistributedCache dc, IDataType dataType) { if (dataType == null) return; - dc.RefreshByJson(DistributedCache.DataTypeCacheRefresherGuid, DataTypeCacheRefresher.SerializeToJsonPayload(dataType)); + var payloads = new[] { new DataTypeCacheRefresher.JsonPayload(dataType.Id, dataType.Key, false) }; + dc.RefreshByPayload(DataTypeCacheRefresher.UniqueId, payloads); } - public static void RemoveDataTypeCache(this DistributedCache dc, IDataTypeDefinition dataType) + public static void RemoveDataTypeCache(this DistributedCache dc, IDataType dataType) { if (dataType == null) return; - dc.RefreshByJson(DistributedCache.DataTypeCacheRefresherGuid, DataTypeCacheRefresher.SerializeToJsonPayload(dataType)); + var payloads = new[] { new DataTypeCacheRefresher.JsonPayload(dataType.Id, dataType.Key, true) }; + dc.RefreshByPayload(DataTypeCacheRefresher.UniqueId, payloads); } #endregion - #region Page cache + #region ContentCache - public static void RefreshAllPageCache(this DistributedCache dc) + public static void RefreshAllContentCache(this DistributedCache dc) { - dc.RefreshAll(DistributedCache.PageCacheRefresherGuid); + var payloads = new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }; + + // note: refresh all content cache does refresh content types too + dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads); } - public static void RefreshPageCache(this DistributedCache dc, int documentId) + public static void RefreshContentCache(this DistributedCache dc, TreeChange[] changes) { - dc.Refresh(DistributedCache.PageCacheRefresherGuid, documentId); - } + if (changes.Length == 0) return; - public static void RefreshPageCache(this DistributedCache dc, params IContent[] content) - { - dc.Refresh(DistributedCache.PageCacheRefresherGuid, x => x.Id, content); - } + var payloads = changes + .Select(x => new ContentCacheRefresher.JsonPayload(x.Item.Id, x.ChangeTypes)); - public static void RemovePageCache(this DistributedCache dc, params IContent[] content) - { - dc.Remove(DistributedCache.PageCacheRefresherGuid, x => x.Id, content); - } - - public static void RemovePageCache(this DistributedCache dc, int documentId) - { - dc.Remove(DistributedCache.PageCacheRefresherGuid, documentId); - } - - public static void RefreshUnpublishedPageCache(this DistributedCache dc, params IContent[] content) - { - dc.Refresh(DistributedCache.UnpublishedPageCacheRefresherGuid, x => x.Id, content); - } - - public static void RemoveUnpublishedPageCache(this DistributedCache dc, params IContent[] content) - { - dc.Remove(DistributedCache.UnpublishedPageCacheRefresherGuid, x => x.Id, content); - } - - public static void RemoveUnpublishedCachePermanently(this DistributedCache dc, params int[] contentIds) - { - dc.RefreshByJson(DistributedCache.UnpublishedPageCacheRefresherGuid, UnpublishedPageCacheRefresher.SerializeToJsonPayloadForPermanentDeletion(contentIds)); + dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads); } #endregion - #region Member cache + #region MemberCache public static void RefreshMemberCache(this DistributedCache dc, params IMember[] members) { - dc.Refresh(DistributedCache.MemberCacheRefresherGuid, x => x.Id, members); + dc.Refresh(MemberCacheRefresher.UniqueId, x => x.Id, members); } public static void RemoveMemberCache(this DistributedCache dc, params IMember[] members) { - dc.Remove(DistributedCache.MemberCacheRefresherGuid, x => x.Id, members); - } + dc.Remove(MemberCacheRefresher.UniqueId, x => x.Id, members); + } [Obsolete("Use the RefreshMemberCache with strongly typed IMember objects instead")] public static void RefreshMemberCache(this DistributedCache dc, int memberId) { - dc.Refresh(DistributedCache.MemberCacheRefresherGuid, memberId); + dc.Refresh(MemberCacheRefresher.UniqueId, memberId); } [Obsolete("Use the RemoveMemberCache with strongly typed IMember objects instead")] public static void RemoveMemberCache(this DistributedCache dc, int memberId) { - dc.Remove(DistributedCache.MemberCacheRefresherGuid, memberId); - } + dc.Remove(MemberCacheRefresher.UniqueId, memberId); + } #endregion - #region Member group cache + #region MemberGroupCache public static void RefreshMemberGroupCache(this DistributedCache dc, int memberGroupId) { - dc.Refresh(DistributedCache.MemberGroupCacheRefresherGuid, memberGroupId); + dc.Refresh(MemberGroupCacheRefresher.UniqueId, memberGroupId); } public static void RemoveMemberGroupCache(this DistributedCache dc, int memberGroupId) { - dc.Remove(DistributedCache.MemberGroupCacheRefresherGuid, memberGroupId); + dc.Remove(MemberGroupCacheRefresher.UniqueId, memberGroupId); } #endregion - #region Media Cache - - public static void RefreshMediaCache(this DistributedCache dc, params IMedia[] media) + #region MediaCache + + public static void RefreshAllMediaCache(this DistributedCache dc) { - dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayload(MediaCacheRefresher.OperationType.Saved, media)); + var payloads = new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }; + + // note: refresh all media cache does refresh content types too + dc.RefreshByPayload(MediaCacheRefresher.UniqueId, payloads); } - public static void RefreshMediaCacheAfterMoving(this DistributedCache dc, params MoveEventInfo[] media) + public static void RefreshMediaCache(this DistributedCache dc, TreeChange[] changes) { - dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayloadForMoving(MediaCacheRefresher.OperationType.Saved, media)); - } + if (changes.Length == 0) return; - // clearing by Id will never work for load balanced scenarios for media since we require a Path - // to clear all of the cache but the media item will be removed before the other servers can - // look it up. Only here for legacy purposes. - [Obsolete("Ensure to clear with other RemoveMediaCache overload")] - public static void RemoveMediaCache(this DistributedCache dc, int mediaId) - { - dc.Remove(new Guid(DistributedCache.MediaCacheRefresherId), mediaId); - } + var payloads = changes + .Select(x => new MediaCacheRefresher.JsonPayload(x.Item.Id, x.ChangeTypes)); - public static void RemoveMediaCacheAfterRecycling(this DistributedCache dc, params MoveEventInfo[] media) - { - dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayloadForMoving(MediaCacheRefresher.OperationType.Trashed, media)); - } - - public static void RemoveMediaCachePermanently(this DistributedCache dc, params int[] mediaIds) - { - dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayloadForPermanentDeletion(mediaIds)); + dc.RefreshByPayload(MediaCacheRefresher.UniqueId, payloads); } #endregion - #region Macro Cache + #region Published Snapshot - public static void ClearAllMacroCacheOnCurrentServer(this DistributedCache dc) + public static void RefreshAllPublishedSnapshot(this DistributedCache dc) { - var macroRefresher = CacheRefreshersResolver.Current.GetById(DistributedCache.MacroCacheRefresherGuid); - macroRefresher.RefreshAll(); + // note: refresh all content & media caches does refresh content types too + dc.RefreshAllContentCache(); + dc.RefreshAllMediaCache(); + dc.RefreshAllDomainCache(); } + #endregion + + #region MacroCache + public static void RefreshMacroCache(this DistributedCache dc, IMacro macro) { if (macro == null) return; - dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); + dc.RefreshByJson(MacroCacheRefresher.UniqueId, MacroCacheRefresher.Serialize(macro)); } public static void RemoveMacroCache(this DistributedCache dc, IMacro macro) { if (macro == null) return; - dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); - } - - public static void RefreshMacroCache(this DistributedCache dc, global::umbraco.cms.businesslogic.macro.Macro macro) - { - if (macro == null) return; - dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); - } - - public static void RemoveMacroCache(this DistributedCache dc, global::umbraco.cms.businesslogic.macro.Macro macro) - { - if (macro == null) return; - dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); - } - - public static void RemoveMacroCache(this DistributedCache dc, macro macro) - { - if (macro == null || macro.Model == null) return; - dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); - } - - #endregion - - #region Document type cache - - public static void RefreshContentTypeCache(this DistributedCache dc, IContentType contentType) - { - if (contentType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(false, contentType)); - } - - public static void RemoveContentTypeCache(this DistributedCache dc, IContentType contentType) - { - if (contentType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(true, contentType)); + dc.RefreshByJson(MacroCacheRefresher.UniqueId, MacroCacheRefresher.Serialize(macro)); } #endregion - #region Media type cache + #region Content/Media/Member type cache - public static void RefreshMediaTypeCache(this DistributedCache dc, IMediaType mediaType) + public static void RefreshContentTypeCache(this DistributedCache dc, ContentTypeChange[] changes) { - if (mediaType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(false, mediaType)); + if (changes.Length == 0) return; + + var payloads = changes + .Select(x => new ContentTypeCacheRefresher.JsonPayload(typeof (IContentType).Name, x.Item.Id, x.ChangeTypes)); + + dc.RefreshByPayload(ContentTypeCacheRefresher.UniqueId, payloads); } - public static void RemoveMediaTypeCache(this DistributedCache dc, IMediaType mediaType) + public static void RefreshContentTypeCache(this DistributedCache dc, ContentTypeChange[] changes) { - if (mediaType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(true, mediaType)); + if (changes.Length == 0) return; + + var payloads = changes + .Select(x => new ContentTypeCacheRefresher.JsonPayload(typeof(IMediaType).Name, x.Item.Id, x.ChangeTypes)); + + dc.RefreshByPayload(ContentTypeCacheRefresher.UniqueId, payloads); } - #endregion - - #region Media type cache - - public static void RefreshMemberTypeCache(this DistributedCache dc, IMemberType memberType) + public static void RefreshContentTypeCache(this DistributedCache dc, ContentTypeChange[] changes) { - if (memberType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(false, memberType)); - } + if (changes.Length == 0) return; - public static void RemoveMemberTypeCache(this DistributedCache dc, IMemberType memberType) - { - if (memberType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(true, memberType)); - } + var payloads = changes + .Select(x => new ContentTypeCacheRefresher.JsonPayload(typeof(IMemberType).Name, x.Item.Id, x.ChangeTypes)); - #endregion - - #region Stylesheet Cache - - public static void RefreshStylesheetPropertyCache(this DistributedCache dc, global::umbraco.cms.businesslogic.web.StylesheetProperty styleSheetProperty) - { - if (styleSheetProperty == null) return; - dc.Refresh(DistributedCache.StylesheetPropertyCacheRefresherGuid, styleSheetProperty.Id); - } - - public static void RemoveStylesheetPropertyCache(this DistributedCache dc, global::umbraco.cms.businesslogic.web.StylesheetProperty styleSheetProperty) - { - if (styleSheetProperty == null) return; - dc.Remove(DistributedCache.StylesheetPropertyCacheRefresherGuid, styleSheetProperty.Id); - } - - public static void RefreshStylesheetCache(this DistributedCache dc, StyleSheet styleSheet) - { - if (styleSheet == null) return; - dc.Refresh(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); - } - - public static void RemoveStylesheetCache(this DistributedCache dc, StyleSheet styleSheet) - { - if (styleSheet == null) return; - dc.Remove(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); - } - - public static void RefreshStylesheetCache(this DistributedCache dc, Umbraco.Core.Models.Stylesheet styleSheet) - { - if (styleSheet == null) return; - dc.Refresh(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); - } - - public static void RemoveStylesheetCache(this DistributedCache dc, Umbraco.Core.Models.Stylesheet styleSheet) - { - if (styleSheet == null) return; - dc.Remove(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); + dc.RefreshByPayload(ContentTypeCacheRefresher.UniqueId, payloads); } #endregion @@ -393,19 +292,21 @@ namespace Umbraco.Web.Cache public static void RefreshDomainCache(this DistributedCache dc, IDomain domain) { if (domain == null) return; - dc.Refresh(DistributedCache.DomainCacheRefresherGuid, domain.Id); + var payloads = new[] { new DomainCacheRefresher.JsonPayload(domain.Id, DomainChangeTypes.Refresh) }; + dc.RefreshByPayload(DomainCacheRefresher.UniqueId, payloads); } public static void RemoveDomainCache(this DistributedCache dc, IDomain domain) { if (domain == null) return; - dc.Remove(DistributedCache.DomainCacheRefresherGuid, domain.Id); + var payloads = new[] { new DomainCacheRefresher.JsonPayload(domain.Id, DomainChangeTypes.Remove) }; + dc.RefreshByPayload(DomainCacheRefresher.UniqueId, payloads); } - public static void ClearDomainCacheOnCurrentServer(this DistributedCache dc) + public static void RefreshAllDomainCache(this DistributedCache dc) { - var domainRefresher = CacheRefreshersResolver.Current.GetById(DistributedCache.DomainCacheRefresherGuid); - domainRefresher.RefreshAll(); + var payloads = new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) }; + dc.RefreshByPayload(DomainCacheRefresher.UniqueId, payloads); } #endregion @@ -415,35 +316,13 @@ namespace Umbraco.Web.Cache public static void RefreshLanguageCache(this DistributedCache dc, ILanguage language) { if (language == null) return; - dc.Refresh(DistributedCache.LanguageCacheRefresherGuid, language.Id); + dc.Refresh(LanguageCacheRefresher.UniqueId, language.Id); } public static void RemoveLanguageCache(this DistributedCache dc, ILanguage language) { if (language == null) return; - dc.Remove(DistributedCache.LanguageCacheRefresherGuid, language.Id); - } - - public static void RefreshLanguageCache(this DistributedCache dc, global::umbraco.cms.businesslogic.language.Language language) - { - if (language == null) return; - dc.Refresh(DistributedCache.LanguageCacheRefresherGuid, language.id); - } - - public static void RemoveLanguageCache(this DistributedCache dc, global::umbraco.cms.businesslogic.language.Language language) - { - if (language == null) return; - dc.Remove(DistributedCache.LanguageCacheRefresherGuid, language.id); - } - - #endregion - - #region Xslt Cache - - public static void ClearXsltCacheOnCurrentServer(this DistributedCache dc) - { - if (UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration <= 0) return; - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes("MS.Internal.Xml.XPath.XPathSelectionIterator"); + dc.Remove(LanguageCacheRefresher.UniqueId, language.Id); } #endregion @@ -452,14 +331,14 @@ namespace Umbraco.Web.Cache public static void RefreshRelationTypeCache(this DistributedCache dc, int id) { - dc.Refresh(DistributedCache.RelationTypeCacheRefresherGuid, id); + dc.Refresh(RelationTypeCacheRefresher.UniqueId, id); } public static void RemoveRelationTypeCache(this DistributedCache dc, int id) { - dc.Remove(DistributedCache.RelationTypeCacheRefresherGuid, id); + dc.Remove(RelationTypeCacheRefresher.UniqueId, id); } #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs index d19d4f6ea0..5e3c0220a8 100644 --- a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs @@ -1,66 +1,89 @@ using System; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Services.Changes; using Umbraco.Web.PublishedCache; -using Umbraco.Web.PublishedCache.XmlPublishedCache; namespace Umbraco.Web.Cache { - /// - /// A cache refresher to ensure language cache is refreshed when languages change - /// - public sealed class DomainCacheRefresher : CacheRefresherBase + public sealed class DomainCacheRefresher : PayloadCacheRefresherBase { - protected override DomainCacheRefresher Instance + private readonly IPublishedSnapshotService _publishedSnapshotService; + + public DomainCacheRefresher(CacheHelper cacheHelper, IPublishedSnapshotService publishedSnapshotService) + : base(cacheHelper) { - get { return this; } + _publishedSnapshotService = publishedSnapshotService; } - public override Guid UniqueIdentifier + #region Define + + protected override DomainCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("11290A79-4B57-4C99-AD72-7748A3CF38AF"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Domain Cache Refresher"; + + #endregion + + #region Refresher + + public override void Refresh(JsonPayload[] payloads) { - get { return new Guid(DistributedCache.DomainCacheRefresherId); } + ClearAllIsolatedCacheByEntityType(); + + // note: must do what's above FIRST else the repositories still have the old cached + // content and when the PublishedCachesService is notified of changes it does not see + // the new content... + + // notify + _publishedSnapshotService.Notify(payloads); + // then trigger event + base.Refresh(payloads); } - public override string Name - { - get { return "Domain cache refresher"; } - } + // these events should never trigger + // everything should be PAYLOAD/JSON public override void RefreshAll() { - ClearCache(); - base.RefreshAll(); + throw new NotSupportedException(); } public override void Refresh(int id) { - ClearCache(); - base.Refresh(id); + throw new NotSupportedException(); + } + + public override void Refresh(Guid id) + { + throw new NotSupportedException(); } public override void Remove(int id) { - ClearCache(); - base.Remove(id); + throw new NotSupportedException(); } - private void ClearCache() - { - ClearAllIsolatedCacheByEntityType(); + #endregion - // SD: we need to clear the routes cache here! - // - // zpqrtbnk: no, not here, in fact the caches should subsribe to refresh events else we - // are creating a nasty dependency - but keep it like that for the time being while - // SD is cleaning cache refreshers up. - if (UmbracoContext.Current != null) + #region Json + + public class JsonPayload + { + public JsonPayload(int id, DomainChangeTypes changeType) { - var contentCache = UmbracoContext.Current.ContentCache.InnerCache as PublishedContentCache; - if (contentCache != null) - contentCache.RoutesCache.Clear(); + Id = id; + ChangeType = changeType; } + + public int Id { get; } + + public DomainChangeTypes ChangeType { get; } } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs index 087aa36764..2270f6471c 100644 --- a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs @@ -1,30 +1,28 @@ using System; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; - namespace Umbraco.Web.Cache { - /// - /// A cache refresher to ensure language cache is refreshed when languages change - /// public sealed class LanguageCacheRefresher : CacheRefresherBase { - protected override LanguageCacheRefresher Instance - { - get { return this; } - } + public LanguageCacheRefresher(CacheHelper cacheHelper) + : base(cacheHelper) + { } - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.LanguageCacheRefresherId); } - } + #region Define - public override string Name - { - get { return "Language cache refresher"; } - } + protected override LanguageCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("3E0F95D8-0BE5-44B8-8394-2B8750B62654"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Language Cache Refresher"; + + #endregion + + #region Refresher public override void Refresh(int id) { @@ -39,5 +37,7 @@ namespace Umbraco.Web.Cache ClearAllIsolatedCacheByEntityType(); base.Remove(id); } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs index 4292a30589..402151fdb9 100644 --- a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs @@ -1,38 +1,115 @@ using System; -using System.Web.Script.Serialization; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; using umbraco; - using Umbraco.Core.Persistence.Repositories; -using umbraco.interfaces; using System.Linq; using Newtonsoft.Json; -using Macro = umbraco.cms.businesslogic.macro.Macro; +using Umbraco.Core.Persistence.Repositories.Implement; namespace Umbraco.Web.Cache { - /// - /// A cache refresher to ensure macro cache is updated when members change - /// - /// - /// This is not intended to be used directly in your code and it should be sealed but due to legacy code we cannot seal it. - /// - public class MacroCacheRefresher : JsonCacheRefresherBase + public sealed class MacroCacheRefresher : JsonCacheRefresherBase { - #region Static helpers - + public MacroCacheRefresher(CacheHelper cacheHelper) + : base(cacheHelper) + { } + + #region Define + + protected override MacroCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("7B1E683C-5F34-43dd-803D-9699EA1E98CA"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Macro Cache Refresher"; + + #endregion + + #region Refresher + + public override void RefreshAll() + { + GetAllMacroCacheKeys().ForEach( + prefix => + CacheHelper.RuntimeCache.ClearCacheByKeySearch(prefix)); + + ClearAllIsolatedCacheByEntityType(); + + CacheHelper.RuntimeCache.ClearCacheObjectTypes(); + + base.RefreshAll(); + } + + public override void Refresh(string json) + { + var payloads = Deserialize(json); + + payloads.ForEach(payload => + { + GetCacheKeysForAlias(payload.Alias).ForEach( + alias => + CacheHelper.RuntimeCache.ClearCacheByKeySearch(alias)); + + var macroRepoCache = CacheHelper.IsolatedRuntimeCache.GetCache(); + if (macroRepoCache) + { + macroRepoCache.Result.ClearCacheItem(RepositoryCacheKeys.GetKey(payload.Id)); + } + }); + + base.Refresh(json); + } + + #endregion + + #region Json + + public class JsonPayload + { + public JsonPayload(int id, string alias) + { + Id = id; + Alias = alias; + } + + public int Id { get; } + + public string Alias { get; } + } + + private static JsonPayload[] Deserialize(string json) + { + return JsonConvert.DeserializeObject(json); + } + + internal static string Serialize(params Macro[] macros) + { + return JsonConvert.SerializeObject(macros.Select(x => new JsonPayload(x.Id, x.Alias)).ToArray()); + } + + internal static string Serialize(params IMacro[] macros) + { + return JsonConvert.SerializeObject(macros.Select(x => new JsonPayload(x.Id, x.Alias)).ToArray()); + } + + #endregion + + #region Helpers + internal static string[] GetAllMacroCacheKeys() { return new[] { - CacheKeys.MacroCacheKey, - CacheKeys.MacroControlCacheKey, - CacheKeys.MacroHtmlCacheKey, - CacheKeys.MacroHtmlDateAddedCacheKey, - CacheKeys.MacroControlDateAddedCacheKey, - CacheKeys.MacroXsltCacheKey, + CacheKeys.MacroCacheKey, // umbraco.cms.businesslogic.macro.Macro objects cache + CacheKeys.MacroContentCacheKey, // macro render cache + //CacheKeys.MacroControlCacheKey, + //CacheKeys.MacroHtmlCacheKey, + //CacheKeys.MacroHtmlDateAddedCacheKey, + //CacheKeys.MacroControlDateAddedCacheKey, + CacheKeys.MacroXsltCacheKey, // XsltMacroEngine transforms cache }; } @@ -41,162 +118,11 @@ namespace Umbraco.Web.Cache return GetAllMacroCacheKeys().Select(x => x + alias).ToArray(); } - /// - /// Converts the json to a JsonPayload object - /// - /// - /// - private static JsonPayload[] DeserializeFromJsonPayload(string json) + public static void ClearMacroContentCache(CacheHelper cacheHelper) { - var jsonObject = JsonConvert.DeserializeObject(json); - return jsonObject; - } - - /// - /// Creates the custom Json payload used to refresh cache amongst the servers - /// - /// - /// - internal static string SerializeToJsonPayload(params Macro[] macros) - { - var items = macros.Select(FromMacro).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } - - /// - /// Creates the custom Json payload used to refresh cache amongst the servers - /// - /// - /// - internal static string SerializeToJsonPayload(params IMacro[] macros) - { - var items = macros.Select(FromMacro).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } - - /// - /// Creates the custom Json payload used to refresh cache amongst the servers - /// - /// - /// - internal static string SerializeToJsonPayload(params macro[] macros) - { - var items = macros.Select(FromMacro).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } - - /// - /// Converts a macro to a jsonPayload object - /// - /// - /// - private static JsonPayload FromMacro(IMacro macro) - { - var payload = new JsonPayload - { - Alias = macro.Alias, - Id = macro.Id - }; - return payload; - } - - /// - /// Converts a macro to a jsonPayload object - /// - /// - /// - private static JsonPayload FromMacro(Macro macro) - { - var payload = new JsonPayload - { - Alias = macro.Alias, - Id = macro.Id - }; - return payload; - } - - /// - /// Converts a macro to a jsonPayload object - /// - /// - /// - private static JsonPayload FromMacro(macro macro) - { - var payload = new JsonPayload - { - Alias = macro.Alias, - Id = macro.Model.Id - }; - return payload; + cacheHelper.RuntimeCache.ClearCacheObjectTypes(); } #endregion - - #region Sub classes - - private class JsonPayload - { - public string Alias { get; set; } - public int Id { get; set; } - } - - #endregion - - protected override MacroCacheRefresher Instance - { - get { return this; } - } - - public override string Name - { - get - { - return "Macro cache refresher"; - } - } - - public override Guid UniqueIdentifier - { - get - { - return new Guid(DistributedCache.MacroCacheRefresherId); - } - } - - public override void RefreshAll() - { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - GetAllMacroCacheKeys().ForEach( - prefix => - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(prefix)); - - ClearAllIsolatedCacheByEntityType(); - - base.RefreshAll(); - } - - public override void Refresh(string jsonPayload) - { - var payloads = DeserializeFromJsonPayload(jsonPayload); - - payloads.ForEach(payload => - { - GetCacheKeysForAlias(payload.Alias).ForEach( - alias => - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(alias)); - - var macroRepoCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); - if (macroRepoCache) - { - macroRepoCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(payload.Id)); - } - }); - - base.Refresh(jsonPayload); - } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs index 3613da426d..8eeb4abcec 100644 --- a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs @@ -1,193 +1,155 @@ using System; -using System.Collections.Generic; -using System.Globalization; -using System.Web.Script.Serialization; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Events; -using Umbraco.Core.IO; using Umbraco.Core.Models; - using Umbraco.Core.Persistence.Repositories; -using umbraco.interfaces; using System.Linq; -using Newtonsoft.Json; -using Umbraco.Web.PublishedCache.XmlPublishedCache; +using System.Xml.Linq; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; +using Umbraco.Web.Composing; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.Cache { - /// - /// A cache refresher to ensure media cache is updated - /// - /// - /// This is not intended to be used directly in your code and it should be sealed but due to legacy code we cannot seal it. - /// - public class MediaCacheRefresher : JsonCacheRefresherBase + public sealed class MediaCacheRefresher : PayloadCacheRefresherBase { - #region Static helpers + private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly IdkMap _idkMap; - /// - /// Converts the json to a JsonPayload object - /// - /// - /// - public static JsonPayload[] DeserializeFromJsonPayload(string json) + public MediaCacheRefresher(CacheHelper cacheHelper, IPublishedSnapshotService publishedSnapshotService, IdkMap idkMap) + : base(cacheHelper) { - var jsonObject = JsonConvert.DeserializeObject(json); - return jsonObject; + _publishedSnapshotService = publishedSnapshotService; + _idkMap = idkMap; } - /// - /// Creates the custom Json payload used to refresh cache amongst the servers - /// - /// - /// - /// - internal static string SerializeToJsonPayload(OperationType operation, params IMedia[] media) - { - var items = media.Select(x => FromMedia(x, operation)).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } + #region Define - internal static string SerializeToJsonPayloadForMoving(OperationType operation, MoveEventInfo[] media) - { - var items = media.Select(x => new JsonPayload - { - Id = x.Entity.Id, - Operation = operation, - Path = x.OriginalPath - }).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } + protected override MediaCacheRefresher This => this; - internal static string SerializeToJsonPayloadForPermanentDeletion(params int[] mediaIds) - { - var items = mediaIds.Select(x => new JsonPayload - { - Id = x, - Operation = OperationType.Deleted - }).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } + public static readonly Guid UniqueId = Guid.Parse("B29286DD-2D40-4DDB-B325-681226589FEC"); - /// - /// Converts a macro to a jsonPayload object - /// - /// - /// - /// - internal static JsonPayload FromMedia(IMedia media, OperationType operation) - { - if (media == null) return null; + public override Guid RefresherUniqueId => UniqueId; - var payload = new JsonPayload - { - Id = media.Id, - Path = media.Path, - Operation = operation - }; - return payload; - } + public override string Name => "Media Cache Refresher"; #endregion - #region Sub classes + #region Refresher - public enum OperationType + public override void Refresh(JsonPayload[] payloads) { - Saved, - Trashed, - Deleted + if (payloads == null) return; + + _publishedSnapshotService.Notify(payloads, out var anythingChanged); + + if (anythingChanged) + { + var mediaCache = CacheHelper.IsolatedRuntimeCache.GetCache(); + + Current.ApplicationCache.ClearPartialViewCache(); + + foreach (var payload in payloads) + { + if (payload.ChangeTypes == TreeChangeTypes.Remove) + _idkMap.ClearCache(payload.Id); + + // note: ClearCacheByKeySearch - does StartsWith(...) + + // legacy alert! + // + // library cache library.GetMedia(int mediaId, bool deep) maintains a cache + // of media xml - and of *deep* media xml - using the key + // MediaCacheKey + "_" + mediaId + "_" + deep + // + // this clears the non-deep xml for the current media + // + Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( + $"{CacheKeys.MediaCacheKey}_{payload.Id}_False"); + + // and then, for the entire path, we have to clear whatever might contain the media + // bearing in mind there are probably nasty race conditions here - this is all legacy + var k = $"{CacheKeys.MediaCacheKey}_{payload.Id}_"; + var x = Current.ApplicationCache.RuntimeCache.GetCacheItem(k) + as Tuple; + if (x == null) continue; + var path = x.Item2; + + foreach (var pathId in path.Split(',').Skip(1).Select(int.Parse)) + { + // this clears the deep xml for the medias in the path (skipping -1) + Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( + $"{CacheKeys.MediaCacheKey}_{pathId}_True"); + } + + // repository cache + // it *was* done for each pathId but really that does not make sense + // only need to do it for the current media + mediaCache.Result.ClearCacheItem(RepositoryCacheKeys.GetKey(payload.Id)); + + // remove those that are in the branch + if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove)) + { + var pathid = "," + payload.Id + ","; + mediaCache.Result.ClearCacheObjectTypes((_, v) => v.Path.Contains(pathid)); + } + } + } + + base.Refresh(payloads); } - public class JsonPayload - { - public string Path { get; set; } - public int Id { get; set; } - public OperationType Operation { get; set; } - } + // these events should never trigger + // everything should be JSON - #endregion - - protected override MediaCacheRefresher Instance + public override void RefreshAll() { - get { return this; } - } - - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.MediaCacheRefresherId); } - } - - public override string Name - { - get { return "Clears Media Cache from umbraco.library"; } - } - - public override void Refresh(string jsonPayload) - { - ClearCache(DeserializeFromJsonPayload(jsonPayload)); - base.Refresh(jsonPayload); + throw new NotSupportedException(); } public override void Refresh(int id) { - ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id), OperationType.Saved)); - base.Refresh(id); + throw new NotSupportedException(); + } + + public override void Refresh(Guid id) + { + throw new NotSupportedException(); } public override void Remove(int id) { - ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id), - //NOTE: we'll just default to trashed for this one. - OperationType.Trashed)); - base.Remove(id); + throw new NotSupportedException(); } - private static void ClearCache(params JsonPayload[] payloads) + #endregion + + #region Json + + public class JsonPayload { - if (payloads == null) return; - - ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); - - foreach (var payload in payloads) + public JsonPayload(int id, TreeChangeTypes changeTypes) { - ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id); - - var mediaCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); - - //if there's no path, then just use id (this will occur on permanent deletion like emptying recycle bin) - if (payload.Path.IsNullOrWhiteSpace()) - { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); - } - else - { - foreach (var idPart in payload.Path.Split(',')) - { - int idPartAsInt; - if (int.TryParse(idPart, out idPartAsInt) && mediaCache) - { - mediaCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(idPartAsInt)); - } - - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}_{1}_True", CacheKeys.MediaCacheKey, idPart)); - - // Also clear calls that only query this specific item! - if (idPart == payload.Id.ToString(CultureInfo.InvariantCulture)) - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); - } - } - - // published cache... - PublishedMediaCache.ClearCache(payload.Id); + Id = id; + ChangeTypes = changeTypes; } + + public int Id { get; } + + public TreeChangeTypes ChangeTypes { get; } } + + #endregion + + #region Indirect + + public static void RefreshMediaTypes(CacheHelper cacheHelper) + { + cacheHelper.IsolatedRuntimeCache.ClearCache(); + } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs index e20359fb10..abb44b2a42 100644 --- a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs @@ -1,39 +1,36 @@ using System; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; - -using umbraco.cms.businesslogic.member; using Umbraco.Core.Persistence.Repositories; -using umbraco.interfaces; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Services; namespace Umbraco.Web.Cache { - /// - /// A cache refresher to ensure member cache is updated when members change - /// - /// - /// This is not intended to be used directly in your code and it should be sealed but due to legacy code we cannot seal it. - /// - public class MemberCacheRefresher : TypedCacheRefresherBase + public sealed class MemberCacheRefresher : TypedCacheRefresherBase { + private readonly IdkMap _idkMap; - protected override MemberCacheRefresher Instance + public MemberCacheRefresher(CacheHelper cacheHelper, IdkMap idkMap) + : base(cacheHelper) { - get { return this; } + _idkMap = idkMap; } - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.MemberCacheRefresherId); } - } + #region Define + + protected override MemberCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("E285DF34-ACDC-4226-AE32-C0CB5CF388DA"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Member Cache Refresher"; + + #endregion + + #region Refresher - public override string Name - { - get { return "Clears Member Cache"; } - } - public override void Refresh(int id) { ClearCache(id); @@ -60,17 +57,26 @@ namespace Umbraco.Web.Cache private void ClearCache(int id) { - ApplicationContext.Current.Services.IdkMap.ClearCache(id); - ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); + _idkMap.ClearCache(id); + CacheHelper.ClearPartialViewCache(); - ApplicationContext.Current.ApplicationCache.RuntimeCache. - ClearCacheByKeySearch(string.Format("{0}_{1}", CacheKeys.MemberLibraryCacheKey, id)); - ApplicationContext.Current.ApplicationCache.RuntimeCache. - ClearCacheByKeySearch(string.Format("{0}{1}", CacheKeys.MemberBusinessLogicCacheKey, id)); + CacheHelper.RuntimeCache.ClearCacheByKeySearch($"{CacheKeys.MemberLibraryCacheKey}_{id}"); + CacheHelper.RuntimeCache.ClearCacheByKeySearch($"{CacheKeys.MemberBusinessLogicCacheKey}{id}"); - var memberCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + var memberCache = CacheHelper.IsolatedRuntimeCache.GetCache(); if (memberCache) - memberCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + memberCache.Result.ClearCacheItem(RepositoryCacheKeys.GetKey(id)); } + + #endregion + + #region Indirect + + public static void RefreshMemberTypes(CacheHelper cacheHelper) + { + cacheHelper.IsolatedRuntimeCache.ClearCache(); + } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs b/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs index 6d3ce6bb57..4540f0b495 100644 --- a/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs @@ -1,90 +1,35 @@ -using System; +using System; using System.Linq; -using System.Web; using Newtonsoft.Json; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; - namespace Umbraco.Web.Cache { public sealed class MemberGroupCacheRefresher : JsonCacheRefresherBase { - #region Static helpers + public MemberGroupCacheRefresher(CacheHelper cacheHelper) + : base(cacheHelper) + { } - /// - /// Converts the json to a JsonPayload object - /// - /// - /// - private static JsonPayload[] DeserializeFromJsonPayload(string json) - { - var jsonObject = JsonConvert.DeserializeObject(json); - return jsonObject; - } + #region Define - /// - /// Creates the custom Json payload used to refresh cache amongst the servers - /// - /// - /// - internal static string SerializeToJsonPayload(params IMemberGroup[] groups) - { - var items = groups.Select(FromMemberGroup).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } + protected override MemberGroupCacheRefresher This => this; - /// - /// Converts a macro to a jsonPayload object - /// - /// - /// - private static JsonPayload FromMemberGroup(IMemberGroup group) - { - if (group == null) return null; + public static readonly Guid UniqueId = Guid.Parse("187F236B-BD21-4C85-8A7C-29FBA3D6C00C"); - var payload = new JsonPayload - { - Id = group.Id, - Name = group.Name - }; - return payload; - } + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Member Group Cache Refresher"; #endregion - #region Sub classes + #region Refresher - private class JsonPayload - { - public string Name { get; set; } - public int Id { get; set; } - } - - #endregion - - protected override MemberGroupCacheRefresher Instance - { - get { return this; } - } - - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.MemberGroupCacheRefresherId); } - } - - public override string Name - { - get { return "Clears Member Group Cache"; } - } - - public override void Refresh(string jsonPayload) + public override void Refresh(string json) { ClearCache(); - base.Refresh(jsonPayload); + base.Refresh(json); } public override void Refresh(int id) @@ -104,7 +49,35 @@ namespace Umbraco.Web.Cache // Since we cache by group name, it could be problematic when renaming to // previously existing names - see http://issues.umbraco.org/issue/U4-10846. // To work around this, just clear all the cache items - ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.ClearCache(); + CacheHelper.IsolatedRuntimeCache.ClearCache(); } + + #endregion + + #region Json + + public class JsonPayload + { + public JsonPayload(int id, string name) + { + Id = id; + Name = name; + } + + public string Name { get; } + public int Id { get; } + } + + private JsonPayload[] Deserialize(string json) + { + return JsonConvert.DeserializeObject(json); + } + + internal static string Serialize(params IMemberGroup[] groups) + { + return JsonConvert.SerializeObject(groups.Select(x => new JsonPayload(x.Id, x.Name)).ToArray()); + } + + #endregion } } diff --git a/src/Umbraco.Web/Cache/PageCacheRefresher.cs b/src/Umbraco.Web/Cache/PageCacheRefresher.cs deleted file mode 100644 index d9bb7e9b88..0000000000 --- a/src/Umbraco.Web/Cache/PageCacheRefresher.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Models; -using Umbraco.Core.Sync; -using umbraco; -using umbraco.cms.businesslogic.web; -using Umbraco.Web.PublishedCache.XmlPublishedCache; - -namespace Umbraco.Web.Cache -{ - /// - /// PageCacheRefresher is the standard CacheRefresher used by Load-Balancing in Umbraco. - /// - /// - /// If Load balancing is enabled (by default disabled, is set in umbracoSettings.config) PageCacheRefresher will be called - /// everytime content is added/updated/removed to ensure that the content cache is identical on all load balanced servers - /// - public class PageCacheRefresher : TypedCacheRefresherBase - { - - protected override PageCacheRefresher Instance - { - get { return this; } - } - - /// - /// Gets the unique identifier of the CacheRefresher. - /// - /// The unique identifier. - public override Guid UniqueIdentifier - { - get - { - return new Guid(DistributedCache.PageCacheRefresherId); - } - } - - /// - /// Gets the name of the CacheRefresher - /// - /// The name. - public override string Name - { - get { return "Page Refresher"; } - } - - /// - /// Refreshes all nodes in umbraco. - /// - public override void RefreshAll() - { - if (Suspendable.PageCacheRefresher.CanRefreshDocumentCacheFromDatabase) - content.Instance.RefreshContentFromDatabase(); - ClearCaches(); - base.RefreshAll(); - } - - /// - /// Refreshes the cache for the node with specified id - /// - /// The id. - public override void Refresh(int id) - { - if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) - content.Instance.UpdateDocumentCache(id); - ClearCaches(); - base.Refresh(id); - } - - /// - /// Removes the node with the specified id from the cache - /// - /// The id. - public override void Remove(int id) - { - if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) - content.Instance.ClearDocumentCache(id, false); - ClearCaches(); - base.Remove(id); - } - - public override void Refresh(IContent instance) - { - if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) - content.Instance.UpdateDocumentCache(new Document(instance)); - ClearCaches(); - base.Refresh(instance); - } - - public override void Remove(IContent instance) - { - if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) - content.Instance.ClearDocumentCache(new Document(instance), false); - ClearCaches(); - base.Remove(instance); - } - - private void ClearCaches() - { - ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); - XmlPublishedContent.ClearRequest(); - DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); - DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); - ClearAllIsolatedCacheByEntityType(); - } - } -} diff --git a/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs b/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs index 922e01e2a2..7c3af1129f 100644 --- a/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs @@ -1,8 +1,4 @@ -using System; -using System.Xml; -using Newtonsoft.Json; -using umbraco.cms.businesslogic.web; -using Umbraco.Core; +using System; using Umbraco.Core.Cache; using Umbraco.Core.Models; @@ -10,20 +6,23 @@ namespace Umbraco.Web.Cache { public sealed class PublicAccessCacheRefresher : CacheRefresherBase { - protected override PublicAccessCacheRefresher Instance - { - get { return this; } - } + public PublicAccessCacheRefresher(CacheHelper cacheHelper) + : base(cacheHelper) + { } - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.PublicAccessCacheRefresherId); } - } + #region Define - public override string Name - { - get { return "Public access cache refresher"; } - } + protected override PublicAccessCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("1DB08769-B104-4F8B-850E-169CAC1DF2EC"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Public Access Cache Refresher"; + + #endregion + + #region Refresher public override void Refresh(Guid id) { @@ -48,5 +47,7 @@ namespace Umbraco.Web.Cache ClearAllIsolatedCacheByEntityType(); base.Remove(id); } + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs index cef308c52b..4dd7282f98 100644 --- a/src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs @@ -1,27 +1,30 @@ using System; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; namespace Umbraco.Web.Cache { public sealed class RelationTypeCacheRefresher : CacheRefresherBase { - protected override RelationTypeCacheRefresher Instance - { - get { return this; } - } + public RelationTypeCacheRefresher(CacheHelper cacheHelper) + : base(cacheHelper) + { } - public override Guid UniqueIdentifier - { - get { return DistributedCache.RelationTypeCacheRefresherGuid; } - } + #region Define - public override string Name - { - get { return "Relation Type Cache Refresher"; } - } + protected override RelationTypeCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("D8375ABA-4FB3-4F86-B505-92FBA1B6F7C9"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Relation Type Cache Refresher"; + + #endregion + + #region Refresher public override void RefreshAll() { @@ -31,8 +34,8 @@ namespace Umbraco.Web.Cache public override void Refresh(int id) { - var cache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); - if (cache) cache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + var cache = CacheHelper.IsolatedRuntimeCache.GetCache(); + if (cache) cache.Result.ClearCacheItem(RepositoryCacheKeys.GetKey(id)); base.Refresh(id); } @@ -44,9 +47,11 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { - var cache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); - if (cache) cache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + var cache = CacheHelper.IsolatedRuntimeCache.GetCache(); + if (cache) cache.Result.ClearCacheItem(RepositoryCacheKeys.GetKey(id)); base.Remove(id); } + + #endregion } } diff --git a/src/Umbraco.Web/Cache/StylesheetCacheRefresher.cs b/src/Umbraco.Web/Cache/StylesheetCacheRefresher.cs deleted file mode 100644 index 959a937e7b..0000000000 --- a/src/Umbraco.Web/Cache/StylesheetCacheRefresher.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.ComponentModel; -using Umbraco.Core; -using Umbraco.Core.Cache; - -namespace Umbraco.Web.Cache -{ - /// - /// A cache refresher to ensure stylesheet cache is refreshed when stylesheets change - /// - [Obsolete("This is no longer used and will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class StylesheetCacheRefresher : CacheRefresherBase - { - protected override StylesheetCacheRefresher Instance - { - get { return this; } - } - - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.StylesheetCacheRefresherId); } - } - - public override string Name - { - get { return "Stylesheet cache refresher"; } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/StylesheetPropertyCacheRefresher.cs b/src/Umbraco.Web/Cache/StylesheetPropertyCacheRefresher.cs deleted file mode 100644 index 5b72e0384b..0000000000 --- a/src/Umbraco.Web/Cache/StylesheetPropertyCacheRefresher.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.ComponentModel; -using Umbraco.Core; -using Umbraco.Core.Cache; - -namespace Umbraco.Web.Cache -{ - /// - /// A cache refresher to ensure stylesheet property cache is refreshed when stylesheet properties change - /// - [Obsolete("This is no longer used and will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class StylesheetPropertyCacheRefresher : CacheRefresherBase - { - protected override StylesheetPropertyCacheRefresher Instance - { - get { return this; } - } - - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.StylesheetPropertyCacheRefresherId); } - } - - public override string Name - { - get { return "Stylesheet property cache refresher"; } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs b/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs index 783d19a664..fa89ba0854 100644 --- a/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs @@ -1,36 +1,33 @@ using System; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; - -using umbraco; -using umbraco.interfaces; +using Umbraco.Core.Services; namespace Umbraco.Web.Cache { - - /// - /// A cache refresher to ensure template cache is updated when members change - /// - /// - /// This is not intended to be used directly in your code and it should be sealed but due to legacy code we cannot seal it. - /// - public class TemplateCacheRefresher : CacheRefresherBase + public sealed class TemplateCacheRefresher : CacheRefresherBase { - protected override TemplateCacheRefresher Instance + private readonly IdkMap _idkMap; + + public TemplateCacheRefresher(CacheHelper cacheHelper, IdkMap idkMap) + : base(cacheHelper) { - get { return this; } + _idkMap = idkMap; } - public override string Name - { - get { return "Template cache refresher"; } - } + #region Define - public override Guid UniqueIdentifier - { - get { return DistributedCache.TemplateRefresherGuid; } - } + protected override TemplateCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("DD12B6A0-14B9-46e8-8800-C154F74047C8"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "Template Cache Refresher"; + + #endregion + + #region Refresher public override void Refresh(int id) { @@ -54,13 +51,13 @@ namespace Umbraco.Web.Cache private void RemoveFromCache(int id) { - ApplicationContext.Current.Services.IdkMap.ClearCache(id); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem( - string.Format("{0}{1}", CacheKeys.TemplateFrontEndCacheKey, id)); + _idkMap.ClearCache(id); + CacheHelper.RuntimeCache.ClearCacheItem($"{CacheKeys.TemplateFrontEndCacheKey}{id}"); //need to clear the runtime cache for templates ClearAllIsolatedCacheByEntityType(); } + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs deleted file mode 100644 index 2941357f19..0000000000 --- a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Web.Script.Serialization; -using umbraco; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Models; -using System.Linq; -using Newtonsoft.Json; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Sync; - -namespace Umbraco.Web.Cache -{ - /// - /// A cache refresher used for non-published content, this is primarily to notify Examine indexes to update and to refresh the RuntimeCacheRefresher - /// - public sealed class UnpublishedPageCacheRefresher : TypedCacheRefresherBase, IJsonCacheRefresher - { - protected override UnpublishedPageCacheRefresher Instance - { - get { return this; } - } - - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.UnpublishedPageCacheRefresherId); } - } - - public override string Name - { - get { return "Unpublished Page Refresher"; } - } - - #region Static helpers - - /// - /// Converts the json to a JsonPayload object - /// - /// - /// - internal static JsonPayload[] DeserializeFromJsonPayload(string json) - { - var jsonObject = JsonConvert.DeserializeObject(json); - return jsonObject; - } - - - internal static string SerializeToJsonPayloadForPermanentDeletion(params int[] contentIds) - { - var items = contentIds.Select(x => new JsonPayload - { - Id = x, - Operation = OperationType.Deleted - }).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } - - #endregion - - #region Sub classes - - internal enum OperationType - { - Deleted - } - - internal class JsonPayload - { - public int Id { get; set; } - public OperationType Operation { get; set; } - } - - #endregion - - public override void RefreshAll() - { - ApplicationContext.Current.Services.IdkMap.ClearCache(); - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); - base.RefreshAll(); - } - - public override void Refresh(int id) - { - ApplicationContext.Current.Services.IdkMap.ClearCache(id); - ClearRepositoryCacheItemById(id); - ClearAllIsolatedCacheByEntityType(); - content.Instance.UpdateSortOrder(id); - DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); - base.Refresh(id); - } - - public override void Remove(int id) - { - ApplicationContext.Current.Services.IdkMap.ClearCache(id); - ClearRepositoryCacheItemById(id); - ClearAllIsolatedCacheByEntityType(); - DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); - base.Remove(id); - } - - - public override void Refresh(IContent instance) - { - ApplicationContext.Current.Services.IdkMap.ClearCache(instance.Id); - ClearRepositoryCacheItemById(instance.Id); - ClearAllIsolatedCacheByEntityType(); - content.Instance.UpdateSortOrder(instance); - DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); - base.Refresh(instance); - } - - public override void Remove(IContent instance) - { - ApplicationContext.Current.Services.IdkMap.ClearCache(instance.Id); - ClearRepositoryCacheItemById(instance.Id); - ClearAllIsolatedCacheByEntityType(); - DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); - base.Remove(instance); - } - - /// - /// Implement the IJsonCacheRefresher so that we can bulk delete the cache based on multiple IDs for when the recycle bin is emptied - /// - /// - public void Refresh(string jsonPayload) - { - ClearAllIsolatedCacheByEntityType(); - - foreach (var payload in DeserializeFromJsonPayload(jsonPayload)) - { - ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id); - ClearRepositoryCacheItemById(payload.Id); - content.Instance.UpdateSortOrder(payload.Id); - } - - DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); - - OnCacheUpdated(Instance, new CacheRefresherEventArgs(jsonPayload, MessageType.RefreshByJson)); - } - - private void ClearRepositoryCacheItemById(int id) - { - var contentCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); - if (contentCache) - { - contentCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/UserCacheRefresher.cs b/src/Umbraco.Web/Cache/UserCacheRefresher.cs index 861d5e6abf..6166c65ce5 100644 --- a/src/Umbraco.Web/Cache/UserCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UserCacheRefresher.cs @@ -1,30 +1,30 @@ using System; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; namespace Umbraco.Web.Cache { - /// - /// Handles User cache invalidation/refreshing - /// public sealed class UserCacheRefresher : CacheRefresherBase { - protected override UserCacheRefresher Instance - { - get { return this; } - } + public UserCacheRefresher(CacheHelper cacheHelper) + : base(cacheHelper) + { } - public override Guid UniqueIdentifier - { - get { return Guid.Parse(DistributedCache.UserCacheRefresherId); } - } + #region Define - public override string Name - { - get { return "User cache refresher"; } - } + protected override UserCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("E057AF6D-2EE6-41F4-8045-3694010F0AA6"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "User Cache Refresher"; + + #endregion + + #region Refresher public override void RefreshAll() { @@ -40,11 +40,12 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { - var userCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + var userCache = CacheHelper.IsolatedRuntimeCache.GetCache(); if (userCache) - userCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); - + userCache.Result.ClearCacheItem(RepositoryCacheKeys.GetKey(id)); + base.Remove(id); } + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/UserGroupCacheRefresher.cs b/src/Umbraco.Web/Cache/UserGroupCacheRefresher.cs index 5fd1e4dfa4..deb49ad624 100644 --- a/src/Umbraco.Web/Cache/UserGroupCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UserGroupCacheRefresher.cs @@ -1,8 +1,8 @@ using System; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; namespace Umbraco.Web.Cache { @@ -14,25 +14,28 @@ namespace Umbraco.Web.Cache /// public sealed class UserGroupCacheRefresher : CacheRefresherBase { - protected override UserGroupCacheRefresher Instance - { - get { return this; } - } + public UserGroupCacheRefresher(CacheHelper cacheHelper) + : base(cacheHelper) + { } - public override Guid UniqueIdentifier - { - get { return Guid.Parse(DistributedCache.UserGroupCacheRefresherId); } - } + #region Define - public override string Name - { - get { return "User group cache refresher"; } - } + protected override UserGroupCacheRefresher This => this; + + public static readonly Guid UniqueId = Guid.Parse("45178038-B232-4FE8-AA1A-F2B949C44762"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "User Group Cache Refresher"; + + #endregion + + #region Refresher public override void RefreshAll() { ClearAllIsolatedCacheByEntityType(); - var userGroupCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + var userGroupCache = CacheHelper.IsolatedRuntimeCache.GetCache(); if (userGroupCache) { userGroupCache.Result.ClearCacheByKeySearch(UserGroupRepository.GetByAliasCacheKeyPrefix); @@ -52,10 +55,10 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { - var userGroupCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + var userGroupCache = CacheHelper.IsolatedRuntimeCache.GetCache(); if (userGroupCache) { - userGroupCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + userGroupCache.Result.ClearCacheItem(RepositoryCacheKeys.GetKey(id)); userGroupCache.Result.ClearCacheByKeySearch(UserGroupRepository.GetByAliasCacheKeyPrefix); } @@ -64,6 +67,7 @@ namespace Umbraco.Web.Cache base.Remove(id); } - + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/UserGroupPermissionsCacheRefresher.cs b/src/Umbraco.Web/Cache/UserGroupPermissionsCacheRefresher.cs index 405da0b306..bd2ad50ef5 100644 --- a/src/Umbraco.Web/Cache/UserGroupPermissionsCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UserGroupPermissionsCacheRefresher.cs @@ -1,8 +1,6 @@ using System; using System.ComponentModel; -using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Models.Membership; namespace Umbraco.Web.Cache { @@ -10,21 +8,24 @@ namespace Umbraco.Web.Cache [EditorBrowsable(EditorBrowsableState.Never)] public sealed class UserGroupPermissionsCacheRefresher : CacheRefresherBase { - protected override UserGroupPermissionsCacheRefresher Instance - { - get { return this; } - } + public UserGroupPermissionsCacheRefresher(CacheHelper cacheHelper) + : base(cacheHelper) + { } - public override Guid UniqueIdentifier - { - get { return Guid.Parse(DistributedCache.UserGroupPermissionsCacheRefresherId); } - } + #region Define + protected override UserGroupPermissionsCacheRefresher This => this; - public override string Name - { - get { return "User group permissions cache refresher"; } - } - + public static readonly Guid UniqueId = Guid.Parse("840AB9C5-5C0B-48DB-A77E-29FE4B80CD3A"); + + public override Guid RefresherUniqueId => UniqueId; + + public override string Name => "User Group Permissions Cache Refresher"; + + #endregion + + #region Refresher + + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/CacheHelperExtensions.cs b/src/Umbraco.Web/CacheHelperExtensions.cs index 1b0451a999..7d3b513e76 100644 --- a/src/Umbraco.Web/CacheHelperExtensions.cs +++ b/src/Umbraco.Web/CacheHelperExtensions.cs @@ -1,43 +1,41 @@ -using System; +using System; using System.Web; using System.Web.Caching; using System.Web.Mvc; using System.Web.Mvc.Html; -using Umbraco.Core; -using umbraco.cms.businesslogic.web; using Umbraco.Core.Cache; namespace Umbraco.Web { - /// - /// Extension methods for the cache helper - /// - public static class CacheHelperExtensions - { + /// + /// Extension methods for the cache helper + /// + public static class CacheHelperExtensions + { - public const string PartialViewCacheKey = "Umbraco.Web.PartialViewCacheKey"; - - /// - /// Outputs and caches a partial view in MVC - /// - /// - /// - /// - /// - /// - /// used to cache the partial view, this key could change if it is cached by page or by member - /// - /// - public static IHtmlString CachedPartialView( - this CacheHelper cacheHelper, - HtmlHelper htmlHelper, - string partialViewName, - object model, - int cachedSeconds, - string cacheKey, - ViewDataDictionary viewData = null) - { + public const string PartialViewCacheKey = "Umbraco.Web.PartialViewCacheKey"; + + /// + /// Outputs and caches a partial view in MVC + /// + /// + /// + /// + /// + /// + /// used to cache the partial view, this key could change if it is cached by page or by member + /// + /// + public static IHtmlString CachedPartialView( + this CacheHelper cacheHelper, + HtmlHelper htmlHelper, + string partialViewName, + object model, + int cachedSeconds, + string cacheKey, + ViewDataDictionary viewData = null) + { //disable cached partials in debug mode: http://issues.umbraco.org/issue/U4-5940 if (htmlHelper.ViewContext.HttpContext.IsDebuggingEnabled) { @@ -46,19 +44,19 @@ namespace Umbraco.Web } return cacheHelper.RuntimeCache.GetCacheItem( - PartialViewCacheKey + cacheKey, + PartialViewCacheKey + cacheKey, () => htmlHelper.Partial(partialViewName, model, viewData), - priority: CacheItemPriority.NotRemovable, //not removable, the same as macros (apparently issue #27610) - timeout: new TimeSpan(0, 0, 0, cachedSeconds)); - } + priority: CacheItemPriority.NotRemovable, //not removable, the same as macros (apparently issue #27610) + timeout: new TimeSpan(0, 0, 0, cachedSeconds)); + } - /// - /// Clears the cache for partial views - /// - /// - public static void ClearPartialViewCache(this CacheHelper cacheHelper) - { - cacheHelper.RuntimeCache.ClearCacheByKeySearch(PartialViewCacheKey); - } - } + /// + /// Clears the cache for partial views + /// + /// + public static void ClearPartialViewCache(this CacheHelper cacheHelper) + { + cacheHelper.RuntimeCache.ClearCacheByKeySearch(PartialViewCacheKey); + } + } } diff --git a/src/Umbraco.Web/CanvasDesignerUtility.cs b/src/Umbraco.Web/CanvasDesignerUtility.cs index d4ad46a4aa..512626e4e4 100644 --- a/src/Umbraco.Web/CanvasDesignerUtility.cs +++ b/src/Umbraco.Web/CanvasDesignerUtility.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using System.Linq; using System.Text.RegularExpressions; +using Umbraco.Web.Composing; namespace Umbraco.Web { @@ -34,7 +35,7 @@ namespace Umbraco.Web internal static string frontBasePath = HttpContext.Current.Server.MapPath(@"\css\canvasdesigner\"); // get style box by tag - internal static String GetStyleBlock(string source, string name) + internal static String GetStyleBlock(string source, string name) { string startTag = string.Format("/***start-{0}***/", name); @@ -55,7 +56,7 @@ namespace Umbraco.Web { // Load current page - var contentService = ApplicationContext.Current.Services.ContentService; + var contentService = Current.Services.ContentService; IContent content = contentService.GetById(pageId); // Get less file path from the page Id @@ -79,7 +80,7 @@ namespace Umbraco.Web } // Get inherited pageId with canvasdesigner - internal static int GetParentOrSelfTunedPageId(string[] path, bool preview) + internal static int GetParentOrSelfTunedPageId(string[] path, bool preview) { string styleCanvasdesigner = preview ? @"{0}{1}.less" : "{0}{1}.css"; @@ -116,7 +117,7 @@ namespace Umbraco.Web { // Load current page - var contentService = ApplicationContext.Current.Services.ContentService; + var contentService = Current.Services.ContentService; IContent content = contentService.GetById(pageId); // Get less file path from the page Id @@ -191,11 +192,11 @@ namespace Umbraco.Web } // Save and publish less style - internal static void SaveAndPublishStyle(string parameters, int pageId, bool inherited) + internal static void SaveAndPublishStyle(string parameters, int pageId, bool inherited) { // Get inherited tuned pageId and path - var contentService = ApplicationContext.Current.Services.ContentService; + var contentService = Current.Services.ContentService; IContent content = contentService.GetById(pageId); int inheritedTunedPageId = CanvasDesignerUtility.GetParentOrSelfTunedPageId(content.Path.Split(','), true); @@ -208,7 +209,7 @@ namespace Umbraco.Web // Update pageId if parameters have changed if (inherited) pageId = inheritedTunedPageId; - + // Create front directory if necesary if (!Directory.Exists(frontBasePath)) Directory.CreateDirectory(frontBasePath); @@ -253,7 +254,7 @@ namespace Umbraco.Web { // Get inherited tuned pageId and path - var contentService = ApplicationContext.Current.Services.ContentService; + var contentService = Current.Services.ContentService; IContent content = contentService.GetById(pageId); int inheritedTunedPageId = CanvasDesignerUtility.GetParentOrSelfTunedPageId(content.Path.Split(','), true); diff --git a/src/Umbraco.Web/Components/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Web/Components/DatabaseServerRegistrarAndMessengerComponent.cs new file mode 100644 index 0000000000..f679b00943 --- /dev/null +++ b/src/Umbraco.Web/Components/DatabaseServerRegistrarAndMessengerComponent.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Examine; +using LightInject; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; +using Umbraco.Web.Composing; +using Umbraco.Web.Routing; +using Umbraco.Web.Scheduling; +using Umbraco.Examine; +using Umbraco.Web.Search; + +namespace Umbraco.Web.Components +{ + /// + /// Ensures that servers are automatically registered in the database, when using the database server registrar. + /// + /// + /// At the moment servers are automatically registered upon first request and then on every + /// request but not more than once per (configurable) period. This really is "for information & debug" purposes so + /// we can look at the table and see what servers are registered - but the info is not used anywhere. + /// Should we actually want to use this, we would need a better and more deterministic way of figuring + /// out the "server address" ie the address to which server-to-server requests should be sent - because it + /// probably is not the "current request address" - especially in multi-domains configurations. + /// + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public sealed class DatabaseServerRegistrarAndMessengerComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + private object _locker = new object(); + private DatabaseServerRegistrar _registrar; + private BatchedDatabaseServerMessenger _messenger; + private IRuntimeState _runtime; + private ILogger _logger; + private IServerRegistrationService _registrationService; + private BackgroundTaskRunner _touchTaskRunner; + private BackgroundTaskRunner _processTaskRunner; + private bool _started; + private IBackgroundTask[] _tasks; + private IExamineManager _examineManager; + + public override void Compose(Composition composition) + { + //fixme inject IUmbracoSettingsSection + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) return; + + composition.SetServerMessenger(factory => + { + var runtime = factory.GetInstance(); + var databaseFactory = factory.GetInstance(); + var globalSettings = factory.GetInstance(); + var proflog = factory.GetInstance(); + var scopeProvider = factory.GetInstance(); + var sqlContext = factory.GetInstance(); + + return new BatchedDatabaseServerMessenger( + runtime, databaseFactory, scopeProvider, sqlContext, proflog, globalSettings, + true, + //Default options for web including the required callbacks to build caches + new DatabaseServerMessengerOptions + { + //These callbacks will be executed if the server has not been synced + // (i.e. it is a new server or the lastsynced.txt file has been removed) + InitializingCallbacks = new Action[] + { + //rebuild the xml cache file if the server is not synced + () => + { + // rebuild the published snapshot caches entirely, if the server is not synced + // this is equivalent to DistributedCache RefreshAll... but local only + // (we really should have a way to reuse RefreshAll... locally) + // note: refresh all content & media caches does refresh content types too + var svc = Current.PublishedSnapshotService; + svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) }); + svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _, out _); + svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _); + }, + + //rebuild indexes if the server is not synced + // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific + // indexes then they can adjust this logic themselves. + () => ExamineComponent.RebuildIndexes(false, _examineManager, _logger) + } + }); + }); + } + + public void Initialize(IRuntimeState runtime, IServerRegistrar serverRegistrar, IServerMessenger serverMessenger, IServerRegistrationService registrationService, ILogger logger, IExamineManager examineManager) + { + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) return; + + _registrar = serverRegistrar as DatabaseServerRegistrar; + if (_registrar == null) throw new Exception("panic: registar."); + + _messenger = serverMessenger as BatchedDatabaseServerMessenger; + if (_messenger == null) throw new Exception("panic: messenger"); + + _runtime = runtime; + _logger = logger; + _registrationService = registrationService; + _examineManager = examineManager; + + _touchTaskRunner = new BackgroundTaskRunner("ServerRegistration", + new BackgroundTaskRunnerOptions { AutoStart = true }, logger); + _processTaskRunner = new BackgroundTaskRunner("ServerInstProcess", + new BackgroundTaskRunnerOptions { AutoStart = true }, logger); + + //We will start the whole process when a successful request is made + UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce; + } + + /// + /// Handle when a request is made + /// + /// + /// + /// + /// We require this because: + /// - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest + /// - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest + /// we are safe, UmbracoApplicationUrl has been initialized + /// + private void RegisterBackgroundTasksOnce(object sender, RoutableAttemptEventArgs e) + { + switch (e.Outcome) + { + case EnsureRoutableOutcome.IsRoutable: + case EnsureRoutableOutcome.NotDocumentRequest: + UmbracoModule.RouteAttempt -= RegisterBackgroundTasksOnce; + RegisterBackgroundTasks(); + break; + } + } + + private void RegisterBackgroundTasks() + { + // only perform this one time ever + LazyInitializer.EnsureInitialized(ref _tasks, ref _started, ref _locker, () => + { + var serverAddress = _runtime.ApplicationUrl.ToString(); + + return new[] + { + RegisterInstructionProcess(), + RegisterTouchServer(_registrationService, serverAddress) + }; + }); + } + + private IBackgroundTask RegisterInstructionProcess() + { + var task = new InstructionProcessTask(_processTaskRunner, + 60000, //delay before first execution + _messenger.Options.ThrottleSeconds*1000, //amount of ms between executions + _messenger); + _processTaskRunner.TryAdd(task); + return task; + } + + private IBackgroundTask RegisterTouchServer(IServerRegistrationService registrationService, string serverAddress) + { + var task = new TouchServerTask(_touchTaskRunner, + 15000, //delay before first execution + _registrar.Options.RecurringSeconds*1000, //amount of ms between executions + registrationService, _registrar, serverAddress, _logger); + _touchTaskRunner.TryAdd(task); + return task; + } + + private class InstructionProcessTask : RecurringTaskBase + { + private readonly DatabaseServerMessenger _messenger; + + public InstructionProcessTask(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + DatabaseServerMessenger messenger) + : base(runner, delayMilliseconds, periodMilliseconds) + { + _messenger = messenger; + } + + public override bool IsAsync => false; + + /// + /// Runs the background task. + /// + /// A value indicating whether to repeat the task. + public override bool PerformRun() + { + // TODO what happens in case of an exception? + _messenger.Sync(); + return true; // repeat + } + } + + private class TouchServerTask : RecurringTaskBase + { + private readonly IServerRegistrationService _svc; + private readonly DatabaseServerRegistrar _registrar; + private readonly string _serverAddress; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + public TouchServerTask(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + IServerRegistrationService svc, DatabaseServerRegistrar registrar, string serverAddress, ILogger logger) + : base(runner, delayMilliseconds, periodMilliseconds) + { + _svc = svc ?? throw new ArgumentNullException(nameof(svc)); + _registrar = registrar; + _serverAddress = serverAddress; + _logger = logger; + } + + public override bool IsAsync => false; + + /// + /// Runs the background task. + /// + /// A value indicating whether to repeat the task. + public override bool PerformRun() + { + try + { + // TouchServer uses a proper unit of work etc underneath so even in a + // background task it is safe to call it without dealing with any scope + _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); + return true; // repeat + } + catch (Exception ex) + { + _logger.Error("Failed to update server record in database.", ex); + return false; // probably stop if we have an error + } + } + } + } +} diff --git a/src/Umbraco.Web/Components/LegacyServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Web/Components/LegacyServerRegistrarAndMessengerComponent.cs new file mode 100644 index 0000000000..d494d239a3 --- /dev/null +++ b/src/Umbraco.Web/Components/LegacyServerRegistrarAndMessengerComponent.cs @@ -0,0 +1,52 @@ +using System; +using LightInject; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Services; +using Umbraco.Web.Runtime; + +namespace Umbraco.Web.Components +{ + // the legacy LB is not enabled by default, because LB is implemented by + // DatabaseServerRegistrarAndMessengerComponent instead + + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + [DisableComponent] // is not enabled by default + public sealed class LegacyServerRegistrarAndMessengerComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public override void Compose(Composition composition) + { + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled == false) return; + + composition.SetServerMessenger(factory => + { + var runtime = factory.GetInstance(); + var logger = factory.GetInstance(); + var userService = factory.GetInstance(); + + return new BatchedWebServiceServerMessenger(() => + { + // we should not proceed to change this if the app/database is not configured since there will + // be no user, plus we don't need to have server messages sent if this is the case. + if (runtime.Level == RuntimeLevel.Run) + { + try + { + var user = userService.GetUserById(UmbracoConfig.For.UmbracoSettings().DistributedCall.UserId); + return Tuple.Create(user.Username, user.RawPasswordValue); + } + catch (Exception e) + { + logger.Error("An error occurred trying to set the IServerMessenger during application startup", e); + return null; + } + } + logger.Warn("Could not initialize the DefaultServerMessenger, the application is not configured or the database is not configured"); + return null; + }); + }); + } + } +} diff --git a/src/Umbraco.Web/Components/NotificationsComponent.cs b/src/Umbraco.Web/Components/NotificationsComponent.cs new file mode 100644 index 0000000000..292bb4bf58 --- /dev/null +++ b/src/Umbraco.Web/Components/NotificationsComponent.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Web._Legacy.Actions; + +namespace Umbraco.Web.Components +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public sealed class NotificationsComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public void Initialize(INotificationService notificationService) + { + ContentService.SentToPublish += (sender, args) => + notificationService.SendNotification(args.Entity, ActionToPublish.Instance); + + //Send notifications for the published action + ContentService.Published += (sender, args) => + args.PublishedEntities.ForEach(content => notificationService.SendNotification(content, ActionPublish.Instance)); + + //Send notifications for the update and created actions + ContentService.Saved += (sender, args) => + { + var newEntities = new List(); + var updatedEntities = new List(); + + //need to determine if this is updating or if it is new + foreach (var entity in args.SavedEntities) + { + var dirty = (IRememberBeingDirty) entity; + if (dirty.WasPropertyDirty("Id")) + { + //it's new + newEntities.Add(entity); + } + else + { + //it's updating + updatedEntities.Add(entity); + } + } + notificationService.SendNotification(newEntities, ActionNew.Instance); + notificationService.SendNotification(updatedEntities, ActionUpdate.Instance); + }; + + //Send notifications for the delete action + ContentService.Deleted += (sender, args) => + args.DeletedEntities.ForEach(content => notificationService.SendNotification(content, ActionDelete.Instance)); + + //Send notifications for the unpublish action + ContentService.UnPublished += (sender, args) => + args.PublishedEntities.ForEach(content => notificationService.SendNotification(content, ActionUnPublish.Instance)); + } + } +} diff --git a/src/Umbraco.Web/Components/PublicAccessComponent.cs b/src/Umbraco.Web/Components/PublicAccessComponent.cs new file mode 100644 index 0000000000..5c18eefc66 --- /dev/null +++ b/src/Umbraco.Web/Components/PublicAccessComponent.cs @@ -0,0 +1,35 @@ +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Web.Composing; + +namespace Umbraco.Web.Components +{ + /// + /// Used to ensure that the public access data file is kept up to date properly + /// + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public sealed class PublicAccessComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public void Initialize() + { + MemberGroupService.Saved += MemberGroupService_Saved; + } + + static void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs e) + { + foreach (var grp in e.SavedEntities) + { + //check if the name has changed + if (grp.AdditionalData.ContainsKey("previousName") + && grp.AdditionalData["previousName"] != null + && grp.AdditionalData["previousName"].ToString().IsNullOrWhiteSpace() == false + && grp.AdditionalData["previousName"].ToString() != grp.Name) + { + Current.Services.PublicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"].ToString(), grp.Name); + } + } + } + } +} diff --git a/src/Umbraco.Web/Composing/CompositionRoots/InstallerCompositionRoot.cs b/src/Umbraco.Web/Composing/CompositionRoots/InstallerCompositionRoot.cs new file mode 100644 index 0000000000..b4f7d435f4 --- /dev/null +++ b/src/Umbraco.Web/Composing/CompositionRoots/InstallerCompositionRoot.cs @@ -0,0 +1,42 @@ +using LightInject; +using Umbraco.Web.Install; +using Umbraco.Web.Install.Controllers; +using Umbraco.Web.Install.InstallSteps; +using Umbraco.Web.Install.Models; + +namespace Umbraco.Web.Composing.CompositionRoots +{ + /// + /// A composition root for dealing with the installer and installer steps + /// + public sealed class InstallerCompositionRoot : ICompositionRoot + { + public void Compose(IServiceRegistry container) + { + //register the installer steps in order + container.RegisterOrdered(typeof(InstallSetupStep), + new[] + { + typeof(NewInstallStep), + typeof(UpgradeStep), + typeof(FilePermissionsStep), + typeof(ConfigureMachineKey), + typeof(DatabaseConfigureStep), + typeof(DatabaseInstallStep), + typeof(DatabaseUpgradeStep), + + //TODO: Add these back once we have a compatible starter kit + //typeof(StarterKitDownloadStep), + //typeof(StarterKitInstallStep), + //typeof(StarterKitCleanupStep), + + typeof(SetUmbracoVersionStep) + }, type => new PerScopeLifetime()); + + container.Register(); + container.Register(); + + + } + } +} diff --git a/src/Umbraco.Web/Composing/CompositionRoots/WebMappingProfilesCompositionRoot.cs b/src/Umbraco.Web/Composing/CompositionRoots/WebMappingProfilesCompositionRoot.cs new file mode 100644 index 0000000000..0c73e64ff2 --- /dev/null +++ b/src/Umbraco.Web/Composing/CompositionRoots/WebMappingProfilesCompositionRoot.cs @@ -0,0 +1,29 @@ +using LightInject; +using Umbraco.Web.Models.Mapping; + +namespace Umbraco.Web.Composing.CompositionRoots +{ + public sealed class WebMappingProfilesCompositionRoot : ICompositionRoot + { + public void Compose(IServiceRegistry container) + { + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + } + } +} diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs new file mode 100644 index 0000000000..b819046a9a --- /dev/null +++ b/src/Umbraco.Web/Composing/Current.cs @@ -0,0 +1,265 @@ +using System; +using System.Threading; +using System.Web; +using LightInject; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Dictionary; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Macros; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence; +using Umbraco.Core.Composing; +using Umbraco.Core.Migrations; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Core.Sync; +using Umbraco.Core._Legacy.PackageActions; +using Umbraco.Web.Cache; +using Umbraco.Web.Editors; +using Umbraco.Web.HealthCheck; +using Umbraco.Web.Media; +using Umbraco.Web.Media.ThumbnailProviders; +using Umbraco.Web.Mvc; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.WebApi; +using Umbraco.Web._Legacy.Actions; +using CoreCurrent = Umbraco.Core.Composing.Current; + +namespace Umbraco.Web.Composing +{ + // see notes in Umbraco.Core.Composing.Current. + public static class Current + { + private static readonly object Locker = new object(); + + private static IUmbracoContextAccessor _umbracoContextAccessor; + + static Current() + { + CoreCurrent.Resetted += (sender, args) => + { + if (_umbracoContextAccessor != null) + ClearUmbracoContext(); + _umbracoContextAccessor = null; + }; + } + + // for UNIT TESTS exclusively! + internal static void Reset() + { + CoreCurrent.Reset(); + } + + /// + /// Gets the DI container. + /// + internal static IServiceContainer Container + => CoreCurrent.Container; + + #region Temp & Special + + // fixme - have to keep this until tests are refactored + // but then, it should all be managed properly in the container + public static IUmbracoContextAccessor UmbracoContextAccessor + { + get + { + if (_umbracoContextAccessor != null) return _umbracoContextAccessor; + return _umbracoContextAccessor = Container.GetInstance(); + } + set => _umbracoContextAccessor = value; // for tests + } + + // clears the "current" umbraco context + // at the moment the "current" umbraco context can end up being disposed and should get cleared + // in the accessor - this should be done differently but for the time being we have to support it + public static void ClearUmbracoContext() + { + lock (Locker) + { + UmbracoContextAccessor.UmbracoContext?.Dispose(); // dispose the one that is being cleared, if any + UmbracoContextAccessor.UmbracoContext = null; + } + } + + #endregion + + #region Web Getters + + public static UmbracoContext UmbracoContext + => UmbracoContextAccessor.UmbracoContext; + + public static DistributedCache DistributedCache + => Container.GetInstance(); + + public static IPublishedShapshot PublishedSnapshot + => Container.GetInstance().PublishedSnapshot; + + public static EventMessages EventMessages + => Container.GetInstance().GetOrDefault(); + + public static UrlProviderCollection UrlProviders + => Container.GetInstance(); + + public static HealthCheckCollectionBuilder HealthCheckCollectionBuilder + => Container.GetInstance(); + + internal static ActionCollectionBuilder ActionCollectionBuilder + => Container.GetInstance(); + + public static ActionCollection Actions + => Container.GetInstance(); + + public static ContentFinderCollection ContentFinders + => Container.GetInstance(); + + public static IContentLastChanceFinder LastChanceContentFinder + => Container.GetInstance(); + + internal static EditorValidatorCollection EditorValidators + => Container.GetInstance(); + + internal static XsltExtensionCollection XsltExtensions + => Container.GetInstance(); + + internal static UmbracoApiControllerTypeCollection UmbracoApiControllerTypes + => Container.GetInstance(); + + internal static SurfaceControllerTypeCollection SurfaceControllerTypes + => Container.GetInstance(); + + public static FilteredControllerFactoryCollection FilteredControllerFactories + => Container.GetInstance(); + + internal static ImageUrlProviderCollection ImageUrlProviders + => Container.GetInstance(); + + internal static IPublishedSnapshotService PublishedSnapshotService + => Container.GetInstance(); + + public static ISiteDomainHelper SiteDomainHelper + => Container.GetInstance(); + + public static ThumbnailProviderCollection ThumbnailProviders + => Container.GetInstance(); + + #endregion + + #region Web Constants + + // these are different - not 'resolving' anything, and nothing that could be managed + // by the container - just registering some sort of application-wide constants or + // settings - but they fit in Current nicely too + + private static Type _defaultRenderMvcControllerType; + + // internal - can only be accessed through Composition at compose time + internal static Type DefaultRenderMvcControllerType + { + get => _defaultRenderMvcControllerType; + set + { + if (value.Implements() == false) + throw new ArgumentException($"Type {value.FullName} does not implement {typeof (IRenderController).FullName}.", nameof(value)); + _defaultRenderMvcControllerType = value; + } + } + + #endregion + + #region Web Actions + + public static void RestartAppPool() + { + // see notes in overload + + var httpContext = HttpContext.Current; + if (httpContext != null) + { + httpContext.Application.Add("AppPoolRestarting", true); + httpContext.User = null; + } + Thread.CurrentPrincipal = null; + HttpRuntime.UnloadAppDomain(); + } + + public static void RestartAppPool(HttpContextBase httpContext) + { + // we're going to put an application wide flag to show that the application is about to restart. + // we're doing this because if there is a script checking if the app pool is fully restarted, then + // it can check if this flag exists... if it does it means the app pool isn't restarted yet. + httpContext.Application.Add("AppPoolRestarting", true); + + // unload app domain - we must null out all identities otherwise we get serialization errors + // http://www.zpqrtbnk.net/posts/custom-iidentity-serialization-issue + httpContext.User = null; + if (HttpContext.Current != null) + HttpContext.Current.User = null; + Thread.CurrentPrincipal = null; + + HttpRuntime.UnloadAppDomain(); + } + + #endregion + + #region Core Getters + + // proxy Core for convenience + + public static IRuntimeState RuntimeState => CoreCurrent.RuntimeState; + + public static TypeLoader TypeLoader => CoreCurrent.TypeLoader; + + public static UrlSegmentProviderCollection UrlSegmentProviders => CoreCurrent.UrlSegmentProviders; + + public static CacheRefresherCollection CacheRefreshers => CoreCurrent.CacheRefreshers; + + public static DataEditorCollection DataEditors => CoreCurrent.DataEditors; + + public static PropertyEditorCollection PropertyEditors => CoreCurrent.PropertyEditors; + + public static ParameterEditorCollection ParameterEditors => CoreCurrent.ParameterEditors; + + internal static ManifestValueValidatorCollection ManifestValidators => CoreCurrent.ManifestValidators; + + internal static PackageActionCollection PackageActions => CoreCurrent.PackageActions; + + internal static PropertyValueConverterCollection PropertyValueConverters => CoreCurrent.PropertyValueConverters; + + internal static IPublishedModelFactory PublishedModelFactory => CoreCurrent.PublishedModelFactory; + + public static IServerMessenger ServerMessenger => CoreCurrent.ServerMessenger; + + public static IServerRegistrar ServerRegistrar => CoreCurrent.ServerRegistrar; + + public static ICultureDictionaryFactory CultureDictionaryFactory => CoreCurrent.CultureDictionaryFactory; + + public static IShortStringHelper ShortStringHelper => CoreCurrent.ShortStringHelper; + + public static ILogger Logger => CoreCurrent.Logger; + + public static IProfiler Profiler => CoreCurrent.Profiler; + + public static ProfilingLogger ProfilingLogger => CoreCurrent.ProfilingLogger; + + public static CacheHelper ApplicationCache => CoreCurrent.ApplicationCache; + + public static ServiceContext Services => CoreCurrent.Services; + + public static IScopeProvider ScopeProvider => CoreCurrent.ScopeProvider; + + public static FileSystems FileSystems => CoreCurrent.FileSystems; + + public static ISqlContext SqlContext=> CoreCurrent.SqlContext; + + public static IPublishedContentTypeFactory PublishedContentTypeFactory => CoreCurrent.PublishedContentTypeFactory; + + #endregion + } +} diff --git a/src/Umbraco.Web/CompositionExtensions.cs b/src/Umbraco.Web/CompositionExtensions.cs new file mode 100644 index 0000000000..881a88c05f --- /dev/null +++ b/src/Umbraco.Web/CompositionExtensions.cs @@ -0,0 +1,206 @@ +using System; +using LightInject; +using Umbraco.Core.Composing; +using Current = Umbraco.Web.Composing.Current; +using Umbraco.Core.Macros; +using Umbraco.Web.Editors; +using Umbraco.Web.HealthCheck; +using Umbraco.Web.Media; +using Umbraco.Web.Media.ThumbnailProviders; +using Umbraco.Web.Mvc; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web._Legacy.Actions; + +// the namespace here is intentional - although defined in Umbraco.Web assembly, +// this class should be visible when using Umbraco.Core.Components, alongside +// Umbraco.Core's own CompositionExtensions class + +// ReSharper disable once CheckNamespace +namespace Umbraco.Core.Components +{ + /// + /// Provides extension methods to the class. + /// + public static class WebCompositionExtensions + { + #region Collection Builders + + /// + /// Gets the actions collection builder. + /// + /// The composition. + /// + internal static ActionCollectionBuilder Actions(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the content finders collection builder. + /// + /// The composition. + /// + internal static XsltExtensionCollectionBuilder XsltExtensions(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the content finders collection builder. + /// + /// The composition. + /// + public static ContentFinderCollectionBuilder ContentFinders(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the editor validators collection builder. + /// + /// The composition. + /// + internal static EditorValidatorCollectionBuilder EditorValidators(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the filtered controller factories collection builder. + /// + /// The composition. + /// + public static FilteredControllerFactoryCollectionBuilder FilderedControllerFactory(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the health checks collection builder. + /// + /// The composition. + public static HealthCheckCollectionBuilder HealthChecks(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the image url providers collection builder. + /// + /// The composition. + internal static ImageUrlProviderCollectionBuilder ImageUrlProviders(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the thumbnail providers collection builder. + /// + /// The composition. + internal static ThumbnailProviderCollectionBuilder ThumbnailProviders(this Composition composition) + => composition.Container.GetInstance(); + + /// + /// Gets the url providers collection builder. + /// + /// The composition. + internal static UrlProviderCollectionBuilder UrlProviders(this Composition composition) + => composition.Container.GetInstance(); + + #endregion + + #region Singletons + + /// + /// Sets the content last chance finder. + /// + /// The type of the content last chance finder. + /// The composition. + public static void SetContentLastChanceFinder(this Composition composition) + where T : IContentLastChanceFinder + { + composition.Container.RegisterSingleton(); + } + + /// + /// Sets the content last chance finder. + /// + /// The composition. + /// A function creating a last chance finder. + public static void SetContentLastChanceFinder(this Composition composition, Func factory) + { + composition.Container.RegisterSingleton(factory); + } + + /// + /// Sets the content last chance finder. + /// + /// The composition. + /// A last chance finder. + public static void SetContentLastChanceFinder(this Composition composition, IContentLastChanceFinder finder) + { + composition.Container.RegisterSingleton(_ => finder); + } + + /// + /// Sets the type of the default rendering controller. + /// + /// The type of the default rendering controller. + /// The composition. + public static void SetDefaultRenderMvcControllerType(this Composition composition) + { + Current.DefaultRenderMvcControllerType = typeof(T); + } + + /// + /// Sets the published snapshot service. + /// + /// The type of the published snapshot service. + /// The composition. + public static void SetPublishedSnapshotService(this Composition composition) + where T : IPublishedSnapshotService + { + composition.Container.RegisterSingleton(); + } + + /// + /// Sets the published snapshot service. + /// + /// The composition. + /// A function creating a published snapshot service. + public static void SetPublishedSnapshotService(this Composition composition, Func factory) + { + composition.Container.RegisterSingleton(factory); + } + + /// + /// Sets the published snapshot service. + /// + /// The composition. + /// A published snapshot service. + public static void SetPublishedSnapshotService(this Composition composition, IPublishedSnapshotService service) + { + composition.Container.RegisterSingleton(_ => service); + } + + /// + /// Sets the site domain helper. + /// + /// The type of the site domain helper. + /// + public static void SetSiteDomainHelper(this Composition composition) + where T : ISiteDomainHelper + { + composition.Container.RegisterSingleton(); + } + + /// + /// Sets the site domain helper. + /// + /// The composition. + /// A function creating a helper. + public static void SetSiteDomainHelper(this Composition composition, Func factory) + { + composition.Container.RegisterSingleton(factory); + } + + /// + /// Sets the site domain helper. + /// + /// The composition. + /// A helper. + public static void SetSiteDomainHelper(this Composition composition, ISiteDomainHelper helper) + { + composition.Container.RegisterSingleton(_ => helper); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/ContextualPublishedCacheExtensions.cs b/src/Umbraco.Web/ContextualPublishedCacheExtensions.cs deleted file mode 100644 index 2901962f07..0000000000 --- a/src/Umbraco.Web/ContextualPublishedCacheExtensions.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System.Linq; -using System.Xml.XPath; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Xml; -using Umbraco.Web.Models; -using Umbraco.Web.PublishedCache; - -namespace Umbraco.Web -{ - /// - /// Provides extension methods to ContextualPublishedCache. - /// - public static class ContextualPublishedCacheExtensions - { - /// - /// Gets a dynamic content identified by its unique identifier. - /// - /// The contextual cache. - /// The content unique identifier. - /// The dynamic content, or null. - public static dynamic GetDynamicById(this ContextualPublishedContentCache cache, int contentId) - { - var content = cache.GetById(contentId); - return content == null ? DynamicNull.Null : new DynamicPublishedContent(content).AsDynamic(); - } - - /// - /// Gets a dynamic content resulting from an XPath query. - /// - /// The contextual cache. - /// The XPath query. - /// Optional XPath variables - /// The dynamic content, or null. - public static dynamic GetDynamicSingleByXPath(this ContextualPublishedContentCache cache, string xpath, params XPathVariable[] vars) - { - var content = cache.GetSingleByXPath(xpath, vars); - return content == null ? DynamicNull.Null : new DynamicPublishedContent(content).AsDynamic(); - } - - /// - /// Gets a dynamic content resulting from an XPath query. - /// - /// The contextual cache. - /// The XPath query. - /// Optional XPath variables - /// The dynamic content, or null. - public static dynamic GetDynamicSingleByXPath(this ContextualPublishedContentCache cache, XPathExpression xpath, params XPathVariable[] vars) - { - var content = cache.GetSingleByXPath(xpath, vars); - return content == null ? DynamicNull.Null : new DynamicPublishedContent(content).AsDynamic(); - } - - /// - /// Gets dynamic contents resulting from an XPath query. - /// - /// The contextual cache. - /// The XPath query. - /// Optional XPath variables - /// The dynamic contents. - public static dynamic GetDynamicByXPath(this ContextualPublishedContentCache cache, string xpath, params XPathVariable[] vars) - { - var content = cache.GetByXPath(xpath, vars); - return new DynamicPublishedContentList(content.Select(c => new DynamicPublishedContent(c))); - } - - /// - /// Gets dynamic contents resulting from an XPath query. - /// - /// The contextual cache. - /// The XPath query. - /// Optional XPath variables - /// The dynamic contents. - public static dynamic GetDynamicByXPath(this ContextualPublishedContentCache cache, XPathExpression xpath, params XPathVariable[] vars) - { - var content = cache.GetByXPath(xpath, vars); - return new DynamicPublishedContentList(content.Select(c => new DynamicPublishedContent(c))); - } - - /// - /// Gets dynamic contents at root. - /// - /// The contextual cache. - /// The dynamic contents. - public static dynamic GetDynamicAtRoot(this ContextualPublishedContentCache cache) - { - var content = cache.GetAtRoot(); - return new DynamicPublishedContentList(content.Select(c => new DynamicPublishedContent(c))); - } - } -} diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index d446df5683..2b70f927b7 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -1,7 +1,4 @@ -using System.Linq; -using System.Web.Mvc; -using System.Web.Security; -using umbraco.cms.businesslogic.member; +using System.Web.Mvc; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Core; @@ -31,15 +28,10 @@ namespace Umbraco.Web.Controllers if (model.RedirectUrl.IsNullOrWhiteSpace() == false) { // validate the redirect url - if (Url.IsLocalUrl(model.RedirectUrl)) - { - return Redirect(model.RedirectUrl); - } - else - { - // if it's not a local url we'll redirect to the root of the current site - return Redirect(base.CurrentPage.Site().Url); - } + // if it's not a local url we'll redirect to the root of the current site + return Redirect(Url.IsLocalUrl(model.RedirectUrl) + ? model.RedirectUrl + : CurrentPage.AncestorOrSelf(1).Url); } //redirect to current page by default diff --git a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs index 9bb8ae7c9a..0364359673 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs @@ -1,7 +1,5 @@ -using System.Linq; -using System.Web.Mvc; +using System.Web.Mvc; using System.Web.Security; -using umbraco.cms.businesslogic.member; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Core; @@ -33,7 +31,7 @@ namespace Umbraco.Web.Controllers } //redirect to current page by default - + return RedirectToCurrentUmbracoPage(); } } diff --git a/src/Umbraco.Web/Controllers/UmbProfileController.cs b/src/Umbraco.Web/Controllers/UmbProfileController.cs index b45723ed30..27c5c32a02 100644 --- a/src/Umbraco.Web/Controllers/UmbProfileController.cs +++ b/src/Umbraco.Web/Controllers/UmbProfileController.cs @@ -1,9 +1,5 @@ using System; -using System.Linq; using System.Web.Mvc; -using System.Web.Security; -using System.Xml; -using umbraco.cms.businesslogic.member; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Core.Security; @@ -17,7 +13,7 @@ namespace Umbraco.Web.Controllers [HttpPost] public ActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model) { - var provider = global::Umbraco.Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); if (provider.IsUmbracoMembershipProvider() == false) { throw new NotSupportedException("Profile editing with the " + typeof(UmbProfileController) + " is not supported when not using the default Umbraco membership provider"); @@ -44,7 +40,7 @@ namespace Umbraco.Web.Controllers return Redirect(model.RedirectUrl); } - //redirect to current page by default + //redirect to current page by default return RedirectToCurrentUmbracoPage(); } } diff --git a/src/Umbraco.Web/Controllers/UmbRegisterController.cs b/src/Umbraco.Web/Controllers/UmbRegisterController.cs index 823d243eec..64f54dd465 100644 --- a/src/Umbraco.Web/Controllers/UmbRegisterController.cs +++ b/src/Umbraco.Web/Controllers/UmbRegisterController.cs @@ -23,10 +23,10 @@ namespace Umbraco.Web.Controllers { model.Name = model.Email; } - + MembershipCreateStatus status; var member = Members.RegisterMember(model, out status, model.LoginOnSuccess); - + switch (status) { case MembershipCreateStatus.Success: @@ -39,6 +39,7 @@ namespace Umbraco.Web.Controllers return Redirect(model.RedirectUrl); } //redirect to current page by default + return RedirectToCurrentUmbracoPage(); case MembershipCreateStatus.InvalidUserName: ModelState.AddModelError((model.UsernameIsEmail || model.Username == null) diff --git a/src/Umbraco.Web/DefaultEventMessagesFactory.cs b/src/Umbraco.Web/DefaultEventMessagesFactory.cs new file mode 100644 index 0000000000..39be829a7d --- /dev/null +++ b/src/Umbraco.Web/DefaultEventMessagesFactory.cs @@ -0,0 +1,29 @@ +using System; +using Umbraco.Core.Events; + +namespace Umbraco.Web +{ + internal class DefaultEventMessagesFactory : IEventMessagesFactory + { + private readonly IEventMessagesAccessor _eventMessagesAccessor; + + public DefaultEventMessagesFactory(IEventMessagesAccessor eventMessagesAccessor) + { + if (eventMessagesAccessor == null) throw new ArgumentNullException(nameof(eventMessagesAccessor)); + _eventMessagesAccessor = eventMessagesAccessor; + } + + public EventMessages Get() + { + var eventMessages = _eventMessagesAccessor.EventMessages; + if (eventMessages == null) + _eventMessagesAccessor.EventMessages = eventMessages = new EventMessages(); + return eventMessages; + } + + public EventMessages GetOrDefault() + { + return _eventMessagesAccessor.EventMessages; + } + } +} diff --git a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs index cb53713e3c..481be85136 100644 --- a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs +++ b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs @@ -1,16 +1,11 @@ using System; using System.Collections.Generic; -using System.Dynamic; using System.Globalization; using System.Linq; -using System.Web; -using Umbraco.Core.Logging; -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.language; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.Composing; namespace Umbraco.Web.Dictionary { @@ -20,78 +15,69 @@ namespace Umbraco.Web.Dictionary /// /// /// TODO: The ICultureDictionary needs to represent the 'fast' way to do dictionary item retrieval - for front-end and back office. - /// The ILocalizationService is the service used for interacting with this data from the database which isn't all that fast + /// The ILocalizationService is the service used for interacting with this data from the database which isn't all that fast /// (even though there is caching involved, if there's lots of dictionary items the caching is not great) /// - public class DefaultCultureDictionary : Umbraco.Core.Dictionary.ICultureDictionary - { - private readonly ILocalizationService _localizationService; + public class DefaultCultureDictionary : Core.Dictionary.ICultureDictionary + { + private readonly ILocalizationService _localizationService; private readonly ICacheProvider _requestCacheProvider; - private readonly CultureInfo _specificCulture = null; + private readonly CultureInfo _specificCulture; - public DefaultCultureDictionary() - : this(ApplicationContext.Current.Services.LocalizationService, ApplicationContext.Current.ApplicationCache.RequestCache) - { - - } + public DefaultCultureDictionary() + : this(Current.Services.LocalizationService, Current.ApplicationCache.RequestCache) + { - public DefaultCultureDictionary(ILocalizationService localizationService, ICacheProvider requestCacheProvider) - { - if (localizationService == null) throw new ArgumentNullException("localizationService"); - if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); - _localizationService = localizationService; - _requestCacheProvider = requestCacheProvider; - } + } + + public DefaultCultureDictionary(ILocalizationService localizationService, ICacheProvider requestCacheProvider) + { + _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + _requestCacheProvider = requestCacheProvider ?? throw new ArgumentNullException(nameof(requestCacheProvider)); + } public DefaultCultureDictionary(CultureInfo specificCulture) - : this(ApplicationContext.Current.Services.LocalizationService, ApplicationContext.Current.ApplicationCache.RequestCache) + : this(Current.Services.LocalizationService, Current.ApplicationCache.RequestCache) { - if (specificCulture == null) throw new ArgumentNullException("specificCulture"); - _specificCulture = specificCulture; + _specificCulture = specificCulture ?? throw new ArgumentNullException(nameof(specificCulture)); } public DefaultCultureDictionary(CultureInfo specificCulture, ILocalizationService localizationService, ICacheProvider requestCacheProvider) { - if (specificCulture == null) throw new ArgumentNullException("specificCulture"); - if (localizationService == null) throw new ArgumentNullException("localizationService"); - if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); - _localizationService = localizationService; - _requestCacheProvider = requestCacheProvider; - _specificCulture = specificCulture; + _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + _requestCacheProvider = requestCacheProvider ?? throw new ArgumentNullException(nameof(requestCacheProvider)); + _specificCulture = specificCulture ?? throw new ArgumentNullException(nameof(specificCulture)); } - /// - /// Returns the dictionary value based on the key supplied - /// - /// - /// - public string this[string key] - { - get - { - var found = _localizationService.GetDictionaryItemByKey(key); - if (found == null) - { - return string.Empty; - } + /// + /// Returns the dictionary value based on the key supplied + /// + /// + /// + public string this[string key] + { + get + { + var found = _localizationService.GetDictionaryItemByKey(key); + if (found == null) + { + return string.Empty; + } - var byLang = found.Translations.FirstOrDefault(x => x.Language.Equals(Language)); - if (byLang == null) - { - return string.Empty; - } + var byLang = found.Translations.FirstOrDefault(x => x.Language.Equals(Language)); + if (byLang == null) + { + return string.Empty; + } - return byLang.Value; - } - } + return byLang.Value; + } + } - /// - /// Returns the current culture - /// - public CultureInfo Culture - { - get { return _specificCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture; } - } + /// + /// Returns the current culture + /// + public CultureInfo Culture => _specificCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture; /// /// Returns the child dictionary entries for a given key @@ -99,7 +85,7 @@ namespace Umbraco.Web.Dictionary /// /// /// - /// NOTE: The result of this is not cached anywhere - the underlying repository does not cache + /// NOTE: The result of this is not cached anywhere - the underlying repository does not cache /// the child lookups because that is done by a query lookup. This method isn't used in our codebase /// so I don't think this is a performance issue but if devs are using this it could be optimized here. /// @@ -132,7 +118,7 @@ namespace Umbraco.Web.Dictionary } private ILanguage Language - { + { get { //ensure it's stored/retrieved from request cache @@ -140,7 +126,6 @@ namespace Umbraco.Web.Dictionary return _requestCacheProvider.GetCacheItem(typeof (DefaultCultureDictionary).Name + "Culture" + Culture.Name, () => _localizationService.GetLanguageByIsoCode(Culture.Name)); } - } - } - + } + } } diff --git a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs index c2662d0616..9c49971014 100644 --- a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs +++ b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionaryFactory.cs @@ -1,16 +1,16 @@ -namespace Umbraco.Web.Dictionary +namespace Umbraco.Web.Dictionary { - /// - /// A culture dictionary factory used to create an Umbraco.Core.Dictionary.ICultureDictionary. - /// - /// - /// In the future this will allow use to potentially store dictionary items elsewhere and allows for maximum flexibility. - /// - internal class DefaultCultureDictionaryFactory : Umbraco.Core.Dictionary.ICultureDictionaryFactory - { - public Umbraco.Core.Dictionary.ICultureDictionary CreateDictionary() - { - return new DefaultCultureDictionary(); - } - } -} \ No newline at end of file + /// + /// A culture dictionary factory used to create an Umbraco.Core.Dictionary.ICultureDictionary. + /// + /// + /// In the future this will allow use to potentially store dictionary items elsewhere and allows for maximum flexibility. + /// + internal class DefaultCultureDictionaryFactory : Umbraco.Core.Dictionary.ICultureDictionaryFactory + { + public Umbraco.Core.Dictionary.ICultureDictionary CreateDictionary() + { + return new DefaultCultureDictionary(); + } + } +} diff --git a/src/Umbraco.Web/Dynamics/DynamicExpression.cs b/src/Umbraco.Web/Dynamics/DynamicExpression.cs deleted file mode 100644 index ae0befbfe8..0000000000 --- a/src/Umbraco.Web/Dynamics/DynamicExpression.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using Umbraco.Core.Dynamics; - -namespace Umbraco.Web.Dynamics -{ - internal static class DynamicExpression - { - //public static bool ConvertDynamicNullToBooleanFalse = false; - - public static Expression Parse(Type resultType, string expression, bool convertDynamicNullToBooleanFalse, params object[] values) - { - //ConvertDynamicNullToBooleanFalse = convertDynamicNullToBooleanFalse; - var parser = new ExpressionParser(null, expression, values, convertDynamicNullToBooleanFalse); - return parser.Parse(resultType); - } - - public static LambdaExpression ParseLambda(Type itType, Type resultType, string expression, bool convertDynamicNullToBooleanFalse, params object[] values) - { - return ParseLambda(new ParameterExpression[] { Expression.Parameter(itType, "") }, resultType, expression, convertDynamicNullToBooleanFalse, values); - } - - public static LambdaExpression ParseLambda(ParameterExpression[] parameters, Type resultType, string expression, bool convertDynamicNullToBooleanFalse, params object[] values) - { - //ConvertDynamicNullToBooleanFalse = convertDynamicNullToBooleanFalse; - var parser = new ExpressionParser(parameters, expression, values, convertDynamicNullToBooleanFalse); - return Expression.Lambda(parser.Parse(resultType), parameters); - } - - public static Expression> ParseLambda(string expression, bool convertDynamicNullToBooleanFalse, params object[] values) - { - return (Expression>)ParseLambda(typeof(T), typeof(S), expression, convertDynamicNullToBooleanFalse, values); - } - - public static Type CreateClass(params DynamicProperty[] properties) - { - return ClassFactory.Instance.GetDynamicClass(properties); - } - - public static Type CreateClass(IEnumerable properties) - { - return ClassFactory.Instance.GetDynamicClass(properties); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Dynamics/DynamicGrouping.cs b/src/Umbraco.Web/Dynamics/DynamicGrouping.cs deleted file mode 100644 index 07ec14ecd3..0000000000 --- a/src/Umbraco.Web/Dynamics/DynamicGrouping.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Collections; -using Umbraco.Core.Models; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Dynamics -{ - public class DynamicGrouping : IEnumerable>, IEnumerable> - { - internal IEnumerable> Inner; - - public DynamicGrouping OrderBy(string expression) - { - return this; - } - - public DynamicGrouping(DynamicPublishedContentList list, string groupBy) - { - Inner = - list - .Items - .Select(node => - { - string predicate = groupBy; - var internalList = new DynamicPublishedContentList(new DynamicPublishedContent[] { node }); - var query = (IQueryable)internalList.Select(predicate, new object[] { }); - var key = query.FirstOrDefault(); - return new - { - Key = key, - Node = node - }; - }) - .Where(item => item.Key != null) - .GroupBy(item => item.Key) - .Select(item => new Grouping() - { - Key = item.Key, - Elements = item.Select(inner => inner.Node) - }); - } - internal DynamicGrouping(IEnumerable> source) - { - this.Inner = source; - } - - IEnumerator> IEnumerable>.GetEnumerator() - { - return Inner.GetEnumerator(); - } - - IEnumerator> IEnumerable>.GetEnumerator() - { - return Inner.GetEnumerator(); - } - - public IEnumerator GetEnumerator() - { - return Inner.GetEnumerator(); - } - } -} diff --git a/src/Umbraco.Web/Dynamics/DynamicPublishedContentIdEqualityComparer.cs b/src/Umbraco.Web/Dynamics/DynamicPublishedContentIdEqualityComparer.cs deleted file mode 100644 index 27bb327bf4..0000000000 --- a/src/Umbraco.Web/Dynamics/DynamicPublishedContentIdEqualityComparer.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Dynamics -{ - internal class DynamicPublishedContentIdEqualityComparer : EqualityComparer - { - - public override bool Equals(DynamicPublishedContent x, DynamicPublishedContent y) - { - //Check whether the compared objects reference the same data. - if (Object.ReferenceEquals(x, y)) return true; - - //Check whether any of the compared objects is null. - if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) - return false; - - //Check whether the nodes ids are equal. - return x.Id == y.Id; - - } - - public override int GetHashCode(DynamicPublishedContent obj) - { - if (Object.ReferenceEquals(obj, null)) return 0; - - //Get hash code for the Name field if it is not null. - int hashId = obj.Id.GetHashCode(); - - return hashId; - } - - } -} diff --git a/src/Umbraco.Web/Dynamics/DynamicPublishedContentListOrdering.cs b/src/Umbraco.Web/Dynamics/DynamicPublishedContentListOrdering.cs deleted file mode 100644 index 3acf23fa61..0000000000 --- a/src/Umbraco.Web/Dynamics/DynamicPublishedContentListOrdering.cs +++ /dev/null @@ -1,251 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Dynamics -{ - public static class DynamicPublishedContentListOrdering - { - - private static TOut Reduce(Func func, DynamicPublishedContent publishedContent) - { - var value = func(publishedContent); - while (value is Func) - { - value = (value as Func)(publishedContent); - } - //when you're sorting a list of properties - //and one of those properties doesn't exist, it will come back as DynamicNull - //when that gets handled by the expression tree parser, it's converted to be false - //which lets .Where work properly with e.g. umbracoNaviHide which may not be defined - //on all properties - //this checks to see if the type of value is bool, and if it is, and it's false - //then return false - //in a case where Reduce is being called, this will return false, which is the - //same as the actual real value - //but when Reduce [a dynamicnode dynamic property call] is called - //it will result in null, (because default(object) is null) - //which will cause the item with the missing property to sort up to the top of the list - if (value.GetType() == typeof(bool) && !((value as bool?) ?? false)) - { - return default(TOut); - } - if (value.GetType() == typeof(string) && string.IsNullOrEmpty((value as string))) - { - return default(TOut); - } - return (TOut)value; - } - public static IOrderedQueryable OrderBy(object source, object key) - { - IEnumerable typedSource = source as IEnumerable; - LambdaExpression lambda = key as LambdaExpression; - //if the lambda we have returns an actual property, not a dynamic one, - //then the TOut of the func will be the actual type, not object - //Func func = (Func)lambda.Compile(); - var func = lambda.Compile(); - var TOut = func.GetType().GetGenericArguments()[1]; - IOrderedQueryable result = null; - if (TOut == typeof(Func)) - { - result = (IOrderedQueryable)typedSource - .OrderBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(object)) - { - result = (IOrderedQueryable)typedSource - .OrderBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(bool)) - { - result = (IOrderedQueryable)typedSource - .OrderBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(decimal)) - { - result = (IOrderedQueryable)typedSource - .OrderBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(int)) - { - result = (IOrderedQueryable)typedSource - .OrderBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(string)) - { - result = (IOrderedQueryable)typedSource - .OrderBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(DateTime)) - { - result = (IOrderedQueryable)typedSource - .OrderBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - return result; - } - public static IOrderedQueryable ThenBy(object source, object key) - { - IOrderedQueryable typedSource = source as IOrderedQueryable; - LambdaExpression lambda = key as LambdaExpression; - var func = lambda.Compile(); - var TOut = func.GetType().GetGenericArguments()[1]; - IOrderedQueryable result = null; - if (TOut == typeof(Func)) - { - result = (IOrderedQueryable)typedSource - .ThenBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(object)) - { - result = (IOrderedQueryable)typedSource - .ThenBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(bool)) - { - result = (IOrderedQueryable)typedSource - .ThenBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(decimal)) - { - result = (IOrderedQueryable)typedSource - .ThenBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(int)) - { - result = (IOrderedQueryable)typedSource - .ThenBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(string)) - { - result = (IOrderedQueryable)typedSource - .ThenBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(DateTime)) - { - result = (IOrderedQueryable)typedSource - .ThenBy(x => Reduce(func as Func, x)) - .AsQueryable(); - } - return result; - } - public static IOrderedQueryable OrderByDescending(object source, object key) - { - IEnumerable typedSource = source as IEnumerable; - LambdaExpression lambda = key as LambdaExpression; - var func = lambda.Compile(); - var TOut = func.GetType().GetGenericArguments()[1]; - IOrderedQueryable result = null; - if (TOut == typeof(Func)) - { - result = (IOrderedQueryable)typedSource - .OrderByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(object)) - { - result = (IOrderedQueryable)typedSource - .OrderByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(bool)) - { - result = (IOrderedQueryable)typedSource - .OrderByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(decimal)) - { - result = (IOrderedQueryable)typedSource - .OrderByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(int)) - { - result = (IOrderedQueryable)typedSource - .OrderByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(string)) - { - result = (IOrderedQueryable)typedSource - .OrderByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(DateTime)) - { - result = (IOrderedQueryable)typedSource - .OrderByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - return result; - } - public static IOrderedQueryable ThenByDescending(object source, object key) - { - IOrderedQueryable typedSource = source as IOrderedQueryable; - LambdaExpression lambda = key as LambdaExpression; - var func = lambda.Compile(); - var TOut = func.GetType().GetGenericArguments()[1]; - IOrderedQueryable result = null; - if (TOut == typeof(Func)) - { - result = (IOrderedQueryable)typedSource - .ThenByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(object)) - { - result = (IOrderedQueryable)typedSource - .ThenByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(bool)) - { - result = (IOrderedQueryable)typedSource - .ThenByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(decimal)) - { - result = (IOrderedQueryable)typedSource - .ThenByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(int)) - { - result = (IOrderedQueryable)typedSource - .ThenByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(string)) - { - result = (IOrderedQueryable)typedSource - .ThenByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - if (TOut == typeof(DateTime)) - { - result = (IOrderedQueryable)typedSource - .ThenByDescending(x => Reduce(func as Func, x)) - .AsQueryable(); - } - return result; - } - - } -} - diff --git a/src/Umbraco.Web/Dynamics/DynamicQueryable.cs b/src/Umbraco.Web/Dynamics/DynamicQueryable.cs deleted file mode 100644 index 122fa0cd29..0000000000 --- a/src/Umbraco.Web/Dynamics/DynamicQueryable.cs +++ /dev/null @@ -1,322 +0,0 @@ -//Copyright (C) Microsoft Corporation. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Linq.Expressions; -using System.Diagnostics; -using Umbraco.Core.Dynamics; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Dynamics -{ - //TODO: Much of this can move to Umbraco.Core.Dynamics - but somehow need to remove all the hard coded references to things like - // dynamicnull, etc... - //NOTE: The OrderBy stuff here seems to be a bit hacked with hard references to umbraco node objects so don't think it can be - // re-used which is why we have the OrderBy stuff that hasn't been hacked in teh Umbraco.Core.Dynamics - - internal static class DynamicQueryable - { - public static IQueryable Where(this IQueryable source, string predicate, params object[] values) - { - return (IQueryable)Where((IQueryable)source, predicate, values); - } - - public static IQueryable Where(this IQueryable source, string predicate, params object[] values) - { - if (source == null) throw new ArgumentNullException("source"); - if (predicate == null) throw new ArgumentNullException("predicate"); - LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, true, values); - if (lambda.Parameters.Count > 0 && lambda.Parameters[0].Type == typeof(T)) - { - //source list is DynamicNode and the lambda returns a Func - IQueryable typedSource = source as IQueryable; - var compiledFunc = lambda.Compile(); - Func func = null; - Func boolFunc = null; - if (compiledFunc is Func) - { - func = (Func)compiledFunc; - } - if (compiledFunc is Func) - { - boolFunc = (Func)compiledFunc; - } - return typedSource.Where(delegate(T node) - { - object value = -1; - //value = func(node); - //I can't figure out why this is double func<>'d - try - { - if (func != null) - { - var firstFuncResult = func(node); - if (firstFuncResult is Func) - { - value = (firstFuncResult as Func)(node); - } - if (firstFuncResult is Func) - { - value = (firstFuncResult as Func)(node); - } - if (firstFuncResult is bool) - { - return (bool)firstFuncResult; - } - if (value is bool) - { - return (bool)value; - } - } - if (boolFunc != null) - { - return boolFunc(node); - } - return false; - } - catch (Exception ex) - { - Trace.WriteLine(ex.Message); - return false; - } - }).AsQueryable(); - } - else - { - return source.Provider.CreateQuery( - Expression.Call( - typeof(Queryable), "Where", - new Type[] { source.ElementType }, - source.Expression, Expression.Quote(lambda))); - } - } - - public static IQueryable Select(this IQueryable source, string selector, params object[] values) - { - if (source == null) throw new ArgumentNullException("source"); - if (selector == null) throw new ArgumentNullException("selector"); - LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(object), selector, false, values); - if (lambda.Parameters.Count > 0 && lambda.Parameters[0].Type == typeof(T)) - { - //source list is DynamicNode and the lambda returns a Func - IQueryable typedSource = source as IQueryable; - var compiledFunc = lambda.Compile(); - Func func = null; - if (compiledFunc is Func) - { - func = (Func)compiledFunc; - } - return typedSource.Select(delegate(T node) - { - object value = null; - value = func(node); - if (value is Func) - { - var innerValue = (value as Func)(node); - return innerValue; - } - return value; - }).AsQueryable(); - } - else - { - return source.Provider.CreateQuery( - Expression.Call( - typeof(Queryable), "Select", - new Type[] { source.ElementType, lambda.Body.Type }, - source.Expression, Expression.Quote(lambda))); - } - } - - public static IQueryable OrderBy(this IQueryable source, string ordering, Func getDynamicListTypeCallback, params object[] values) - { - return (IQueryable)OrderBy((IQueryable)source, ordering, getDynamicListTypeCallback, values); - } - - public static IQueryable OrderBy(this IQueryable source, string ordering, Func getDynamicListTypeCallback, params object[] values) - { - if (source == null) throw new ArgumentNullException("source"); - if (ordering == null) throw new ArgumentNullException("ordering"); - - IQueryable typedSource = source as IQueryable; - if (!ordering.Contains(",")) - { - bool descending = false; - if (ordering.IndexOf(" descending", StringComparison.CurrentCultureIgnoreCase) >= 0) - { - ordering = ordering.Replace(" descending", ""); - descending = true; - } - if (ordering.IndexOf(" desc", StringComparison.CurrentCultureIgnoreCase) >= 0) - { - ordering = ordering.Replace(" desc", ""); - descending = true; - } - - LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(object), ordering, false, values); - if (lambda.Parameters.Count > 0 && lambda.Parameters[0].Type == typeof(T)) - { - //source list is DynamicNode and the lambda returns a Func - Func func = (Func)lambda.Compile(); - //get the values out - var query = typedSource.ToList().ConvertAll(item => new { node = item, key = EvaluateDynamicNodeFunc(item, func) }); - if (query.Count == 0) - { - return source; - } - var types = from i in query - group i by i.key.GetType() into g - where g.Key != typeof(DynamicNull) - orderby g.Count() descending - select new { g, Instances = g.Count() }; - var dominantType = types.First().g.Key; - - // NH - add culture dependencies - StringComparer comp = StringComparer.Create(CultureInfo.CurrentCulture, true); - - if (!descending) - { - // if the dominant type is a string we'll ensure that strings are sorted based on culture settings on node - if (dominantType.FullName == "System.String") - return query.OrderBy(item => item.key.ToString(), comp).Select(item => item.node).AsQueryable(); - else - return query.OrderBy(item => GetObjectAsTypeOrDefault(item.key, dominantType)).Select(item => item.node).AsQueryable(); - } - else - { - if (dominantType.FullName == "System.String") - return query.OrderByDescending(item => item.key.ToString(), comp).Select(item => item.node).AsQueryable(); - else - return query.OrderByDescending(item => GetObjectAsTypeOrDefault(item.key, dominantType)).Select(item => item.node).AsQueryable(); - } - } - } - - bool isDynamicNodeList = false; - if (typedSource != null) - { - isDynamicNodeList = true; - } - - ParameterExpression[] parameters = new ParameterExpression[] { - Expression.Parameter(source.ElementType, "") }; - var parser = new ExpressionParser(parameters, ordering, values, false); - IEnumerable orderings = parser.ParseOrdering(); - Expression queryExpr = source.Expression; - string methodAsc = "OrderBy"; - string methodDesc = "OrderByDescending"; - foreach (DynamicOrdering o in orderings) - { - if (!isDynamicNodeList) - { - queryExpr = Expression.Call( - typeof(Queryable), o.Ascending ? methodAsc : methodDesc, - new Type[] { source.ElementType, o.Selector.Type }, - queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters))); - } - else - { - //reroute each stacked Expression.Call into our own methods that know how to deal - //with DynamicNode - queryExpr = Expression.Call( - getDynamicListTypeCallback(), - o.Ascending ? methodAsc : methodDesc, - null, - queryExpr, - Expression.Quote(Expression.Lambda(o.Selector, parameters)) - ); - } - methodAsc = "ThenBy"; - methodDesc = "ThenByDescending"; - } - if (isDynamicNodeList) - { - return typedSource.Provider.CreateQuery(queryExpr); - } - return source.Provider.CreateQuery(queryExpr); - - } - private static object GetObjectAsTypeOrDefault(object value, Type type) - { - if (type.IsAssignableFrom(value.GetType())) - { - return (object)Convert.ChangeType(value, type); - } - else - { - if (type.IsValueType) - { - return Activator.CreateInstance(type); - } - return null; - } - } - private static object EvaluateDynamicNodeFunc(T publishedContent, Func func) - { - object value = -1; - var firstFuncResult = func(publishedContent); - if (firstFuncResult is Func) - { - value = (firstFuncResult as Func)(publishedContent); - } - if (firstFuncResult.GetType().IsValueType || firstFuncResult is string) - { - value = firstFuncResult; - } - return value; - } - public static IQueryable Take(this IQueryable source, int count) - { - if (source == null) throw new ArgumentNullException("source"); - return source.Provider.CreateQuery( - Expression.Call( - typeof(Queryable), "Take", - new Type[] { source.ElementType }, - source.Expression, Expression.Constant(count))); - } - - public static IQueryable Skip(this IQueryable source, int count) - { - if (source == null) throw new ArgumentNullException("source"); - return source.Provider.CreateQuery( - Expression.Call( - typeof(Queryable), "Skip", - new Type[] { source.ElementType }, - source.Expression, Expression.Constant(count))); - } - - public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values) - { - if (source == null) throw new ArgumentNullException("source"); - if (keySelector == null) throw new ArgumentNullException("keySelector"); - if (elementSelector == null) throw new ArgumentNullException("elementSelector"); - LambdaExpression keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, true, values); - LambdaExpression elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, true, values); - return source.Provider.CreateQuery( - Expression.Call( - typeof(Queryable), "GroupBy", - new Type[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type }, - source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda))); - } - - public static bool Any(this IQueryable source) - { - if (source == null) throw new ArgumentNullException("source"); - return (bool)source.Provider.Execute( - Expression.Call( - typeof(Queryable), "Any", - new Type[] { source.ElementType }, source.Expression)); - } - - public static int Count(this IQueryable source) - { - if (source == null) throw new ArgumentNullException("source"); - return (int)source.Provider.Execute( - Expression.Call( - typeof(Queryable), "Count", - new Type[] { source.ElementType }, source.Expression)); - } - } -} diff --git a/src/Umbraco.Web/Dynamics/ExpressionParser.cs b/src/Umbraco.Web/Dynamics/ExpressionParser.cs deleted file mode 100644 index 6ca357a996..0000000000 --- a/src/Umbraco.Web/Dynamics/ExpressionParser.cs +++ /dev/null @@ -1,2311 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Dynamic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Dynamics; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Dynamics -{ - //SD: I wish all of this wasn't hacked and was just the original dynamic linq from MS... sigh. Just - // means we can't really use it for anything other than dynamic node (i think) - // I'm fairly sure it's just hte convert to dynamic null stuff... still seems to work for normal linq operations would love to make it - // properly one day. - - internal class ExpressionParser - { - struct Token - { - public TokenId Id { get; set; } - public string Text { get; set; } - public int Pos { get; set; } - } - - enum TokenId - { - Unknown, - End, - Identifier, - StringLiteral, - IntegerLiteral, - RealLiteral, - Exclamation, - Percent, - Amphersand, - OpenParen, - CloseParen, - Asterisk, - Plus, - Comma, - Minus, - Dot, - Slash, - Colon, - LessThan, - Equal, - GreaterThan, - Question, - OpenBracket, - CloseBracket, - Bar, - ExclamationEqual, - DoubleAmphersand, - LessThanEqual, - LessGreater, - DoubleEqual, - GreaterThanEqual, - DoubleBar - } - - interface ILogicalSignatures - { - void F(bool x, bool y); - void F(bool? x, bool? y); - } - - interface IArithmeticSignatures - { - void F(int x, int y); - void F(uint x, uint y); - void F(long x, long y); - void F(ulong x, ulong y); - void F(float x, float y); - void F(double x, double y); - void F(decimal x, decimal y); - void F(int? x, int? y); - void F(uint? x, uint? y); - void F(long? x, long? y); - void F(ulong? x, ulong? y); - void F(float? x, float? y); - void F(double? x, double? y); - void F(decimal? x, decimal? y); - } - - interface IRelationalSignatures : IArithmeticSignatures - { - void F(string x, string y); - void F(char x, char y); - void F(DateTime x, DateTime y); - void F(TimeSpan x, TimeSpan y); - void F(char? x, char? y); - void F(DateTime? x, DateTime? y); - void F(TimeSpan? x, TimeSpan? y); - } - - interface IEqualitySignatures : IRelationalSignatures - { - void F(bool x, bool y); - void F(bool? x, bool? y); - } - - interface IAddSignatures : IArithmeticSignatures - { - void F(DateTime x, TimeSpan y); - void F(TimeSpan x, TimeSpan y); - void F(DateTime? x, TimeSpan? y); - void F(TimeSpan? x, TimeSpan? y); - } - - interface ISubtractSignatures : IAddSignatures - { - void F(DateTime x, DateTime y); - void F(DateTime? x, DateTime? y); - } - - interface INegationSignatures - { - void F(int x); - void F(long x); - void F(float x); - void F(double x); - void F(decimal x); - void F(int? x); - void F(long? x); - void F(float? x); - void F(double? x); - void F(decimal? x); - } - - interface INotSignatures - { - void F(bool x); - void F(bool? x); - } - - interface IEnumerableSignatures - { - void Where(bool predicate); - void Any(); - void Any(bool predicate); - void All(bool predicate); - void Count(); - void Count(bool predicate); - void Min(object selector); - void Max(object selector); - void Sum(int selector); - void Sum(int? selector); - void Sum(long selector); - void Sum(long? selector); - void Sum(float selector); - void Sum(float? selector); - void Sum(double selector); - void Sum(double? selector); - void Sum(decimal selector); - void Sum(decimal? selector); - void Average(int selector); - void Average(int? selector); - void Average(long selector); - void Average(long? selector); - void Average(float selector); - void Average(float? selector); - void Average(double selector); - void Average(double? selector); - void Average(decimal selector); - void Average(decimal? selector); - } - - static readonly Type[] predefinedTypes = { - typeof(Object), - typeof(Boolean), - typeof(Char), - typeof(String), - typeof(SByte), - typeof(Byte), - typeof(Int16), - typeof(UInt16), - typeof(Int32), - typeof(UInt32), - typeof(Int64), - typeof(UInt64), - typeof(Single), - typeof(Double), - typeof(Decimal), - typeof(DateTime), - typeof(TimeSpan), - typeof(Guid), - typeof(Math), - typeof(Convert) - }; - - static readonly Expression trueLiteral = Expression.Constant(true); - static readonly Expression falseLiteral = Expression.Constant(false); - static readonly Expression nullLiteral = Expression.Constant(null); - - static readonly string keywordIt = "it"; - static readonly string keywordIif = "iif"; - static readonly string keywordNew = "new"; - - static Dictionary keywords; - - Dictionary symbols; - IDictionary externals; - Dictionary literals; - ParameterExpression it; - string text; - private readonly bool _flagConvertDynamicNullToBooleanFalse; - int textPos; - int textLen; - char ch; - Token token; - - public ExpressionParser(ParameterExpression[] parameters, string expression, object[] values, bool flagConvertDynamicNullToBooleanFalse) - { - if (expression == null) throw new ArgumentNullException("expression"); - if (keywords == null) keywords = CreateKeywords(); - symbols = new Dictionary(StringComparer.OrdinalIgnoreCase); - literals = new Dictionary(); - if (parameters != null) ProcessParameters(parameters); - if (values != null) ProcessValues(values); - text = expression; - _flagConvertDynamicNullToBooleanFalse = flagConvertDynamicNullToBooleanFalse; - textLen = text.Length; - SetTextPos(0); - NextToken(); - } - - void ProcessParameters(ParameterExpression[] parameters) - { - foreach (ParameterExpression pe in parameters) - if (!String.IsNullOrEmpty(pe.Name)) - AddSymbol(pe.Name, pe); - if (parameters.Length == 1 && String.IsNullOrEmpty(parameters[0].Name)) - it = parameters[0]; - } - - void ProcessValues(object[] values) - { - for (int i = 0; i < values.Length; i++) - { - object value = values[i]; - if (i == values.Length - 1 && value is IDictionary) - { - externals = (IDictionary)value; - } - else - { - AddSymbol("@" + i.ToString(System.Globalization.CultureInfo.InvariantCulture), value); - } - } - } - - void AddSymbol(string name, object value) - { - if (symbols.ContainsKey(name)) - throw ParseError(Res.DuplicateIdentifier, name); - symbols.Add(name, value); - } - - public Expression Parse(Type resultType) - { - int exprPos = token.Pos; - Expression expr = ParseExpression(); - if (resultType != null) - if ((expr = PromoteExpression(expr, resultType, true)) == null) - throw ParseError(exprPos, Res.ExpressionTypeMismatch, GetTypeName(resultType)); - ValidateToken(TokenId.End, Res.SyntaxError); - return expr; - } - -#pragma warning disable 0219 - public IEnumerable ParseOrdering() - { - List orderings = new List(); - while (true) - { - Expression expr = ParseExpression(); - bool ascending = true; - if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending")) - { - NextToken(); - } - else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending")) - { - NextToken(); - ascending = false; - } - orderings.Add(new DynamicOrdering { Selector = expr, Ascending = ascending }); - if (token.Id != TokenId.Comma) break; - NextToken(); - } - ValidateToken(TokenId.End, Res.SyntaxError); - return orderings; - } -#pragma warning restore 0219 - - // ?: operator - Expression ParseExpression() - { - int errorPos = token.Pos; - Expression expr = ParseLogicalOr(); - if (token.Id == TokenId.Question) - { - NextToken(); - Expression expr1 = ParseExpression(); - ValidateToken(TokenId.Colon, Res.ColonExpected); - NextToken(); - Expression expr2 = ParseExpression(); - expr = GenerateConditional(expr, expr1, expr2, errorPos); - } - return expr; - } - - // ||, or operator - Expression ParseLogicalOr() - { - Expression left = ParseLogicalAnd(); - while (token.Id == TokenId.DoubleBar || TokenIdentifierIs("or")) - { - Token op = token; - NextToken(); - Expression right = ParseLogicalAnd(); - CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Text, ref left, ref right, op.Pos); - left = HandleDynamicNodeLambdas(ExpressionType.OrElse, left, right); - } - return left; - } - - // &&, and operator - Expression ParseLogicalAnd() - { - Expression left = ParseComparison(); - while (token.Id == TokenId.DoubleAmphersand || TokenIdentifierIs("and")) - { - Token op = token; - NextToken(); - Expression right = ParseComparison(); - CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Text, ref left, ref right, op.Pos); - left = HandleDynamicNodeLambdas(ExpressionType.AndAlso, left, right); - } - return left; - } - - // =, ==, !=, <>, >, >=, <, <= operators - Expression ParseComparison() - { - Expression left = ParseAdditive(); - while (token.Id == TokenId.Equal || token.Id == TokenId.DoubleEqual || - token.Id == TokenId.ExclamationEqual || token.Id == TokenId.LessGreater || - token.Id == TokenId.GreaterThan || token.Id == TokenId.GreaterThanEqual || - token.Id == TokenId.LessThan || token.Id == TokenId.LessThanEqual) - { - Token op = token; - NextToken(); - Expression right = ParseAdditive(); - bool isEquality = op.Id == TokenId.Equal || op.Id == TokenId.DoubleEqual || - op.Id == TokenId.ExclamationEqual || op.Id == TokenId.LessGreater; - if (isEquality && !left.Type.IsValueType && !right.Type.IsValueType) - { - if (left.Type != right.Type) - { - if (left.Type.IsAssignableFrom(right.Type)) - { - right = Expression.Convert(right, left.Type); - } - else if (right.Type.IsAssignableFrom(left.Type)) - { - left = Expression.Convert(left, right.Type); - } - else if (left is LambdaExpression || right is LambdaExpression) - { - //do nothing here (but further down we'll handle the lambdaexpression) - } - else - { - throw IncompatibleOperandsError(op.Text, left, right, op.Pos); - } - } - } - else if (IsEnumType(left.Type) || IsEnumType(right.Type)) - { - if (left.Type != right.Type) - { - Expression e; - if ((e = PromoteExpression(right, left.Type, true)) != null) - { - right = e; - } - else if ((e = PromoteExpression(left, right.Type, true)) != null) - { - left = e; - } - else - { - throw IncompatibleOperandsError(op.Text, left, right, op.Pos); - } - } - } - else - { - CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), - op.Text, ref left, ref right, op.Pos); - } - switch (op.Id) - { - case TokenId.Equal: - case TokenId.DoubleEqual: - left = HandleDynamicNodeLambdas(ExpressionType.Equal, left, right); - break; - case TokenId.ExclamationEqual: - case TokenId.LessGreater: - left = HandleDynamicNodeLambdas(ExpressionType.NotEqual, left, right); - break; - case TokenId.GreaterThan: - left = HandleDynamicNodeLambdas(ExpressionType.GreaterThan, left, right); - break; - case TokenId.GreaterThanEqual: - left = HandleDynamicNodeLambdas(ExpressionType.GreaterThanOrEqual, left, right); - break; - case TokenId.LessThan: - left = HandleDynamicNodeLambdas(ExpressionType.LessThan, left, right); - break; - case TokenId.LessThanEqual: - left = HandleDynamicNodeLambdas(ExpressionType.LessThanOrEqual, left, right); - break; - } - } - return left; - } - - // +, -, & operators - Expression ParseAdditive() - { - Expression left = ParseMultiplicative(); - while (token.Id == TokenId.Plus || token.Id == TokenId.Minus || - token.Id == TokenId.Amphersand) - { - Token op = token; - NextToken(); - Expression right = ParseMultiplicative(); - switch (op.Id) - { - case TokenId.Plus: - if (left.Type == typeof(string) || right.Type == typeof(string)) - goto case TokenId.Amphersand; - CheckAndPromoteOperands(typeof(IAddSignatures), op.Text, ref left, ref right, op.Pos); - left = GenerateAdd(left, right); - break; - case TokenId.Minus: - CheckAndPromoteOperands(typeof(ISubtractSignatures), op.Text, ref left, ref right, op.Pos); - left = GenerateSubtract(left, right); - break; - case TokenId.Amphersand: - left = GenerateStringConcat(left, right); - break; - } - } - return left; - } - - // *, /, %, mod operators - Expression ParseMultiplicative() - { - Expression left = ParseUnary(); - while (token.Id == TokenId.Asterisk || token.Id == TokenId.Slash || - token.Id == TokenId.Percent || TokenIdentifierIs("mod")) - { - Token op = token; - NextToken(); - Expression right = ParseUnary(); - CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.Text, ref left, ref right, op.Pos); - switch (op.Id) - { - case TokenId.Asterisk: - left = Expression.Multiply(left, right); - break; - case TokenId.Slash: - left = Expression.Divide(left, right); - break; - case TokenId.Percent: - case TokenId.Identifier: - left = HandleDynamicNodeLambdas(ExpressionType.Modulo, left, right); - break; - } - } - return left; - } - - // -, !, not unary operators - Expression ParseUnary() - { - if (token.Id == TokenId.Minus || token.Id == TokenId.Exclamation || - TokenIdentifierIs("not")) - { - Token op = token; - NextToken(); - if (op.Id == TokenId.Minus && (token.Id == TokenId.IntegerLiteral || - token.Id == TokenId.RealLiteral)) - { - token.Text = "-" + token.Text; - token.Pos = op.Pos; - return ParsePrimary(); - } - Expression expr = ParseUnary(); - if (op.Id == TokenId.Minus) - { - CheckAndPromoteOperand(typeof(INegationSignatures), op.Text, ref expr, op.Pos); - expr = Expression.Negate(expr); - } - else - { - CheckAndPromoteOperand(typeof(INotSignatures), op.Text, ref expr, op.Pos); - if (expr is LambdaExpression) - { - ParameterExpression[] parameters = new ParameterExpression[(expr as LambdaExpression).Parameters.Count]; - (expr as LambdaExpression).Parameters.CopyTo(parameters, 0); - var invokedExpr = Expression.Invoke(expr, parameters); - var not = Expression.Not(Expression.TypeAs(invokedExpr, typeof(Nullable))); - expr = Expression.Lambda>( - Expression.Condition( - Expression.Property(not, "HasValue"), - Expression.Property(not, "Value"), - Expression.Constant(false, typeof(bool)) - ), parameters); - } - else - { - expr = Expression.Not(expr); - } - } - return expr; - } - return ParsePrimary(); - } - - Expression ParsePrimary() - { - Expression expr = ParsePrimaryStart(); - while (true) - { - if (token.Id == TokenId.Dot) - { - NextToken(); - expr = ParseMemberAccess(null, expr); - } - else if (token.Id == TokenId.OpenBracket) - { - expr = ParseElementAccess(expr); - } - else - { - break; - } - } - return expr; - } - - Expression ParsePrimaryStart() - { - switch (token.Id) - { - case TokenId.Identifier: - return ParseIdentifier(); - case TokenId.StringLiteral: - return ParseStringLiteral(); - case TokenId.IntegerLiteral: - return ParseIntegerLiteral(); - case TokenId.RealLiteral: - return ParseRealLiteral(); - case TokenId.OpenParen: - return ParseParenExpression(); - default: - throw ParseError(Res.ExpressionExpected); - } - } - - Expression ParseStringLiteral() - { - ValidateToken(TokenId.StringLiteral); - char quote = token.Text[0]; - string s = token.Text.Substring(1, token.Text.Length - 2); - int start = 0; - while (true) - { - int i = s.IndexOf(quote, start); - if (i < 0) break; - s = s.Remove(i, 1); - start = i + 1; - } - if (quote == '\'') - { - if (s.Length != 1) - throw ParseError(Res.InvalidCharacterLiteral); - NextToken(); - return CreateLiteral(s[0], s); - } - NextToken(); - return CreateLiteral(s, s); - } - - Expression ParseIntegerLiteral() - { - ValidateToken(TokenId.IntegerLiteral); - string text = token.Text; - if (text[0] != '-') - { - ulong value; - if (!UInt64.TryParse(text, out value)) - throw ParseError(Res.InvalidIntegerLiteral, text); - NextToken(); - if (value <= (ulong)Int32.MaxValue) return CreateLiteral((int)value, text); - if (value <= (ulong)UInt32.MaxValue) return CreateLiteral((uint)value, text); - if (value <= (ulong)Int64.MaxValue) return CreateLiteral((long)value, text); - return CreateLiteral(value, text); - } - else - { - long value; - if (!Int64.TryParse(text, out value)) - throw ParseError(Res.InvalidIntegerLiteral, text); - NextToken(); - if (value >= Int32.MinValue && value <= Int32.MaxValue) - return CreateLiteral((int)value, text); - return CreateLiteral(value, text); - } - } - - Expression ParseRealLiteral() - { - ValidateToken(TokenId.RealLiteral); - string text = token.Text; - object value = null; - char last = text[text.Length - 1]; - if (last == 'F' || last == 'f') - { - float f; - if (Single.TryParse(text.Substring(0, text.Length - 1), out f)) value = f; - } - else - { - double d; - if (Double.TryParse(text, out d)) value = d; - } - if (value == null) throw ParseError(Res.InvalidRealLiteral, text); - NextToken(); - return CreateLiteral(value, text); - } - - Expression CreateLiteral(object value, string text) - { - ConstantExpression expr = Expression.Constant(value); - literals.Add(expr, text); - return expr; - } - - Expression ParseParenExpression() - { - ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); - NextToken(); - Expression e = ParseExpression(); - ValidateToken(TokenId.CloseParen, Res.CloseParenOrOperatorExpected); - NextToken(); - return e; - } - - Expression ParseIdentifier() - { - ValidateToken(TokenId.Identifier); - object value; - if (keywords.TryGetValue(token.Text, out value)) - { - if (value is Type) return ParseTypeAccess((Type)value); - if (value == (object)keywordIt) return ParseIt(); - if (value == (object)keywordIif) return ParseIif(); - if (value == (object)keywordNew) return ParseNew(); - NextToken(); - return (Expression)value; - } - if (symbols.TryGetValue(token.Text, out value) || - externals != null && externals.TryGetValue(token.Text, out value)) - { - Expression expr = value as Expression; - if (expr == null) - { - expr = Expression.Constant(value); - } - else - { - LambdaExpression lambda = expr as LambdaExpression; - if (lambda != null) return ParseLambdaInvocation(lambda); - } - NextToken(); - return expr; - } - if (it != null) return ParseMemberAccess(null, it); - throw ParseError(Res.UnknownIdentifier, token.Text); - } - - Expression ParseIt() - { - if (it == null) - throw ParseError(Res.NoItInScope); - NextToken(); - return it; - } - - Expression ParseIif() - { - int errorPos = token.Pos; - NextToken(); - Expression[] args = ParseArgumentList(); - if (args.Length != 3) - throw ParseError(errorPos, Res.IifRequiresThreeArgs); - return GenerateConditional(args[0], args[1], args[2], errorPos); - } - - Expression GenerateConditional(Expression test, Expression expr1, Expression expr2, int errorPos) - { - if (test.Type != typeof(bool)) - throw ParseError(errorPos, Res.FirstExprMustBeBool); - if (expr1.Type != expr2.Type) - { - Expression expr1as2 = expr2 != nullLiteral ? PromoteExpression(expr1, expr2.Type, true) : null; - Expression expr2as1 = expr1 != nullLiteral ? PromoteExpression(expr2, expr1.Type, true) : null; - if (expr1as2 != null && expr2as1 == null) - { - expr1 = expr1as2; - } - else if (expr2as1 != null && expr1as2 == null) - { - expr2 = expr2as1; - } - else - { - string type1 = expr1 != nullLiteral ? expr1.Type.Name : "null"; - string type2 = expr2 != nullLiteral ? expr2.Type.Name : "null"; - if (expr1as2 != null && expr2as1 != null) - throw ParseError(errorPos, Res.BothTypesConvertToOther, type1, type2); - throw ParseError(errorPos, Res.NeitherTypeConvertsToOther, type1, type2); - } - } - return Expression.Condition(test, expr1, expr2); - } - - Expression ParseNew() - { - NextToken(); - ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); - NextToken(); - List properties = new List(); - List expressions = new List(); - while (true) - { - int exprPos = token.Pos; - Expression expr = ParseExpression(); - string propName; - if (TokenIdentifierIs("as")) - { - NextToken(); - propName = GetIdentifier(); - NextToken(); - } - else - { - MemberExpression me = expr as MemberExpression; - if (me == null) throw ParseError(exprPos, Res.MissingAsClause); - propName = me.Member.Name; - } - expressions.Add(expr); - properties.Add(new DynamicProperty(propName, expr.Type)); - if (token.Id != TokenId.Comma) break; - NextToken(); - } - ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); - NextToken(); - Type type = DynamicExpression.CreateClass(properties); - MemberBinding[] bindings = new MemberBinding[properties.Count]; - for (int i = 0; i < bindings.Length; i++) - bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]); - return Expression.MemberInit(Expression.New(type), bindings); - } - - Expression ParseLambdaInvocation(LambdaExpression lambda) - { - int errorPos = token.Pos; - NextToken(); - Expression[] args = ParseArgumentList(); - MethodBase method; - if (FindMethod(lambda.Type, "Invoke", false, args, out method) != 1) - throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda); - return Expression.Invoke(lambda, args); - } - - Expression ParseTypeAccess(Type type) - { - int errorPos = token.Pos; - NextToken(); - if (token.Id == TokenId.Question) - { - if (!type.IsValueType || IsNullableType(type)) - throw ParseError(errorPos, Res.TypeHasNoNullableForm, GetTypeName(type)); - type = typeof(Nullable<>).MakeGenericType(type); - NextToken(); - } - if (token.Id == TokenId.OpenParen) - { - Expression[] args = ParseArgumentList(); - MethodBase method; - switch (FindBestMethod(type.GetConstructors(), args, out method)) - { - case 0: - if (args.Length == 1) - return GenerateConversion(args[0], type, errorPos); - throw ParseError(errorPos, Res.NoMatchingConstructor, GetTypeName(type)); - case 1: - return Expression.New((ConstructorInfo)method, args); - default: - throw ParseError(errorPos, Res.AmbiguousConstructorInvocation, GetTypeName(type)); - } - } - ValidateToken(TokenId.Dot, Res.DotOrOpenParenExpected); - NextToken(); - return ParseMemberAccess(type, null); - } - - Expression GenerateConversion(Expression expr, Type type, int errorPos) - { - Type exprType = expr.Type; - if (exprType == type) return expr; - if (exprType.IsValueType && type.IsValueType) - { - if ((IsNullableType(exprType) || IsNullableType(type)) && - GetNonNullableType(exprType) == GetNonNullableType(type)) - return Expression.Convert(expr, type); - if ((IsNumericType(exprType) || IsEnumType(exprType)) && - (IsNumericType(type)) || IsEnumType(type)) - return Expression.ConvertChecked(expr, type); - } - if (exprType.IsAssignableFrom(type) || type.IsAssignableFrom(exprType) || - exprType.IsInterface || type.IsInterface) - return Expression.Convert(expr, type); - throw ParseError(errorPos, Res.CannotConvertValue, - GetTypeName(exprType), GetTypeName(type)); - } - - Expression ParseMemberAccess(Type type, Expression instance) - { - //NOTE: SD: There is a lot of string checking going on here and I'm 99% sure this can all be done better - // in a more generic sense to support any types with any extension methods, etc... - // Too bad whoever wrote this decided not to put any code comments in :( - // This is how to support method calls, etc... in dynamic statements. - - if (instance != null) type = instance.Type; - int errorPos = token.Pos; - string id = GetIdentifier(); - NextToken(); - if (token.Id == TokenId.OpenParen) - { - if (instance != null && type != typeof(string)) - { - Type enumerableType = FindGenericType(typeof(IEnumerable<>), type); - if (enumerableType != null) - { - Type elementType = enumerableType.GetGenericArguments()[0]; - return ParseAggregate(instance, elementType, id, errorPos); - } - } - Expression[] args = ParseArgumentList(); - MethodBase mb; - LambdaExpression instanceAsString = null; - ParameterExpression instanceExpression = Expression.Parameter(typeof(T), "instance"); - if (type.IsGenericType && type != typeof(string)) - { - var typeArgs = type.GetGenericArguments(); - if (typeArgs[0] == typeof(T)) - { - if (instance != null && instance is LambdaExpression) - { - //not sure why this is object or why we need to do this but if we change it, things die... - //also not sure why it is changed to string, i think this might be to ensure string methods are supported - //but seems to me that we then won't support other types of methods? - if (typeArgs[1] == typeof(object)) - { - instanceAsString = StringFormat(instance as LambdaExpression, instanceExpression); - type = typeof(string); - } - else if (typeArgs[1] == typeof(string)) - { - instanceAsString = instance as LambdaExpression; - type = typeof(string); - } - //else - //{ - // instanceAsString = instance as LambdaExpression; - // type = typeArgs[1]; - //} - - } - } - } - switch (FindMethod(type, id, instance == null, args, out mb)) - { - case 0: - //not found - - //SD: I have yet to see extension methods actually being called in the dynamic parsing... need to unit test these - // scenarios and figure out why all this type checking occurs. - - var runtimeCache = ApplicationContext.Current != null ? ApplicationContext.Current.ApplicationCache.RuntimeCache : new NullCacheProvider(); - - if (type == typeof(string) && instanceAsString != null) - { - Expression[] newArgs = (new List() { Expression.Invoke(instanceAsString, instanceExpression) }).Concat(args).ToArray(); - mb = ExtensionMethodFinder.FindExtensionMethod(runtimeCache, typeof(string), newArgs, id, true); - if (mb != null) - { - return CallMethodOnDynamicNode(instance, newArgs, instanceAsString, instanceExpression, (MethodInfo)mb, true); - } - } - if (type == typeof(string) && instanceAsString == null && instance is MemberExpression) - { - Expression[] newArgs = (new List() { instance }).Concat(args).ToArray(); - mb = ExtensionMethodFinder.FindExtensionMethod(runtimeCache, typeof(string), newArgs, id, true); - if (mb != null) - { - return Expression.Call(null, (MethodInfo)mb, newArgs); - } - } - - throw ParseError(errorPos, Res.NoApplicableMethod, - id, GetTypeName(type)); - case 1: - MethodInfo method = (MethodInfo)mb; - if (!IsPredefinedType(method.DeclaringType)) - throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType)); - if (method.ReturnType == typeof(void)) - throw ParseError(errorPos, Res.MethodIsVoid, - id, GetTypeName(method.DeclaringType)); - if (instanceAsString != null) - { - return CallMethodOnDynamicNode(instance, args, instanceAsString, instanceExpression, method, false); - } - return Expression.Call(instance, (MethodInfo)method, args); - default: - throw ParseError(errorPos, Res.AmbiguousMethodInvocation, - id, GetTypeName(type)); - } - } - else - { - //Looks for a member on the type, but above, we're rerouting that into TryGetMember - MemberInfo member = FindPropertyOrField(type, id, instance == null); - if (member == null) - { - if (typeof(DynamicObject).IsAssignableFrom(type)) - { - //We are going to generate a dynamic method by hand coding the expression tree - //this will invoke TryGetMember (but wrapped in an expression tree) - //so that when it's evaluated, DynamicNode should be supported - - ParameterExpression instanceExpression = Expression.Parameter(typeof(T), "instance"); - ParameterExpression convertDynamicNullToBooleanFalse = Expression.Parameter(typeof(bool), "convertDynamicNullToBooleanFalse"); - ParameterExpression result = Expression.Parameter(typeof(object), "result"); - ParameterExpression binder = Expression.Variable(typeof(DynamicQueryableGetMemberBinder), "binder"); - ParameterExpression ignoreCase = Expression.Variable(typeof(bool), "ignoreCase"); - ConstructorInfo getMemberBinderConstructor = typeof(DynamicQueryableGetMemberBinder).GetConstructor(new Type[] { typeof(string), typeof(bool) }); - LabelTarget blockReturnLabel = Expression.Label(typeof(object)); - MethodInfo method = typeof(T).GetMethod("TryGetMember"); - - BlockExpression block = Expression.Block( - typeof(object), - new[] { ignoreCase, binder, result, convertDynamicNullToBooleanFalse }, - Expression.Assign(convertDynamicNullToBooleanFalse, Expression.Constant(_flagConvertDynamicNullToBooleanFalse, typeof(bool))), - Expression.Assign(ignoreCase, Expression.Constant(false, typeof(bool))), - Expression.Assign(binder, Expression.New(getMemberBinderConstructor, Expression.Constant(id, typeof(string)), ignoreCase)), - Expression.Assign(result, Expression.Constant(null)), - Expression.IfThen(Expression.NotEqual(Expression.Constant(null), instanceExpression), - Expression.Call(instanceExpression, method, binder, result)), - Expression.IfThen( - Expression.AndAlso( - Expression.TypeEqual(result, typeof(DynamicNull)), - Expression.Equal(convertDynamicNullToBooleanFalse, Expression.Constant(true, typeof(bool))) - ), - Expression.Assign(result, Expression.Constant(false, typeof(object))) - ), - Expression.Return(blockReturnLabel, result), - Expression.Label(blockReturnLabel, Expression.Constant(-2, typeof(object))) - ); - LambdaExpression lax = Expression.Lambda>(block, instanceExpression); - return lax; - } - if (typeof(Func).IsAssignableFrom(type)) - { - //accessing a property off an already resolved DynamicNode TryGetMember call - //e.g. uBlogsyPostDate.Date - //SD: Removed the NonPublic accessor here because this will never work in medium trust, wondering why it is NonPublic vs Public ? Have changed to Public. - //MethodInfo ReflectPropertyValue = this.GetType().GetMethod("ReflectPropertyValue", BindingFlags.NonPublic | BindingFlags.Static); - MethodInfo reflectPropertyValue = this.GetType().GetMethod("ReflectPropertyValue", BindingFlags.Public | BindingFlags.Static); - ParameterExpression convertDynamicNullToBooleanFalse = Expression.Parameter(typeof(bool), "convertDynamicNullToBooleanFalse"); - ParameterExpression result = Expression.Parameter(typeof(object), "result"); - ParameterExpression idParam = Expression.Parameter(typeof(string), "id"); - ParameterExpression lambdaResult = Expression.Parameter(typeof(object), "lambdaResult"); - ParameterExpression lambdaInstanceExpression = Expression.Parameter(typeof(T), "lambdaInstanceExpression"); - ParameterExpression instanceExpression = Expression.Parameter(typeof(Func), "instance"); - LabelTarget blockReturnLabel = Expression.Label(typeof(object)); - - BlockExpression block = Expression.Block( - typeof(object), - new[] { lambdaResult, result, idParam, convertDynamicNullToBooleanFalse }, - Expression.Assign(convertDynamicNullToBooleanFalse, Expression.Constant(_flagConvertDynamicNullToBooleanFalse, typeof(bool))), - Expression.Assign(lambdaResult, Expression.Invoke(instance, lambdaInstanceExpression)), - Expression.Assign(result, Expression.Call(reflectPropertyValue, lambdaResult, Expression.Constant(id))), - Expression.IfThen( - Expression.AndAlso( - Expression.TypeEqual(result, typeof(DynamicNull)), - Expression.Equal(convertDynamicNullToBooleanFalse, Expression.Constant(true, typeof(bool))) - ), - Expression.Assign(result, Expression.Constant(false, typeof(object))) - ), - Expression.Return(blockReturnLabel, result), - Expression.Label(blockReturnLabel, Expression.Constant(-2, typeof(object))) - ); - LambdaExpression lax = Expression.Lambda>(block, lambdaInstanceExpression); - return lax; - } - } - else - { - return member is PropertyInfo ? - Expression.Property(instance, (PropertyInfo)member) : - Expression.Field(instance, (FieldInfo)member); - } - throw ParseError(errorPos, Res.UnknownPropertyOrField, - id, GetTypeName(type)); - - } - } - public static object ReflectPropertyValue(object o, string name) - { - PropertyInfo propertyInfo = o.GetType().GetProperty(name); - if (propertyInfo != null) - { - object result = propertyInfo.GetValue(o, null); - return result; - } - return null; - } - private static Expression CallMethodOnDynamicNode(Expression instance, Expression[] args, LambdaExpression instanceAsString, ParameterExpression instanceExpression, MethodInfo method, bool isStatic) - { - Type methodReturnType = method.ReturnType; - - var defaultReturnValue = Expression.Constant(methodReturnType.GetDefaultValue(), methodReturnType); - - ParameterExpression result = Expression.Parameter(method.ReturnType, "result"); - LabelTarget blockReturnLabel = Expression.Label(method.ReturnType); - BlockExpression block = Expression.Block( - method.ReturnType, - new[] { result }, - Expression.Assign(result, - Expression.Call( - isStatic ? null : Expression.Invoke(instanceAsString, instanceExpression), - method, - args) - ), - Expression.Return(blockReturnLabel, result), - Expression.Label(blockReturnLabel, defaultReturnValue) - ); - - Type func = typeof(Func<,>); - Type generic = func.MakeGenericType(typeof(T), methodReturnType); - return Expression.Lambda(generic, block, instanceExpression); - - //if (methodReturnType == typeof(string)) - // return Expression.Lambda>(block, instanceExpression); - //if (methodReturnType == typeof(int)) - // return Expression.Lambda>(block, instanceExpression); - //if (methodReturnType == typeof(bool)) - // return Expression.Lambda>(block, instanceExpression); - //if (methodReturnType == typeof(string[])) - //return Expression.Lambda>(block, instanceExpression); - - //return Expression.Call(instance, (MethodInfo)method, args); - - //return Expression.Lambda>( - // Expression.Convert(block, typeof(object)), instanceExpression); - - } - - static Type FindGenericType(Type generic, Type type) - { - while (type != null && type != typeof(object)) - { - if (type.IsGenericType && type.GetGenericTypeDefinition() == generic) return type; - if (generic.IsInterface) - { - foreach (Type intfType in type.GetInterfaces()) - { - Type found = FindGenericType(generic, intfType); - if (found != null) return found; - } - } - type = type.BaseType; - } - return null; - } - LambdaExpression StringFormat(LambdaExpression lax, ParameterExpression instanceExpression) - { - ParameterExpression cresult = Expression.Parameter(typeof(string), "cresult"); - ParameterExpression temp = Expression.Parameter(typeof(object), "temp"); - ParameterExpression stemp = Expression.Parameter(typeof(string), "string"); - LabelTarget cblockReturnLabel = Expression.Label(typeof(string)); - - MethodInfo stringFormat = typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) }); - BlockExpression cblock = Expression.Block( - typeof(string), - new[] { cresult, temp }, - Expression.Assign(temp, Expression.Invoke(lax, instanceExpression)), - Expression.Assign(cresult, Expression.Call(stringFormat, Expression.Constant("{0}"), temp)), - Expression.Return(cblockReturnLabel, cresult), - Expression.Label(cblockReturnLabel, Expression.Constant(null, typeof(string)))); - - LambdaExpression lax2 = Expression.Lambda>(cblock, instanceExpression); - var expression = Expression.Lambda>(cblock, instanceExpression); - return expression; - - } - Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos) - { - ParameterExpression outerIt = it; - ParameterExpression innerIt = Expression.Parameter(elementType, ""); - it = innerIt; - Expression[] args = ParseArgumentList(); - it = outerIt; - MethodBase signature; - if (FindMethod(typeof(IEnumerableSignatures), methodName, false, args, out signature) != 1) - throw ParseError(errorPos, Res.NoApplicableAggregate, methodName); - Type[] typeArgs; - if (signature.Name == "Min" || signature.Name == "Max") - { - typeArgs = new Type[] { elementType, args[0].Type }; - } - else - { - typeArgs = new Type[] { elementType }; - } - if (args.Length == 0) - { - args = new Expression[] { instance }; - } - else - { - args = new Expression[] { instance, Expression.Lambda(args[0], innerIt) }; - } - return Expression.Call(typeof(Enumerable), signature.Name, typeArgs, args); - } - - Expression[] ParseArgumentList() - { - ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); - NextToken(); - Expression[] args = token.Id != TokenId.CloseParen ? ParseArguments() : new Expression[0]; - ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); - NextToken(); - return args; - } - - Expression[] ParseArguments() - { - List argList = new List(); - while (true) - { - argList.Add(ParseExpression()); - if (token.Id != TokenId.Comma) break; - NextToken(); - } - return argList.ToArray(); - } - - Expression ParseElementAccess(Expression expr) - { - int errorPos = token.Pos; - ValidateToken(TokenId.OpenBracket, Res.OpenParenExpected); - NextToken(); - Expression[] args = ParseArguments(); - ValidateToken(TokenId.CloseBracket, Res.CloseBracketOrCommaExpected); - NextToken(); - if (expr.Type.IsArray) - { - if (expr.Type.GetArrayRank() != 1 || args.Length != 1) - throw ParseError(errorPos, Res.CannotIndexMultiDimArray); - Expression index = PromoteExpression(args[0], typeof(int), true); - if (index == null) - throw ParseError(errorPos, Res.InvalidIndex); - return Expression.ArrayIndex(expr, index); - } - else - { - MethodBase mb; - switch (FindIndexer(expr.Type, args, out mb)) - { - case 0: - throw ParseError(errorPos, Res.NoApplicableIndexer, - GetTypeName(expr.Type)); - case 1: - return Expression.Call(expr, (MethodInfo)mb, args); - default: - throw ParseError(errorPos, Res.AmbiguousIndexerInvocation, - GetTypeName(expr.Type)); - } - } - } - - static bool IsPredefinedType(Type type) - { - foreach (Type t in predefinedTypes) if (t == type) return true; - return false; - } - - static bool IsNullableType(Type type) - { - return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); - } - - static Type GetNonNullableType(Type type) - { - return IsNullableType(type) ? type.GetGenericArguments()[0] : type; - } - - static string GetTypeName(Type type) - { - Type baseType = GetNonNullableType(type); - string s = baseType.Name; - if (type != baseType) s += '?'; - return s; - } - - static bool IsNumericType(Type type) - { - return GetNumericTypeKind(type) != 0; - } - - static bool IsSignedIntegralType(Type type) - { - return GetNumericTypeKind(type) == 2; - } - - static bool IsUnsignedIntegralType(Type type) - { - return GetNumericTypeKind(type) == 3; - } - - static int GetNumericTypeKind(Type type) - { - type = GetNonNullableType(type); - if (type.IsEnum) return 0; - switch (Type.GetTypeCode(type)) - { - case TypeCode.Char: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return 1; - case TypeCode.SByte: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - return 2; - case TypeCode.Byte: - case TypeCode.UInt16: - case TypeCode.UInt32: - case TypeCode.UInt64: - return 3; - default: - return 0; - } - } - - static bool IsEnumType(Type type) - { - return GetNonNullableType(type).IsEnum; - } - - void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr, int errorPos) - { - Expression[] args = new Expression[] { expr }; - MethodBase method; - if (FindMethod(signatures, "F", false, args, out method) != 1) - throw ParseError(errorPos, Res.IncompatibleOperand, - opName, GetTypeName(args[0].Type)); - expr = args[0]; - } - - void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left, ref Expression right, int errorPos) - { - Expression[] args = new Expression[] { left, right }; - MethodBase method; - if (FindMethod(signatures, "F", false, args, out method) != 1) - throw IncompatibleOperandsError(opName, left, right, errorPos); - left = args[0]; - right = args[1]; - } - - Exception IncompatibleOperandsError(string opName, Expression left, Expression right, int pos) - { - return ParseError(pos, Res.IncompatibleOperands, - opName, GetTypeName(left.Type), GetTypeName(right.Type)); - } - - MemberInfo FindPropertyOrField(Type type, string memberName, bool staticAccess) - { - BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | - (staticAccess ? BindingFlags.Static : BindingFlags.Instance); - foreach (Type t in SelfAndBaseTypes(type)) - { - MemberInfo[] members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, - flags, Type.FilterNameIgnoreCase, memberName); - if (members.Length != 0) return members[0]; - } - return null; - } - - int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method) - { - BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | - (staticAccess ? BindingFlags.Static : BindingFlags.Instance); - foreach (Type t in SelfAndBaseTypes(type)) - { - MemberInfo[] members = t.FindMembers(MemberTypes.Method, - flags, Type.FilterNameIgnoreCase, methodName); - int count = FindBestMethod(members.Cast(), args, out method); - if (count != 0) return count; - } - method = null; - return 0; - } - - int FindIndexer(Type type, Expression[] args, out MethodBase method) - { - foreach (Type t in SelfAndBaseTypes(type)) - { - MemberInfo[] members = t.GetDefaultMembers(); - if (members.Length != 0) - { - IEnumerable methods = members. - OfType(). - Select(p => (MethodBase)p.GetGetMethod()). - Where(m => m != null); - int count = FindBestMethod(methods, args, out method); - if (count != 0) return count; - } - } - method = null; - return 0; - } - - static IEnumerable SelfAndBaseTypes(Type type) - { - if (type.IsInterface) - { - List types = new List(); - AddInterface(types, type); - return types; - } - return SelfAndBaseClasses(type); - } - - static IEnumerable SelfAndBaseClasses(Type type) - { - while (type != null) - { - yield return type; - type = type.BaseType; - } - } - - static void AddInterface(List types, Type type) - { - if (!types.Contains(type)) - { - types.Add(type); - foreach (Type t in type.GetInterfaces()) AddInterface(types, t); - } - } - - class MethodData - { - public MethodBase MethodBase; - public ParameterInfo[] Parameters; - public Expression[] Args; - } - - int FindBestMethod(IEnumerable methods, Expression[] args, out MethodBase method) - { - MethodData[] applicable = methods. - Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }). - Where(m => IsApplicable(m, args)). - ToArray(); - if (applicable.Length > 1) - { - applicable = applicable. - Where(m => applicable.All(n => m == n || IsBetterThan(args, m, n))). - ToArray(); - } - if (applicable.Length == 1) - { - MethodData md = applicable[0]; - for (int i = 0; i < args.Length; i++) args[i] = md.Args[i]; - method = md.MethodBase; - } - else - { - method = null; - } - return applicable.Length; - } - - bool IsApplicable(MethodData method, Expression[] args) - { - if (method.Parameters.Length != args.Length) return false; - Expression[] promotedArgs = new Expression[args.Length]; - for (int i = 0; i < args.Length; i++) - { - ParameterInfo pi = method.Parameters[i]; - if (pi.IsOut) return false; - Expression promoted; - - //TODO: Turns out this is real difficult to parse and don't really have time to figure this out at the moment - // to parse params parameter arrays. - - ////here we need to check if it is a params array parameter - //if (pi.ParameterType.IsArray - // && pi.ParameterType.GetElementType() != null - // && pi.GetCustomAttributes(typeof(ParamArrayAttribute), false).Any()) - //{ - // //it is a params parameter so convert the value to an array - // promoted = PromoteExpression(args[i], pi.ParameterType.GetElementType(), false); - //} - //else - //{ - promoted = PromoteExpression(args[i], pi.ParameterType, false); - //} - if (promoted == null) return false; - promotedArgs[i] = promoted; - } - method.Args = promotedArgs; - return true; - } - - Expression PromoteExpression(Expression expr, Type type, bool exact) - { - //if the type of the expression is the correct target type, just return it here - if (expr.Type == type) return expr; - //if the type of the expression is a func - invokable returning object, - //we are going to return it here, because we can get the real value when we actually have the instance - //if (typeof(Func).IsAssignableFrom(expr.Type)) return expr; - if (expr is LambdaExpression && ((LambdaExpression)expr).Parameters.Count > 0 && ((LambdaExpression)expr).Parameters[0].Type == typeof(T)) - { - return expr; - } - if (expr is ConstantExpression) - { - ConstantExpression ce = (ConstantExpression)expr; - if (ce == nullLiteral) - { - if (!type.IsValueType || IsNullableType(type)) - return Expression.Constant(null, type); - } - else - { - string text; - if (literals.TryGetValue(ce, out text)) - { - Type target = GetNonNullableType(type); - Object value = null; - switch (Type.GetTypeCode(ce.Type)) - { - case TypeCode.Int32: - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - value = ParseNumber(text, target); - break; - case TypeCode.Double: - if (target == typeof(decimal)) value = ParseNumber(text, target); - break; - case TypeCode.String: - value = ParseEnum(text, target); - break; - } - if (value != null) - return Expression.Constant(value, type); - } - } - } - if (IsCompatibleWith(expr.Type, type)) - { - if (type.IsValueType || exact) return Expression.Convert(expr, type); - return expr; - } - return null; - } - - static object ParseNumber(string text, Type type) - { - switch (Type.GetTypeCode(GetNonNullableType(type))) - { - case TypeCode.SByte: - sbyte sb; - if (sbyte.TryParse(text, out sb)) return sb; - break; - case TypeCode.Byte: - byte b; - if (byte.TryParse(text, out b)) return b; - break; - case TypeCode.Int16: - short s; - if (short.TryParse(text, out s)) return s; - break; - case TypeCode.UInt16: - ushort us; - if (ushort.TryParse(text, out us)) return us; - break; - case TypeCode.Int32: - int i; - if (int.TryParse(text, out i)) return i; - break; - case TypeCode.UInt32: - uint ui; - if (uint.TryParse(text, out ui)) return ui; - break; - case TypeCode.Int64: - long l; - if (long.TryParse(text, out l)) return l; - break; - case TypeCode.UInt64: - ulong ul; - if (ulong.TryParse(text, out ul)) return ul; - break; - case TypeCode.Single: - float f; - if (float.TryParse(text, out f)) return f; - break; - case TypeCode.Double: - double d; - if (double.TryParse(text, out d)) return d; - break; - case TypeCode.Decimal: - decimal e; - if (decimal.TryParse(text, out e)) return e; - break; - } - return null; - } - - static object ParseEnum(string name, Type type) - { - if (type.IsEnum) - { - MemberInfo[] memberInfos = type.FindMembers(MemberTypes.Field, - BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static, - Type.FilterNameIgnoreCase, name); - if (memberInfos.Length != 0) return ((FieldInfo)memberInfos[0]).GetValue(null); - } - return null; - } - - static bool IsCompatibleWith(Type source, Type target) - { - if (source == target) return true; - if (!target.IsValueType) return target.IsAssignableFrom(source); - Type st = GetNonNullableType(source); - Type tt = GetNonNullableType(target); - if (st != source && tt == target) return false; - TypeCode sc = st.IsEnum ? TypeCode.Object : Type.GetTypeCode(st); - TypeCode tc = tt.IsEnum ? TypeCode.Object : Type.GetTypeCode(tt); - switch (sc) - { - case TypeCode.SByte: - switch (tc) - { - case TypeCode.SByte: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Byte: - switch (tc) - { - case TypeCode.Byte: - case TypeCode.Int16: - case TypeCode.UInt16: - case TypeCode.Int32: - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Int16: - switch (tc) - { - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.UInt16: - switch (tc) - { - case TypeCode.UInt16: - case TypeCode.Int32: - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Int32: - switch (tc) - { - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.UInt32: - switch (tc) - { - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Int64: - switch (tc) - { - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.UInt64: - switch (tc) - { - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Single: - switch (tc) - { - case TypeCode.Single: - case TypeCode.Double: - return true; - } - break; - default: - if (st == tt) return true; - break; - } - return false; - } - - static bool IsBetterThan(Expression[] args, MethodData m1, MethodData m2) - { - bool better = false; - for (int i = 0; i < args.Length; i++) - { - int c = CompareConversions(args[i].Type, - m1.Parameters[i].ParameterType, - m2.Parameters[i].ParameterType); - if (c < 0) return false; - if (c > 0) better = true; - } - return better; - } - - // Return 1 if s -> t1 is a better conversion than s -> t2 - // Return -1 if s -> t2 is a better conversion than s -> t1 - // Return 0 if neither conversion is better - static int CompareConversions(Type s, Type t1, Type t2) - { - if (t1 == t2) return 0; - if (s == t1) return 1; - if (s == t2) return -1; - bool t1t2 = IsCompatibleWith(t1, t2); - bool t2t1 = IsCompatibleWith(t2, t1); - if (t1t2 && !t2t1) return 1; - if (t2t1 && !t1t2) return -1; - if (IsSignedIntegralType(t1) && IsUnsignedIntegralType(t2)) return 1; - if (IsSignedIntegralType(t2) && IsUnsignedIntegralType(t1)) return -1; - return 0; - } - - Expression GenerateEqual(Expression left, Expression right) - { - return HandleDynamicNodeLambdas(ExpressionType.Equal, left, right); - } - - private static Expression HandleDynamicNodeLambdas(ExpressionType expressionType, Expression left, Expression right) - { - bool leftIsLambda = false, rightIsLambda = false; - Expression innerLeft = null; - Expression innerRight = null; - UnaryExpression unboxedLeft = null, unboxedRight = null; - ParameterExpression[] parameters = null; - - if (left is LambdaExpression && (left as LambdaExpression).Type.GetGenericArguments().First() == typeof(T)) - { - leftIsLambda = true; - } - - if (right is LambdaExpression && (right as LambdaExpression).Type.GetGenericArguments().First() == typeof(T)) - { - rightIsLambda = true; - } - - if (leftIsLambda && !rightIsLambda) - { - parameters = new ParameterExpression[(left as LambdaExpression).Parameters.Count]; - (left as LambdaExpression).Parameters.CopyTo(parameters, 0); - if (right is ConstantExpression) - { - //left lambda, right constant - var invokedExpr = Expression.Invoke(left, (left as LambdaExpression).Parameters.Cast()); - innerLeft = Expression.Convert(invokedExpr, (right as ConstantExpression).Type); - } - if (leftIsLambda && !rightIsLambda && right is MemberExpression) - { - var invokedExpr = Expression.Invoke(left, (left as LambdaExpression).Parameters.Cast()); - innerLeft = Expression.Convert(invokedExpr, (right as MemberExpression).Type); - } - } - if (rightIsLambda && !leftIsLambda) - { - parameters = new ParameterExpression[(right as LambdaExpression).Parameters.Count]; - (right as LambdaExpression).Parameters.CopyTo(parameters, 0); - if (left is ConstantExpression) - { - //right lambda, left constant - var invokedExpr = Expression.Invoke(right, (right as LambdaExpression).Parameters.Cast()); - innerRight = Expression.Convert(invokedExpr, (left as ConstantExpression).Type); - } - if (right is MemberExpression) - { - var invokedExpr = Expression.Invoke(right, (right as LambdaExpression).Parameters.Cast()); - innerRight = Expression.Convert(invokedExpr, (left as MemberExpression).Type); - } - } - bool sequenceEqual = false; - if (leftIsLambda && rightIsLambda) - { - { - Type leftType = ((LambdaExpression)left).Type; - Type rightType = ((LambdaExpression)right).Type; - Type[] leftTypeGenericArguments = leftType.GetGenericArguments(); - Type[] rightTypeGenericArguments = rightType.GetGenericArguments(); - if (leftTypeGenericArguments.SequenceEqual(rightTypeGenericArguments)) - { - sequenceEqual = true; - if (leftTypeGenericArguments.Length == 2) - { - Type TOut = leftTypeGenericArguments[1]; - - if (expressionType == ExpressionType.AndAlso) - { - return ExpressionExtensions.And(left as Expression>, right as Expression>); - } - if (expressionType == ExpressionType.OrElse) - { - return ExpressionExtensions.Or(left as Expression>, right as Expression>); - } - - } - } - else - { - if (leftTypeGenericArguments.Length == 2) - { - //sequence not equal - could be Func && Func - if (leftTypeGenericArguments.First() == rightTypeGenericArguments.First()) - { - bool leftIsObject = leftTypeGenericArguments.ElementAt(1) == typeof(object); - bool rightIsObject = rightTypeGenericArguments.ElementAt(1) == typeof(object); - //if one is an object but not the other - if (leftIsObject ^ rightIsObject) - { - if (leftIsObject) - { - //left side is object - if (innerLeft == null) - { - parameters = new ParameterExpression[(left as LambdaExpression).Parameters.Count]; - (left as LambdaExpression).Parameters.CopyTo(parameters, 0); - innerLeft = Expression.Invoke(left, parameters); - } - unboxedLeft = Expression.Unbox(innerLeft, rightTypeGenericArguments.ElementAt(1)); - - //left is invoked and unboxed to right's TOut, right was not boxed - if (expressionType == ExpressionType.AndAlso) - { - return ExpressionExtensions.And(right as Expression>, Expression.Lambda>(unboxedLeft, parameters) as Expression>); - } - if (expressionType == ExpressionType.OrElse) - { - return ExpressionExtensions.And(right as Expression>, Expression.Lambda>(unboxedLeft, parameters) as Expression>); - } - } - else - { - //right side is object - if (innerRight == null) - { - parameters = new ParameterExpression[(right as LambdaExpression).Parameters.Count]; - (right as LambdaExpression).Parameters.CopyTo(parameters, 0); - innerRight = Expression.Invoke(right, parameters); - } - unboxedRight = Expression.Unbox(innerRight, leftTypeGenericArguments.ElementAt(1)); - - //right is invoked and unboxed to left's TOut, left was not boxed - if (expressionType == ExpressionType.AndAlso) - { - return ExpressionExtensions.And(left as Expression>, Expression.Lambda>(unboxedRight, parameters) as Expression>); - } - if (expressionType == ExpressionType.OrElse) - { - return ExpressionExtensions.And(left as Expression>, Expression.Lambda>(unboxedRight, parameters) as Expression>); - } - } - - } - } - } - } - } - } - - if (leftIsLambda && innerLeft == null) - { - //left is a lambda, but the right was an unhandled expression type - //!ConstantExpression, !MemberExpression - //make sure the left gets invoked - if (parameters == null) - { - parameters = new ParameterExpression[(left as LambdaExpression).Parameters.Count]; - (left as LambdaExpression).Parameters.CopyTo(parameters, 0); - } - innerLeft = Expression.Invoke(left, parameters); - } - if (rightIsLambda && innerRight == null) - { - //right is a lambda, but the left was an unhandled expression type - //!ConstantExpression, !MemberExpression - //make sure the right gets invoked - if (parameters == null) - { - parameters = new ParameterExpression[(right as LambdaExpression).Parameters.Count]; - (right as LambdaExpression).Parameters.CopyTo(parameters, 0); - } - innerRight = Expression.Invoke(right, parameters); - } - if (leftIsLambda && !rightIsLambda && innerLeft != null && !(innerLeft is UnaryExpression) && innerLeft.Type == typeof(object)) - { - //innerLeft is an invoke - unboxedLeft = Expression.Unbox(innerLeft, right.Type); - } - if (rightIsLambda && !leftIsLambda && innerRight != null && !(innerRight is UnaryExpression) && innerRight.Type == typeof(object)) - { - //innerRight is an invoke - unboxedRight = Expression.Unbox(innerRight, left.Type); - } - - BinaryExpression binaryExpression = null; - var finalLeft = unboxedLeft ?? innerLeft ?? left; - var finalRight = unboxedRight ?? innerRight ?? right; - switch (expressionType) - { - case ExpressionType.Equal: - binaryExpression = Expression.Equal(finalLeft, finalRight); - break; - case ExpressionType.NotEqual: - binaryExpression = Expression.NotEqual(finalLeft, finalRight); - break; - case ExpressionType.GreaterThan: - binaryExpression = Expression.GreaterThan(finalLeft, finalRight); - break; - case ExpressionType.LessThan: - binaryExpression = Expression.LessThan(finalLeft, finalRight); - break; - case ExpressionType.GreaterThanOrEqual: - binaryExpression = Expression.GreaterThanOrEqual(finalLeft, finalRight); - break; - case ExpressionType.LessThanOrEqual: - binaryExpression = Expression.LessThanOrEqual(finalLeft, finalRight); - break; - case ExpressionType.Modulo: - binaryExpression = Expression.Modulo(finalLeft, finalRight); - return (Expression.Lambda>(binaryExpression, parameters)); - case ExpressionType.AndAlso: - if ((leftIsLambda && rightIsLambda && sequenceEqual) || (!leftIsLambda && !rightIsLambda)) - { - return Expression.AndAlso(left, right); - } - else - { - return (Expression.Lambda>(Expression.AndAlso(finalLeft, finalRight), parameters)); - } - case ExpressionType.OrElse: - if (leftIsLambda && rightIsLambda && sequenceEqual || (!leftIsLambda && !rightIsLambda)) - { - return Expression.OrElse(left, right); - } - else - { - return (Expression.Lambda>(Expression.OrElse(finalLeft, finalRight), parameters)); - } - default: - return Expression.Equal(left, right); - } - if (leftIsLambda || rightIsLambda) - { - var body = Expression.Condition(Expression.TypeEqual(innerLeft, right.Type), binaryExpression, Expression.Constant(false)); - return Expression.Lambda>(body, parameters); - } - else - { - return binaryExpression; - } - - } - - Expression GenerateNotEqual(Expression left, Expression right) - { - return HandleDynamicNodeLambdas(ExpressionType.NotEqual, left, right); - } - - Expression GenerateGreaterThan(Expression left, Expression right) - { - if (left.Type == typeof(string)) - { - return Expression.GreaterThan( - GenerateStaticMethodCall("Compare", left, right), - Expression.Constant(0) - ); - } - return HandleDynamicNodeLambdas(ExpressionType.GreaterThan, left, right); - } - - Expression GenerateGreaterThanEqual(Expression left, Expression right) - { - if (left.Type == typeof(string)) - { - return Expression.GreaterThanOrEqual( - GenerateStaticMethodCall("Compare", left, right), - Expression.Constant(0) - ); - } - return HandleDynamicNodeLambdas(ExpressionType.GreaterThanOrEqual, left, right); - } - - Expression GenerateLessThan(Expression left, Expression right) - { - if (left.Type == typeof(string)) - { - return Expression.LessThan( - GenerateStaticMethodCall("Compare", left, right), - Expression.Constant(0) - ); - } - return HandleDynamicNodeLambdas(ExpressionType.LessThan, left, right); - } - - Expression GenerateLessThanEqual(Expression left, Expression right) - { - if (left.Type == typeof(string)) - { - return Expression.LessThanOrEqual( - GenerateStaticMethodCall("Compare", left, right), - Expression.Constant(0) - ); - } - return HandleDynamicNodeLambdas(ExpressionType.LessThanOrEqual, left, right); - } - - Expression GenerateAdd(Expression left, Expression right) - { - if (left.Type == typeof(string) && right.Type == typeof(string)) - { - return GenerateStaticMethodCall("Concat", left, right); - } - return Expression.Add(left, right); - } - - Expression GenerateSubtract(Expression left, Expression right) - { - return Expression.Subtract(left, right); - } - - Expression GenerateStringConcat(Expression left, Expression right) - { - return Expression.Call( - null, - typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object) }), - new[] { left, right }); - } - - MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) - { - return left.Type.GetMethod(methodName, new[] { left.Type, right.Type }); - } - - Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) - { - return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right }); - } - - void SetTextPos(int pos) - { - textPos = pos; - ch = textPos < textLen ? text[textPos] : '\0'; - } - - void NextChar() - { - if (textPos < textLen) textPos++; - ch = textPos < textLen ? text[textPos] : '\0'; - } - - void NextToken() - { - while (Char.IsWhiteSpace(ch)) NextChar(); - TokenId t; - int tokenPos = textPos; - switch (ch) - { - case '!': - NextChar(); - if (ch == '=') - { - NextChar(); - t = TokenId.ExclamationEqual; - } - else - { - t = TokenId.Exclamation; - } - break; - case '%': - NextChar(); - t = TokenId.Percent; - break; - case '&': - NextChar(); - if (ch == '&') - { - NextChar(); - t = TokenId.DoubleAmphersand; - } - else - { - t = TokenId.Amphersand; - } - break; - case '(': - NextChar(); - t = TokenId.OpenParen; - break; - case ')': - NextChar(); - t = TokenId.CloseParen; - break; - case '*': - NextChar(); - t = TokenId.Asterisk; - break; - case '+': - NextChar(); - t = TokenId.Plus; - break; - case ',': - NextChar(); - t = TokenId.Comma; - break; - case '-': - NextChar(); - t = TokenId.Minus; - break; - case '.': - NextChar(); - t = TokenId.Dot; - break; - case '/': - NextChar(); - t = TokenId.Slash; - break; - case ':': - NextChar(); - t = TokenId.Colon; - break; - case '<': - NextChar(); - if (ch == '=') - { - NextChar(); - t = TokenId.LessThanEqual; - } - else if (ch == '>') - { - NextChar(); - t = TokenId.LessGreater; - } - else - { - t = TokenId.LessThan; - } - break; - case '=': - NextChar(); - if (ch == '=') - { - NextChar(); - t = TokenId.DoubleEqual; - } - else - { - t = TokenId.Equal; - } - break; - case '>': - NextChar(); - if (ch == '=') - { - NextChar(); - t = TokenId.GreaterThanEqual; - } - else - { - t = TokenId.GreaterThan; - } - break; - case '?': - NextChar(); - t = TokenId.Question; - break; - case '[': - NextChar(); - t = TokenId.OpenBracket; - break; - case ']': - NextChar(); - t = TokenId.CloseBracket; - break; - case '|': - NextChar(); - if (ch == '|') - { - NextChar(); - t = TokenId.DoubleBar; - } - else - { - t = TokenId.Bar; - } - break; - case '"': - case '\'': - char quote = ch; - do - { - NextChar(); - while (textPos < textLen && ch != quote) NextChar(); - if (textPos == textLen) - throw ParseError(textPos, Res.UnterminatedStringLiteral); - NextChar(); - } while (ch == quote); - t = TokenId.StringLiteral; - break; - default: - if (Char.IsLetter(ch) || ch == '@' || ch == '_') - { - do - { - NextChar(); - } while (Char.IsLetterOrDigit(ch) || ch == '_'); - t = TokenId.Identifier; - break; - } - if (Char.IsDigit(ch)) - { - t = TokenId.IntegerLiteral; - do - { - NextChar(); - } while (Char.IsDigit(ch)); - if (ch == '.') - { - t = TokenId.RealLiteral; - NextChar(); - ValidateDigit(); - do - { - NextChar(); - } while (Char.IsDigit(ch)); - } - if (ch == 'E' || ch == 'e') - { - t = TokenId.RealLiteral; - NextChar(); - if (ch == '+' || ch == '-') NextChar(); - ValidateDigit(); - do - { - NextChar(); - } while (Char.IsDigit(ch)); - } - if (ch == 'F' || ch == 'f') NextChar(); - break; - } - if (textPos == textLen) - { - t = TokenId.End; - break; - } - throw ParseError(textPos, Res.InvalidCharacter, ch); - } - token.Id = t; - token.Text = text.Substring(tokenPos, textPos - tokenPos); - token.Pos = tokenPos; - } - - bool TokenIdentifierIs(string id) - { - return token.Id == TokenId.Identifier && String.Equals(id, token.Text, StringComparison.OrdinalIgnoreCase); - } - - string GetIdentifier() - { - ValidateToken(TokenId.Identifier, Res.IdentifierExpected); - string id = token.Text; - if (id.Length > 1 && id[0] == '@') id = id.Substring(1); - return id; - } - - void ValidateDigit() - { - if (!Char.IsDigit(ch)) throw ParseError(textPos, Res.DigitExpected); - } - - void ValidateToken(TokenId t, string errorMessage) - { - if (token.Id != t) throw ParseError(errorMessage); - } - - void ValidateToken(TokenId t) - { - if (token.Id != t) throw ParseError(Res.SyntaxError); - } - - Exception ParseError(string format, params object[] args) - { - return ParseError(token.Pos, format, args); - } - - Exception ParseError(int pos, string format, params object[] args) - { - return new ParseException(string.Format(System.Globalization.CultureInfo.CurrentCulture, format, args), pos); - } - - static Dictionary CreateKeywords() - { - Dictionary d = new Dictionary(StringComparer.OrdinalIgnoreCase); - d.Add("true", trueLiteral); - d.Add("false", falseLiteral); - d.Add("null", nullLiteral); - d.Add(keywordIt, keywordIt); - d.Add(keywordIif, keywordIif); - d.Add(keywordNew, keywordNew); - foreach (Type type in predefinedTypes) d.Add(type.Name, type); - return d; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Dynamics/ExtensionMethods.cs b/src/Umbraco.Web/Dynamics/ExtensionMethods.cs deleted file mode 100644 index 16eb3e9077..0000000000 --- a/src/Umbraco.Web/Dynamics/ExtensionMethods.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Dynamics -{ - internal static class ExtensionMethods - { - public static DynamicPublishedContentList Random(this DynamicPublishedContentList source, int min, int max) - { - return Random(source, new Random().Next(min, max)); - } - - public static DynamicPublishedContentList Random(this DynamicPublishedContentList source, int max) - { - return new DynamicPublishedContentList(source.OrderByRandom().Take(max)); - } - - public static DynamicPublishedContent Random(this DynamicPublishedContentList source) - { - return new DynamicPublishedContent(source.OrderByRandom().First()); - } - - private static IEnumerable OrderByRandom(this DynamicPublishedContentList source) - { - return source.Items.OrderBy(x => Guid.NewGuid()); - } - - public static DynamicPublishedContentList Children(this DynamicPublishedContent content) - { - return content.Children; - } - - public static DynamicPublishedContentList Children(this DynamicPublishedContent content, string contentTypeAlias) - { - return new DynamicPublishedContentList(content.PublishedContent.Children().OfTypes(contentTypeAlias)); - } - } -} diff --git a/src/Umbraco.Web/Dynamics/Grouping.cs b/src/Umbraco.Web/Dynamics/Grouping.cs deleted file mode 100644 index 4675b16e1b..0000000000 --- a/src/Umbraco.Web/Dynamics/Grouping.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Collections; -using System.Dynamic; -using Umbraco.Core.Dynamics; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Dynamics -{ - internal class Grouping : IGrouping where T : DynamicObject - { - public K Key { get; set; } - public IEnumerable Elements { get; set; } - - public IEnumerator GetEnumerator() - { - var temp = new DynamicPublishedContentList(Elements.Cast()); - return (IEnumerator)temp.GetEnumerator(); - } - IEnumerator IEnumerable.GetEnumerator() - { - return (IEnumerator)GetEnumerator(); - } - - public DynamicPublishedContentList OrderBy(string ordering) - { - bool descending = false; - if (ordering.IndexOf(" descending", StringComparison.CurrentCultureIgnoreCase) >= 0) - { - ordering = ordering.Replace(" descending", ""); - descending = true; - } - if (ordering.IndexOf(" desc", StringComparison.CurrentCultureIgnoreCase) >= 0) - { - ordering = ordering.Replace(" desc", ""); - descending = true; - } - - if (!descending) - { - return new DynamicPublishedContentList(Elements.OrderBy(item => - { - object key = null; - (item as DynamicObject).TryGetMember(new DynamicQueryableGetMemberBinder(ordering, false), out key); - return key; - }).Cast()); - } - else - { - return new DynamicPublishedContentList(Elements.OrderByDescending(item => - { - object key = null; - (item as DynamicObject).TryGetMember(new DynamicQueryableGetMemberBinder(ordering, false), out key); - return key; - }).Cast()); - } - } - } - -} diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 8f70935c99..50cad49657 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -1,8 +1,8 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Collections.Generic; using System.Security.Principal; using System.Threading.Tasks; using System.Web; @@ -13,8 +13,6 @@ using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; @@ -26,6 +24,9 @@ using Umbraco.Web.Security; using Umbraco.Web.Security.Identity; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Web.Composing; using IUser = Umbraco.Core.Models.Membership.IUser; namespace Umbraco.Web.Editors @@ -40,6 +41,7 @@ namespace Umbraco.Web.Editors public class AuthenticationController : UmbracoApiController { + //fixme inject these private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; protected BackOfficeUserManager UserManager @@ -100,7 +102,7 @@ namespace Umbraco.Web.Editors await SignInManager.SignInAsync(identityUser, false, false); - var user = ApplicationContext.Services.UserService.GetUserById(id); + var user = Services.UserService.GetUserById(id); return Mapper.Map(user); } @@ -141,7 +143,6 @@ namespace Umbraco.Web.Editors return false; } - /// /// Returns the currently logged in Umbraco user /// @@ -169,8 +170,8 @@ namespace Umbraco.Web.Editors } /// - /// When a user is invited they are not approved but we need to resolve the partially logged on (non approved) - /// user. + /// When a user is invited they are not approved but we need to resolve the partially logged on (non approved) + /// user. /// /// /// @@ -204,7 +205,7 @@ namespace Umbraco.Web.Editors [ValidateAngularAntiForgeryToken] public async Task> GetCurrentUserLinkedLogins() { - var identityUser = await UserManager.FindByIdAsync(UmbracoContext.Security.GetUserId()); + var identityUser = await UserManager.FindByIdAsync(UmbracoContext.Security.GetUserId().ResultOr(0)); return identityUser.Logins.ToDictionary(x => x.LoginProvider, x => x.ProviderKey); } @@ -216,9 +217,9 @@ namespace Umbraco.Web.Editors public async Task PostLogin(LoginModel loginModel) { var http = EnsureHttpContext(); - var owinContext = TryGetOwinContext().Result; + var owinContext = TryGetOwinContext().Result; - //Sign the user in with username/password, this also gives a chance for developers to + //Sign the user in with username/password, this also gives a chance for developers to //custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker var result = await SignInManager.PasswordSignInAsync( loginModel.Username, loginModel.Password, isPersistent: true, shouldLockout: true); @@ -226,7 +227,7 @@ namespace Umbraco.Web.Editors switch (result) { case SignInStatus.Success: - + //get the user var user = Services.UserService.GetByUsername(loginModel.Username); UserManager.RaiseLoginSuccessEvent(user.Id); @@ -305,13 +306,13 @@ namespace Umbraco.Web.Editors var message = Services.TextService.Localize("resetPasswordEmailCopyFormat", //Ensure the culture of the found user is used for the email! - UserExtensions.GetUserCulture(identityUser.Culture, Services.TextService), + UserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings), new[] { identityUser.UserName, callbackUrl }); await UserManager.SendEmailAsync(identityUser.Id, Services.TextService.Localize("login/resetPasswordEmailCopySubject", //Ensure the culture of the found user is used for the email! - UserExtensions.GetUserCulture(identityUser.Culture, Services.TextService)), + UserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings)), message); UserManager.RaiseForgotPasswordRequestedEvent(user.Id); @@ -375,7 +376,7 @@ namespace Umbraco.Web.Editors } var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: true, rememberBrowser: false); - var owinContext = TryGetOwinContext().Result; + var owinContext = TryGetOwinContext().Result; var user = Services.UserService.GetByUsername(userName); switch (result) @@ -406,22 +407,19 @@ namespace Umbraco.Web.Editors if (lockedOut) { Logger.Info( - "User {0} is currently locked out, unlocking and resetting AccessFailedCount", - () => model.UserId); + $"User {model.UserId} is currently locked out, unlocking and resetting AccessFailedCount"); //var user = await UserManager.FindByIdAsync(model.UserId); var unlockResult = await UserManager.SetLockoutEndDateAsync(model.UserId, DateTimeOffset.Now); if (unlockResult.Succeeded == false) { - Logger.Warn("Could not unlock for user {0} - error {1}", - () => model.UserId, () => unlockResult.Errors.First()); + Logger.Warn(() => $"Could not unlock for user {model.UserId} - error {unlockResult.Errors.First()}"); } var resetAccessFailedCountResult = await UserManager.ResetAccessFailedCountAsync(model.UserId); if (resetAccessFailedCountResult.Succeeded == false) { - Logger.Warn("Could not reset access failed count {0} - error {1}", - () => model.UserId, () => unlockResult.Errors.First()); + Logger.Warn(() => $"Could not reset access failed count {model.UserId} - error {unlockResult.Errors.First()}"); } } @@ -447,9 +445,7 @@ namespace Umbraco.Web.Editors Core.Constants.Security.BackOfficeAuthenticationType, Core.Constants.Security.BackOfficeExternalAuthenticationType); - Logger.Info("User {0} from IP address {1} has logged out", - () => User.Identity == null ? "UNKNOWN" : User.Identity.Name, - () => owinContext.Request.RemoteIpAddress); + Logger.Info($"User {(User.Identity == null ? "UNKNOWN" : User.Identity.Name)} from IP address {owinContext.Request.RemoteIpAddress} has logged out"); if (UserManager != null) { @@ -470,7 +466,7 @@ namespace Umbraco.Web.Editors private HttpResponseMessage SetPrincipalAndReturnUserDetail(IUser user, IPrincipal principal) { if (user == null) throw new ArgumentNullException("user"); - if (principal == null) throw new ArgumentNullException("principal"); + if (principal == null) throw new ArgumentNullException(nameof(principal)); var userDetail = Mapper.Map(user); //update the userDetail and set their remaining seconds @@ -493,13 +489,13 @@ namespace Umbraco.Web.Editors var action = urlHelper.Action("ValidatePasswordResetCode", "BackOffice", new { - area = GlobalSettings.UmbracoMvcArea, + area = GlobalSettings.GetUmbracoMvcArea(), u = userId, r = code }); // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = new Uri(ApplicationContext.UmbracoApplicationUrl); + var applicationUri = Current.RuntimeState.ApplicationUrl; var callbackUri = new Uri(applicationUri, action); return callbackUri.ToString(); } @@ -524,4 +520,4 @@ namespace Umbraco.Web.Editors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/BackOfficeAssetsController.cs b/src/Umbraco.Web/Editors/BackOfficeAssetsController.cs new file mode 100644 index 0000000000..eb38957633 --- /dev/null +++ b/src/Umbraco.Web/Editors/BackOfficeAssetsController.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Http; +using Umbraco.Core.IO; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web.Editors +{ + [PluginController("UmbracoApi")] + public class BackOfficeAssetsController : UmbracoAuthorizedJsonController + { + private readonly FileSystems _fileSystems; + + public BackOfficeAssetsController(FileSystems fileSystems) + { + _fileSystems = fileSystems; + } + + [HttpGet] + public IEnumerable GetSupportedMomentLocales() + { + const string momentLocaleFolder = "moment"; + var fileSystem = _fileSystems.JavaScriptLibraryFileSystem; + var cultures = fileSystem.GetFiles(momentLocaleFolder, "*.js").ToList(); + for (var i = 0; i < cultures.Count; i++) + { + cultures[i] = cultures[i] + .Substring(cultures[i].IndexOf(momentLocaleFolder, StringComparison.Ordinal) + momentLocaleFolder.Length + 1); + } + return cultures; + } + } +} diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 13447a50ff..3307c373a6 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using System.Web.UI; +using LightInject; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; @@ -29,18 +30,35 @@ using Umbraco.Web.Security.Identity; using Umbraco.Web.Trees; using Umbraco.Web.UI.JavaScript; using Umbraco.Core.Services; -using Action = umbraco.BusinessLogic.Actions.Action; +using Umbraco.Web.Composing; +using Umbraco.Web.Features; +using Action = Umbraco.Web._Legacy.Actions.Action; using Constants = Umbraco.Core.Constants; +using JArray = Newtonsoft.Json.Linq.JArray; namespace Umbraco.Web.Editors { + public class BackOfficeModel + { + public BackOfficeModel(string path, UmbracoFeatures features) + { + Path = path; + Features = features; + } + + public string Path { get; } + public UmbracoFeatures Features { get; } + } + /// - /// A controller to render out the default back office view and JS results + /// Represents a controller user to render out the default back office view and JS results. /// [UmbracoRequireHttps] [DisableClientCache] public class BackOfficeController : UmbracoController { + private readonly ManifestParser _manifestParser; + private readonly UmbracoFeatures _features; private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; @@ -48,19 +66,17 @@ namespace Umbraco.Web.Editors private const string TokenPasswordResetCode = "PasswordResetCode"; private static readonly string[] TempDataTokenNames = { TokenExternalSignInError, TokenPasswordResetCode }; - protected BackOfficeSignInManager SignInManager + public BackOfficeController(ManifestParser manifestParser, UmbracoFeatures features) { - get { return _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager()); } - } - protected BackOfficeUserManager UserManager - { - get { return _userManager ?? (_userManager = OwinContext.GetBackOfficeUserManager()); } + _manifestParser = manifestParser; + _features = features; } - protected IAuthenticationManager AuthenticationManager - { - get { return OwinContext.Authentication; } - } + protected BackOfficeSignInManager SignInManager => _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager()); + + protected BackOfficeUserManager UserManager => _userManager ?? (_userManager = OwinContext.GetBackOfficeUserManager()); + + protected IAuthenticationManager AuthenticationManager => OwinContext.Authentication; /// /// Render the default view @@ -69,8 +85,8 @@ namespace Umbraco.Web.Editors public async Task Default() { return await RenderDefaultOrProcessExternalLoginAsync( - () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml"), - () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml")); + () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(GlobalSettings.Path, _features)), + () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(GlobalSettings.Path, _features))); } [HttpGet] @@ -134,16 +150,16 @@ namespace Umbraco.Web.Editors } /// - /// This Action is used by the installer when an upgrade is detected but the admin user is not logged in. We need to + /// This Action is used by the installer when an upgrade is detected but the admin user is not logged in. We need to /// ensure the user is authenticated before the install takes place so we redirect here to show the standard login screen. /// - /// + /// [HttpGet] public async Task AuthorizeUpgrade() { return await RenderDefaultOrProcessExternalLoginAsync( //The default view to render when there is no external login info or errors - () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/AuthorizeUpgrade.cshtml"), + () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/AuthorizeUpgrade.cshtml", new BackOfficeModel(GlobalSettings.Path, _features)), //The ActionResult to perform if external login is successful () => Redirect("/")); } @@ -180,13 +196,11 @@ namespace Umbraco.Web.Editors [OutputCache(Order = 1, VaryByParam = "none", Location = OutputCacheLocation.Server, Duration = 5000)] public JavaScriptResult Application() { - var plugins = new DirectoryInfo(Server.MapPath("~/App_Plugins")); - var parser = new ManifestParser(plugins, ApplicationContext.ApplicationCache.RuntimeCache); - var initJs = new JsInitialization(parser); - var initCss = new CssInitialization(parser); + var initJs = new JsInitialization(_manifestParser); + var initCss = new CssInitialization(_manifestParser); //get the legacy ActionJs file references to append as well - var legacyActionJsRef = new JArray(GetLegacyActionJs(LegacyJsActionType.JsUrl)); + var legacyActionJsRef = GetLegacyActionJs(LegacyJsActionType.JsUrl); var result = initJs.GetJavascriptInitialization(HttpContext, JsInitialization.GetDefaultInitialization(), legacyActionJsRef); result += initCss.GetStylesheetInitialization(HttpContext); @@ -202,25 +216,23 @@ namespace Umbraco.Web.Editors [HttpGet] public JsonNetResult GetManifestAssetList() { - Func getResult = () => + JArray GetAssetList() { - var plugins = new DirectoryInfo(Server.MapPath("~/App_Plugins")); - var parser = new ManifestParser(plugins, ApplicationContext.ApplicationCache.RuntimeCache); - var initJs = new JsInitialization(parser); - var initCss = new CssInitialization(parser); - var jsResult = initJs.GetJavascriptInitializationArray(HttpContext, new JArray()); - var cssResult = initCss.GetStylesheetInitializationArray(HttpContext); - ManifestParser.MergeJArrays(jsResult, cssResult); - return jsResult; - }; + var initJs = new JsInitialization(_manifestParser); + var initCss = new CssInitialization(_manifestParser); + var assets = new List(); + assets.AddRange(initJs.GetScriptFiles(HttpContext, Enumerable.Empty())); + assets.AddRange(initCss.GetStylesheetFiles(HttpContext)); + return new JArray(assets); + } //cache the result if debugging is disabled var result = HttpContext.IsDebuggingEnabled - ? getResult() - : ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem( - typeof(BackOfficeController) + "GetManifestAssetList", - () => getResult(), - new TimeSpan(0, 10, 0)); + ? GetAssetList() + : ApplicationCache.RuntimeCache.GetCacheItem( + "Umbraco.Web.Editors.BackOfficeController.GetManifestAssetList", + GetAssetList, + new TimeSpan(0, 2, 0)); return new JsonNetResult { Data = result, Formatting = Formatting.Indented }; } @@ -231,7 +243,7 @@ namespace Umbraco.Web.Editors { var gridConfig = UmbracoConfig.For.GridConfig( Logger, - ApplicationContext.ApplicationCache.RuntimeCache, + ApplicationCache.RuntimeCache, new DirectoryInfo(Server.MapPath(SystemDirectories.AppPlugins)), new DirectoryInfo(Server.MapPath(SystemDirectories.Config)), HttpContext.IsDebuggingEnabled); @@ -239,7 +251,7 @@ namespace Umbraco.Web.Editors return new JsonNetResult { Data = gridConfig.EditorsConfig.Editors, Formatting = Formatting.Indented }; } - + /// /// Returns the JavaScript object representing the static server variables javascript object @@ -249,12 +261,12 @@ namespace Umbraco.Web.Editors [MinifyJavaScriptResult(Order = 1)] public JavaScriptResult ServerVariables() { - var serverVars = new BackOfficeServerVariables(Url, ApplicationContext, UmbracoConfig.For.UmbracoSettings()); + var serverVars = new BackOfficeServerVariables(Url, Current.RuntimeState, _features, GlobalSettings); //cache the result if debugging is disabled var result = HttpContext.IsDebuggingEnabled ? ServerVariablesParser.Parse(serverVars.GetServerVariables()) - : ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem( + : ApplicationCache.RuntimeCache.GetCacheItem( typeof(BackOfficeController) + "ServerVariables", () => ServerVariablesParser.Parse(serverVars.GetServerVariables()), new TimeSpan(0, 10, 0)); @@ -262,7 +274,7 @@ namespace Umbraco.Web.Editors return JavaScript(result); } - + [HttpPost] public ActionResult ExternalLogin(string provider, string redirectUrl = null) @@ -332,10 +344,10 @@ namespace Umbraco.Web.Editors } /// - /// Used by Default and AuthorizeUpgrade to render as per normal if there's no external login info, + /// Used by Default and AuthorizeUpgrade to render as per normal if there's no external login info, /// otherwise process the external login info. /// - /// + /// private async Task RenderDefaultOrProcessExternalLoginAsync( Func defaultResponse, Func externalSignInResponse) @@ -343,7 +355,7 @@ namespace Umbraco.Web.Editors if (defaultResponse == null) throw new ArgumentNullException("defaultResponse"); if (externalSignInResponse == null) throw new ArgumentNullException("externalSignInResponse"); - ViewBag.UmbracoPath = GlobalSettings.UmbracoMvcArea; + ViewBag.UmbracoPath = GlobalSettings.GetUmbracoMvcArea(); //check if there is the TempData with the any token name specified, if so, assign to view bag and render the view foreach (var tempDataTokenName in TempDataTokenNames) @@ -390,10 +402,10 @@ namespace Umbraco.Web.Editors var user = await UserManager.FindAsync(loginInfo.Login); if (user != null) { - //TODO: It might be worth keeping some of the claims associated with the ExternalLoginInfo, in which case we - // wouldn't necessarily sign the user in here with the standard login, instead we'd update the + //TODO: It might be worth keeping some of the claims associated with the ExternalLoginInfo, in which case we + // wouldn't necessarily sign the user in here with the standard login, instead we'd update the // UseUmbracoBackOfficeExternalCookieAuthentication extension method to have the correct provider and claims factory, - // ticket format, etc.. to create our back office user including the claims assigned and in this method we'd just ensure + // ticket format, etc.. to create our back office user including the claims assigned and in this method we'd just ensure // that the ticket is created and stored and that the user is logged in. var shouldSignIn = true; @@ -530,7 +542,7 @@ namespace Umbraco.Web.Editors //cache the result if debugging is disabled var result = HttpContext.IsDebuggingEnabled ? getResult() - : ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem( + : ApplicationCache.RuntimeCache.GetCacheItem( typeof(BackOfficeController) + "LegacyTreeJs", () => getResult(), new TimeSpan(0, 10, 0)); diff --git a/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs index 0910ec936e..66e7a4734a 100644 --- a/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs @@ -1,23 +1,15 @@ -using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Editors { /// - /// An abstract controller that automatically checks if any request is a non-GET and if the + /// An abstract controller that automatically checks if any request is a non-GET and if the /// resulting message is INotificationModel in which case it will append any Event Messages /// currently in the request. /// [AppendCurrentEventMessages] [PrefixlessBodyModelValidator] public abstract class BackOfficeNotificationsController : UmbracoAuthorizedJsonController - { - protected BackOfficeNotificationsController() - { - } - - protected BackOfficeNotificationsController(UmbracoContext umbracoContext) : base(umbracoContext) - { - } - } -} \ No newline at end of file + { } +} diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index d682244eef..c37f3426d8 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Configuration; @@ -10,9 +10,11 @@ using ClientDependency.Core.Config; using Microsoft.Owin; using Microsoft.Owin.Security; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; +using Umbraco.Web.Features; using Umbraco.Web.HealthCheck; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; @@ -28,14 +30,18 @@ namespace Umbraco.Web.Editors internal class BackOfficeServerVariables { private readonly UrlHelper _urlHelper; - private readonly ApplicationContext _applicationContext; + private readonly IRuntimeState _runtimeState; + private readonly UmbracoFeatures _features; + private readonly IGlobalSettings _globalSettings; private readonly HttpContextBase _httpContext; private readonly IOwinContext _owinContext; - public BackOfficeServerVariables(UrlHelper urlHelper, ApplicationContext applicationContext, IUmbracoSettingsSection umbracoSettings) + internal BackOfficeServerVariables(UrlHelper urlHelper, IRuntimeState runtimeState, UmbracoFeatures features, IGlobalSettings globalSettings) { _urlHelper = urlHelper; - _applicationContext = applicationContext; + _runtimeState = runtimeState; + _features = features; + _globalSettings = globalSettings; _httpContext = _urlHelper.RequestContext.HttpContext; _owinContext = _httpContext.GetOwinContext(); } @@ -52,7 +58,8 @@ namespace Umbraco.Web.Editors {"umbracoUrls", new[] {"authenticationApiBaseUrl", "serverVarsJs", "externalLoginsUrl", "currentUserApiBaseUrl"}}, {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage"}}, {"application", new[] {"applicationPath", "cacheBuster"}}, - {"isDebuggingEnabled", new string[] { }} + {"isDebuggingEnabled", new string[] { }}, + {"features", new [] {"disabledFeatures"}} }; //now do the filtering... var defaults = GetServerVariables(); @@ -81,7 +88,7 @@ namespace Umbraco.Web.Editors //TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address // so based on compat and how things are currently working we need to replace the serverVarsJs one - ((Dictionary) defaults["umbracoUrls"])["serverVarsJs"] = _urlHelper.Action("ServerVariables", "BackOffice"); + ((Dictionary)defaults["umbracoUrls"])["serverVarsJs"] = _urlHelper.Action("ServerVariables", "BackOffice"); return defaults; } @@ -265,16 +272,32 @@ namespace Umbraco.Web.Editors "codeFileApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetByPath("", "")) }, + { + "publishedStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetPublishedStatusUrl()) + }, + { + "nuCacheStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetStatus()) + }, { "helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetContextHelpForPage("","","")) + }, + { + "backOfficeAssetsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetSupportedMomentLocales()) + }, + { + "languageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllLanguages()) } } }, { "umbracoSettings", new Dictionary { - {"umbracoPath", GlobalSettings.Path}, + {"umbracoPath", _globalSettings.Path}, {"mediaPath", IOHelper.ResolveUrl(SystemDirectories.Media).TrimEnd('/')}, {"appPluginsPath", IOHelper.ResolveUrl(SystemDirectories.AppPlugins).TrimEnd('/')}, { @@ -328,6 +351,18 @@ namespace Umbraco.Web.Editors .ToArray() } } + }, + { + "features", new Dictionary + { + { + "disabledFeatures", new Dictionary + { + { "disableTemplates", _features.Disabled.DisableTemplates} + } + } + + } } }; return defaultVals; @@ -352,9 +387,9 @@ namespace Umbraco.Web.Editors .ToArray(); return (from p in pluginTreesWithAttributes - let treeAttr = p.attributes.OfType().Single() - let pluginAttr = p.attributes.OfType().Single() - select new Dictionary + let treeAttr = p.attributes.OfType().Single() + let pluginAttr = p.attributes.OfType().Single() + select new Dictionary { {"alias", treeAttr.Alias}, {"packageFolder", pluginAttr.AreaName} }).ToArray(); @@ -366,9 +401,10 @@ namespace Umbraco.Web.Editors /// /// /// We are doing this because if we constantly resolve the tree controller types from the PluginManager it will re-scan and also re-log that - /// it's resolving which is unecessary and annoying. + /// it's resolving which is unecessary and annoying. /// - private static readonly Lazy> TreeControllerTypes = new Lazy>(() => PluginManager.Current.ResolveAttributedTreeControllers().ToArray()); + private static readonly Lazy> TreeControllerTypes + = new Lazy>(() => Current.TypeLoader.GetAttributedTreeControllers().ToArray()); // fixme inject /// /// Returns the server variables regarding the application state @@ -376,17 +412,16 @@ namespace Umbraco.Web.Editors /// private Dictionary GetApplicationState() { - if (_applicationContext.IsConfigured == false) - return null; - var app = new Dictionary { {"assemblyVersion", UmbracoVersion.AssemblyVersion} }; - var version = UmbracoVersion.GetSemanticVersion().ToSemanticString(); + var version = _runtimeState.SemanticVersion.ToSemanticString(); + + //the value is the hash of the version, cdf version and the configured state + app.Add("cacheBuster", $"{version}.{_runtimeState.Level}.{ClientDependencySettings.Instance.Version}".GenerateHash()); - app.Add("cacheBuster", string.Format("{0}.{1}", version, ClientDependencySettings.Instance.Version).GenerateHash()); app.Add("version", version); //useful for dealing with virtual paths on the client side when hosted in virtual directories especially @@ -398,11 +433,11 @@ namespace Umbraco.Web.Editors return app; } - private string GetMaxRequestLength() + private static string GetMaxRequestLength() { - var section = ConfigurationManager.GetSection("system.web/httpRuntime") as HttpRuntimeSection; - if (section == null) return string.Empty; - return section.MaxRequestLength.ToString(); + return ConfigurationManager.GetSection("system.web/httpRuntime") is HttpRuntimeSection section + ? section.MaxRequestLength.ToString() + : string.Empty; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/CanvasDesignerController.cs b/src/Umbraco.Web/Editors/CanvasDesignerController.cs index 722167d18a..13b270ebb4 100644 --- a/src/Umbraco.Web/Editors/CanvasDesignerController.cs +++ b/src/Umbraco.Web/Editors/CanvasDesignerController.cs @@ -1,29 +1,15 @@ -using System.Collections.Generic; -using System.Net; +using System.Collections.Generic; using System.Net.Http; -using System.Text; using System.Web.Http; -using AutoMapper; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using umbraco; using Umbraco.Web.WebApi; using System; using System.Net.Http.Headers; using System.Web; -using System.IO; -using Umbraco.Core.Models; -using System.Text.RegularExpressions; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Services; namespace Umbraco.Web.Editors { - public class CanvasDesignerController : UmbracoApiController { - [HttpGet] public HttpResponseMessage GetGoogleFont() { @@ -51,7 +37,7 @@ namespace Umbraco.Web.Editors public HttpResponseMessage Load() { - // Get style less url + // Get style less url var pageId = int.Parse(HttpContext.Current.Request["pageId"]); // Get all parameters @@ -59,7 +45,7 @@ namespace Umbraco.Web.Editors // Prepare string parameter result string[] paramLines = paramBlock.Trim().Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); - IList parameters = new List(); + IList parameters = new List(); foreach (var line in paramLines) { if (!line.Contains("@import")) @@ -121,4 +107,4 @@ namespace Umbraco.Web.Editors } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/CodeFileController.cs b/src/Umbraco.Web/Editors/CodeFileController.cs index a7f420fc3b..6cf6d61c44 100644 --- a/src/Umbraco.Web/Editors/CodeFileController.cs +++ b/src/Umbraco.Web/Editors/CodeFileController.cs @@ -11,6 +11,7 @@ using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -248,7 +249,7 @@ namespace Umbraco.Web.Editors codeFileDisplay.Path = Url.GetTreePathFromFilePath(id); } - codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.TrimStart("~"); + codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.TrimStart("~"); codeFileDisplay.FileType = type; return codeFileDisplay; } @@ -265,7 +266,7 @@ namespace Umbraco.Web.Editors { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); if (string.IsNullOrWhiteSpace(virtualPath)) throw new ArgumentException("Value cannot be null or whitespace.", "virtualPath"); - + virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath); switch (type) @@ -375,7 +376,7 @@ namespace Umbraco.Web.Editors break; - + default: throw new HttpResponseException(HttpStatusCode.NotFound); @@ -400,7 +401,7 @@ namespace Umbraco.Web.Editors var virtualPath = display.VirtualPath ?? string.Empty; // this is all weird, should be using relative paths everywhere! - var relPath = FileSystemProviderManager.Current.ScriptsFileSystem.GetRelativePath(virtualPath); + var relPath = Current.FileSystems.ScriptsFileSystem.GetRelativePath(virtualPath); if (relPath.EndsWith(".js") == false) { @@ -408,7 +409,7 @@ namespace Umbraco.Web.Editors relPath = relPath.IsNullOrWhiteSpace() ? relPath + display.Name : relPath.EnsureEndsWith('/') + display.Name; - } + } var script = Services.FileService.GetScriptByName(relPath); if (script != null) @@ -434,13 +435,13 @@ namespace Umbraco.Web.Editors private Attempt CreateOrUpdatePartialView(CodeFileDisplay display) { return CreateOrUpdatePartialView(display, SystemDirectories.PartialViews, - Services.FileService.GetPartialView, Services.FileService.SavePartialView, Services.FileService.CreatePartialView); + Services.FileService.GetPartialView, Services.FileService.SavePartialView, Services.FileService.CreatePartialView); } - + private Attempt CreateOrUpdatePartialViewMacro(CodeFileDisplay display) { return CreateOrUpdatePartialView(display, SystemDirectories.MacroPartials, - Services.FileService.GetPartialViewMacro, Services.FileService.SavePartialViewMacro, Services.FileService.CreatePartialViewMacro); + Services.FileService.GetPartialViewMacro, Services.FileService.SavePartialViewMacro, Services.FileService.CreatePartialViewMacro); } /// diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index a61ca7e249..0bcb218fb0 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -9,13 +9,11 @@ using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; using AutoMapper; -using umbraco.BusinessLogic.Actions; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Publishing; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; @@ -23,10 +21,15 @@ using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; -using umbraco.cms.businesslogic.web; -using umbraco.presentation.preview; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Web.PublishedCache; using Umbraco.Core.Events; +using Umbraco.Core.Models.Validation; +using Umbraco.Web.Models; +using Umbraco.Web._Legacy.Actions; using Constants = Umbraco.Core.Constants; +using ContentVariation = Umbraco.Core.Models.ContentVariation; +using Language = Umbraco.Web.Models.ContentEditing.Language; namespace Umbraco.Web.Editors { @@ -42,21 +45,12 @@ namespace Umbraco.Web.Editors [ContentControllerConfiguration] public class ContentController : ContentControllerBase { - /// - /// Constructor - /// - public ContentController() - : this(UmbracoContext.Current) - { - } + private readonly IPublishedSnapshotService _publishedSnapshotService; - /// - /// Constructor - /// - /// - public ContentController(UmbracoContext umbracoContext) - : base(umbracoContext) + public ContentController(IPublishedSnapshotService publishedSnapshotService) { + if (publishedSnapshotService == null) throw new ArgumentNullException(nameof(publishedSnapshotService)); + _publishedSnapshotService = publishedSnapshotService; } /// @@ -80,7 +74,7 @@ namespace Umbraco.Web.Editors public IEnumerable GetByIds([FromUri]int[] ids) { var foundContent = Services.ContentService.GetByIds(ids); - return foundContent.Select(Mapper.Map); + return foundContent.Select(x => MapToDisplay(x)); } /// @@ -95,14 +89,14 @@ namespace Umbraco.Web.Editors public IEnumerable PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel) { if (saveModel.ContentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - //TODO: Should non-admins be alowed to set granular permissions? + + //TODO: Should non-admins be alowed to set granular permissions? var content = Services.ContentService.GetById(saveModel.ContentId); if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - + //current permissions explicitly assigned to this content item - var contentPermissions = Services.ContentService.GetPermissionsForEntity(content) + var contentPermissions = Services.ContentService.GetPermissions(content) .ToDictionary(x => x.UserGroupId, x => x); var allUserGroups = Services.UserService.GetAllUserGroups().ToArray(); @@ -136,10 +130,10 @@ namespace Umbraco.Web.Editors //if they are different we need to update, otherwise there's nothing to update else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false) { - + Services.UserService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id); - } - } + } + } } return GetDetailedPermissions(content, allUserGroups); @@ -158,9 +152,9 @@ namespace Umbraco.Web.Editors { if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); var content = Services.ContentService.GetById(contentId); - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - //TODO: Should non-admins be able to see detailed permissions? + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + //TODO: Should non-admins be able to see detailed permissions? var allUserGroups = Services.UserService.GetAllUserGroups(); @@ -178,7 +172,7 @@ namespace Umbraco.Web.Editors .ToDictionary(x => Convert.ToInt32(x.Id), x => x); //get the actual assigned permissions - var assignedPermissionsByGroup = Services.ContentService.GetPermissionsForEntity(content).ToArray(); + var assignedPermissionsByGroup = Services.ContentService.GetPermissions(content).ToArray(); //iterate over assigned and update the defaults with the real values foreach (var assignedGroupPermission in assignedPermissionsByGroup) @@ -195,9 +189,9 @@ namespace Umbraco.Web.Editors permission.Checked = false; permission.Checked = assignedGroupPermission.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); } - + } - + return defaultPermissionsByGroup; } @@ -232,7 +226,7 @@ namespace Umbraco.Web.Editors HandleContentNotFound(id); } - var content = Mapper.Map(foundContent); + var content = MapToDisplay(foundContent); SetupBlueprint(content, foundContent); @@ -259,18 +253,20 @@ namespace Umbraco.Web.Editors /// Gets the content json for the content id /// /// + /// /// [OutgoingEditorModelEvent] [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id) + public ContentItemDisplay GetById(int id, int? languageId = null) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); if (foundContent == null) { HandleContentNotFound(id); + return null;//irrelevant since the above throws } - var content = Mapper.Map(foundContent); + var content = MapToDisplay(foundContent, languageId); return content; } @@ -283,12 +279,12 @@ namespace Umbraco.Web.Editors HandleContentNotFound(id); } - var content = Mapper.Map(foundContent); + var content = MapToDisplay(foundContent); return content; } /// - /// Gets an empty content item for the + /// Gets an empty content item for the /// /// /// @@ -299,18 +295,23 @@ namespace Umbraco.Web.Editors [OutgoingEditorModelEvent] public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) { - var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); + var contentType = Services.ContentTypeService.Get(contentTypeAlias); if (contentType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id); - var mapped = Mapper.Map(emptyContent); + var emptyContent = Services.ContentService.Create("", parentId, contentType.Alias, Security.GetUserId().ResultOr(0)); + var mapped = MapToDisplay(emptyContent); //remove this tab if it exists: umbContainerView var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + + //Remove all variants except for the default since currently the default must be saved before other variants can be edited + //TODO: Allow for editing all variants at once ... this will be a future task + mapped.Variants = new[] {mapped.Variants.First(x => x.IsCurrent)}; + return mapped; } @@ -373,13 +374,13 @@ namespace Umbraco.Web.Editors { return GetNiceUrl(guidUdi.Guid); } - throw new HttpResponseException(HttpStatusCode.NotFound); + throw new HttpResponseException(HttpStatusCode.NotFound); } /// /// Gets the children for the content id passed in /// - /// + /// [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren( int id, @@ -389,14 +390,25 @@ namespace Umbraco.Web.Editors Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") - { + { long totalChildren; IContent[] children; if (pageNumber > 0 && pageSize > 0) { + IQuery queryFilter = null; + if (filter.IsNullOrWhiteSpace() == false) + { + //add the default text filter + queryFilter = SqlContext.Query() + .Where(x => x.Name.Contains(filter)); + } + children = Services.ContentService - .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren - , orderBy, orderDirection, orderBySystemField, filter).ToArray(); + .GetPagedChildren( + id, (pageNumber - 1), pageSize, + out totalChildren, + orderBy, orderDirection, orderBySystemField, + queryFilter).ToArray(); } else { @@ -464,7 +476,7 @@ namespace Umbraco.Web.Editors } /// - /// Creates a blueprint from a content item + /// Creates a blueprint from a content item /// /// The content id to copy /// The name of the blueprint @@ -480,9 +492,9 @@ namespace Umbraco.Web.Editors EnsureUniqueName(name, content, "name"); - var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.GetUserId()); + var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.GetUserId().ResultOr(0)); - Services.ContentService.SaveBlueprint(blueprint, Security.GetUserId()); + Services.ContentService.SaveBlueprint(blueprint, Security.GetUserId().ResultOr(0)); var notificationModel = new SimpleNotificationModel(); notificationModel.AddSuccessNotification( @@ -509,8 +521,7 @@ namespace Umbraco.Web.Editors /// [FileUploadCleanupFilter] [ContentPostValidate] - public ContentItemDisplay PostSaveBlueprint( - [ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) + public ContentItemDisplay PostSaveBlueprint([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) { var contentItemDisplay = PostSaveInternal(contentItem, content => @@ -519,7 +530,7 @@ namespace Umbraco.Web.Editors Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id); //we need to reuse the underlying logic so return the result that it wants - return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, new EventMessages())); + return OperationResult.Succeed(new EventMessages()); }); SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent); @@ -533,15 +544,21 @@ namespace Umbraco.Web.Editors [FileUploadCleanupFilter] [ContentPostValidate] [OutgoingEditorModelEvent] - public ContentItemDisplay PostSave( - [ModelBinder(typeof(ContentItemBinder))] - ContentItemSave contentItem) + public ContentItemDisplay PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) { - return PostSaveInternal(contentItem, - content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + var contentItemDisplay = PostSaveInternal(contentItem, content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + //ensure the active language is still selected + if (contentItem.LanguageId.HasValue) + { + foreach (var contentVariation in contentItemDisplay.Variants) + { + contentVariation.IsCurrent = contentVariation.Language.Id == contentItem.LanguageId; + } + } + return contentItemDisplay; } - - private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func> saveMethod) + + private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func saveMethod) { //If we've reached here it means: // * Our model has been bound @@ -555,15 +572,15 @@ namespace Umbraco.Web.Editors // * We still need to save the entity even if there are validation value errors // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) // then we cannot continue saving, we can only display errors - // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display + // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display // a message indicating this if (ModelState.IsValid == false) { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action)) + if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem) && IsCreatingAction(contentItem.Action)) { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the modelstate to the outgoing object and throw a validation message - var forDisplay = Mapper.Map(contentItem.PersistedContent); + var forDisplay = MapToDisplay(contentItem.PersistedContent, contentItem.LanguageId); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); @@ -582,7 +599,7 @@ namespace Umbraco.Web.Editors } //initialize this to successful - var publishStatus = Attempt.Succeed(); + var publishStatus = new PublishResult(null, contentItem.PersistedContent); var wasCancelled = false; if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) @@ -590,7 +607,7 @@ namespace Umbraco.Web.Editors //save the item var saveResult = saveMethod(contentItem.PersistedContent); - wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; + wasCancelled = saveResult.Success == false && saveResult.Result == OperationResultType.FailedCancelledByEvent; } else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) { @@ -600,17 +617,45 @@ namespace Umbraco.Web.Editors else { //publish the item and check if it worked, if not we will show a diff msg below - publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id); - wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent; + contentItem.PersistedContent.PublishValues(contentItem.LanguageId); //we are not checking for a return value here because we've alraedy pre-validated the property values + + //check if we are publishing other variants and validate them + var allLangs = Services.LocalizationService.GetAllLanguages().ToList(); + var variantsToValidate = contentItem.PublishVariations.Where(x => x.LanguageId != contentItem.LanguageId).ToList(); + foreach (var publishVariation in variantsToValidate) + { + if (!contentItem.PersistedContent.PublishValues(publishVariation.LanguageId)) + { + var errMsg = Services.TextService.Localize("speechBubbles/contentLangValidationError", new[]{allLangs.First(x => x.Id == publishVariation.LanguageId).CultureName}); + ModelState.AddModelError("publish_variant_" + publishVariation.LanguageId + "_", errMsg); + } + } + + //validate any mandatory variants that are not in the list + var mandatoryLangs = Mapper.Map, IEnumerable>(allLangs) + .Where(x => variantsToValidate.All(v => v.LanguageId != x.Id)) //don't include variants above + .Where(x => x.Id != contentItem.LanguageId) //don't include the current variant + .Where(x => x.Mandatory); + foreach (var lang in mandatoryLangs) + { + if (contentItem.PersistedContent.Validate(lang.Id).Length > 0) + { + var errMsg = Services.TextService.Localize("speechBubbles/contentReqLangValidationError", new[]{allLangs.First(x => x.Id == lang.Id).CultureName}); + ModelState.AddModelError("publish_variant_" + lang.Id + "_", errMsg); + } + } + + publishStatus = Services.ContentService.SaveAndPublish(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = publishStatus.Result == PublishResultType.FailedCancelledByEvent; } - //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); - + //get the updated model + var display = MapToDisplay(contentItem.PersistedContent, contentItem.LanguageId); + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); - //put the correct msgs in + //put the correct msgs in switch (contentItem.Action) { case ContentSaveAction.Save: @@ -641,10 +686,10 @@ namespace Umbraco.Web.Editors break; case ContentSaveAction.Publish: case ContentSaveAction.PublishNew: - ShowMessageForPublishStatus(publishStatus.Result, display); + ShowMessageForPublishStatus(publishStatus, display); break; } - + //If the item is new and the operation was cancelled, we need to return a different // status code so the UI can handle it since it won't be able to redirect since there // is no Id to redirect to! @@ -667,7 +712,7 @@ namespace Umbraco.Web.Editors /// The CanAccessContentAuthorize attribute will deny access to this method if the current user /// does not have Publish access to this node. /// - /// + /// [EnsureUserPermissionForContent("id", 'U')] public HttpResponseMessage PostPublishById(int id) { @@ -678,11 +723,12 @@ namespace Umbraco.Web.Editors return HandleContentNotFound(id, false); } - var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.GetUserId()); + foundContent.PublishValues(); // fixme variants? + var publishResult = Services.ContentService.SaveAndPublish(foundContent, Security.GetUserId().ResultOr(0)); if (publishResult.Success == false) { var notificationModel = new SimpleNotificationModel(); - ShowMessageForPublishStatus(publishResult.Result, notificationModel); + ShowMessageForPublishStatus(publishResult, notificationModel); return Request.CreateValidationErrorResponse(notificationModel); } @@ -729,22 +775,22 @@ namespace Umbraco.Web.Editors } //if the current item is in the recycle bin - if (foundContent.IsInRecycleBin() == false) + if (foundContent.Trashed == false) { - var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.GetUserId()); - if (moveResult == false) + var moveResult = Services.ContentService.MoveToRecycleBin(foundContent, Security.GetUserId().ResultOr(0)); + if (moveResult.Success == false) { - //returning an object of INotificationModel will ensure that any pending + //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); } } else { - var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.GetUserId()); - if (deleteResult == false) + var deleteResult = Services.ContentService.Delete(foundContent, Security.GetUserId().ResultOr(0)); + if (deleteResult.Success == false) { - //returning an object of INotificationModel will ensure that any pending + //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); } @@ -796,14 +842,14 @@ namespace Umbraco.Web.Editors // Save content with new sort order and update content xml in db accordingly if (contentService.Sort(sorted.IdSortOrder) == false) { - LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled"); + Logger.Warn("Content sorting failed, this was probably caused by an event being cancelled"); return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); } return Request.CreateResponse(HttpStatusCode.OK); } catch (Exception ex) { - LogHelper.Error("Could not update content sort order", ex); + Logger.Error("Could not update content sort order", ex); throw; } } @@ -856,11 +902,11 @@ namespace Umbraco.Web.Editors if (foundContent == null) HandleContentNotFound(id); - var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); + var unpublishResult = Services.ContentService.Unpublish(foundContent, Security.CurrentUser.Id); - var content = Mapper.Map(foundContent); + var content = MapToDisplay(foundContent); - if (unpublishResult == false) + if (unpublishResult.Success == false) { AddCancelMessage(content); throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); @@ -894,7 +940,7 @@ namespace Umbraco.Web.Editors if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) { //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); - LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); + Logger.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); } else { @@ -903,7 +949,10 @@ namespace Umbraco.Web.Editors } } - base.MapPropertyValues(contentItem); + base.MapPropertyValues( + contentItem, + (save, property) => property.GetValue(save.LanguageId), //get prop val + (save, property, v) => property.SetValue(v, save.LanguageId)); //set prop val } /// @@ -963,50 +1012,50 @@ namespace Umbraco.Web.Editors return toMove; } - private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display) + private void ShowMessageForPublishStatus(PublishResult status, INotificationModel display) { - switch (status.StatusType) + switch (status.Result) { - case PublishStatusType.Success: - case PublishStatusType.SuccessAlreadyPublished: + case PublishResultType.Success: + case PublishResultType.SuccessAlready: display.AddSuccessNotification( Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), Services.TextService.Localize("speechBubbles/editContentPublishedText")); break; - case PublishStatusType.FailedPathNotPublished: + case PublishResultType.FailedPathNotPublished: display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedByParent", - new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); + new[] { string.Format("{0} ({1})", status.Content.Name, status.Content.Id) }).Trim()); break; - case PublishStatusType.FailedCancelledByEvent: + case PublishResultType.FailedCancelledByEvent: AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); break; - case PublishStatusType.FailedAwaitingRelease: + case PublishResultType.FailedAwaitingRelease: display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", - new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); + new[] { string.Format("{0} ({1})", status.Content.Name, status.Content.Id) }).Trim()); break; - case PublishStatusType.FailedHasExpired: + case PublishResultType.FailedHasExpired: display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedExpired", new[] { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + string.Format("{0} ({1})", status.Content.Name, status.Content.Id), }).Trim()); break; - case PublishStatusType.FailedIsTrashed: + case PublishResultType.FailedIsTrashed: //TODO: We should add proper error messaging for this! break; - case PublishStatusType.FailedContentInvalid: + case PublishResultType.FailedContentInvalid: display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedInvalid", new[] { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + string.Format("{0} ({1})", status.Content.Name, status.Content.Id), string.Join(",", status.InvalidProperties.Select(x => x.Alias)) }).Trim()); break; @@ -1015,9 +1064,8 @@ namespace Umbraco.Web.Editors } } - /// - /// Performs a permissions check for the user to check if it has access to the node based on + /// Performs a permissions check for the user to check if it has access to the node based on /// start node and/or permissions for the node /// /// The storage to add the content item to so it can be reused @@ -1048,7 +1096,7 @@ namespace Umbraco.Web.Editors if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) { contentItem = contentService.GetById(nodeId); - //put the content item into storage so it can be retreived + //put the content item into storage so it can be retreived // in the controller (saves a lookup) storage[typeof(IContent).ToString()] = contentItem; } @@ -1057,7 +1105,7 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(HttpStatusCode.NotFound); } - + var hasPathAccess = (nodeId == Constants.System.Root) ? user.HasContentRootAccess(entityService) : (nodeId == Constants.System.RecycleBinContent) @@ -1082,7 +1130,7 @@ namespace Umbraco.Web.Editors var allowed = true; foreach (var p in permissionsToCheck) { - if (permission == null + if (permission == null || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false) { allowed = false; @@ -1090,6 +1138,28 @@ namespace Umbraco.Web.Editors } return allowed; } + + /// + /// Used to map an instance to a and ensuring a language is present if required + /// + /// + /// + /// + private ContentItemDisplay MapToDisplay(IContent content, int? languageId = null) + { + //a languageId must exist in the mapping context if this content item has any property type that can be varied by language + //otherwise the property validation will fail since it's expecting to be get/set with a language ID. If a languageId is not explicitly + //sent up, then it means that the user is editing the default variant language. + if (!languageId.HasValue && content.HasLanguageVariantPropertyType()) + { + languageId = Services.LocalizationService.GetDefaultVariantLanguage().Id; + } + var display = ContextMapper.Map(content, UmbracoContext, + new Dictionary { { ContextMapper.LanguageKey, languageId } }); + + return display; + } + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index f67ce13950..70baabecf5 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -1,15 +1,15 @@ -using System; -using System.Collections.Generic; +using System; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; -using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; @@ -23,37 +23,20 @@ namespace Umbraco.Web.Editors [OutgoingDateTimeFormat] public abstract class ContentControllerBase : BackOfficeNotificationsController { - /// - /// Constructor - /// - protected ContentControllerBase() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - protected ContentControllerBase(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - protected HttpResponseMessage HandleContentNotFound(object id, bool throwException = true) { - ModelState.AddModelError("id", string.Format("content with id: {0} was not found", id)); + ModelState.AddModelError("id", $"content with id: {id} was not found"); var errorResponse = Request.CreateErrorResponse( HttpStatusCode.NotFound, ModelState); if (throwException) { - throw new HttpResponseException(errorResponse); + throw new HttpResponseException(errorResponse); } return errorResponse; } - protected void UpdateName(ContentBaseItemSave contentItem) + protected void UpdateName(ContentBaseItemSave contentItem) where TPersisted : IContentBase { //Don't update the name if it is empty @@ -83,62 +66,67 @@ namespace Umbraco.Web.Editors /// Maps the dto property values to the persisted model /// /// + /// /// - protected virtual void MapPropertyValues(ContentBaseItemSave contentItem) - where TPersisted : IContentBase + /// + /// + protected void MapPropertyValues( + TSaved contentItem, + Func getPropertyValue, + Action savePropertyValue) + where TPersisted : IContentBase + where TSaved : ContentBaseItemSave { - //Map the property values - foreach (var property in contentItem.ContentDto.Properties) + // map the property values + foreach (var propertyDto in contentItem.ContentDto.Properties) { - //get the dbo property - var dboProperty = contentItem.PersistedContent.Properties[property.Alias]; - - //create the property data to send to the property editor - var dictionary = new Dictionary(); - //add the files if any - var files = contentItem.UploadedFiles.Where(x => x.PropertyAlias == property.Alias).ToArray(); - if (files.Length > 0) + // get the property editor + if (propertyDto.PropertyEditor == null) { - dictionary.Add("files", files); + Logger.Warn("No property editor found for property " + propertyDto.Alias); + continue; } + + // get the value editor + // nothing to save/map if it is readonly + var valueEditor = propertyDto.PropertyEditor.GetValueEditor(); + if (valueEditor.IsReadOnly) continue; + + // get the property + var property = contentItem.PersistedContent.Properties[propertyDto.Alias]; + + // prepare files, if any + var files = contentItem.UploadedFiles.Where(x => x.PropertyAlias == propertyDto.Alias).ToArray(); foreach (var file in files) file.FileName = file.FileName.ToSafeFileName(); - // add extra things needed to figure out where to put the files - dictionary.Add("cuid", contentItem.PersistedContent.Key); - dictionary.Add("puid", dboProperty.PropertyType.Key); - - var data = new ContentPropertyData(property.Value, property.PreValues, dictionary); - - //get the deserialized value from the property editor - if (property.PropertyEditor == null) + // create the property data for the property editor + var data = new ContentPropertyData(propertyDto.Value, propertyDto.DataType.Configuration) { - LogHelper.Warn("No property editor found for property " + property.Alias); + ContentKey = contentItem.PersistedContent.Key, + PropertyTypeKey = property.PropertyType.Key, + Files = files + }; + + // let the editor convert the value that was received, deal with files, etc + var value = valueEditor.FromEditor(data, getPropertyValue(contentItem, property)); + + // set the value - tags are special + var tagAttribute = propertyDto.PropertyEditor.GetTagAttribute(); + if (tagAttribute != null) + { + var tagConfiguration = ConfigurationEditor.ConfigurationAs(propertyDto.DataType.Configuration); + if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = tagAttribute.Delimiter; + //fixme how is this supposed to work with variants? + property.SetTagsValue(value, tagConfiguration); } else - { - var valueEditor = property.PropertyEditor.ValueEditor; - //don't persist any bound value if the editor is readonly - if (valueEditor.IsReadOnly == false) - { - var propVal = property.PropertyEditor.ValueEditor.ConvertEditorToDb(data, dboProperty.Value); - var supportTagsAttribute = TagExtractor.GetAttribute(property.PropertyEditor); - if (supportTagsAttribute != null) - { - TagExtractor.SetPropertyTags(dboProperty, data, propVal, supportTagsAttribute); - } - else - { - dboProperty.Value = propVal; - } - } - - } + savePropertyValue(contentItem, property, value); } } - protected void HandleInvalidModelState(ContentItemDisplayBase display) - where TPersisted : IContentBase + protected void HandleInvalidModelState(ContentItemDisplayBase display) + where TPersisted : IContentBase where T : ContentPropertyBasic { //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 @@ -167,7 +155,7 @@ namespace Umbraco.Web.Editors return Request.Properties.ContainsKey(typeof(TPersisted).ToString()) && Request.Properties[typeof(TPersisted).ToString()] != null ? (TPersisted) Request.Properties[typeof (TPersisted).ToString()] : getFromService(); - } + } /// /// Returns true if the action passed in means we need to create something new @@ -179,14 +167,15 @@ namespace Umbraco.Web.Editors return (action.ToString().EndsWith("New")); } - protected void AddCancelMessage(INotificationModel display, - string header = "speechBubbles/operationCancelledHeader", + protected void AddCancelMessage(INotificationModel display, + string header = "speechBubbles/operationCancelledHeader", string message = "speechBubbles/operationCancelledText", bool localizeHeader = true, bool localizeMessage = true) { - //if there's already a default event message, don't add our default one - var msgs = UmbracoContext.GetCurrentEventMessages(); + //if there's already a default event message, don't add our default one + //fixme inject + var msgs = Current.EventMessages; if (msgs != null && msgs.GetAll().Any(x => x.IsDefaultEventMessage)) return; display.AddWarningNotification( diff --git a/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs b/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs index 97472a074f..e150966910 100644 --- a/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/ContentPostValidateAttribute.cs @@ -7,10 +7,11 @@ using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Security; using Umbraco.Web.WebApi; -using umbraco.BusinessLogic.Actions; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.Editors { @@ -25,45 +26,33 @@ namespace Umbraco.Web.Editors private readonly IEntityService _entityService; public ContentPostValidateAttribute() - { - } + { } + // fixme wtf is this? public ContentPostValidateAttribute(IContentService contentService, IUserService userService, IEntityService entityService, WebSecurity security) { - if (contentService == null) throw new ArgumentNullException("contentService"); - if (userService == null) throw new ArgumentNullException("userService"); - if (entityService == null) throw new ArgumentNullException("entityService"); - if (security == null) throw new ArgumentNullException("security"); - _contentService = contentService; - _userService = userService; - _entityService = entityService; - _security = security; + _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); + _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); + _security = security ?? throw new ArgumentNullException(nameof(security)); } + // fixme all these should be injected properties + private IContentService ContentService - { - get { return _contentService ?? ApplicationContext.Current.Services.ContentService; } - } + => _contentService ?? Current.Services.ContentService; private WebSecurity Security - { - get { return _security ?? UmbracoContext.Current.Security; } - } + => _security ?? UmbracoContext.Current.Security; private IUserService UserService - { - get { return _userService ?? ApplicationContext.Current.Services.UserService; } - } + => _userService ?? Current.Services.UserService; private IEntityService EntityService - { - get { return _entityService ?? ApplicationContext.Current.Services.EntityService; } - } + => _entityService ?? Current.Services.EntityService; public override bool AllowMultiple - { - get { return true; } - } + => true; public override void OnActionExecuting(HttpActionContext actionContext) { @@ -96,7 +85,7 @@ namespace Umbraco.Web.Editors case ContentSaveAction.SaveNew: //Save new requires ActionNew - permissionToCheck.Add(ActionNew.Instance.Letter); + permissionToCheck.Add(ActionNew.Instance.Letter); if (contentItem.ParentId != Constants.System.Root) { @@ -125,7 +114,7 @@ namespace Umbraco.Web.Editors break; case ContentSaveAction.PublishNew: //Publish new requires both ActionNew AND ActionPublish - //TODO: Shoudn't publish also require ActionUpdate since it will definitely perform an update to publish but maybe that's just implied + //TODO: Shoudn't publish also require ActionUpdate since it will definitely perform an update to publish but maybe that's just implied permissionToCheck.Add(ActionNew.Instance.Letter); permissionToCheck.Add(ActionPublish.Instance.Letter); @@ -147,16 +136,11 @@ namespace Umbraco.Web.Editors if (ContentController.CheckPermissions( actionContext.Request.Properties, Security.CurrentUser, - UserService, - ContentService, - EntityService, - contentIdToCheck, - permissionToCheck.ToArray(), - contentToCheck) == false) + UserService, ContentService, EntityService, + contentIdToCheck, permissionToCheck.ToArray(), contentToCheck) == false) { actionContext.Response = actionContext.Request.CreateUserNoAccessResponse(); - return; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 8581e28d6d..0aa43c8cfb 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Configuration; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Web.Http; @@ -10,16 +8,12 @@ using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Services; -using Umbraco.Core.PropertyEditors; using System.Net.Http; -using umbraco; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Strings; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Logging; -using Umbraco.Web.Models; +using Umbraco.Web.Composing; namespace Umbraco.Web.Editors { @@ -34,33 +28,16 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] [EnableOverrideAuthorization] - public class ContentTypeController : ContentTypeControllerBase + public class ContentTypeController : ContentTypeControllerBase { - /// - /// Constructor - /// - public ContentTypeController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public ContentTypeController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - public int GetCount() { - return Services.ContentTypeService.CountContentTypes(); + return Services.ContentTypeService.Count(); } public DocumentTypeDisplay GetById(int id) { - var ct = Services.ContentTypeService.GetContentType(id); + var ct = Services.ContentTypeService.Get(id); if (ct == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -79,7 +56,7 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage DeleteById(int id) { - var foundType = Services.ContentTypeService.GetContentType(id); + var foundType = Services.ContentTypeService.Get(id); if (foundType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -99,7 +76,7 @@ namespace Umbraco.Web.Editors Constants.Trees.MemberTypes, Constants.Trees.Members)] public IEnumerable GetAllPropertyTypeAliases() { - return ApplicationContext.Services.ContentTypeService.GetAllPropertyTypeAliases(); + return Services.ContentTypeService.GetAllPropertyTypeAliases(); } /// @@ -140,22 +117,22 @@ namespace Umbraco.Web.Editors Constants.Trees.MemberTypes, Constants.Trees.Members)] public ContentPropertyDisplay GetPropertyTypeScaffold(int id) { - var dataTypeDiff = Services.DataTypeService.GetDataTypeDefinitionById(id); + var dataTypeDiff = Services.DataTypeService.GetDataType(id); if (dataTypeDiff == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - var preVals = UmbracoContext.Current.Application.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(id); - var editor = PropertyEditorResolver.Current.GetByAlias(dataTypeDiff.PropertyEditorAlias); + var configuration = Current.Services.DataTypeService.GetDataType(id).Configuration; + var editor = Current.PropertyEditors[dataTypeDiff.EditorAlias]; return new ContentPropertyDisplay() { - Editor = dataTypeDiff.PropertyEditorAlias, - Validation = new PropertyTypeValidation() { }, - View = editor.ValueEditor.View, - Config = editor.PreValueEditor.ConvertDbToEditor(editor.DefaultPreValues, preVals) + Editor = dataTypeDiff.EditorAlias, + Validation = new PropertyTypeValidation(), + View = editor.GetValueEditor().View, + Config = editor.GetConfigurationEditor().ToConfigurationEditor(configuration) }; } @@ -168,14 +145,14 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage DeleteContainer(int id) { - Services.ContentTypeService.DeleteContentTypeContainer(id, Security.CurrentUser.Id); + Services.ContentTypeService.DeleteContainer(id, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); } public HttpResponseMessage PostCreateContainer(int parentId, string name) { - var result = Services.ContentTypeService.CreateContentTypeContainer(parentId, name, Security.CurrentUser.Id); + var result = Services.ContentTypeService.CreateContainer(parentId, name, Security.CurrentUser.Id); return result ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id @@ -184,7 +161,7 @@ namespace Umbraco.Web.Editors public HttpResponseMessage PostRenameContainer(int id, string name) { - var result = Services.ContentTypeService.RenameContentTypeContainer(id, name, Security.CurrentUser.Id); + var result = Services.ContentTypeService.RenameContainer(id, name, Security.CurrentUser.Id); return result ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id @@ -193,9 +170,9 @@ namespace Umbraco.Web.Editors public DocumentTypeDisplay PostSave(DocumentTypeSave contentTypeSave) { - var savedCt = PerformPostSave( + var savedCt = PerformPostSave( contentTypeSave: contentTypeSave, - getContentType: i => Services.ContentTypeService.GetContentType(i), + getContentType: i => Services.ContentTypeService.Get(i), saveContentType: type => Services.ContentTypeService.Save(type), beforeCreateNew: ctSave => { @@ -208,10 +185,7 @@ namespace Umbraco.Web.Editors var tryCreateTemplate = Services.FileService.CreateTemplateForContentType(ctSave.Alias, ctSave.Name); if (tryCreateTemplate == false) { - Logger.Warn( - "Could not create a template for the Content Type: {0}, status: {1}", - () => ctSave.Alias, - () => tryCreateTemplate.Result.StatusType); + Logger.Warn(() => $"Could not create a template for the Content Type: {ctSave.Alias}, status: {tryCreateTemplate.Result.Result}"); } template = tryCreateTemplate.Result.Entity; } @@ -241,7 +215,7 @@ namespace Umbraco.Web.Editors IContentType ct; if (parentId != Constants.System.Root) { - var parent = Services.ContentTypeService.GetContentType(parentId); + var parent = Services.ContentTypeService.Get(parentId); ct = parent != null ? new ContentType(parent, string.Empty) : new ContentType(parentId); } else @@ -259,7 +233,7 @@ namespace Umbraco.Web.Editors /// public IEnumerable GetAll() { - var types = Services.ContentTypeService.GetAllContentTypes(); + var types = Services.ContentTypeService.GetAll(); var basics = types.Select(Mapper.Map); return basics.Select(basic => @@ -283,7 +257,7 @@ namespace Umbraco.Web.Editors IEnumerable types; if (contentId == Constants.System.Root) { - types = Services.ContentTypeService.GetAllContentTypes().ToList(); + types = Services.ContentTypeService.GetAll().ToList(); //if no allowed root types are set, just return everything if (types.Any(x => x.AllowedAsRoot)) @@ -301,7 +275,7 @@ namespace Umbraco.Web.Editors if (ids.Any() == false) return Enumerable.Empty(); - types = Services.ContentTypeService.GetAllContentTypes(ids).ToList(); + types = Services.ContentTypeService.GetAll(ids).ToList(); } var basics = types.Select(Mapper.Map).ToList(); @@ -311,8 +285,8 @@ namespace Umbraco.Web.Editors { basic.Name = localizedTextService.UmbracoDictionaryTranslate(basic.Name); basic.Description = localizedTextService.UmbracoDictionaryTranslate(basic.Description); - } - + } + //map the blueprints var blueprints = Services.ContentService.GetBlueprintsForContentTypes(types.Select(x => x.Id).ToArray()).ToArray(); foreach (var basic in basics) @@ -322,7 +296,7 @@ namespace Umbraco.Web.Editors { basic.Blueprints[blueprint.Id] = blueprint.Name; } - } + } return basics; } @@ -336,8 +310,8 @@ namespace Umbraco.Web.Editors { return PerformMove( move, - getContentType: i => Services.ContentTypeService.GetContentType(i), - doMove: (type, i) => Services.ContentTypeService.MoveContentType(type, i)); + getContentType: i => Services.ContentTypeService.Get(i), + doMove: (type, i) => Services.ContentTypeService.Move(type, i)); } /// @@ -349,8 +323,8 @@ namespace Umbraco.Web.Editors { return PerformCopy( copy, - getContentType: i => Services.ContentTypeService.GetContentType(i), - doCopy: (type, i) => Services.ContentTypeService.CopyContentType(type, i)); + getContentType: i => Services.ContentTypeService.Get(i), + doCopy: (type, i) => Services.ContentTypeService.Copy(type, i)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index 0e699b591a..030d59de8f 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -4,21 +4,17 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Text; -using System.Text.RegularExpressions; using System.Web.Http; using AutoMapper; -using Newtonsoft.Json; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.Exceptions; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { @@ -27,27 +23,11 @@ namespace Umbraco.Web.Editors /// [PluginController("UmbracoApi")] [PrefixlessBodyModelValidator] - public abstract class ContentTypeControllerBase : UmbracoAuthorizedJsonController + public abstract class ContentTypeControllerBase : UmbracoAuthorizedJsonController + where TContentType : class, IContentTypeComposition { private ICultureDictionary _cultureDictionary; - /// - /// Constructor - /// - protected ContentTypeControllerBase() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - protected ContentTypeControllerBase(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - /// /// Returns the available composite content types for a given content type /// @@ -79,19 +59,19 @@ namespace Umbraco.Web.Editors case UmbracoObjectTypes.DocumentType: if (contentTypeId > 0) { - source = Services.ContentTypeService.GetContentType(contentTypeId); + source = Services.ContentTypeService.Get(contentTypeId); if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } - allContentTypes = Services.ContentTypeService.GetAllContentTypes().Cast().ToArray(); + allContentTypes = Services.ContentTypeService.GetAll().Cast().ToArray(); break; case UmbracoObjectTypes.MediaType: if (contentTypeId > 0) { - source = Services.ContentTypeService.GetMediaType(contentTypeId); + source = Services.MediaTypeService.Get(contentTypeId); if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } - allContentTypes = Services.ContentTypeService.GetAllMediaTypes().Cast().ToArray(); + allContentTypes = Services.MediaTypeService.GetAll().Cast().ToArray(); break; case UmbracoObjectTypes.MemberType: @@ -148,12 +128,11 @@ namespace Umbraco.Web.Editors return CultureDictionary[text].IfNullOrWhiteSpace(text); } - protected TContentType PerformPostSave( + protected TContentType PerformPostSave( TContentTypeSave contentTypeSave, Func getContentType, Action saveContentType, Action beforeCreateNew = null) - where TContentType : class, IContentTypeComposition where TContentTypeDisplay : ContentTypeCompositionDisplay where TContentTypeSave : ContentTypeSave where TPropertyType : PropertyTypeBasic @@ -173,8 +152,8 @@ namespace Umbraco.Web.Editors ModelState.AddModelError("Alias", "A content type, media type or member type with this alias already exists"); } - //now let the external validators execute - ValidationHelper.ValidateEditorModelWithResolver(ModelState, contentTypeSave); + // execute the externam validators + EditorValidator.Validate(ModelState, contentTypeSave); if (ModelState.IsValid == false) { @@ -271,7 +250,7 @@ namespace Umbraco.Web.Editors protected HttpResponseMessage PerformMove( MoveOrCopy move, Func getContentType, - Func>> doMove) + Func>> doMove) where TContentType : IContentTypeComposition { var toMove = getContentType(move.Id); @@ -288,7 +267,7 @@ namespace Umbraco.Web.Editors return response; } - switch (result.Result.StatusType) + switch (result.Result.Result) { case MoveOperationStatusType.FailedParentNotFound: return Request.CreateResponse(HttpStatusCode.NotFound); @@ -312,11 +291,10 @@ namespace Umbraco.Web.Editors /// /// /// - protected HttpResponseMessage PerformCopy( + protected HttpResponseMessage PerformCopy( MoveOrCopy move, Func getContentType, - Func>> doCopy) - where TContentType : IContentTypeComposition + Func>> doCopy) { var toMove = getContentType(move.Id); if (toMove == null) @@ -333,7 +311,7 @@ namespace Umbraco.Web.Editors return response; } - switch (result.Result.StatusType) + switch (result.Result.Result) { case MoveOperationStatusType.FailedParentNotFound: return Request.CreateResponse(HttpStatusCode.NotFound); @@ -356,12 +334,13 @@ namespace Umbraco.Web.Editors /// /// /// - private HttpResponseException CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, IContentTypeComposition composition) + private HttpResponseException CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, TContentType composition) where TContentTypeSave : ContentTypeSave where TPropertyType : PropertyTypeBasic where TContentTypeDisplay : ContentTypeCompositionDisplay { - var validateAttempt = Services.ContentTypeService.ValidateComposition(composition); + var service = Services.GetContentTypeService(); + var validateAttempt = service.ValidateComposition(composition); if (validateAttempt == false) { //if it's not successful then we need to return some model state for the property aliases that @@ -468,15 +447,6 @@ namespace Umbraco.Web.Editors } private ICultureDictionary CultureDictionary - { - get - { - return - _cultureDictionary ?? - (_cultureDictionary = CultureDictionaryFactoryResolver.Current.Factory.CreateDictionary()); - } - } - - + => _cultureDictionary ?? (_cultureDictionary = Current.CultureDictionaryFactory.CreateDictionary()); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/CurrentUserController.cs b/src/Umbraco.Web/Editors/CurrentUserController.cs index 9ed65888d4..a7d858c322 100644 --- a/src/Umbraco.Web/Editors/CurrentUserController.cs +++ b/src/Umbraco.Web/Editors/CurrentUserController.cs @@ -1,30 +1,22 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Net; -using System.Text; +using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; -using System.Web.Security; using AutoMapper; -using Umbraco.Core.Configuration; +using Umbraco.Core.Composing; using Umbraco.Core.Services; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; -using Umbraco.Web.UI; using Umbraco.Web.WebApi; -using umbraco; -using legacyUser = umbraco.BusinessLogic.User; -using System.Net.Http; -using System.Collections.Specialized; using System.Linq; using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Security; +using Umbraco.Web.Security; using Umbraco.Web.WebApi.Filters; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors @@ -42,12 +34,12 @@ namespace Umbraco.Web.Editors /// public IEnumerable PostSetUserTour(UserTourStatus status) { - if (status == null) throw new ArgumentNullException("status"); + if (status == null) throw new ArgumentNullException(nameof(status)); List userTours; if (Security.CurrentUser.TourData.IsNullOrWhiteSpace()) { - userTours = new List {status}; + userTours = new List { status }; Security.CurrentUser.TourData = JsonConvert.SerializeObject(userTours); Services.UserService.Save(Security.CurrentUser); return userTours; @@ -92,7 +84,7 @@ namespace Umbraco.Web.Editors [OverrideAuthorization] public async Task PostSetInvitedUserPassword([FromBody]string newPassword) { - var result = await UserManager.AddPasswordAsync(Security.GetUserId(), newPassword); + var result = await UserManager.AddPasswordAsync(Security.GetUserId().ResultOr(0), newPassword); if (result.Succeeded == false) { @@ -125,7 +117,7 @@ namespace Umbraco.Web.Editors public async Task PostSetAvatar() { //borrow the logic from the user controller - return await UsersController.PostSetAvatarInternal(Request, Services.UserService, ApplicationContext.ApplicationCache.StaticCache, Security.GetUserId()); + return await UsersController.PostSetAvatarInternal(Request, Services.UserService, Current.ApplicationCache.StaticCache, Security.GetUserId().ResultOr(0)); } /// @@ -144,15 +136,12 @@ namespace Umbraco.Web.Editors { var userMgr = this.TryGetOwinContext().Result.GetBackOfficeUserManager(); - //raise the appropriate event + //raise the reset event + //TODO: I don't think this is required anymore since from 7.7 we no longer display the reset password checkbox since that didn't make sense. if (data.Reset.HasValue && data.Reset.Value) { userMgr.RaisePasswordResetEvent(Security.CurrentUser.Id); } - else - { - userMgr.RaisePasswordChangedEvent(Security.CurrentUser.Id); - } //even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword var result = new ModelWithNotifications(passwordChangeResult.Result.ResetPassword); diff --git a/src/Umbraco.Web/Editors/DashboardController.cs b/src/Umbraco.Web/Editors/DashboardController.cs index 55286d99b4..d23e262412 100644 --- a/src/Umbraco.Web/Editors/DashboardController.cs +++ b/src/Umbraco.Web/Editors/DashboardController.cs @@ -38,12 +38,12 @@ namespace Umbraco.Web.Editors var user = Security.CurrentUser; var allowedSections = string.Join(",", user.AllowedSections); var language = user.Language; - var version = UmbracoVersion.GetSemanticVersion().ToSemanticString(); + var version = UmbracoVersion.SemanticVersion.ToSemanticString(); var url = string.Format(baseUrl + "{0}?section={0}&allowed={1}&lang={2}&version={3}", section, allowedSections, language, version); var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section; - var content = ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem(key); + var content = ApplicationCache.RuntimeCache.GetCacheItem(key); var result = new JObject(); if (content != null) { @@ -62,14 +62,14 @@ namespace Umbraco.Web.Editors result = content; } - ApplicationContext.ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); + ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); } catch (HttpRequestException ex) { - LogHelper.Debug(string.Format("Error getting dashboard content from '{0}': {1}\n{2}", url, ex.Message, ex.InnerException)); + Logger.Debug($"Error getting dashboard content from '{url}': {ex.Message}\n{ex.InnerException}"); //it's still new JObject() - we return it like this to avoid error codes which triggers UI warnings - ApplicationContext.ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); + ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); } } @@ -81,7 +81,7 @@ namespace Umbraco.Web.Editors var url = string.Format(baseUrl + "css/dashboard.css?section={0}", section); var key = "umbraco-dynamic-dashboard-css-" + section; - var content = ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem(key); + var content = ApplicationCache.RuntimeCache.GetCacheItem(key); var result = string.Empty; if (content != null) @@ -102,15 +102,15 @@ namespace Umbraco.Web.Editors result = content; //save server content for 30 mins - ApplicationContext.ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); + ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); } } catch (HttpRequestException ex) { - LogHelper.Debug(string.Format("Error getting dashboard CSS from '{0}': {1}\n{2}", url, ex.Message, ex.InnerException)); + Logger.Debug(string.Format("Error getting dashboard CSS from '{0}': {1}\n{2}", url, ex.Message, ex.InnerException)); //it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings - ApplicationContext.ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); + ApplicationCache.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); } } @@ -119,12 +119,12 @@ namespace Umbraco.Web.Editors Content = new StringContent(result, Encoding.UTF8, "text/css") }; } - + [ValidateAngularAntiForgeryToken] public IEnumerable> GetDashboard(string section) { var dashboardHelper = new DashboardHelper(Services.SectionService); - return dashboardHelper.GetDashboard(section, Security.CurrentUser); + return dashboardHelper.GetDashboard(section, Security.CurrentUser); } } } diff --git a/src/Umbraco.Web/Editors/DashboardHelper.cs b/src/Umbraco.Web/Editors/DashboardHelper.cs index 8b43335ebe..b40f289d04 100644 --- a/src/Umbraco.Web/Editors/DashboardHelper.cs +++ b/src/Umbraco.Web/Editors/DashboardHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -69,6 +69,7 @@ namespace Umbraco.Web.Editors var dashboardControl = new DashboardControl(); var controlPath = control.ControlPath.Trim(); + dashboardControl.Caption = control.PanelCaption; dashboardControl.Path = IOHelper.FindFile(controlPath); if (controlPath.ToLowerInvariant().EndsWith(".ascx".ToLowerInvariant())) dashboardControl.ServerSide = true; @@ -93,4 +94,4 @@ namespace Umbraco.Web.Editors return tabs; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/DashboardSecurity.cs b/src/Umbraco.Web/Editors/DashboardSecurity.cs index d1db432d6c..c7e8852a16 100644 --- a/src/Umbraco.Web/Editors/DashboardSecurity.cs +++ b/src/Umbraco.Web/Editors/DashboardSecurity.cs @@ -118,4 +118,4 @@ namespace Umbraco.Web.Editors return allowedSoFar; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index d42b158cfb..1ae29902eb 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -4,26 +4,24 @@ using System.Data; using System.Linq; using System.Net; using System.Web.Http; -using System.Web.Http.ModelBinding; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; -using umbraco; using Constants = Umbraco.Core.Constants; using System.Net.Http; using System.Text; +using Umbraco.Web.Composing; +using Umbraco.Core.Configuration; namespace Umbraco.Web.Editors -{ - +{ + /// /// The API controller used for editing data types /// @@ -36,6 +34,13 @@ namespace Umbraco.Web.Editors [EnableOverrideAuthorization] public class DataTypeController : BackOfficeNotificationsController { + private readonly PropertyEditorCollection _propertyEditors; + + public DataTypeController(PropertyEditorCollection propertyEditors) + { + _propertyEditors = propertyEditors; + } + /// /// Gets data type by name /// @@ -43,8 +48,8 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay GetByName(string name) { - var dataType = Services.DataTypeService.GetDataTypeDefinitionByName(name); - return dataType == null ? null : Mapper.Map(dataType); + var dataType = Services.DataTypeService.GetDataType(name); + return dataType == null ? null : Mapper.Map(dataType); } /// @@ -54,12 +59,12 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay GetById(int id) { - var dataType = Services.DataTypeService.GetDataTypeDefinitionById(id); + var dataType = Services.DataTypeService.GetDataType(id); if (dataType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(dataType); + return Mapper.Map(dataType); } /// @@ -71,12 +76,12 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage DeleteById(int id) { - var foundType = Services.DataTypeService.GetDataTypeDefinitionById(id); + var foundType = Services.DataTypeService.GetDataType(id); if (foundType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); - } - + } + Services.DataTypeService.Delete(foundType, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); @@ -84,8 +89,10 @@ namespace Umbraco.Web.Editors public DataTypeDisplay GetEmpty(int parentId) { - var dt = new DataTypeDefinition(parentId, ""); - return Mapper.Map(dt); + // cannot create an "empty" data type, so use something by default. + var editor = _propertyEditors[Constants.PropertyEditors.Aliases.NoEdit]; + var dt = new DataType(editor, parentId); + return Mapper.Map(dt); } /// @@ -95,13 +102,13 @@ namespace Umbraco.Web.Editors /// a DataTypeDisplay public DataTypeDisplay GetCustomListView(string contentTypeAlias) { - var dt = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); + var dt = Services.DataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); if (dt == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(dt); + return Mapper.Map(dt); } /// @@ -111,17 +118,17 @@ namespace Umbraco.Web.Editors /// public DataTypeDisplay PostCreateCustomListView(string contentTypeAlias) { - var dt = Services.DataTypeService.GetDataTypeDefinitionByName(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); - - //if it doesnt exist yet, we will create it. + var dt = Services.DataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); + + //if it doesnt exist yet, we will create it. if (dt == null) { - dt = new DataTypeDefinition(Constants.PropertyEditors.ListViewAlias); - dt.Name = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; + var editor = _propertyEditors[Constants.PropertyEditors.Aliases.ListView]; + dt = new DataType(editor) { Name = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias }; Services.DataTypeService.Save(dt); } - return Mapper.Map(dt); + return Mapper.Map(dt); } /// @@ -130,9 +137,9 @@ namespace Umbraco.Web.Editors /// /// The data type id for the pre-values, -1 if it is a new data type /// - public IEnumerable GetPreValues(string editorAlias, int dataTypeId = -1) + public IEnumerable GetPreValues(string editorAlias, int dataTypeId = -1) { - var propEd = PropertyEditorResolver.Current.GetByAlias(editorAlias); + var propEd = Current.PropertyEditors[editorAlias]; if (propEd == null) { throw new InvalidOperationException("Could not find property editor with alias " + editorAlias); @@ -141,11 +148,11 @@ namespace Umbraco.Web.Editors if (dataTypeId == -1) { //this is a new data type, so just return the field editors with default values - return Mapper.Map>(propEd); + return Mapper.Map>(propEd); } //we have a data type associated - var dataType = Services.DataTypeService.GetDataTypeDefinitionById(dataTypeId); + var dataType = Services.DataTypeService.GetDataType(dataTypeId); if (dataType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -154,14 +161,14 @@ namespace Umbraco.Web.Editors //now, lets check if the data type has the current editor selected, if that is true //we will need to wire up it's saved values. Otherwise it's an existing data type //that is changing it's underlying property editor, in which case there's no values. - if (dataType.PropertyEditorAlias == editorAlias) + if (dataType.EditorAlias == editorAlias) { //this is the currently assigned pre-value editor, return with values. - return Mapper.Map>(dataType); + return Mapper.Map>(dataType); } //these are new pre-values, so just return the field editors with default values - return Mapper.Map>(propEd); + return Mapper.Map>(propEd); } /// @@ -183,7 +190,7 @@ namespace Umbraco.Web.Editors var result = Services.DataTypeService.CreateContainer(parentId, name, Security.CurrentUser.Id); return result - ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id + ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); } @@ -197,25 +204,22 @@ namespace Umbraco.Web.Editors { //If we've made it here, then everything has been wired up and validated by the attribute - //finally we need to save the data type and it's pre-vals - var dtService = ApplicationContext.Services.DataTypeService; - - //TODO: Check if the property editor has changed, if it has ensure we don't pass the + //TODO: Check if the property editor has changed, if it has ensure we don't pass the // existing values to the new property editor! - //get the prevalues, current and new - var preValDictionary = dataType.PreValues.ToDictionary(x => x.Key, x => x.Value); - var currVal = Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dataType.PersistedDataType.Id); + // get the current configuration, + // get the new configuration as a dictionary (this is how we get it from model) + // and map to an actual configuration object + var currentConfiguration = dataType.PersistedDataType.Configuration; + var configurationDictionary = dataType.ConfigurationFields.ToDictionary(x => x.Key, x => x.Value); + var configuration = dataType.PropertyEditor.GetConfigurationEditor().FromConfigurationEditor(configurationDictionary, currentConfiguration); - //we need to allow for the property editor to deserialize the prevalues - var formattedVal = dataType.PropertyEditor.PreValueEditor.ConvertEditorToDb( - preValDictionary, - currVal); + dataType.PersistedDataType.Configuration = configuration; + // save the data type try { - //save the data type - dtService.SaveDataTypeAndPreValues(dataType.PersistedDataType, formattedVal, (int)Security.CurrentUser.Id); + Services.DataTypeService.Save(dataType.PersistedDataType, Security.CurrentUser.Id); } catch (DuplicateNameException ex) { @@ -223,10 +227,9 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); } - var display = Mapper.Map(dataType.PersistedDataType); - display.AddSuccessNotification(ui.Text("speechBubbles", "dataTypeSaved"), ""); - - //now return the updated model + // map back to display model, and return + var display = Mapper.Map(dataType.PersistedDataType); + display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/dataTypeSaved"), ""); return display; } @@ -237,7 +240,7 @@ namespace Umbraco.Web.Editors /// public HttpResponseMessage PostMove(MoveOrCopy move) { - var toMove = Services.DataTypeService.GetDataTypeDefinitionById(move.Id); + var toMove = Services.DataTypeService.GetDataType(move.Id); if (toMove == null) { return Request.CreateResponse(HttpStatusCode.NotFound); @@ -251,12 +254,12 @@ namespace Umbraco.Web.Editors return response; } - switch (result.Result.StatusType) + switch (result.Result.Result) { case MoveOperationStatusType.FailedParentNotFound: return Request.CreateResponse(HttpStatusCode.NotFound); case MoveOperationStatusType.FailedCancelledByEvent: - //returning an object of INotificationModel will ensure that any pending + //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); case MoveOperationStatusType.FailedNotAllowedByPath: @@ -270,7 +273,7 @@ namespace Umbraco.Web.Editors public HttpResponseMessage PostRenameContainer(int id, string name) { - var result = Services.ContentTypeService.RenameDataTypeContainer(id, name, Security.CurrentUser.Id); + var result = Services.DataTypeService.RenameContainer(id, name, Security.CurrentUser.Id); return result ? Request.CreateResponse(HttpStatusCode.OK, result.Result) @@ -284,15 +287,15 @@ namespace Umbraco.Web.Editors /// /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members - /// + /// [UmbracoApplicationAuthorize( Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, Constants.Applications.Settings, Constants.Applications.Developer)] public IEnumerable GetAll() { return Services.DataTypeService - .GetAllDataTypeDefinitions() - .Select(Mapper.Map).Where(x => x.IsSystemDataType == false); + .GetAll() + .Select(Mapper.Map).Where(x => x.IsSystemDataType == false); } /// @@ -301,24 +304,24 @@ namespace Umbraco.Web.Editors /// /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members - /// + /// [UmbracoTreeAuthorize( Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, Constants.Applications.Settings, Constants.Applications.Developer)] public IDictionary> GetGroupedDataTypes() { var dataTypes = Services.DataTypeService - .GetAllDataTypeDefinitions() - .Select(Mapper.Map) + .GetAll() + .Select(Mapper.Map) .ToArray(); - var propertyEditors = PropertyEditorResolver.Current.PropertyEditors.ToArray(); + var propertyEditors = Current.PropertyEditors.ToArray(); foreach (var dataType in dataTypes) { var propertyEditor = propertyEditors.SingleOrDefault(x => x.Alias == dataType.Alias); if (propertyEditor != null) - dataType.HasPrevalues = propertyEditor.PreValueEditor.Fields.Any(); ; + dataType.HasPrevalues = propertyEditor.GetConfigurationEditor().Fields.Any(); ; } var grouped = dataTypes @@ -334,18 +337,20 @@ namespace Umbraco.Web.Editors /// /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members - /// + /// [UmbracoTreeAuthorize( Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, Constants.Applications.Settings, Constants.Applications.Developer)] public IDictionary> GetGroupedPropertyEditors() { - var datatypes = new List(); - - var propertyEditors = PropertyEditorResolver.Current.PropertyEditors; + var datatypes = new List(); + var showDeprecatedPropertyEditors = UmbracoConfig.For.UmbracoSettings().Content.ShowDeprecatedPropertyEditors; + + var propertyEditors = Current.PropertyEditors + .Where(x=>x.IsDeprecated == false || showDeprecatedPropertyEditors); foreach (var propertyEditor in propertyEditors) { - var hasPrevalues = propertyEditor.PreValueEditor.Fields.Any(); + var hasPrevalues = propertyEditor.GetConfigurationEditor().Fields.Any(); var basic = Mapper.Map(propertyEditor); basic.HasPrevalues = hasPrevalues; datatypes.Add(basic); @@ -365,16 +370,16 @@ namespace Umbraco.Web.Editors /// /// /// Permission is granted to this method if the user has access to any of these sections: Content, media, settings, developer, members - /// + /// [UmbracoTreeAuthorize( Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, Constants.Applications.Settings, Constants.Applications.Developer)] public IEnumerable GetAllPropertyEditors() { - return PropertyEditorResolver.Current.PropertyEditors + return Current.PropertyEditors .OrderBy(x => x.Name) .Select(Mapper.Map); } #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs index ec7089d25d..f8f2b5ac23 100644 --- a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs @@ -2,15 +2,15 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using AutoMapper; +using LightInject; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.WebApi; @@ -21,106 +21,81 @@ namespace Umbraco.Web.Editors /// internal sealed class DataTypeValidateAttribute : ActionFilterAttribute { - private readonly IDataTypeService _dataTypeService; + // LightInject can inject dependencies into properties - public DataTypeValidateAttribute() - { - } + [Inject] + public IDataTypeService DataTypeService { get; set; } - public DataTypeValidateAttribute(IDataTypeService dataTypeService) - { - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - _dataTypeService = dataTypeService; - } - - private IDataTypeService DataTypeService - { - get { return _dataTypeService ?? ApplicationContext.Current.Services.DataTypeService; } - } + [Inject] + public PropertyEditorCollection PropertyEditors { get; set; } public override void OnActionExecuting(HttpActionContext actionContext) { - var dataType = (DataTypeSave)actionContext.ActionArguments["dataType"]; + var dataType = (DataTypeSave) actionContext.ActionArguments["dataType"]; dataType.Name = dataType.Name.CleanForXss('[', ']', '(', ')', ':'); dataType.Alias = dataType.Alias == null ? dataType.Name : dataType.Alias.CleanForXss('[', ']', '(', ')', ':'); - //Validate that the property editor exists - var propertyEditor = PropertyEditorResolver.Current.GetByAlias(dataType.SelectedEditor); - if (propertyEditor == null) + // get the property editor, ensuring that it exits + if (!PropertyEditors.TryGet(dataType.EditorAlias, out var propertyEditor)) { - var message = string.Format("Property editor with id: {0} was not found", dataType.SelectedEditor); + var message = $"Property editor \"{dataType.EditorAlias}\" was not found."; actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); return; } - //assign the prop editor to the model + // assign dataType.PropertyEditor = propertyEditor; - //Validate the data type exists or create one if required - IDataTypeDefinition persisted; + // validate that the data type exists, or create one if required + IDataType persisted; switch (dataType.Action) { case ContentSaveAction.Save: - persisted = DataTypeService.GetDataTypeDefinitionById(Convert.ToInt32(dataType.Id)); + persisted = DataTypeService.GetDataType(Convert.ToInt32(dataType.Id)); if (persisted == null) { - var message = string.Format("Data type with id: {0} was not found", dataType.Id); + var message = $"Data type with id {dataType.Id} was not found."; actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); return; } - //map the model to the persisted instance + // map the model to the persisted instance Mapper.Map(dataType, persisted); break; + case ContentSaveAction.SaveNew: - //create the persisted model from mapping the saved model - persisted = Mapper.Map(dataType); - ((DataTypeDefinition)persisted).ResetIdentity(); + // create the persisted model from mapping the saved model + persisted = Mapper.Map(dataType); + ((DataType) persisted).ResetIdentity(); break; + default: actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, new ArgumentOutOfRangeException()); return; } - //now assign the persisted entity to the model so we can use it in the action + // assign (so it's available in the action) dataType.PersistedDataType = persisted; - //Validate each field - foreach (var preVal in dataType.PreValues) + // validate the configuration + // which is posted as a set of fields with key (string) and value (object) + var configurationEditor = propertyEditor.GetConfigurationEditor(); + foreach (var field in dataType.ConfigurationFields) { - var postedValue = preVal.Value; - - foreach (var v in propertyEditor.PreValueEditor.Fields.Where(x => x.Key == preVal.Key).SelectMany(x => x.Validators)) - { - foreach (var result in v.Validate(postedValue, null, propertyEditor)) - { - //if there are no member names supplied then we assume that the validation message is for the overall property - // not a sub field on the property editor - if (!result.MemberNames.Any()) - { - //add a model state error for the entire property - actionContext.ModelState.AddModelError(string.Format("{0}.{1}", "Properties", preVal.Key), result.ErrorMessage); - } - else - { - //there's assigned field names so we'll combine the field name with the property name - // so that we can try to match it up to a real sub field of this editor - foreach (var field in result.MemberNames) - { - actionContext.ModelState.AddModelError(string.Format("{0}.{1}.{2}", "Properties", preVal.Key, field), result.ErrorMessage); - } - } - } - } + var editorField = configurationEditor.Fields.SingleOrDefault(x => x.Key == field.Key); + if (editorField == null) continue; + + // run each IValueValidator (with null valueType and dataTypeConfiguration: not relevant here) - fixme - editing + foreach (var validator in editorField.Validators) + foreach (var result in validator.Validate(field.Value, null, null)) + actionContext.ModelState.AddValidationError(result, "Properties", field.Key); } if (actionContext.ModelState.IsValid == false) { - //if it is not valid, do not continue and return the model state + // if it is not valid, do not continue and return the model state actionContext.Response = actionContext.Request.CreateValidationErrorResponse(actionContext.ModelState); - return; } - } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/EditorModelEventManager.cs b/src/Umbraco.Web/Editors/EditorModelEventManager.cs index e204bdc044..e2a248cb88 100644 --- a/src/Umbraco.Web/Editors/EditorModelEventManager.cs +++ b/src/Umbraco.Web/Editors/EditorModelEventManager.cs @@ -1,4 +1,4 @@ -using System.Web.Http.Filters; +using System.Web.Http.Filters; using Umbraco.Core.Events; using Umbraco.Web.Models.ContentEditing; @@ -17,25 +17,25 @@ namespace Umbraco.Web.Editors private static void OnSendingUserModel(HttpActionExecutedContext sender, EditorModelEventArgs e) { var handler = SendingUserModel; - if (handler != null) handler(sender, e); + handler?.Invoke(sender, e); } private static void OnSendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs e) { var handler = SendingContentModel; - if (handler != null) handler(sender, e); + handler?.Invoke(sender, e); } private static void OnSendingMediaModel(HttpActionExecutedContext sender, EditorModelEventArgs e) { var handler = SendingMediaModel; - if (handler != null) handler(sender, e); + handler?.Invoke(sender, e); } private static void OnSendingMemberModel(HttpActionExecutedContext sender, EditorModelEventArgs e) { var handler = SendingMemberModel; - if (handler != null) handler(sender, e); + handler?.Invoke(sender, e); } /// @@ -45,30 +45,17 @@ namespace Umbraco.Web.Editors /// internal static void EmitEvent(HttpActionExecutedContext sender, EditorModelEventArgs e) { - var contentItemDisplay = e.Model as ContentItemDisplay; - if (contentItemDisplay != null) - { + if (e.Model is ContentItemDisplay) OnSendingContentModel(sender, new EditorModelEventArgs(e)); - } - var mediaItemDisplay = e.Model as MediaItemDisplay; - if (mediaItemDisplay != null) - { + if (e.Model is MediaItemDisplay) OnSendingMediaModel(sender, new EditorModelEventArgs(e)); - } - var memberItemDisplay = e.Model as MemberDisplay; - if (memberItemDisplay != null) - { + if (e.Model is MemberDisplay) OnSendingMemberModel(sender, new EditorModelEventArgs(e)); - } - var userDisplay = e.Model as UserDisplay; - if (userDisplay != null) - { + if (e.Model is UserDisplay) OnSendingUserModel(sender, new EditorModelEventArgs(e)); - } } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/EditorValidationResolver.cs b/src/Umbraco.Web/Editors/EditorValidationResolver.cs deleted file mode 100644 index d0c5edf5af..0000000000 --- a/src/Umbraco.Web/Editors/EditorValidationResolver.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.Editors -{ - internal class EditorValidationResolver : LazyManyObjectsResolverBase - { - public EditorValidationResolver(IServiceProvider serviceProvider, ILogger logger, Func> migrations) - : base(serviceProvider, logger, migrations, ObjectLifetimeScope.Application) - { - } - - public virtual IEnumerable EditorValidators - { - get { return Values; } - } - - public IEnumerable Validate(object model) - { - return EditorValidators - .Where(x => model.GetType() == x.ModelType) - .WhereNotNull() - .SelectMany(x => x.Validate(model)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/EditorValidator.cs b/src/Umbraco.Web/Editors/EditorValidator.cs index d123838f52..2010fae943 100644 --- a/src/Umbraco.Web/Editors/EditorValidator.cs +++ b/src/Umbraco.Web/Editors/EditorValidator.cs @@ -1,21 +1,29 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Web.Http.ModelBinding; +using Umbraco.Web.Composing; namespace Umbraco.Web.Editors { - internal abstract class EditorValidator : IEditorValidator + /// + /// Provides a method to validate an object using validation. + /// + internal static class EditorValidator { - public Type ModelType + /// + /// Validates an object. + /// + public static void Validate(ModelStateDictionary modelState, object model) { - get { return typeof (T); } - } + var modelType = model.GetType(); - protected abstract IEnumerable PerformValidate(T model); + var validationResults = Current.EditorValidators // fixme inject + .Where(x => x.ModelType == modelType) + .SelectMany(x => x.Validate(model)) + .Where(x => !string.IsNullOrWhiteSpace(x.ErrorMessage) && x.MemberNames.Any()); - public IEnumerable Validate(object model) - { - return PerformValidate((T) model); + foreach (var r in validationResults) + foreach (var m in r.MemberNames) + modelState.AddModelError(m, r.ErrorMessage); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/EditorValidatorCollection.cs b/src/Umbraco.Web/Editors/EditorValidatorCollection.cs new file mode 100644 index 0000000000..6fc6bb5de2 --- /dev/null +++ b/src/Umbraco.Web/Editors/EditorValidatorCollection.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Editors +{ + internal class EditorValidatorCollection : BuilderCollectionBase + { + public EditorValidatorCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Web/Editors/EditorValidatorCollectionBuilder.cs b/src/Umbraco.Web/Editors/EditorValidatorCollectionBuilder.cs new file mode 100644 index 0000000000..e05c85a1b6 --- /dev/null +++ b/src/Umbraco.Web/Editors/EditorValidatorCollectionBuilder.cs @@ -0,0 +1,14 @@ +using LightInject; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Editors +{ + internal class EditorValidatorCollectionBuilder : LazyCollectionBuilderBase + { + public EditorValidatorCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override EditorValidatorCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Web/Editors/EditorValidatorOfT.cs b/src/Umbraco.Web/Editors/EditorValidatorOfT.cs new file mode 100644 index 0000000000..4ca008cf0d --- /dev/null +++ b/src/Umbraco.Web/Editors/EditorValidatorOfT.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Umbraco.Web.Editors +{ + /// + /// Provides a base class for implementations. + /// + /// The validated object type. + internal abstract class EditorValidator : IEditorValidator + { + public Type ModelType => typeof (T); + + public IEnumerable Validate(object model) => Validate((T) model); + + protected abstract IEnumerable Validate(T model); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 8bde435ef6..a558648ffc 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -1,9 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; -using System.Globalization; using System.Net; -using System.Text; using System.Web.Http; using AutoMapper; using Umbraco.Core; @@ -14,12 +12,9 @@ using System.Linq; using System.Net.Http; using Umbraco.Core.Models; using Constants = Umbraco.Core.Constants; -using Examine; -using Umbraco.Web.Dynamics; -using System.Text.RegularExpressions; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using System.Web.Http.Controllers; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Xml; using Umbraco.Web.Search; using Umbraco.Web.Trees; @@ -36,7 +31,6 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class EntityController : UmbracoAuthorizedJsonController { - /// /// Configures this controller with a custom action selector /// @@ -46,7 +40,7 @@ namespace Umbraco.Web.Editors { controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - //This is a special case, we'll accept a String here so that we can get page members when the special "all-members" + //This is a special case, we'll accept a String here so that we can get page members when the special "all-members" //id is passed in eventually we'll probably want to support GUID + Udi too new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)), @@ -56,6 +50,12 @@ namespace Umbraco.Web.Editors } private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); + private readonly SearchableTreeCollection _searchableTreeCollection; + + public EntityController(SearchableTreeCollection searchableTreeCollection) + { + _searchableTreeCollection = searchableTreeCollection; + } /// /// Returns an Umbraco alias given a string @@ -65,7 +65,7 @@ namespace Umbraco.Web.Editors /// public dynamic GetSafeAlias(string value, bool camelCase = true) { - var returnValue = (string.IsNullOrWhiteSpace(value)) ? string.Empty : value.ToSafeAlias(camelCase); + var returnValue = string.IsNullOrWhiteSpace(value) ? string.Empty : value.ToSafeAlias(camelCase); dynamic returnObj = new System.Dynamic.ExpandoObject(); returnObj.alias = returnValue; returnObj.original = value; @@ -102,12 +102,12 @@ namespace Umbraco.Web.Editors /// /// /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to edit, we need - /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search + /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result. - /// + /// /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search /// methods might be used in things like pickers in the content editor. - /// + /// [HttpGet] public IDictionary SearchAll(string query) { @@ -115,17 +115,17 @@ namespace Umbraco.Web.Editors if (string.IsNullOrEmpty(query)) return result; - + var allowedSections = Security.CurrentUser.AllowedSections.ToArray(); - var searchableTrees = SearchableTreeResolver.Current.GetSearchableTrees(); - + var searchableTrees = _searchableTreeCollection.AsReadOnlyDictionary(); + foreach (var searchableTree in searchableTrees) { if (allowedSections.Contains(searchableTree.Value.AppAlias)) { var tree = Services.ApplicationTreeService.GetByAlias(searchableTree.Key); if (tree == null) continue; //shouldn't occur - + var searchableTreeAttribute = searchableTree.Value.SearchableTree.GetType().GetCustomAttribute(false); var treeAttribute = tree.GetTreeAttribute(); @@ -140,7 +140,7 @@ namespace Umbraco.Web.Editors JsFormatterMethod = searchableTreeAttribute == null ? "" : searchableTreeAttribute.MethodName }; } - } + } return result; } @@ -183,7 +183,7 @@ namespace Umbraco.Web.Editors { return GetPath(guidUdi.Guid, type); } - throw new HttpResponseException(HttpStatusCode.NotFound); + throw new HttpResponseException(HttpStatusCode.NotFound); } /// @@ -207,7 +207,7 @@ namespace Umbraco.Web.Editors { Content = new StringContent(returnUrl) }; - } + } } var ancestors = GetAncestors(id, type); @@ -224,7 +224,7 @@ namespace Umbraco.Web.Editors Content = new StringContent(returnUrl) }; } - + [Obsolete("Use GetyById instead")] [EditorBrowsable(EditorBrowsableState.Never)] public EntityBasic GetByKey(Guid id, UmbracoEntityTypes type) @@ -243,13 +243,13 @@ namespace Umbraco.Web.Editors { //TODO: Rename this!!! It's misleading, it should be GetByXPath - + if (type != UmbracoEntityTypes.Document) throw new ArgumentException("Get by query is only compatible with enitities of type Document"); var q = ParseXPathQuery(query, nodeContextId); - var node = Umbraco.TypedContentSingleAtXPath(q); + var node = Umbraco.ContentSingleAtXPath(q); if (node == null) return null; @@ -268,7 +268,7 @@ namespace Umbraco.Web.Editors var ent = Services.EntityService.Get(nodeid); return ent.Path.Split(',').Reverse(); }, - publishedContentExists: i => Umbraco.TypedContent(i) != null); + publishedContentExists: i => Umbraco.Content(i) != null); } #region GetById @@ -309,7 +309,7 @@ namespace Umbraco.Web.Editors return GetResultForKey(guidUdi.Guid, type); } throw new HttpResponseException(HttpStatusCode.NotFound); - } + } #endregion #region GetByIds @@ -388,7 +388,7 @@ namespace Umbraco.Web.Editors } throw new HttpResponseException(HttpStatusCode.NotFound); - } + } #endregion [Obsolete("Use GetyByIds instead")] @@ -457,7 +457,7 @@ namespace Umbraco.Web.Editors return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); } - //the EntityService cannot search members of a certain type, this is currently not supported and would require + //the EntityService cannot search members of a certain type, this is currently not supported and would require //quite a bit of plumbing to do in the Services/Repository, we'll revert to a paged search long total; @@ -466,7 +466,7 @@ namespace Umbraco.Web.Editors return new PagedResult(total, pageNumber, pageSize) { Items = searchResult - }; + }; } /// @@ -483,7 +483,7 @@ namespace Umbraco.Web.Editors public PagedResult GetPagedChildren( int id, UmbracoEntityTypes type, - int pageNumber, + int pageNumber, int pageSize, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, @@ -493,7 +493,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); if (pageSize <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); - + var objectType = ConvertToObjectType(type); if (objectType.HasValue) { @@ -563,7 +563,7 @@ namespace Umbraco.Web.Editors } entities = aids == null || aids.Contains(Constants.System.Root) - ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) + ? Services.EntityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); } else @@ -603,11 +603,6 @@ namespace Umbraco.Web.Editors return GetResultForAncestors(id, type); } - public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) - { - return GetResultForAll(type, postFilter, postFilterParams); - } - /// /// Searches for results based on the entity type /// @@ -620,7 +615,9 @@ namespace Umbraco.Web.Editors long total; return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, searchFrom); } - + + + private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) { @@ -706,74 +703,6 @@ namespace Umbraco.Web.Editors } } - /// - /// Gets the result for the entity list based on the type - /// - /// - /// A string where filter that will filter the results dynamically with linq - optional - /// the parameters to fill in the string where filter - optional - /// - private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null, IDictionary postFilterParams = null) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Should we order this by something ? - var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(Mapper.Map); - return ExecutePostFilter(entities, postFilter, postFilterParams); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.Template: - var templates = Services.FileService.GetTemplates(); - var filteredTemplates = ExecutePostFilter(templates, postFilter, postFilterParams); - return filteredTemplates.Select(Mapper.Map); - - case UmbracoEntityTypes.Macro: - //Get all macros from the macro service - var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); - var filteredMacros = ExecutePostFilter(macros, postFilter, postFilterParams); - return filteredMacros.Select(Mapper.Map); - - case UmbracoEntityTypes.PropertyType: - - //get all document types, then combine all property types into one list - var propertyTypes = Services.ContentTypeService.GetAllContentTypes().Cast() - .Concat(Services.ContentTypeService.GetAllMediaTypes()) - .ToArray() - .SelectMany(x => x.PropertyTypes) - .DistinctBy(composition => composition.Alias); - var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredPropertyTypes); - - case UmbracoEntityTypes.PropertyGroup: - - //get all document types, then combine all property types into one list - var propertyGroups = Services.ContentTypeService.GetAllContentTypes().Cast() - .Concat(Services.ContentTypeService.GetAllMediaTypes()) - .ToArray() - .SelectMany(x => x.PropertyGroups) - .DistinctBy(composition => composition.Name); - var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredpropertyGroups); - - case UmbracoEntityTypes.User: - - int total; - var users = Services.UserService.GetAll(0, int.MaxValue, out total); - var filteredUsers = ExecutePostFilter(users, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredUsers); - - case UmbracoEntityTypes.Domain: - - case UmbracoEntityTypes.Language: - - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType) { if (keys.Length == 0) @@ -843,7 +772,7 @@ namespace Umbraco.Web.Editors var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { - var found = Services.EntityService.GetByKey(key, objectType.Value); + var found = Services.EntityService.Get(key, objectType.Value); if (found == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -881,22 +810,22 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } return Mapper.Map(found); - } + } //now we need to convert the unknown ones switch (entityType) { case UmbracoEntityTypes.PropertyType: - + case UmbracoEntityTypes.PropertyGroup: case UmbracoEntityTypes.Domain: - + case UmbracoEntityTypes.Language: - + case UmbracoEntityTypes.User: - + case UmbracoEntityTypes.Macro: - + default: throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); } @@ -932,26 +861,91 @@ namespace Umbraco.Web.Editors } } + // fixme - need to implement GetAll for backoffice controllers - dynamics? + + public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) + { + return GetResultForAll(type, postFilter, postFilterParams); + } + /// - /// Executes the post filter against a collection of objects + /// Gets the result for the entity list based on the type /// - /// - /// - /// - /// + /// + /// A string where filter that will filter the results dynamically with linq - optional + /// the parameters to fill in the string where filter - optional /// + private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null, IDictionary postFilterParams = null) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + //TODO: Should we order this by something ? + var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(Mapper.Map); + return ExecutePostFilter(entities, postFilter, postFilterParams); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.Template: + var templates = Services.FileService.GetTemplates(); + var filteredTemplates = ExecutePostFilter(templates, postFilter, postFilterParams); + return filteredTemplates.Select(Mapper.Map); + + case UmbracoEntityTypes.Macro: + //Get all macros from the macro service + var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); + var filteredMacros = ExecutePostFilter(macros, postFilter, postFilterParams); + return filteredMacros.Select(Mapper.Map); + + case UmbracoEntityTypes.PropertyType: + + //get all document types, then combine all property types into one list + var propertyTypes = Services.ContentTypeService.GetAll().Cast() + .Concat(Services.MediaTypeService.GetAll()) + .ToArray() + .SelectMany(x => x.PropertyTypes) + .DistinctBy(composition => composition.Alias); + var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredPropertyTypes); + + case UmbracoEntityTypes.PropertyGroup: + + //get all document types, then combine all property types into one list + var propertyGroups = Services.ContentTypeService.GetAll().Cast() + .Concat(Services.MediaTypeService.GetAll()) + .ToArray() + .SelectMany(x => x.PropertyGroups) + .DistinctBy(composition => composition.Name); + var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredpropertyGroups); + + case UmbracoEntityTypes.User: + + long total; + var users = Services.UserService.GetAll(0, int.MaxValue, out total); + var filteredUsers = ExecutePostFilter(users, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredUsers); + + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter, IDictionary postFilterParams) { - //if a post filter is assigned then try to execute it + // if a post filter is assigned then try to execute it if (postFilter.IsNullOrWhiteSpace() == false) { - return postFilterParams == null - ? entities.AsQueryable().Where(postFilter).ToArray() - : entities.AsQueryable().Where(postFilter, postFilterParams).ToArray(); - + // fixme - trouble is, we've killed the dynamic Where thing! + throw new NotImplementedException("oops"); + //return postFilterParams == null + // ? entities.AsQueryable().Where(postFilter).ToArray() + // : entities.AsQueryable().Where(postFilter, postFilterParams).ToArray(); } return entities; - } - + } } } diff --git a/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs b/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs index 571b29ed01..cae0759141 100644 --- a/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs +++ b/src/Umbraco.Web/Editors/EntityControllerConfigurationAttribute.cs @@ -1,8 +1,8 @@ -using System; +using System; using System.Web.Http.Controllers; using Umbraco.Web.WebApi; namespace Umbraco.Web.Editors { - -} \ No newline at end of file + +} diff --git a/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs b/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs index bb275f8fa2..b0a1c6ebaf 100644 --- a/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs +++ b/src/Umbraco.Web/Editors/FromJsonPathAttribute.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Net.Http; using System.Web.Http; using System.Web.Http.Controllers; @@ -16,12 +16,12 @@ namespace Umbraco.Web.Editors /// /// An example would be if you had json like: /// { ids: [1,2,3,4] } - /// + /// /// And you had an action like: GetByIds(int[] ids, UmbracoEntityTypes type) - /// - /// The ids array will not bind because the object being sent up is an object and not an array so the - /// normal json formatter will not figure this out. - /// + /// + /// The ids array will not bind because the object being sent up is an object and not an array so the + /// normal json formatter will not figure this out. + /// /// This would also let you bind sub levels of the JSON being sent up too if you wanted with any jsonpath /// internal class FromJsonPathAttribute : ModelBinderAttribute @@ -81,7 +81,7 @@ namespace Umbraco.Web.Editors } var json = JsonConvert.DeserializeObject(strJson); - + //if no explicit json path then use the model name var match = json.SelectToken(_jsonPath ?? bindingContext.ModelName); @@ -95,7 +95,7 @@ namespace Umbraco.Web.Editors return true; } } - - + + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/IEditorValidator.cs b/src/Umbraco.Web/Editors/IEditorValidator.cs index 8cbd4e2949..bec978788c 100644 --- a/src/Umbraco.Web/Editors/IEditorValidator.cs +++ b/src/Umbraco.Web/Editors/IEditorValidator.cs @@ -1,13 +1,36 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using umbraco.interfaces; +using Umbraco.Core.Composing; namespace Umbraco.Web.Editors { + // note - about IEditorValidator + // + // interface: IEditorValidator + // base class: EditorValidator + // static validation: EditorValidator.Validate() + // composition: via EditorValidationCollection and builder + // initialized with all IEditorValidator instances + // + // validation is used exclusively in ContentTypeControllerBase + // the whole thing is internal at the moment, never released + // and, there are no IEditorValidator implementation in Core + // so... this all mechanism is basically useless + + /// + /// Provides a general object validator. + /// internal interface IEditorValidator : IDiscoverable { + /// + /// Gets the object type validated by this validator. + /// Type ModelType { get; } + + /// + /// Validates an object. + /// IEnumerable Validate(object model); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/ImagesController.cs b/src/Umbraco.Web/Editors/ImagesController.cs index fa84ff345f..f8f49ba2da 100644 --- a/src/Umbraco.Web/Editors/ImagesController.cs +++ b/src/Umbraco.Web/Editors/ImagesController.cs @@ -1,16 +1,10 @@ using System; -using System.Drawing; using System.IO; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.IO; -using Umbraco.Core.Media; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors @@ -21,6 +15,13 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class ImagesController : UmbracoAuthorizedApiController { + private readonly MediaFileSystem _mediaFileSystem; + + public ImagesController(MediaFileSystem mediaFileSystem) + { + _mediaFileSystem = mediaFileSystem; + } + /// /// Gets the big thumbnail image for the media id /// @@ -34,12 +35,13 @@ namespace Umbraco.Web.Editors var media = Services.MediaService.GetById(mediaId); if (media == null) return Request.CreateResponse(HttpStatusCode.NotFound); - + var imageProp = media.Properties[Constants.Conventions.Media.File]; if (imageProp == null) return Request.CreateResponse(HttpStatusCode.NotFound); - var imagePath = imageProp.Value.ToString(); + //fixme doesn't take into account variants + var imagePath = imageProp.GetValue().ToString(); return GetBigThumbnail(imagePath); } @@ -53,8 +55,8 @@ namespace Umbraco.Web.Editors /// public HttpResponseMessage GetBigThumbnail(string originalImagePath) { - return string.IsNullOrWhiteSpace(originalImagePath) - ? Request.CreateResponse(HttpStatusCode.OK) + return string.IsNullOrWhiteSpace(originalImagePath) + ? Request.CreateResponse(HttpStatusCode.OK) : GetResized(originalImagePath, 500, "big-thumb"); } @@ -72,12 +74,13 @@ namespace Umbraco.Web.Editors var media = Services.MediaService.GetById(mediaId); if (media == null) return new HttpResponseMessage(HttpStatusCode.NotFound); - + var imageProp = media.Properties[Constants.Conventions.Media.File]; if (imageProp == null) return new HttpResponseMessage(HttpStatusCode.NotFound); - var imagePath = imageProp.Value.ToString(); + //fixme doesn't take into account variants + var imagePath = imageProp.GetValue().ToString(); return GetResized(imagePath, width); } @@ -104,18 +107,17 @@ namespace Umbraco.Web.Editors /// private HttpResponseMessage GetResized(string imagePath, int width, string sizeName) { - var fs = FileSystemProviderManager.Current.MediaFileSystem; var ext = Path.GetExtension(imagePath); // we need to check if it is an image by extension - if (fs.IsImageFile(ext) == false) + if (_mediaFileSystem.IsImageFile(ext) == false) return Request.CreateResponse(HttpStatusCode.NotFound); //redirect to ImageProcessor thumbnail with rnd generated from last modified time of original media file var response = Request.CreateResponse(HttpStatusCode.Found); - var imageLastModified = fs.GetLastModified(imagePath); - response.Headers.Location = new Uri( string.Format( "{0}?rnd={1}&width={2}", imagePath, string.Format( "{0:yyyyMMddHHmmss}", imageLastModified ), width ), UriKind.Relative ); + var imageLastModified = _mediaFileSystem.GetLastModified(imagePath); + response.Headers.Location = new Uri($"{imagePath}?rnd={imageLastModified:yyyyMMddHHmmss}&width={width}", UriKind.Relative ); return response; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/IsCurrentUserModelFilterAttribute.cs b/src/Umbraco.Web/Editors/IsCurrentUserModelFilterAttribute.cs index f466090e65..a791be7aed 100644 --- a/src/Umbraco.Web/Editors/IsCurrentUserModelFilterAttribute.cs +++ b/src/Umbraco.Web/Editors/IsCurrentUserModelFilterAttribute.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Net.Http; using System.Web.Http.Filters; using Umbraco.Web.Models.ContentEditing; @@ -47,10 +47,9 @@ namespace Umbraco.Web.Editors } } } - } base.OnActionExecuted(actionExecutedContext); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/LanguageController.cs b/src/Umbraco.Web/Editors/LanguageController.cs new file mode 100644 index 0000000000..8bf9d70a12 --- /dev/null +++ b/src/Umbraco.Web/Editors/LanguageController.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Web.Http; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Web.Models; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using Language = Umbraco.Web.Models.ContentEditing.Language; + +namespace Umbraco.Web.Editors +{ + /// + /// Backoffice controller supporting the dashboard for language administration. + /// + [PluginController("UmbracoApi")] + [PrefixlessBodyModelValidator] + public class LanguageController : UmbracoAuthorizedJsonController + { + /// + /// Returns all cultures available for creating languages. + /// + /// + [HttpGet] + public IDictionary GetAllCultures() + { + return + CultureInfo.GetCultures(CultureTypes.AllCultures) + .Where(x => !x.Name.IsNullOrWhiteSpace()) + .OrderBy(x => x.DisplayName).ToDictionary(x => x.Name, x => x.DisplayName); + } + + /// + /// Returns all currently configured languages. + /// + /// + [HttpGet] + public IEnumerable GetAllLanguages() + { + var allLanguages = Services.LocalizationService.GetAllLanguages(); + + return Mapper.Map, IEnumerable>(allLanguages); + } + + [HttpGet] + public Language GetLanguage(int id) + { + var lang = Services.LocalizationService.GetLanguageById(id); + if (lang == null) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var model = Mapper.Map(lang); + + //if there's only one language, by default it is the default + var allLangs = Services.LocalizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); + if (!lang.IsDefaultVariantLanguage) + { + if (allLangs.Count == 1) + { + model.IsDefaultVariantLanguage = true; + model.Mandatory = true; + } + else if (allLangs.All(x => !x.IsDefaultVariantLanguage)) + { + //if no language has the default flag, then the defaul language is the one with the lowest id + model.IsDefaultVariantLanguage = allLangs[0].Id == lang.Id; + model.Mandatory = allLangs[0].Id == lang.Id; + } + } + + + return model; + } + + /// + /// Deletes a language with a given ID + /// + [HttpDelete] + [HttpPost] + public IHttpActionResult DeleteLanguage(int id) + { + var language = Services.LocalizationService.GetLanguageById(id); + if (language == null) return NotFound(); + + var totalLangs = Services.LocalizationService.GetAllLanguages().Count(); + + if (language.IsDefaultVariantLanguage || totalLangs == 1) + { + var message = $"Language '{language.IsoCode}' is currently set to 'default' or it is the only installed language and can not be deleted."; + throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(message)); + } + + Services.LocalizationService.Delete(language); + + return Ok(); + } + + /// + /// Creates or saves a language + /// + [HttpPost] + public Language SaveLanguage(Language language) + { + if (!ModelState.IsValid) + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + + var found = Services.LocalizationService.GetLanguageByIsoCode(language.IsoCode); + + if (found != null && language.Id != found.Id) + { + //someone is trying to create a language that alraedy exist + ModelState.AddModelError("IsoCode", "The language " + language.IsoCode + " already exists"); + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + + if (found == null) + { + CultureInfo culture; + try + { + culture = CultureInfo.GetCultureInfo(language.IsoCode); + } + catch (CultureNotFoundException) + { + ModelState.AddModelError("IsoCode", "No Culture found with name " + language.IsoCode); + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + + //create it + var newLang = new Umbraco.Core.Models.Language(culture.Name) + { + CultureName = culture.DisplayName, + IsDefaultVariantLanguage = language.IsDefaultVariantLanguage, + Mandatory = language.Mandatory + }; + Services.LocalizationService.Save(newLang); + return Mapper.Map(newLang); + } + + found.Mandatory = language.Mandatory; + found.IsDefaultVariantLanguage = language.IsDefaultVariantLanguage; + Services.LocalizationService.Save(found); + return Mapper.Map(found); + } + + } +} diff --git a/src/Umbraco.Web/Editors/LegacyController.cs b/src/Umbraco.Web/Editors/LegacyController.cs index 59218771e8..3e8b6f54e0 100644 --- a/src/Umbraco.Web/Editors/LegacyController.cs +++ b/src/Umbraco.Web/Editors/LegacyController.cs @@ -3,39 +3,19 @@ using System.Net; using System.Net.Http; using System.Web; using System.Web.Http; -using Umbraco.Core; using Umbraco.Web.Mvc; -using Umbraco.Web.UI; -using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.UI; namespace Umbraco.Web.Editors { /// /// The API controller used for dealing with legacy content - /// + /// [PluginController("UmbracoApi")] [ValidationFilter] public class LegacyController : UmbracoAuthorizedJsonController { - - /// - /// Constructor - /// - public LegacyController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - internal LegacyController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - /// /// This will perform the delete operation for legacy items which include any item that /// has functionality included in the ui.xml structure. @@ -44,7 +24,7 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage DeleteLegacyItem(string nodeId, string alias, string nodeType) { - //U4-2686 - alias is html encoded, make sure to decode + //U4-2686 - alias is html encoded, make sure to decode alias = HttpUtility.HtmlDecode(alias); //In order to process this request we MUST have an HttpContext available @@ -54,20 +34,20 @@ namespace Umbraco.Web.Editors //this is a hack check based on legacy if (nodeType == "memberGroups") { - LegacyDialogHandler.Delete(httpContextAttempt.Result, UmbracoUser, nodeType, 0, alias); + LegacyDialogHandler.Delete(httpContextAttempt.Result, Security.CurrentUser, nodeType, 0, alias); return Request.CreateResponse(HttpStatusCode.OK); } int id; if (int.TryParse(nodeId, out id)) { - LegacyDialogHandler.Delete(httpContextAttempt.Result, UmbracoUser, nodeType, id, alias); + LegacyDialogHandler.Delete(httpContextAttempt.Result, Security.CurrentUser, nodeType, id, alias); return Request.CreateResponse(HttpStatusCode.OK); } - //the way this legacy stuff used to work is that if the node id didn't parse, we would + //the way this legacy stuff used to work is that if the node id didn't parse, we would //pass the node id as the alias with an id of zero = sure whatevs. - LegacyDialogHandler.Delete(httpContextAttempt.Result, UmbracoUser, nodeType, 0, nodeId); + LegacyDialogHandler.Delete(httpContextAttempt.Result, Security.CurrentUser, nodeType, 0, nodeId); return Request.CreateResponse(HttpStatusCode.OK); } @@ -76,4 +56,4 @@ namespace Umbraco.Web.Editors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/LogController.cs b/src/Umbraco.Web/Editors/LogController.cs index 5122a458f6..d8b0fe48ca 100644 --- a/src/Umbraco.Web/Editors/LogController.cs +++ b/src/Umbraco.Web/Editors/LogController.cs @@ -3,13 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using System.Web.Http; +using System.Windows.Forms; using AutoMapper; -using Umbraco.Core; using Umbraco.Web.Models.ContentEditing; -using umbraco.BusinessLogic; +using Umbraco.Core.Models; using Umbraco.Core.Models; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; @@ -30,10 +27,10 @@ namespace Umbraco.Web.Editors DateTime? sinceDate = null) { long totalRecords; - var dateQuery = sinceDate.HasValue ? Query.Builder.Where(x => x.CreateDate >= sinceDate) : null; + var dateQuery = sinceDate.HasValue ? SqlContext.Query().Where(x => x.CreateDate >= sinceDate) : null; var result = Services.AuditService.GetPagedItemsByEntity(id, pageNumber - 1, pageSize, out totalRecords, orderDirection, customFilter: dateQuery); var mapped = Mapper.Map>(result); - + var page = new PagedResult(totalRecords, pageNumber, pageSize) { Items = MapAvatarsAndNames(mapped) @@ -49,15 +46,16 @@ namespace Umbraco.Web.Editors DateTime? sinceDate = null) { long totalRecords; - var dateQuery = sinceDate.HasValue ? Query.Builder.Where(x => x.CreateDate >= sinceDate) : null; - var result = Services.AuditService.GetPagedItemsByUser(Security.GetUserId(), pageNumber - 1, pageSize, out totalRecords, orderDirection, customFilter:dateQuery); + var dateQuery = sinceDate.HasValue ? SqlContext.Query().Where(x => x.CreateDate >= sinceDate) : null; + var userId = Security.GetUserId().ResultOr(0); + var result = Services.AuditService.GetPagedItemsByUser(userId, pageNumber - 1, pageSize, out totalRecords, orderDirection, customFilter:dateQuery); var mapped = Mapper.Map>(result); return new PagedResult(totalRecords, pageNumber + 1, pageSize) { Items = MapAvatarsAndNames(mapped) }; - } - + } + [Obsolete("Use GetPagedLog instead")] public IEnumerable GetEntityLog(int id) { @@ -65,17 +63,18 @@ namespace Umbraco.Web.Editors var result = Services.AuditService.GetPagedItemsByEntity(id, 1, int.MaxValue, out totalRecords); return Mapper.Map>(result); } - + //TODO: Move to CurrentUserController? [Obsolete("Use GetPagedCurrentUserLog instead")] public IEnumerable GetCurrentUserLog(AuditType logType, DateTime? sinceDate) { long totalRecords; - var dateQuery = sinceDate.HasValue ? Query.Builder.Where(x => x.CreateDate >= sinceDate) : null; - var result = Services.AuditService.GetPagedItemsByUser(Security.GetUserId(), 1, int.MaxValue, out totalRecords, auditTypeFilter: new[] {logType},customFilter: dateQuery); + var dateQuery = sinceDate.HasValue ? SqlContext.Query().Where(x => x.CreateDate >= sinceDate) : null; + var userId = Security.GetUserId().ResultOr(0); + var result = Services.AuditService.GetPagedItemsByUser(userId, 1, int.MaxValue, out totalRecords, auditTypeFilter: new[] {logType},customFilter: dateQuery); return Mapper.Map>(result); } - + [Obsolete("Use GetPagedLog instead")] public IEnumerable GetLog(AuditType logType, DateTime? sinceDate) { @@ -83,14 +82,14 @@ namespace Umbraco.Web.Editors sinceDate = DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0, 0)); return Mapper.Map>( - Log.Instance.GetLogItems(Enum.Parse(logType.ToString()), sinceDate.Value)); + Services.AuditService.GetLogs(logType, sinceDate.Value)); } private IEnumerable MapAvatarsAndNames(IEnumerable items) { var userIds = items.Select(x => x.UserId).ToArray(); var users = Services.UserService.GetUsersById(userIds) - .ToDictionary(x => x.Id, x => x.GetUserAvatarUrls(ApplicationContext.ApplicationCache.RuntimeCache)); + .ToDictionary(x => x.Id, x => x.GetUserAvatarUrls(ApplicationCache.RuntimeCache)); var userNames = Services.UserService.GetUsersById(userIds).ToDictionary(x => x.Id, x => x.Name); foreach (var item in items) { diff --git a/src/Umbraco.Web/Editors/MacroController.cs b/src/Umbraco.Web/Editors/MacroController.cs index 129c78cbcd..dc4d42a48c 100644 --- a/src/Umbraco.Web/Editors/MacroController.cs +++ b/src/Umbraco.Web/Editors/MacroController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -9,7 +9,7 @@ using System.Web.SessionState; using AutoMapper; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; -using umbraco; +using Umbraco.Web.Macros; using Umbraco.Core; using Umbraco.Core.Models; @@ -93,15 +93,13 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - //need to get a legacy macro object - eventually we'll have a new format but nto yet - var macro = new macro(macroAlias); - if (macro == null) - { + var m = Services.MacroService.GetByAlias(macroAlias); + if (m == null) throw new HttpResponseException(HttpStatusCode.NotFound); - } + var macro = new MacroModel(m); //if it isn't supposed to be rendered in the editor then return an empty string - if (macro.DontRenderInEditor) + if (macro.RenderInEditor == false) { var response = Request.CreateResponse(); //need to create a specific content result formatted as html since this controller has been configured @@ -158,4 +156,4 @@ namespace Umbraco.Web.Editors public string VirtualPath { get; set; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 72af1c4c70..2c7a5f30c6 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -27,10 +27,13 @@ using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Configuration; +using Umbraco.Core.Persistence.Querying; using Umbraco.Web.UI; using Notification = Umbraco.Web.Models.ContentEditing.Notification; using Umbraco.Core.Persistence; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.Models.Validation; namespace Umbraco.Web.Editors { @@ -56,23 +59,6 @@ namespace Umbraco.Web.Editors } } - /// - /// Constructor - /// - public MediaController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public MediaController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - /// /// Gets an empty content item for the /// @@ -82,14 +68,14 @@ namespace Umbraco.Web.Editors [OutgoingEditorModelEvent] public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) { - var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); + var contentType = Services.MediaTypeService.Get(contentTypeAlias); if (contentType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, UmbracoUser.Id); - var mapped = Mapper.Map(emptyContent); + var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, Security.GetUserId().ResultOr(0)); + var mapped = ContextMapper.Map(emptyContent, UmbracoContext); //remove this tab if it exists: umbContainerView var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); @@ -137,7 +123,7 @@ namespace Umbraco.Web.Editors //HandleContentNotFound will throw an exception return null; } - return Mapper.Map(foundContent); + return ContextMapper.Map(foundContent, UmbracoContext); } /// @@ -157,7 +143,7 @@ namespace Umbraco.Web.Editors //HandleContentNotFound will throw an exception return null; } - return Mapper.Map(foundContent); + return ContextMapper.Map(foundContent, UmbracoContext); } /// @@ -186,7 +172,7 @@ namespace Umbraco.Web.Editors public IEnumerable GetByIds([FromUri]int[] ids) { var foundMedia = Services.MediaService.GetByIds(ids); - return foundMedia.Select(Mapper.Map); + return foundMedia.Select(media => ContextMapper.Map(media, UmbracoContext)); } /// @@ -214,8 +200,8 @@ namespace Umbraco.Web.Editors { //Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... //if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" - var folderTypes = Services.ContentTypeService - .GetAllMediaTypes() + var folderTypes = Services.MediaTypeService + .GetAll() .Where(x => x.Alias.EndsWith("Folder")) .Select(x => x.Id) .ToArray(); @@ -289,9 +275,20 @@ namespace Umbraco.Web.Editors IMedia[] children; if (pageNumber > 0 && pageSize > 0) { + IQuery queryFilter = null; + if (filter.IsNullOrWhiteSpace() == false) + { + //add the default text filter + queryFilter = SqlContext.Query() + .Where(x => x.Name.Contains(filter)); + } + children = Services.MediaService - .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren - , orderBy, orderDirection, orderBySystemField, filter).ToArray(); + .GetPagedChildren( + id, (pageNumber - 1), pageSize, + out totalChildren, + orderBy, orderDirection, orderBySystemField, + queryFilter).ToArray(); } else { @@ -331,7 +328,7 @@ namespace Umbraco.Web.Editors bool orderBySystemField = true, string filter = "") { - var entity = Services.EntityService.GetByKey(id); + var entity = Services.EntityService.Get(id); if (entity != null) { return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); @@ -362,7 +359,7 @@ namespace Umbraco.Web.Editors var guidUdi = id as GuidUdi; if (guidUdi != null) { - var entity = Services.EntityService.GetByKey(guidUdi.Guid); + var entity = Services.EntityService.Get(guidUdi.Guid); if (entity != null) { return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); @@ -414,7 +411,7 @@ namespace Umbraco.Web.Editors } //if the current item is in the recycle bin - if (foundMedia.IsInRecycleBin() == false) + if (foundMedia.Trashed == false) { var moveResult = Services.MediaService.WithResult().MoveToRecycleBin(foundMedia, (int)Security.CurrentUser.Id); if (moveResult == false) @@ -473,7 +470,11 @@ namespace Umbraco.Web.Editors // * we have a reference to the DTO object and the persisted object // * Permissions are valid - MapPropertyValues(contentItem); + UpdateName(contentItem); + MapPropertyValues( + contentItem, + (save, property) => property.GetValue(), //get prop val + (save, property, v) => property.SetValue(v)); //set prop val //We need to manually check the validation results here because: // * We still need to save the entity even if there are validation value errors @@ -483,12 +484,12 @@ namespace Umbraco.Web.Editors // a message indicating this if (ModelState.IsValid == false) { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) + if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem) && (contentItem.Action == ContentSaveAction.SaveNew)) { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the modelstate to the outgoing object and throw validation response - var forDisplay = Mapper.Map(contentItem.PersistedContent); + var forDisplay = ContextMapper.Map(contentItem.PersistedContent, UmbracoContext); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); } @@ -498,7 +499,7 @@ namespace Umbraco.Web.Editors var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id); //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); + var display = ContextMapper.Map(contentItem.PersistedContent, UmbracoContext); //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); @@ -521,7 +522,7 @@ namespace Umbraco.Web.Editors //If the item is new and the operation was cancelled, we need to return a different // status code so the UI can handle it since it won't be able to redirect since there // is no Id to redirect to! - if (saveStatus.Result.StatusType == OperationStatusType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) + if (saveStatus.Result.Result == OperationResultType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) { throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); } @@ -532,19 +533,7 @@ namespace Umbraco.Web.Editors return display; } - - /// - /// Maps the property values to the persisted entity - /// - /// - protected override void MapPropertyValues(ContentBaseItemSave contentItem) - { - UpdateName(contentItem); - - //use the base method to map the rest of the properties - base.MapPropertyValues(contentItem); - } - + /// /// Empties the recycle bin /// @@ -577,7 +566,7 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK); } - var mediaService = base.ApplicationContext.Services.MediaService; + var mediaService = Services.MediaService; var sortedMedia = new List(); try { @@ -586,14 +575,14 @@ namespace Umbraco.Web.Editors // Save Media with new sort order and update content xml in db accordingly if (mediaService.Sort(sortedMedia) == false) { - LogHelper.Warn("Media sorting failed, this was probably caused by an event being cancelled"); + Logger.Warn("Media sorting failed, this was probably caused by an event being cancelled"); return Request.CreateValidationErrorResponse("Media sorting failed, this was probably caused by an event being cancelled"); } return Request.CreateResponse(HttpStatusCode.OK); } catch (Exception ex) { - LogHelper.Error("Could not update media sort order", ex); + Logger.Error("Could not update media sort order", ex); throw; } } @@ -602,12 +591,12 @@ namespace Umbraco.Web.Editors { var intParentId = GetParentIdAsInt(folder.ParentId, validatePermissions:true); - var mediaService = ApplicationContext.Services.MediaService; + var mediaService = Services.MediaService; var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder); mediaService.Save(f, Security.CurrentUser.Id); - return Mapper.Map(f); + return ContextMapper.Map(f, UmbracoContext); } /// @@ -643,7 +632,7 @@ namespace Umbraco.Web.Editors int parentId = GetParentIdAsInt(currentFolderId, validatePermissions: true); var tempFiles = new PostedFiles(); - var mediaService = ApplicationContext.Services.MediaService; + var mediaService = Services.MediaService; //in case we pass a path with a folder in it, we will create it and upload media to it. if (result.FormData.ContainsKey("path")) @@ -681,7 +670,7 @@ namespace Umbraco.Web.Editors " returned null"); //look for matching folder - folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + folderMediaItem = mediaRoot.Children(Services.MediaService).FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); if (folderMediaItem == null) { //if null, create a folder @@ -746,7 +735,7 @@ namespace Umbraco.Web.Editors } else { - tempFiles.UploadedFiles.Add(new ContentItemFile + tempFiles.UploadedFiles.Add(new ContentPropertyFile { FileName = fileName, PropertyAlias = Constants.Conventions.Media.File, @@ -806,7 +795,7 @@ namespace Umbraco.Web.Editors Guid idGuid; if (Guid.TryParse(parentId, out idGuid)) { - var entity = Services.EntityService.GetByKey(idGuid); + var entity = Services.EntityService.Get(idGuid); if (entity != null) { intParentId = entity.Id; diff --git a/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs b/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs index 1d38c4fce4..bdf35718a9 100644 --- a/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/MediaPostValidateAttribute.cs @@ -7,6 +7,7 @@ using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Security; using Umbraco.Web.WebApi; @@ -26,44 +27,39 @@ namespace Umbraco.Web.Editors { } + // fixme wtf is this? public MediaPostValidateAttribute(IMediaService mediaService, IEntityService entityService, WebSecurity security) { - if (mediaService == null) throw new ArgumentNullException("mediaService"); - if (security == null) throw new ArgumentNullException("security"); - _mediaService = mediaService; + _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); _entityService = entityService; - _security = security; + _security = security ?? throw new ArgumentNullException(nameof(security)); } + // fixme all these should be injected properties + private IMediaService MediaService - { - get { return _mediaService ?? ApplicationContext.Current.Services.MediaService; } - } + => _mediaService ?? Current.Services.MediaService; private IEntityService EntityService - { - get { return _entityService ?? ApplicationContext.Current.Services.EntityService; } - } + => _entityService ?? Current.Services.EntityService; private WebSecurity Security - { - get { return _security ?? UmbracoContext.Current.Security; } - } - + => _security ?? UmbracoContext.Current.Security; + public override void OnActionExecuting(HttpActionContext actionContext) { var mediaItem = (MediaItemSave)actionContext.ActionArguments["contentItem"]; //We now need to validate that the user is allowed to be doing what they are doing. //Then if it is new, we need to lookup those permissions on the parent. - IMedia contentToCheck = null; + IMedia contentToCheck; int contentIdToCheck; switch (mediaItem.Action) { case ContentSaveAction.Save: contentToCheck = mediaItem.PersistedContent; contentIdToCheck = contentToCheck.Id; - break; + break; case ContentSaveAction.SaveNew: contentToCheck = MediaService.GetById(mediaItem.ParentId); @@ -87,13 +83,11 @@ namespace Umbraco.Web.Editors if (MediaController.CheckPermissions( actionContext.Request.Properties, Security.CurrentUser, - MediaService, - EntityService, - contentIdToCheck, - contentToCheck) == false) + MediaService, EntityService, + contentIdToCheck, contentToCheck) == false) { throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index 43312fb7c3..efc3fa2e4c 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -11,11 +11,11 @@ using System.Net; using System.Net.Http; using Umbraco.Web.WebApi; using Umbraco.Core.Services; -using Umbraco.Core.Models.EntityBase; using System; using System.ComponentModel; using System.Web.Http.Controllers; using Umbraco.Core; +using Umbraco.Web.Composing; namespace Umbraco.Web.Editors { @@ -30,7 +30,7 @@ namespace Umbraco.Web.Editors [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] [EnableOverrideAuthorization] [MediaTypeControllerControllerConfiguration] - public class MediaTypeController : ContentTypeControllerBase + public class MediaTypeController : ContentTypeControllerBase { /// /// Configures this controller with a custom action selector @@ -44,33 +44,15 @@ namespace Umbraco.Web.Editors } } - /// - /// Constructor - /// - public MediaTypeController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public MediaTypeController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - - } - public int GetCount() { - return Services.ContentTypeService.CountContentTypes(); + return Services.ContentTypeService.Count(); } [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] public MediaTypeDisplay GetById(int id) { - var ct = Services.ContentTypeService.GetMediaType(id); + var ct = Services.MediaTypeService.Get(id); if (ct == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -89,13 +71,13 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage DeleteById(int id) { - var foundType = Services.ContentTypeService.GetMediaType(id); + var foundType = Services.MediaTypeService.Get(id); if (foundType == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } - Services.ContentTypeService.Delete(foundType, Security.CurrentUser.Id); + Services.MediaTypeService.Delete(foundType, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); } @@ -143,7 +125,7 @@ namespace Umbraco.Web.Editors public IEnumerable GetAll() { - return Services.ContentTypeService.GetAllMediaTypes() + return Services.MediaTypeService.GetAll() .Select(Mapper.Map); } @@ -156,14 +138,14 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage DeleteContainer(int id) { - Services.ContentTypeService.DeleteMediaTypeContainer(id, Security.CurrentUser.Id); + Services.MediaTypeService.DeleteContainer(id, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); } public HttpResponseMessage PostCreateContainer(int parentId, string name) { - var result = Services.ContentTypeService.CreateMediaTypeContainer(parentId, name, Security.CurrentUser.Id); + var result = Services.MediaTypeService.CreateContainer(parentId, name, Security.CurrentUser.Id); return result ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id @@ -172,8 +154,8 @@ namespace Umbraco.Web.Editors public HttpResponseMessage PostRenameContainer(int id, string name) { - - var result = Services.ContentTypeService.RenameMediaTypeContainer(id, name, Security.CurrentUser.Id); + + var result = Services.MediaTypeService.RenameContainer(id, name, Security.CurrentUser.Id); return result ? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id @@ -182,10 +164,10 @@ namespace Umbraco.Web.Editors public MediaTypeDisplay PostSave(MediaTypeSave contentTypeSave) { - var savedCt = PerformPostSave( + var savedCt = PerformPostSave( contentTypeSave: contentTypeSave, - getContentType: i => Services.ContentTypeService.GetMediaType(i), - saveContentType: type => Services.ContentTypeService.Save(type)); + getContentType: i => Services.MediaTypeService.Get(i), + saveContentType: type => Services.MediaTypeService.Save(type)); var display = Mapper.Map(savedCt); @@ -211,7 +193,7 @@ namespace Umbraco.Web.Editors IEnumerable types; if (contentId == Constants.System.Root) { - types = Services.ContentTypeService.GetAllMediaTypes().ToList(); + types = Services.MediaTypeService.GetAll().ToList(); //if no allowed root types are set, just return everything if (types.Any(x => x.AllowedAsRoot)) @@ -229,7 +211,7 @@ namespace Umbraco.Web.Editors if (ids.Any() == false) return Enumerable.Empty(); - types = Services.ContentTypeService.GetAllMediaTypes(ids).ToList(); + types = Services.MediaTypeService.GetAll(ids).ToList(); } var basics = types.Select(Mapper.Map).ToList(); @@ -250,7 +232,7 @@ namespace Umbraco.Web.Editors [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] public IEnumerable GetAllowedChildren(Guid contentId) { - var entity = ApplicationContext.Services.EntityService.GetByKey(contentId); + var entity = Current.Services.EntityService.Get(contentId); if (entity != null) { return GetAllowedChildren(entity.Id); @@ -269,7 +251,7 @@ namespace Umbraco.Web.Editors var guidUdi = contentId as GuidUdi; if (guidUdi != null) { - var entity = ApplicationContext.Services.EntityService.GetByKey(guidUdi.Guid); + var entity = Current.Services.EntityService.Get(guidUdi.Guid); if (entity != null) { return GetAllowedChildren(entity.Id); @@ -295,7 +277,7 @@ namespace Umbraco.Web.Editors } throw new HttpResponseException(HttpStatusCode.NotFound); - } + } #endregion /// @@ -307,8 +289,8 @@ namespace Umbraco.Web.Editors { return PerformMove( move, - getContentType: i => Services.ContentTypeService.GetMediaType(i), - doMove: (type, i) => Services.ContentTypeService.MoveMediaType(type, i)); + getContentType: i => Services.MediaTypeService.Get(i), + doMove: (type, i) => Services.MediaTypeService.Move(type, i)); } /// @@ -320,8 +302,8 @@ namespace Umbraco.Web.Editors { return PerformCopy( copy, - getContentType: i => Services.ContentTypeService.GetMediaType(i), - doCopy: (type, i) => Services.ContentTypeService.CopyMediaType(type, i)); + getContentType: i => Services.MediaTypeService.Get(i), + doCopy: (type, i) => Services.MediaTypeService.Copy(type, i)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 5aad40aa70..bf60560574 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -1,36 +1,31 @@ using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Net; using System.Net.Http; -using System.Text; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; using System.Threading.Tasks; using System.Web; using System.Web.Http; using System.Web.Http.ModelBinding; using System.Web.Security; using AutoMapper; -using Examine.LuceneEngine.SearchCriteria; -using Examine.SearchCriteria; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; using Umbraco.Web.Models.Mapping; using Umbraco.Web.WebApi; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; -using umbraco; using Constants = Umbraco.Core.Constants; -using Examine; namespace Umbraco.Web.Editors { @@ -43,26 +38,7 @@ namespace Umbraco.Web.Editors [OutgoingNoHyphenGuidFormat] public class MemberController : ContentControllerBase { - /// - /// Constructor - /// - public MemberController() - : this(UmbracoContext.Current) - { - _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - } - - /// - /// Constructor - /// - /// - public MemberController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - } - - private readonly MembershipProvider _provider; + private readonly MembershipProvider _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); /// /// Returns the currently configured membership scenario for members in umbraco @@ -90,16 +66,18 @@ namespace Umbraco.Web.Editors if (MembershipScenario == MembershipScenario.NativeUmbraco) { - long totalRecords; var members = Services.MemberService - .GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter).ToArray(); + .GetAll((pageNumber - 1), pageSize, out var totalRecords, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter).ToArray(); if (totalRecords == 0) { return new PagedResult(0, 0, 0); } - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize); - pagedResult.Items = members - .Select(Mapper.Map); + + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = members + .Select(x => ContextMapper.Map(x, UmbracoContext)) + }; return pagedResult; } else @@ -127,10 +105,13 @@ namespace Umbraco.Web.Editors { return new PagedResult(0, 0, 0); } - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize); - pagedResult.Items = members - .Cast() - .Select(Mapper.Map); + + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = members + .Cast() + .Select(Mapper.Map) + }; return pagedResult; } @@ -178,7 +159,7 @@ namespace Umbraco.Web.Editors { HandleContentNotFound(key); } - return Mapper.Map(foundMember); + return ContextMapper.Map(foundMember, UmbracoContext); case MembershipScenario.CustomProviderWithUmbracoLink: //TODO: Support editing custom properties for members with a custom membership provider here. @@ -212,7 +193,7 @@ namespace Umbraco.Web.Editors } /// - /// Gets an empty content item for the + /// Gets an empty content item for the /// /// /// @@ -234,11 +215,11 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); emptyContent = new Member(contentType); emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(provider.MinRequiredPasswordLength, provider.MinRequiredNonAlphanumericCharacters); - return Mapper.Map(emptyContent); + return ContextMapper.Map(emptyContent, UmbracoContext); case MembershipScenario.CustomProviderWithUmbracoLink: //TODO: Support editing custom properties for members with a custom membership provider here. @@ -247,14 +228,14 @@ namespace Umbraco.Web.Editors //we need to return a scaffold of a 'simple' member - basically just what a membership provider can edit emptyContent = MemberService.CreateGenericMembershipProviderMember("", "", "", ""); emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); - return Mapper.Map(emptyContent); + return ContextMapper.Map(emptyContent, UmbracoContext); } } /// /// Saves member /// - /// + /// [FileUploadCleanupFilter] [OutgoingEditorModelEvent] public MemberDisplay PostSave( @@ -287,7 +268,7 @@ namespace Umbraco.Web.Editors //Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors if (ModelState.IsValid == false) { - var forDisplay = Mapper.Map(contentItem.PersistedContent); + var forDisplay = ContextMapper.Map(contentItem.PersistedContent, UmbracoContext); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); } @@ -299,7 +280,7 @@ namespace Umbraco.Web.Editors } //We're gonna look up the current roles now because the below code can cause - // events to be raised and developers could be manually adding roles to members in + // events to be raised and developers could be manually adding roles to members in // their handlers. If we don't look this up now there's a chance we'll just end up // removing the roles they've assigned. var currRoles = Roles.GetRolesForUser(contentItem.PersistedContent.Username); @@ -329,12 +310,12 @@ namespace Umbraco.Web.Editors //If we've had problems creating/updating the user with the provider then return the error if (ModelState.IsValid == false) { - var forDisplay = Mapper.Map(contentItem.PersistedContent); + var forDisplay = ContextMapper.Map(contentItem.PersistedContent, UmbracoContext); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); } - //save the IMember - + //save the IMember - //TODO: When we support the CustomProviderWithUmbracoLink scenario, we'll need to save the custom properties for that here too if (MembershipScenario == MembershipScenario.NativeUmbraco) { @@ -362,18 +343,18 @@ namespace Umbraco.Web.Editors } //set the generated password (if there was one) - in order to do this we'll chuck the gen'd password into the - // additional data of the IUmbracoEntity of the persisted item - then we can retrieve this in the model mapper and set + // additional data of the IUmbracoEntity of the persisted item - then we can retrieve this in the model mapper and set // the value to be given to the UI. Hooray for AdditionalData :) contentItem.PersistedContent.AdditionalData["GeneratedPassword"] = generatedPassword; //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); + var display = ContextMapper.Map(contentItem.PersistedContent, UmbracoContext); //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); var localizedTextService = Services.TextService; - //put the correct msgs in + //put the correct msgs in switch (contentItem.Action) { case ContentSaveAction.Save: @@ -394,11 +375,14 @@ namespace Umbraco.Web.Editors UpdateName(contentItem); //map the custom properties - this will already be set for new entities in our member binder - contentItem.PersistedContent.Email = contentItem.Email; + contentItem.PersistedContent.Email = contentItem.Email; contentItem.PersistedContent.Username = contentItem.Username; //use the base method to map the rest of the properties - base.MapPropertyValues(contentItem); + base.MapPropertyValues( + contentItem, + (save, property) => property.GetValue(), //get prop val + (save, property, v) => property.SetValue(v)); //set prop val } /// @@ -423,6 +407,33 @@ namespace Umbraco.Web.Editors var shouldReFetchMember = false; var providedUserName = contentItem.PersistedContent.Username; + //if the user doesn't have access to sensitive values, then we need to check if any of the built in member property types + //have been marked as sensitive. If that is the case we cannot change these persisted values no matter what value has been posted. + //There's only 3 special ones we need to deal with that are part of the MemberSave instance + if (Security.CurrentUser.HasAccessToSensitiveData() == false) + { + var sensitiveProperties = contentItem.PersistedContent.ContentType + .PropertyTypes.Where(x => contentItem.PersistedContent.ContentType.IsSensitiveProperty(x.Alias)) + .ToList(); + + foreach (var sensitiveProperty in sensitiveProperties) + { + //if found, change the value of the contentItem model to the persisted value so it remains unchanged + switch (sensitiveProperty.Alias) + { + case Constants.Conventions.Member.Comments: + contentItem.Comments = contentItem.PersistedContent.Comments; + break; + case Constants.Conventions.Member.IsApproved: + contentItem.IsApproved = contentItem.PersistedContent.IsApproved; + break; + case Constants.Conventions.Member.IsLockedOut: + contentItem.IsLockedOut = contentItem.PersistedContent.IsLockedOut; + break; + } + } + } + //Update the membership user if it has changed try { @@ -433,13 +444,13 @@ namespace Umbraco.Web.Editors if (requiredUpdating.Success) { - //re-map these values + //re-map these values shouldReFetchMember = true; } } catch (Exception ex) { - LogHelper.WarnWithException("Could not update member, the provider returned an error", ex); + Logger.Warn(ex, "Could not update member, the provider returned an error"); ModelState.AddPropertyError( //specify 'default' just so that it shows up as a notification - is not assigned to a property new ValidationResult("Could not update member, the provider returned an error: " + ex.Message + " (see log for full details)"), "default"); @@ -473,10 +484,10 @@ namespace Umbraco.Web.Editors ModelState.AddModelError("custom", "An admin cannot lock a user"); } - //password changes ? + //password changes ? if (contentItem.Password == null) { - //If the provider has changed some values, these values need to be reflected in the member object + //If the provider has changed some values, these values need to be reflected in the member object //that will get mapped to the display object if (shouldReFetchMember) { @@ -490,7 +501,7 @@ namespace Umbraco.Web.Editors var passwordChangeResult = Members.ChangePassword(membershipUser.UserName, contentItem.Password, _provider); if (passwordChangeResult.Success) { - //If the provider has changed some values, these values need to be reflected in the member object + //If the provider has changed some values, these values need to be reflected in the member object //that will get mapped to the display object if (shouldReFetchMember) { @@ -559,16 +570,18 @@ namespace Umbraco.Web.Editors var valueMapped = currProps.FirstOrDefault(x => x.Alias == p.Alias); if (builtInAliases.Contains(p.Alias) == false && valueMapped != null) { - p.Value = valueMapped.Value; - p.TagSupport.Behavior = valueMapped.TagSupport.Behavior; - p.TagSupport.Enable = valueMapped.TagSupport.Enable; - p.TagSupport.Tags = valueMapped.TagSupport.Tags; + p.SetValue(valueMapped.GetValue()); + + // fixme - ok, I give up, at that point tags are dead here, until we figure it out + //p.TagChanges.Behavior = valueMapped.TagChanges.Behavior; + //p.TagChanges.Enable = valueMapped.TagChanges.Enable; + //p.TagChanges.Tags = valueMapped.TagChanges.Tags; } } } /// - /// Following a refresh of member data called during an update if the membership provider has changed some underlying data, + /// Following a refresh of member data called during an update if the membership provider has changed some underlying data, /// we don't want to lose the provided, and potentiallly changed, username /// /// @@ -586,18 +599,18 @@ namespace Umbraco.Web.Editors /// /// /// Depending on if the Umbraco membership provider is active or not, the process differs slightly: - /// - /// * If the umbraco membership provider is used - we create the membership user first with the membership provider, since + /// + /// * If the umbraco membership provider is used - we create the membership user first with the membership provider, since /// it's the umbraco membership provider, this writes to the umbraco tables. When that is complete we re-fetch the IMember /// model data from the db. In this case we don't care what the provider user key is. - /// * If we're using a non-umbraco membership provider - we check if there is a 'Member' member type - if so + /// * If we're using a non-umbraco membership provider - we check if there is a 'Member' member type - if so /// we create an empty IMember instance first (of type 'Member'), this gives us a unique ID (GUID) - /// that we then use to create the member in the custom membership provider. This acts as the link between Umbraco data and + /// that we then use to create the member in the custom membership provider. This acts as the link between Umbraco data and /// the custom membership provider data. This gives us the ability to eventually have custom membership properties but still use /// a custom memberhip provider. If there is no 'Member' member type, then we will simply just create the membership provider member /// with no link to our data. - /// - /// If this is successful, it will go and re-fetch the IMember from the db because it will now have an ID because the Umbraco provider + /// + /// If this is successful, it will go and re-fetch the IMember from the db because it will now have an ID because the Umbraco provider /// uses the umbraco data store - then of course we need to re-map it to the saved property values. /// private MembershipUser CreateWithMembershipProvider(MemberSave contentItem, out MembershipCreateStatus status) @@ -621,7 +634,7 @@ namespace Umbraco.Web.Editors break; case MembershipScenario.CustomProviderWithUmbracoLink: //We are using a custom membership provider, we'll create an empty IMember first to get the unique id to use - // as the provider user key. + // as the provider user key. //create it - this persisted item has already been set in the MemberBinder based on the 'Member' member type: Services.MemberService.Save(contentItem.PersistedContent); @@ -733,7 +746,7 @@ namespace Umbraco.Web.Editors /// /// /// - /// + /// [HttpPost] public HttpResponseMessage DeleteByKey(Guid key) { @@ -774,5 +787,38 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK); } + + /// + /// Exports member data based on their unique Id + /// + /// The unique member identifier + /// + [HttpGet] + public HttpResponseMessage ExportMemberData(Guid key) + { + var currentUser = Security.CurrentUser; + + var httpResponseMessage = Request.CreateResponse(); + if (currentUser.HasAccessToSensitiveData() == false) + { + httpResponseMessage.StatusCode = HttpStatusCode.Forbidden; + return httpResponseMessage; + } + + var member = ((MemberService)Services.MemberService).ExportMember(key); + + var fileName = $"{member.Name}_{member.Email}.txt"; + + httpResponseMessage.Content = new ObjectContent(member, new JsonMediaTypeFormatter { Indent = true }); + httpResponseMessage.Content.Headers.Add("x-filename", fileName); + httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); + httpResponseMessage.Content.Headers.ContentDisposition.FileName = fileName; + httpResponseMessage.StatusCode = HttpStatusCode.OK; + + return httpResponseMessage; + } } + + } diff --git a/src/Umbraco.Web/Editors/MemberGroupController.cs b/src/Umbraco.Web/Editors/MemberGroupController.cs new file mode 100644 index 0000000000..4ea823b25a --- /dev/null +++ b/src/Umbraco.Web/Editors/MemberGroupController.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Web.Http; +using System.Web.Security; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Editors +{ + /// + /// An API controller used for dealing with member groups + /// + [PluginController("UmbracoApi")] + [UmbracoTreeAuthorize(Constants.Trees.MemberGroups)] + public class MemberGroupController : UmbracoAuthorizedJsonController + { + private readonly MembershipProvider _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + + public MemberGroupDisplay GetById(int id) + { + var memberGroup = Services.MemberGroupService.GetById(id); + if (memberGroup == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var dto = Mapper.Map(memberGroup); + return dto; + } + + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var memberGroup = Services.MemberGroupService.GetById(id); + if (memberGroup == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + Services.MemberGroupService.Delete(memberGroup); + return Request.CreateResponse(HttpStatusCode.OK); + } + + public IEnumerable GetAllGroups() + { + if (_provider.IsUmbracoMembershipProvider()) + { + return Services.MemberGroupService.GetAll() + .Select(Mapper.Map); + } + + return Enumerable.Empty(); + } + + public MemberGroupDisplay GetEmpty() + { + var item = new MemberGroup(); + return Mapper.Map(item); + } + + public MemberGroupDisplay PostSave(MemberGroupSave saveModel) + { + var service = Services.MemberGroupService; + + var id = int.Parse(saveModel.Id.ToString()); + var memberGroup = id > 0 ? service.GetById(id) : new MemberGroup(); + if (memberGroup == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + memberGroup.Name = saveModel.Name; + service.Save(memberGroup); + + var display = Mapper.Map(memberGroup); + display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/memberGroupSaved"), string.Empty); + return display; + } + } +} diff --git a/src/Umbraco.Web/Editors/MemberTypeController.cs b/src/Umbraco.Web/Editors/MemberTypeController.cs index 1d8766f253..da2a5de727 100644 --- a/src/Umbraco.Web/Editors/MemberTypeController.cs +++ b/src/Umbraco.Web/Editors/MemberTypeController.cs @@ -1,52 +1,30 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Net; +using System.Net.Http; +using System.Web.Http; using System.Web.Security; using AutoMapper; -using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Services; using Umbraco.Core.Security; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; -using System.Web.Http; -using System.Net; -using Umbraco.Core.PropertyEditors; -using System; -using System.Net.Http; -using ContentType = System.Net.Mime.ContentType; namespace Umbraco.Web.Editors { - + /// - /// An API controller used for dealing with content types + /// An API controller used for dealing with member types /// [PluginController("UmbracoApi")] - [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] - public class MemberTypeController : ContentTypeControllerBase + [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] + public class MemberTypeController : ContentTypeControllerBase { - /// - /// Constructor - /// - public MemberTypeController() - : this(UmbracoContext.Current) - { - _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - } - - /// - /// Constructor - /// - /// - public MemberTypeController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - } - - private readonly MembershipProvider _provider; + private readonly MembershipProvider _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); public MemberTypeDisplay GetById(int id) { @@ -115,7 +93,7 @@ namespace Umbraco.Web.Editors return dto; } - + /// /// Returns all member types /// @@ -124,16 +102,54 @@ namespace Umbraco.Web.Editors if (_provider.IsUmbracoMembershipProvider()) { return Services.MemberTypeService.GetAll() - .Select(Mapper.Map); + .Select(Mapper.Map); } return Enumerable.Empty(); } public MemberTypeDisplay PostSave(MemberTypeSave contentTypeSave) { - var savedCt = PerformPostSave( + //get the persisted member type + var ctId = Convert.ToInt32(contentTypeSave.Id); + var ct = ctId > 0 ? Services.MemberTypeService.Get(ctId) : null; + + if (UmbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false) + { + //We need to validate if any properties on the contentTypeSave have had their IsSensitiveValue changed, + //and if so, we need to check if the current user has access to sensitive values. If not, we have to return an error + var props = contentTypeSave.Groups.SelectMany(x => x.Properties); + if (ct != null) + { + foreach (var prop in props) + { + // Id 0 means the property was just added, no need to look it up + if (prop.Id == 0) + continue; + + var foundOnContentType = ct.PropertyTypes.FirstOrDefault(x => x.Id == prop.Id); + if (foundOnContentType == null) + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, "No property type with id " + prop.Id + " found on the content type")); + if (ct.IsSensitiveProperty(foundOnContentType.Alias) && prop.IsSensitiveData == false) + { + //if these don't match, then we cannot continue, this user is not allowed to change this value + throw new HttpResponseException(HttpStatusCode.Forbidden); + } + } + } + else + { + //if it is new, then we can just verify if any property has sensitive data turned on which is not allowed + if (props.Any(prop => prop.IsSensitiveData)) + { + throw new HttpResponseException(HttpStatusCode.Forbidden); + } + } + } + + + var savedCt = PerformPostSave( contentTypeSave: contentTypeSave, - getContentType: i => Services.MemberTypeService.Get(i), + getContentType: i => ct, saveContentType: type => Services.MemberTypeService.Save(type)); var display = Mapper.Map(savedCt); @@ -145,4 +161,4 @@ namespace Umbraco.Web.Editors return display; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 6b5f460171..1564ed47cf 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -8,7 +8,6 @@ using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; using System.Xml; -using umbraco; using umbraco.cms.businesslogic.packager; using umbraco.cms.businesslogic.packager.repositories; using umbraco.cms.presentation.Trees; @@ -21,6 +20,8 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Packaging.Models; using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Web.Composing; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; @@ -70,7 +71,7 @@ namespace Umbraco.Web.Editors .Where(x => x.Data.Name == pack.Data.Name && x.Data.Id != pack.Data.Id)) { //remove from teh xml - installed.Delete(Security.GetUserId()); + installed.Delete(Security.GetUserId().ResultOr(0)); } } catch (Exception e) @@ -96,7 +97,7 @@ namespace Umbraco.Web.Editors var removedMacros = new List(); var removedContentTypes = new List(); var removedDictionaryItems = new List(); - var removedDataTypes = new List(); + var removedDataTypes = new List(); var removedFiles = new List(); //Uninstall templates @@ -108,7 +109,7 @@ namespace Umbraco.Web.Editors if (found != null) { removedTemplates.Add(found); - ApplicationContext.Services.FileService.DeleteTemplate(found.Alias, Security.GetUserId()); + Services.FileService.DeleteTemplate(found.Alias, Security.GetUserId().ResultOr(0)); } pack.Data.Templates.Remove(nId.ToString()); } @@ -134,7 +135,7 @@ namespace Umbraco.Web.Editors { int nId; if (int.TryParse(item, out nId) == false) continue; - var contentType = contentTypeService.GetContentType(nId); + var contentType = contentTypeService.Get(nId); if (contentType == null) continue; contentTypes.Add(contentType); pack.Data.Documenttypes.Remove(nId.ToString(CultureInfo.InvariantCulture)); @@ -172,7 +173,7 @@ namespace Umbraco.Web.Editors { int nId; if (int.TryParse(item, out nId) == false) continue; - var dtd = Services.DataTypeService.GetDataTypeDefinitionById(nId); + var dtd = Services.DataTypeService.GetDataType(nId); if (dtd != null) { removedDataTypes.Add(dtd); @@ -193,7 +194,7 @@ namespace Umbraco.Web.Editors var actionsXml = new XmlDocument(); actionsXml.LoadXml("" + pack.Data.Actions + ""); - LogHelper.Debug("executing undo actions: {0}", () => actionsXml.OuterXml); + Logger.Debug(() => $"executing undo actions: {actionsXml.OuterXml}"); foreach (XmlNode n in actionsXml.DocumentElement.SelectNodes("//Action")) { @@ -204,13 +205,13 @@ namespace Umbraco.Web.Editors } catch (Exception ex) { - LogHelper.Error("An error occurred running undo actions", ex); + Logger.Error("An error occurred running undo actions", ex); } } } catch (Exception ex) { - LogHelper.Error("An error occurred running undo actions", ex); + Logger.Error("An error occurred running undo actions", ex); } } @@ -230,11 +231,12 @@ namespace Umbraco.Web.Editors if (File.Exists(filePath)) File.Delete(filePath); + } pack.Data.Files.Remove(file); } pack.Save(); - pack.Delete(Security.GetUserId()); + pack.Delete(Security.GetUserId().ResultOr(0)); // create a summary of what was actually removed, for PackagingService.UninstalledPackage var summary = new UninstallationSummary @@ -252,13 +254,7 @@ namespace Umbraco.Web.Editors // trigger the UninstalledPackage event PackagingService.OnUninstalledPackage(new UninstallPackageEventArgs(summary, false)); - //TODO: Legacy - probably not needed - if (refreshCache) - { - library.RefreshContent(); - } TreeDefinitionCollection.Instance.ReRegisterTrees(); - global::umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers(); } /// @@ -337,7 +333,6 @@ namespace Umbraco.Web.Editors model.ConflictingStyleSheetNames = ins.ConflictingStyleSheetNames; model.ConflictingTemplateAliases = ins.ConflictingTemplateAliases; model.ContainsBinaryFileErrors = ins.ContainsBinaryFileErrors; - model.ContainsLegacyPropertyEditors = ins.ContainsLegacyPropertyEditors; model.ContainsMacroConflict = ins.ContainsMacroConflict; model.ContainsStyleSheetConflicts = ins.ContainsStyleSheeConflicts; model.ContainsTemplateConflicts = ins.ContainsTemplateConflicts; @@ -479,7 +474,7 @@ namespace Umbraco.Web.Editors string path = Path.Combine("packages", packageGuid + ".umb"); if (File.Exists(IOHelper.MapPath(Path.Combine(SystemDirectories.Data, path))) == false) { - path = Services.PackagingService.FetchPackageFile(Guid.Parse(packageGuid), UmbracoVersion.Current, Security.GetUserId()); + path = Services.PackagingService.FetchPackageFile(Guid.Parse(packageGuid), UmbracoVersion.Current, Security.GetUserId().ResultOr(0)); } var model = new LocalPackageInstallModel @@ -545,8 +540,8 @@ namespace Umbraco.Web.Editors ins.InstallFiles(model.Id, IOHelper.MapPath(model.TemporaryDirectoryPath)); //set a restarting marker and reset the app pool - ApplicationContext.RestartApplicationPool(Request.TryGetHttpContext().Result); - + Current.RestartAppPool(Request.TryGetHttpContext().Result); + model.IsRestarting = true; return model; @@ -593,14 +588,12 @@ namespace Umbraco.Web.Editors ins.LoadConfig(IOHelper.MapPath(model.TemporaryDirectoryPath)); ins.InstallCleanUp(model.Id, IOHelper.MapPath(model.TemporaryDirectoryPath)); - var clientDependencyConfig = new Umbraco.Core.Configuration.ClientDependencyConfiguration(ApplicationContext.ProfilingLogger.Logger); + var clientDependencyConfig = new Umbraco.Core.Configuration.ClientDependencyConfiguration(Logger); var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber(); //clear the tree cache - we'll do this here even though the browser will reload, but just in case it doesn't can't hurt. //these bits are super old, but cant find another way to do this currently global::umbraco.cms.presentation.Trees.TreeDefinitionCollection.Instance.ReRegisterTrees(); - global::umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers(); - var redirectUrl = ""; if (ins.Control.IsNullOrWhiteSpace() == false) diff --git a/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs index 53e25c146a..03e982c7cd 100644 --- a/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs +++ b/src/Umbraco.Web/Editors/ParameterSwapControllerActionSelector.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Linq; using System.Net.Http; @@ -20,7 +20,7 @@ namespace Umbraco.Web.Editors /// /// As an example, lets say we have 2 methods: GetChildren(int id) and GetChildren(Guid id), by default Web Api won't allow this since /// it won't know what to select, but if this Tuple is passed in new Tuple{string, string}("GetChildren", "id") - /// + /// /// This supports POST values too however only for JSON values /// internal class ParameterSwapControllerActionSelector : ApiControllerActionSelector @@ -110,7 +110,7 @@ namespace Umbraco.Web.Editors } if (requestParam != null) - { + { foreach (var paramType in paramTypes) { //check if this is IEnumerable and if so this will get it's type @@ -160,4 +160,4 @@ namespace Umbraco.Web.Editors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs index 7ddf361d23..08f010ecd4 100644 --- a/src/Umbraco.Web/Editors/PasswordChanger.cs +++ b/src/Umbraco.Web/Editors/PasswordChanger.cs @@ -1,12 +1,9 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Threading.Tasks; using System.Web; using System.Web.Http.ModelBinding; using System.Web.Security; -using Microsoft.AspNet.Identity; -using umbraco.cms.businesslogic.packager; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -14,7 +11,6 @@ using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Models; -using Umbraco.Web.Security; using IUser = Umbraco.Core.Models.Membership.IUser; namespace Umbraco.Web.Editors @@ -43,19 +39,18 @@ namespace Umbraco.Web.Editors public async Task> ChangePasswordWithIdentityAsync( IUser currentUser, IUser savingUser, - ChangingPasswordModel passwordModel, + ChangingPasswordModel passwordModel, BackOfficeUserManager userMgr) { - if (passwordModel == null) throw new ArgumentNullException("passwordModel"); - if (userMgr == null) throw new ArgumentNullException("userMgr"); + if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel)); + if (userMgr == null) throw new ArgumentNullException(nameof(userMgr)); //check if this identity implementation is powered by an underlying membership provider (it will be in most cases) var membershipPasswordHasher = userMgr.PasswordHasher as IMembershipProviderPasswordHasher; //check if this identity implementation is powered by an IUserAwarePasswordHasher (it will be by default in 7.7+ but not for upgrades) - var userAwarePasswordHasher = userMgr.PasswordHasher as IUserAwarePasswordHasher; - if (membershipPasswordHasher != null && userAwarePasswordHasher == null) + if (membershipPasswordHasher != null && !(userMgr.PasswordHasher is IUserAwarePasswordHasher)) { //if this isn't using an IUserAwarePasswordHasher, then fallback to the old way if (membershipPasswordHasher.MembershipProvider.RequiresQuestionAndAnswer) @@ -93,15 +88,15 @@ namespace Umbraco.Web.Editors ? userMgr.GeneratePassword() : passwordModel.NewPassword; - var resetResult = await userMgr.ResetPasswordAsync(savingUser.Id, resetToken, newPass); + var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id, resetToken, newPass); if (resetResult.Succeeded == false) { var errors = string.Join(". ", resetResult.Errors); - _logger.Warn(string.Format("Could not reset user password {0}", errors)); + _logger.Warn($"Could not reset user password {errors}"); return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, errors: " + errors, new[] { "resetPassword" }) }); } - + return Attempt.Succeed(new PasswordChangedModel()); } @@ -110,7 +105,7 @@ namespace Umbraco.Web.Editors if (passwordModel.NewPassword.IsNullOrWhiteSpace()) { return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) }); - } + } //we cannot arbitrarily change the password without knowing the old one and no old password was supplied - need to return an error //TODO: What if the current user is admin? We should allow manually changing then? @@ -127,10 +122,10 @@ namespace Umbraco.Web.Editors if (changeResult.Succeeded == false) { var errors = string.Join(". ", changeResult.Errors); - _logger.Warn(string.Format("Could not change user password {0}", errors)); + _logger.Warn($"Could not change user password {errors}"); return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, errors: " + errors, new[] { "oldPassword" }) }); } - return Attempt.Succeed(new PasswordChangedModel()); + return Attempt.Succeed(new PasswordChangedModel()); } //We shouldn't really get here @@ -143,13 +138,13 @@ namespace Umbraco.Web.Editors /// The username of the user having their password changed /// /// - /// + /// public Attempt ChangePasswordWithMembershipProvider(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) { - // YES! It is completely insane how many options you have to take into account based on the membership provider. yikes! + // YES! It is completely insane how many options you have to take into account based on the membership provider. yikes! - if (passwordModel == null) throw new ArgumentNullException("passwordModel"); - if (membershipProvider == null) throw new ArgumentNullException("membershipProvider"); + if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel)); + if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider)); BackOfficeUserManager backofficeUserManager = null; var userId = -1; @@ -166,6 +161,7 @@ namespace Umbraco.Web.Editors } //Are we resetting the password?? + //TODO: I don't think this is required anymore since from 7.7 we no longer display the reset password checkbox since that didn't make sense. if (passwordModel.Reset.HasValue && passwordModel.Reset.Value) { var canReset = membershipProvider.CanResetPassword(_userService); @@ -192,7 +188,7 @@ namespace Umbraco.Web.Editors } catch (Exception ex) { - _logger.WarnWithException("Could not reset member password", ex); + _logger.Warn(ex, "Could not reset member password"); return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, error: " + ex.Message + " (see log for full details)", new[] { "resetPassword" }) }); } } @@ -205,8 +201,7 @@ namespace Umbraco.Web.Editors } //This is an edge case and is only necessary for backwards compatibility: - var umbracoBaseProvider = membershipProvider as MembershipProviderBase; - if (umbracoBaseProvider != null && umbracoBaseProvider.AllowManuallyChangingPassword) + if (membershipProvider is MembershipProviderBase umbracoBaseProvider && umbracoBaseProvider.AllowManuallyChangingPassword) { //this provider allows manually changing the password without the old password, so we can just do it try @@ -218,7 +213,7 @@ namespace Umbraco.Web.Editors } catch (Exception ex) { - _logger.WarnWithException("Could not change member password", ex); + _logger.Warn(ex, "Could not change member password"); return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) }); } } @@ -247,7 +242,7 @@ namespace Umbraco.Web.Editors } catch (Exception ex) { - _logger.WarnWithException("Could not change member password", ex); + _logger.Warn(ex, "Could not change member password"); return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) }); } } @@ -279,16 +274,16 @@ namespace Umbraco.Web.Editors } catch (Exception ex1) { - _logger.WarnWithException("Could not change member password", ex1); + _logger.Warn(ex1, "Could not change member password"); return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex1.Message + " (see log for full details)", new[] { "value" }) }); } } catch (Exception ex2) { - _logger.WarnWithException("Could not retrieve member password", ex2); + _logger.Warn(ex2, "Could not retrieve member password"); return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) }); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs new file mode 100644 index 0000000000..9c0d46cedb --- /dev/null +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -0,0 +1,50 @@ +using System; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Web.Features; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web.Editors +{ + [DisableBrowserCache] + public class PreviewController : Controller + { + private readonly UmbracoFeatures _features; + private readonly IGlobalSettings _globalSettings; + + public PreviewController(UmbracoFeatures features, IGlobalSettings globalSettings) + { + _features = features; + _globalSettings = globalSettings; + } + + [UmbracoAuthorize(redirectToUmbracoLogin: true)] + public ActionResult Index() + { + var model = new BackOfficePreview + { + DisableDevicePreview = _features.Disabled.DisableDevicePreview, + PreviewExtendedHeaderView = _features.Enabled.PreviewExtendedView + }; + + if (model.PreviewExtendedHeaderView.IsNullOrWhiteSpace() == false) + { + var viewEngineResult = ViewEngines.Engines.FindPartialView(ControllerContext, model.PreviewExtendedHeaderView); + if (viewEngineResult.View == null) + { + throw new InvalidOperationException("Could not find the view " + model.PreviewExtendedHeaderView + ", the following locations were searched: " + Environment.NewLine + string.Join(Environment.NewLine, viewEngineResult.SearchedLocations)); + } + } + + return View(_globalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + "Index.cshtml", model); + } + + public ActionResult Editors(string editor) + { + if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException(nameof(editor)); + return View(_globalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + editor.Replace(".html", string.Empty) + ".cshtml"); + } + } +} diff --git a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs b/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs index 9fb7a63c9f..d63eaa2b48 100644 --- a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs @@ -3,8 +3,8 @@ using System.Web.Http; using System.Xml; using System.Collections.Generic; using System.Linq; +using System.Security; using AutoMapper; -using umbraco.businesslogic.Exceptions; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -18,6 +18,12 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class RedirectUrlManagementController : UmbracoAuthorizedApiController { + private readonly ILogger _logger; + + public RedirectUrlManagementController(ILogger logger) + { + _logger = logger; + } /// /// Returns true/false of whether redirect tracking is enabled or not @@ -39,7 +45,7 @@ namespace Umbraco.Web.Editors var redirectUrlService = Services.RedirectUrlService; long resultCount; - var redirects = string.IsNullOrWhiteSpace(searchTerm) + var redirects = string.IsNullOrWhiteSpace(searchTerm) ? redirectUrlService.GetAllRedirectUrls(page, pageSize, out resultCount) : redirectUrlService.SearchRedirectUrls(searchTerm, page, pageSize, out resultCount); @@ -53,7 +59,7 @@ namespace Umbraco.Web.Editors searchResult.TotalCount = resultCount; searchResult.CurrentPage = page; searchResult.PageCount = ((int)resultCount + pageSize - 1) / pageSize; - + return searchResult; } @@ -73,8 +79,8 @@ namespace Umbraco.Web.Editors if (userIsAdmin == false) { var errorMessage = "User is not a member of the administrators group and so is not allowed to toggle the URL tracker"; - LogHelper.Debug(errorMessage); - throw new UserAuthorizationException(errorMessage); + _logger.Debug(errorMessage); + throw new SecurityException(errorMessage); } var httpContext = TryGetHttpContext(); @@ -84,20 +90,20 @@ namespace Umbraco.Web.Editors var action = disable ? "disable" : "enable"; if (File.Exists(configFilePath) == false) - return BadRequest(string.Format("Couldn't {0} URL Tracker, the umbracoSettings.config file does not exist.", action)); + return BadRequest($"Couldn't {action} URL Tracker, the umbracoSettings.config file does not exist."); var umbracoConfig = new XmlDocument { PreserveWhitespace = true }; umbracoConfig.Load(configFilePath); var webRoutingElement = umbracoConfig.SelectSingleNode("//web.routing") as XmlElement; if (webRoutingElement == null) - return BadRequest(string.Format("Couldn't {0} URL Tracker, the web.routing element was not found in umbracoSettings.config.", action)); + return BadRequest($"Couldn't {action} URL Tracker, the web.routing element was not found in umbracoSettings.config."); // note: this adds the attribute if it does not exist webRoutingElement.SetAttribute("disableRedirectUrlTracking", disable.ToString().ToLowerInvariant()); umbracoConfig.Save(configFilePath); - return Ok(string.Format("URL tracker is now {0}d", action)); + return Ok($"URL tracker is now {action}d."); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/RelationController.cs b/src/Umbraco.Web/Editors/RelationController.cs index 099d5b5c13..c287e8a429 100644 --- a/src/Umbraco.Web/Editors/RelationController.cs +++ b/src/Umbraco.Web/Editors/RelationController.cs @@ -13,59 +13,49 @@ using Relation = Umbraco.Web.Models.ContentEditing.Relation; namespace Umbraco.Web.Editors { - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] public class RelationController : UmbracoAuthorizedJsonController - { - public RelationController() - : this(UmbracoContext.Current) - { - } - - public RelationController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - + { public Relation GetById(int id) - { - return Mapper.Map(Services.RelationService.GetById(id)); - } + { + return Mapper.Map(Services.RelationService.GetById(id)); + } //[EnsureUserPermissionForContent("childId")] public IEnumerable GetByChildId(int childId, string relationTypeAlias = "") - { - var relations = Services.RelationService.GetByChildId(childId).ToArray(); + { + var relations = Services.RelationService.GetByChildId(childId).ToArray(); - if (relations.Any() == false) - { - return Enumerable.Empty(); - } + if (relations.Any() == false) + { + return Enumerable.Empty(); + } - if (string.IsNullOrWhiteSpace(relationTypeAlias) == false) - { - return - Mapper.Map, IEnumerable>( - relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))); - } + if (string.IsNullOrWhiteSpace(relationTypeAlias) == false) + { + return + Mapper.Map, IEnumerable>( + relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))); + } - return Mapper.Map, IEnumerable>(relations); - } + return Mapper.Map, IEnumerable>(relations); + } - [HttpDelete] - [HttpPost] - public HttpResponseMessage DeleteById(int id) - { - var foundRelation = Services.RelationService.GetById(id); + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundRelation = Services.RelationService.GetById(id); - if (foundRelation == null) - { - return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No relation found with the specified id"); - } + if (foundRelation == null) + { + return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No relation found with the specified id"); + } - Services.RelationService.Delete(foundRelation); + Services.RelationService.Delete(foundRelation); - return Request.CreateResponse(HttpStatusCode.OK); - } - } -} \ No newline at end of file + return Request.CreateResponse(HttpStatusCode.OK); + } + } +} diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index 6c94b410f9..76821cfc33 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -3,6 +3,7 @@ using AutoMapper; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using System.Linq; +using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Web.Trees; using Section = Umbraco.Web.Models.ContentEditing.Section; @@ -18,29 +19,30 @@ namespace Umbraco.Web.Editors public IEnumerable
GetSections() { - var sections = Services.SectionService.GetAllowedSections(Security.GetUserId()); + var sections = Services.SectionService.GetAllowedSections(Security.GetUserId().ResultOr(0)); var sectionModels = sections.Select(Mapper.Map).ToArray(); //Check if there are empty dashboards or dashboards that will end up empty based on the current user's access //and add the meta data about them var dashboardHelper = new DashboardHelper(Services.SectionService); - //this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that - //since tree's by nature are controllers and require request contextual data. - var appTreeController = new ApplicationTreeController - { - ControllerContext = ControllerContext - }; + + // this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that + // since tree's by nature are controllers and require request contextual data - and then we have to + // remember to inject properties - nasty indeed + var appTreeController = new ApplicationTreeController(); + Current.Container.InjectProperties(appTreeController); + appTreeController.ControllerContext = ControllerContext; + var dashboards = dashboardHelper.GetDashboards(Security.CurrentUser); //now we can add metadata for each section so that the UI knows if there's actually anything at all to render for //a dashboard for a given section, then the UI can deal with it accordingly (i.e. redirect to the first tree) foreach (var section in sectionModels) { var hasDashboards = false; - IEnumerable> dashboardsForSection; - if (dashboards.TryGetValue(section.Alias, out dashboardsForSection)) + if (dashboards.TryGetValue(section.Alias, out var dashboardsForSection)) { - if (dashboardsForSection.Any()) + if (dashboardsForSection.Any()) hasDashboards = true; } @@ -49,14 +51,14 @@ namespace Umbraco.Web.Editors //get the first tree in the section and get it's root node route path var sectionTrees = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; section.RoutePath = sectionTrees.IsContainer == false || sectionTrees.Children.Count == 0 - ? sectionTrees.RoutePath + ? sectionTrees.RoutePath : sectionTrees.Children[0].RoutePath; } } return sectionModels; } - + /// /// Returns all the sections that the user has access to /// @@ -72,4 +74,4 @@ namespace Umbraco.Web.Editors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/StylesheetController.cs b/src/Umbraco.Web/Editors/StylesheetController.cs index 535351a14f..83832c2544 100644 --- a/src/Umbraco.Web/Editors/StylesheetController.cs +++ b/src/Umbraco.Web/Editors/StylesheetController.cs @@ -1,40 +1,27 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; -using System.Web; -using System.Web.Http; -using System.Web.Services.Description; -using Newtonsoft.Json.Linq; -using umbraco.cms.businesslogic.web; using Umbraco.Core; -using Umbraco.Core.IO; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Editors { /// /// The API controller used for retrieving available stylesheets /// - [PluginController("UmbracoApi")] + [PluginController("UmbracoApi")] public class StylesheetController : UmbracoAuthorizedJsonController { public IEnumerable GetAll() { return Services.FileService.GetStylesheets() - .Select(x => + .Select(x => new Stylesheet() { Name = x.Alias, Path = x.VirtualPath }); } - + public IEnumerable GetRulesByName(string name) { var css = Services.FileService.GetStylesheetByName(name.EnsureEndsWith(".css")); diff --git a/src/Umbraco.Web/Editors/TemplateController.cs b/src/Umbraco.Web/Editors/TemplateController.cs index 78855c95c2..d1db42c8fb 100644 --- a/src/Umbraco.Web/Editors/TemplateController.cs +++ b/src/Umbraco.Web/Editors/TemplateController.cs @@ -49,7 +49,7 @@ namespace Umbraco.Web.Editors var template = Services.FileService.GetTemplate(id); if (template == null) throw new HttpResponseException(HttpStatusCode.NotFound); - + return Mapper.Map(template); } @@ -87,7 +87,7 @@ namespace Umbraco.Web.Editors var content = ViewHelper.GetDefaultFileContent( layoutPageAlias: dt.MasterTemplateAlias ); var scaffold = Mapper.Map(dt); - + scaffold.Content = content + "\r\n\r\n@* the fun starts here *@\r\n\r\n"; return scaffold; } @@ -105,7 +105,7 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); } - + if (display.Id > 0) { // update @@ -138,11 +138,11 @@ namespace Umbraco.Web.Editors { //template ID to find var templateIdInPath = "," + display.Id + ","; - + if (string.IsNullOrEmpty(childTemplate.Path)) { continue; - } + } //Find position in current comma seperate string path (so we get the correct children path) var positionInPath = childTemplate.Path.IndexOf(templateIdInPath) + templateIdInPath.Length; diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index d7351b0e97..d51a00b3a2 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Umbraco.Core.Models; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using System; using System.Diagnostics; -using Umbraco.Web.Dynamics; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Models.TemplateQuery; using Umbraco.Core.Services; @@ -19,13 +18,6 @@ namespace Umbraco.Web.Editors [JsonCamelCaseFormatter] public class TemplateQueryController : UmbracoAuthorizedJsonController { - public TemplateQueryController() - { } - - public TemplateQueryController(UmbracoContext umbracoContext) - : base(umbracoContext) - { } - private IEnumerable Terms { get @@ -67,7 +59,7 @@ namespace Umbraco.Web.Editors public QueryResultModel PostTemplateQuery(QueryModel model) { - var umbraco = new UmbracoHelper(UmbracoContext); + var umbraco = new UmbracoHelper(UmbracoContext, Services, ApplicationCache); var queryResult = new QueryResultModel(); @@ -75,11 +67,12 @@ namespace Umbraco.Web.Editors var indention = Environment.NewLine + "\t\t\t\t\t\t"; sb.Append("Model.Content.Site()"); + var timer = new Stopwatch(); timer.Start(); - var currentPage = umbraco.TypedContentAtRoot().FirstOrDefault(); + var currentPage = umbraco.ContentAtRoot().FirstOrDefault(); timer.Stop(); var pointerNode = currentPage; @@ -87,7 +80,7 @@ namespace Umbraco.Web.Editors // adjust the "FROM" if (model != null && model.Source != null && model.Source.Id > 0) { - var targetNode = umbraco.TypedContent(model.Source.Id); + var targetNode = umbraco.Content(model.Source.Id); if (targetNode != null) { @@ -166,13 +159,15 @@ namespace Umbraco.Web.Editors timer.Start(); //trial-run the tokenized clause to time the execution - //for review - this uses a tonized query rather then the normal linq query. - contents = contents.AsQueryable().Where(tokenizedClause, model.Filters.Select(this.GetConstraintValue).ToArray()); + //for review - this uses a tonized query rather then the normal linq query. + // fixme - that cannot work anymore now that we have killed dynamic support + //contents = contents.AsQueryable().Where(clause, model.Filters.Select(this.GetConstraintValue).ToArray()); + throw new NotImplementedException(); + contents = contents.Where(x => x.IsVisible()); timer.Stop(); - //the query to output to the editor sb.Append(indention); sb.Append(".Where(x => x.IsVisible())"); @@ -297,12 +292,11 @@ namespace Umbraco.Web.Editors /// public IEnumerable GetContentTypes() { - var contentTypes = - ApplicationContext.Services.ContentTypeService.GetAllContentTypes() - .Select(x => new ContentTypeModel() { Alias = x.Alias, Name = Services.TextService.Localize("template/contentOfType", tokens: new string[] { x.Name }) }) - .OrderBy(x => x.Name).ToList(); + var contentTypes = Services.ContentTypeService.GetAll() + .Select(x => new ContentTypeModel { Alias = x.Alias, Name = Services.TextService.Localize("template/contentOfType", tokens: new string[] { x.Name } ) }) + .OrderBy(x => x.Name).ToList(); - contentTypes.Insert(0, new ContentTypeModel() { Alias = string.Empty, Name = Services.TextService.Localize("template/allContent") }); + contentTypes.Insert(0, new ContentTypeModel { Alias = string.Empty, Name = Services.TextService.Localize("template/allContent") }); return contentTypes; } @@ -323,4 +317,4 @@ namespace Umbraco.Web.Editors return Terms; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/TourController.cs b/src/Umbraco.Web/Editors/TourController.cs index da16659cfe..97208c88e5 100644 --- a/src/Umbraco.Web/Editors/TourController.cs +++ b/src/Umbraco.Web/Editors/TourController.cs @@ -2,11 +2,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using LightInject; using Newtonsoft.Json; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Web.Models; using Umbraco.Web.Mvc; +using Umbraco.Web.Tour; namespace Umbraco.Web.Editors { @@ -20,7 +23,7 @@ namespace Umbraco.Web.Editors if (UmbracoConfig.For.UmbracoSettings().BackOffice.Tours.EnableTours == false) return result; - var filters = TourFilterResolver.Current.Filters.ToList(); + var filters = Current.Container.GetInstance().ToList(); // fixme inject //get all filters that will be applied to all tour aliases var aliasOnlyFilters = filters.Where(x => x.PluginName == null && x.TourFileName == null).ToList(); @@ -70,14 +73,20 @@ namespace Umbraco.Web.Editors //Checking to see if the user has access to the required tour sections, else we remove the tour foreach (var backOfficeTourFile in result) { - foreach (var tour in backOfficeTourFile.Tours) + if (backOfficeTourFile.Tours != null) { - foreach (var toursRequiredSection in tour.RequiredSections) + foreach (var tour in backOfficeTourFile.Tours) { - if (allowedSections.Contains(toursRequiredSection) == false) + if (tour.RequiredSections != null) { - toursToBeRemoved.Add(backOfficeTourFile); - break; + foreach (var toursRequiredSection in tour.RequiredSections) + { + if (allowedSections.Contains(toursRequiredSection) == false) + { + toursToBeRemoved.Add(backOfficeTourFile); + break; + } + } } } } diff --git a/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs b/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs index 8c0f793d03..184511cbf0 100644 --- a/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs +++ b/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs @@ -1,7 +1,4 @@ -using System.Web.Http.Controllers; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Security; -using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Editors @@ -10,23 +7,11 @@ namespace Umbraco.Web.Editors /// An abstract API controller that only supports JSON and all requests must contain the correct csrf header ///
/// - /// Inheriting from this controller means that ALL of your methods are JSON methods that are called by Angular, + /// Inheriting from this controller means that ALL of your methods are JSON methods that are called by Angular, /// methods that are not called by Angular or don't contain a valid csrf header will NOT work. /// [ValidateAngularAntiForgeryToken] - [AngularJsonOnlyConfiguration] + [AngularJsonOnlyConfiguration] public abstract class UmbracoAuthorizedJsonController : UmbracoAuthorizedApiController - { - protected UmbracoAuthorizedJsonController() - { - } - - protected UmbracoAuthorizedJsonController(UmbracoContext umbracoContext) : base(umbracoContext) - { - } - - protected UmbracoAuthorizedJsonController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper, BackOfficeUserManager backOfficeUserManager) : base(umbracoContext, umbracoHelper, backOfficeUserManager) - { - } - } -} \ No newline at end of file + { } +} diff --git a/src/Umbraco.Web/Editors/UpdateCheckController.cs b/src/Umbraco.Web/Editors/UpdateCheckController.cs index e42ef0ffb9..ceab9ff240 100644 --- a/src/Umbraco.Web/Editors/UpdateCheckController.cs +++ b/src/Umbraco.Web/Editors/UpdateCheckController.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Editors public UpgradeCheckResponse GetCheck() { var updChkCookie = Request.Headers.GetCookies("UMB_UPDCHK").FirstOrDefault(); - var updateCheckCookie = updChkCookie != null ? updChkCookie["UMB_UPDCHK"].Value : ""; + var updateCheckCookie = updChkCookie != null ? updChkCookie["UMB_UPDCHK"].Value : ""; if (GlobalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && Security.CurrentUser.IsAdmin()) { try @@ -58,9 +58,9 @@ namespace Umbraco.Web.Editors var cookie = new CookieHeaderValue("UMB_UPDCHK", "1") { Path = "/", - Expires = DateTimeOffset.Now.AddDays(GlobalSettings.VersionCheckPeriod), + Expires = DateTimeOffset.Now.AddDays(UmbracoConfig.For.GlobalSettings().VersionCheckPeriod), HttpOnly = true, - Secure = GlobalSettings.UseSSL + Secure = UmbracoConfig.For.GlobalSettings().UseHttps }; context.Response.Headers.AddCookies(new[] { cookie }); } diff --git a/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs b/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs index 5dcd5572e4..e0e9a8ed55 100644 --- a/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs +++ b/src/Umbraco.Web/Editors/UserEditorAuthorizationHelper.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; @@ -23,7 +23,7 @@ namespace Umbraco.Web.Editors } /// - /// Checks if the current user has access to save the user data + /// Checks if the current user has access to save the user data /// /// The current user trying to save user data /// The user instance being saved (can be null if it's a new user) @@ -41,7 +41,7 @@ namespace Umbraco.Web.Editors // a) A non-admin cannot save an admin if (savingUser != null) - { + { if (savingUser.IsAdmin() && currentIsAdmin == false) return Attempt.Fail("The current user is not an administrator so cannot save another administrator"); } @@ -65,9 +65,9 @@ namespace Umbraco.Web.Editors var pathResult = AuthorizePath(currentUser, changedStartContentIds, changedStartMediaIds); if (pathResult == false) return pathResult; - + // c) an admin can manage any group or section access - + if (currentIsAdmin) return Attempt.Succeed(); @@ -91,16 +91,16 @@ namespace Umbraco.Web.Editors // d) A user cannot assign a group to another user that they do not belong to var currentUserGroups = currentUser.Groups.Select(x => x.Alias).ToArray(); - + foreach (var group in newGroups) { if (currentUserGroups.Contains(group) == false) { return Attempt.Fail("Cannot assign the group " + group + ", the current user is not a member"); } - } + } } - } + } return Attempt.Succeed(); } @@ -145,11 +145,11 @@ namespace Umbraco.Web.Editors var hasAccess = currentUser.HasPathAccess(media, _entityService); if (hasAccess == false) return Attempt.Fail("The current user does not have access to the media path " + media.Path); - } - } + } + } } return Attempt.Succeed(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/UserGroupAuthorizationAttribute.cs b/src/Umbraco.Web/Editors/UserGroupAuthorizationAttribute.cs index a7da90c740..6b0bac0d69 100644 --- a/src/Umbraco.Web/Editors/UserGroupAuthorizationAttribute.cs +++ b/src/Umbraco.Web/Editors/UserGroupAuthorizationAttribute.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using System.Web.Http.Controllers; using Umbraco.Core; +using Umbraco.Core.Composing; namespace Umbraco.Web.Editors { @@ -42,7 +43,7 @@ namespace Umbraco.Web.Editors { var umbCtx = GetUmbracoContext(); var currentUser = umbCtx.Security.CurrentUser; - + var queryString = actionContext.Request.GetQueryNameValuePairs(); var ids = queryString.Where(x => x.Key == _paramName).ToArray(); @@ -51,11 +52,11 @@ namespace Umbraco.Web.Editors var intIds = ids.Select(x => x.Value.TryConvertTo()).Where(x => x.Success).Select(x => x.Result).ToArray(); var authHelper = new UserGroupEditorAuthorizationHelper( - umbCtx.Application.Services.UserService, - umbCtx.Application.Services.ContentService, - umbCtx.Application.Services.MediaService, - umbCtx.Application.Services.EntityService); - return authHelper.AuthorizeGroupAccess(currentUser, intIds); + Current.Services.UserService, + Current.Services.ContentService, + Current.Services.MediaService, + Current.Services.EntityService); + return authHelper.AuthorizeGroupAccess(currentUser, intIds); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/UserGroupEditorAuthorizationHelper.cs b/src/Umbraco.Web/Editors/UserGroupEditorAuthorizationHelper.cs index 6bc39b376d..26dfb9fb1f 100644 --- a/src/Umbraco.Web/Editors/UserGroupEditorAuthorizationHelper.cs +++ b/src/Umbraco.Web/Editors/UserGroupEditorAuthorizationHelper.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; @@ -119,4 +119,4 @@ namespace Umbraco.Web.Editors return Attempt.Succeed(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/UserGroupValidateAttribute.cs b/src/Umbraco.Web/Editors/UserGroupValidateAttribute.cs index 98a64b7568..b3bc59554d 100644 --- a/src/Umbraco.Web/Editors/UserGroupValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/UserGroupValidateAttribute.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Net; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -22,22 +23,19 @@ namespace Umbraco.Web.Editors public UserGroupValidateAttribute(IUserService userService) { - if (_userService == null) throw new ArgumentNullException("userService"); + if (_userService == null) throw new ArgumentNullException(nameof(userService)); _userService = userService; } - private IUserService UserService - { - get { return _userService ?? ApplicationContext.Current.Services.UserService; } - } + private IUserService UserService => _userService ?? Current.Services.UserService; // fixme inject public override void OnActionExecuting(HttpActionContext actionContext) { - var userGroupSave = (UserGroupSave)actionContext.ActionArguments["userGroupSave"]; + var userGroupSave = (UserGroupSave) actionContext.ActionArguments["userGroupSave"]; userGroupSave.Name = userGroupSave.Name.CleanForXss('[', ']', '(', ')', ':'); userGroupSave.Alias = userGroupSave.Alias.CleanForXss('[', ']', '(', ')', ':'); - + //Validate the usergroup exists or create one if required IUserGroup persisted; switch (userGroupSave.Action) @@ -46,7 +44,7 @@ namespace Umbraco.Web.Editors persisted = UserService.GetUserGroupById(Convert.ToInt32(userGroupSave.Id)); if (persisted == null) { - var message = string.Format("User group with id: {0} was not found", userGroupSave.Id); + var message = $"User group with id: {userGroupSave.Id} was not found"; actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); return; } @@ -78,10 +76,7 @@ namespace Umbraco.Web.Editors { //if it is not valid, do not continue and return the model state actionContext.Response = actionContext.Request.CreateValidationErrorResponse(actionContext.ModelState); - return; } - } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/UserGroupsController.cs b/src/Umbraco.Web/Editors/UserGroupsController.cs index 66011e01b2..83f5db08ad 100644 --- a/src/Umbraco.Web/Editors/UserGroupsController.cs +++ b/src/Umbraco.Web/Editors/UserGroupsController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -25,7 +25,7 @@ namespace Umbraco.Web.Editors [UserGroupValidate] public UserGroupDisplay PostSaveUserGroup(UserGroupSave userGroupSave) { - if (userGroupSave == null) throw new ArgumentNullException("userGroupSave"); + if (userGroupSave == null) throw new ArgumentNullException(nameof(userGroupSave)); //authorize that the user has access to save this user group var authHelper = new UserGroupEditorAuthorizationHelper( @@ -52,7 +52,7 @@ namespace Umbraco.Web.Editors //save the group Services.UserService.Save(userGroupSave.PersistedUserGroup, userGroupSave.Users.ToArray()); - + //deal with permissions //remove ones that have been removed @@ -68,7 +68,7 @@ namespace Umbraco.Web.Editors foreach (var assignedPermission in userGroupSave.AssignedPermissions) { Services.UserService.ReplaceUserGroupPermissions( - userGroupSave.PersistedUserGroup.Id, + userGroupSave.PersistedUserGroup.Id, assignedPermission.Value.Select(x => x[0]), assignedPermission.Key); } @@ -81,7 +81,7 @@ namespace Umbraco.Web.Editors /// /// Returns the scaffold for creating a new user group - /// + ///
/// public UserGroupDisplay GetEmptyUserGroup() { @@ -122,7 +122,7 @@ namespace Umbraco.Web.Editors var found = Services.UserService.GetUserGroupById(id); if (found == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - + var display = Mapper.Map(found); return display; @@ -148,4 +148,4 @@ namespace Umbraco.Web.Editors Services.TextService.Localize("speechBubbles/deleteUserGroupSuccess", new[] {userGroups[0].Name})); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 2e6cd5dbbf..0f1ba95c78 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Collections.Generic; -using System.Configuration; using System.IO; using System.Linq; using System.Net; @@ -11,17 +10,15 @@ using System.Web; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Mvc; -using System.Web.Routing; -using System.Web.Security; -using System.Web.WebPages; using AutoMapper; -using ClientDependency.Core; using Microsoft.AspNet.Identity; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -45,35 +42,13 @@ namespace Umbraco.Web.Editors [IsCurrentUserModelFilter] public class UsersController : UmbracoAuthorizedJsonController { - /// - /// Constructor - /// - public UsersController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public UsersController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - - public UsersController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper, BackOfficeUserManager backOfficeUserManager) - : base(umbracoContext, umbracoHelper, backOfficeUserManager) - { - } - /// /// Returns a list of the sizes of gravatar urls for the user or null if the gravatar server cannot be reached /// /// public string[] GetCurrentUserAvatarUrls() { - var urls = UmbracoContext.Security.CurrentUser.GetUserAvatarUrls(ApplicationContext.ApplicationCache.StaticCache); + var urls = UmbracoContext.Security.CurrentUser.GetUserAvatarUrls(ApplicationCache.StaticCache); if (urls == null) throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Could not access Gravatar endpoint")); @@ -84,7 +59,7 @@ namespace Umbraco.Web.Editors [FileUploadCleanupFilter(false)] public async Task PostSetAvatar(int id) { - return await PostSetAvatarInternal(Request, Services.UserService, ApplicationContext.ApplicationCache.StaticCache, id); + return await PostSetAvatarInternal(Request, Services.UserService, ApplicationCache.StaticCache, id); } internal static async Task PostSetAvatarInternal(HttpRequestMessage request, IUserService userService, ICacheProvider staticCache, int id) @@ -129,13 +104,13 @@ namespace Umbraco.Web.Editors using (var fs = System.IO.File.OpenRead(file.LocalFileName)) { - FileSystemProviderManager.Current.MediaFileSystem.AddFile(user.Avatar, fs, true); + Current.FileSystems.MediaFileSystem.AddFile(user.Avatar, fs, true); } userService.Save(user); //track the temp file so the cleanup filter removes it - tempFiles.UploadedFiles.Add(new ContentItemFile + tempFiles.UploadedFiles.Add(new ContentPropertyFile { TempFilePath = file.LocalFileName }); @@ -154,7 +129,7 @@ namespace Umbraco.Web.Editors var filePath = found.Avatar; //if the filePath is already null it will mean that the user doesn't have a custom avatar and their gravatar is currently - //being used (if they have one). This means they want to remove their gravatar too which we can do by setting a special value + //being used (if they have one). This means they want to remove their gravatar too which we can do by setting a special value //for the avatar. if (filePath.IsNullOrWhiteSpace() == false) { @@ -170,11 +145,11 @@ namespace Umbraco.Web.Editors if (filePath.IsNullOrWhiteSpace() == false) { - if (FileSystemProviderManager.Current.MediaFileSystem.FileExists(filePath)) - FileSystemProviderManager.Current.MediaFileSystem.DeleteFile(filePath); + if (Current.FileSystems.MediaFileSystem.FileExists(filePath)) + Current.FileSystems.MediaFileSystem.DeleteFile(filePath); } - return Request.CreateResponse(HttpStatusCode.OK, found.GetUserAvatarUrls(ApplicationContext.ApplicationCache.StaticCache)); + return Request.CreateResponse(HttpStatusCode.OK, found.GetUserAvatarUrls(ApplicationCache.StaticCache)); } /// @@ -227,13 +202,13 @@ namespace Umbraco.Web.Editors excludeUserGroups = new[] {Constants.Security.AdminGroupAlias}; } - var filterQuery = Query.Builder; + var filterQuery = Current.SqlContext.Query(); - //if the current user is not the administrator, then don't include this in the results. - var isAdminUser = Security.CurrentUser.Id == 0; - if (isAdminUser == false) + if (!Security.CurrentUser.IsSuper()) { - filterQuery.Where(x => x.Id != 0); + // only super can see super - but don't use IsSuper, cannot be mapped to SQL - fixme NOW + //filterQuery.Where(x => !x.IsSuper()); + filterQuery.Where(x => x.Id != Constants.Security.SuperId); } if (filter.IsNullOrWhiteSpace() == false) @@ -244,7 +219,7 @@ namespace Umbraco.Web.Editors long pageIndex = pageNumber - 1; long total; var result = Services.UserService.GetAll(pageIndex, pageSize, out total, orderBy, orderDirection, userStates, userGroups, excludeUserGroups, filterQuery); - + var paged = new PagedUserResult(total, pageNumber, pageSize) { Items = Mapper.Map>(result), @@ -267,7 +242,7 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); } - + if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail) { //ensure they are the same if we're using it @@ -336,7 +311,7 @@ namespace Umbraco.Web.Editors /// /// Invites a user /// - /// + /// /// /// /// This will email the user an invite and generate a token that will be validated in the email @@ -352,7 +327,7 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); } - + if (EmailSender.CanSendRequiredEmail == false) { throw new HttpResponseException( @@ -371,7 +346,7 @@ namespace Umbraco.Web.Editors user = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default(DateTime) || u.EmailConfirmedDate.HasValue); } user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default(DateTime) || u.EmailConfirmedDate.HasValue); - + //Perform authorization here to see if the current user can actually save this user with the info being requested var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService); var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, user, null, null, userSave.UserGroups); @@ -410,7 +385,7 @@ namespace Umbraco.Web.Editors //send the email - await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, user, userSave.Message); + await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, Security.CurrentUser.Email, user, userSave.Message); return display; } @@ -447,7 +422,7 @@ namespace Umbraco.Web.Editors return attempt.Result; } - private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from, IUser to, string message) + private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from, string fromEmail, IUser to, string message) { var token = await UserManager.GenerateEmailConfirmationTokenAsync((int)userDisplay.Id); @@ -462,21 +437,21 @@ namespace Umbraco.Web.Editors var action = urlHelper.Action("VerifyInvite", "BackOffice", new { - area = GlobalSettings.UmbracoMvcArea, + area = GlobalSettings.GetUmbracoMvcArea(), invite = inviteToken }); // Construct full URL using configured application URL (which will fall back to request) - var applicationUri = new Uri(ApplicationContext.UmbracoApplicationUrl); + var applicationUri = RuntimeState.ApplicationUrl; var inviteUri = new Uri(applicationUri, action); var emailSubject = Services.TextService.Localize("user/inviteEmailCopySubject", //Ensure the culture of the found user is used for the email! - UserExtensions.GetUserCulture(to.Language, Services.TextService)); + UserExtensions.GetUserCulture(to.Language, Services.TextService, GlobalSettings)); var emailBody = Services.TextService.Localize("user/inviteEmailCopyFormat", //Ensure the culture of the found user is used for the email! - UserExtensions.GetUserCulture(to.Language, Services.TextService), - new[] { userDisplay.Name, from, message, inviteUri.ToString() }); + UserExtensions.GetUserCulture(to.Language, Services.TextService, GlobalSettings), + new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail }); await UserManager.EmailService.SendAsync( //send the special UmbracoEmailMessage which configures it's own sender @@ -520,7 +495,7 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, canSaveUser.Result)); } - + var hasErrors = false; var existing = Services.UserService.GetByEmail(userSave.Email); @@ -556,23 +531,15 @@ namespace Umbraco.Web.Editors { userSave.Username = userSave.Email; } - + if (userSave.ChangePassword != null) { var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext); + //this will change the password and raise appropriate events var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, userSave.ChangePassword, UserManager); if (passwordChangeResult.Success) { - var userMgr = this.TryGetOwinContext().Result.GetBackOfficeUserManager(); - - //raise the event - NOTE that the ChangePassword.Reset value here doesn't mean it's been 'reset', it means - //it's been changed by a back office user - if (userSave.ChangePassword.Reset.HasValue && userSave.ChangePassword.Reset.Value) - { - userMgr.RaisePasswordChangedEvent(intId.Result); - } - //need to re-get the user found = Services.UserService.GetUserById(intId.Result); } @@ -596,10 +563,10 @@ namespace Umbraco.Web.Editors Services.UserService.Save(user); var display = Mapper.Map(user); - + display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/operationSavedHeader"), Services.TextService.Localize("speechBubbles/editUserSaved")); return display; - } + } /// /// Disables the users with the given user ids @@ -607,7 +574,8 @@ namespace Umbraco.Web.Editors /// public HttpResponseMessage PostDisableUsers([FromUri]int[] userIds) { - if (userIds.Contains(Security.GetUserId())) + var tryGetCurrentUserId = Security.GetUserId(); + if (tryGetCurrentUserId && userIds.Contains(tryGetCurrentUserId.Result)) { throw new HttpResponseException( Request.CreateNotificationValidationErrorResponse("The current user cannot disable itself")); @@ -651,7 +619,7 @@ namespace Umbraco.Web.Editors } return Request.CreateNotificationSuccessResponse( - Services.TextService.Localize("speechBubbles/enableUserSuccess", new[] { users[0].Name })); + Services.TextService.Localize("speechBubbles/enableUserSuccess", new[] { users[0].Name })); } /// @@ -673,7 +641,7 @@ namespace Umbraco.Web.Editors } var user = await UserManager.FindByIdAsync(userIds[0]); return Request.CreateNotificationSuccessResponse( - Services.TextService.Localize("speechBubbles/unlockUserSuccess", new[] { user.Name })); + Services.TextService.Localize("speechBubbles/unlockUserSuccess", new[] { user.Name })); } foreach (var u in userIds) @@ -720,6 +688,6 @@ namespace Umbraco.Web.Editors [DataMember(Name = "userStates")] public IDictionary UserStates { get; set; } } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/ValidationHelper.cs b/src/Umbraco.Web/Editors/ValidationHelper.cs deleted file mode 100644 index e1633fa6bf..0000000000 --- a/src/Umbraco.Web/Editors/ValidationHelper.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Web; -using System.Web.Http.ModelBinding; -using Umbraco.Core; -using Umbraco.Core.Models.Validation; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Editors -{ - internal class ValidationHelper - { - internal static void ValidateEditorModelWithResolver(ModelStateDictionary modelState, object model) - { - var validationResult = EditorValidationResolver.Current.Validate(model); - foreach (var vr in validationResult - .WhereNotNull() - .Where(x => x.ErrorMessage.IsNullOrWhiteSpace() == false) - .Where(x => x.MemberNames.Any())) - { - foreach (var memberName in vr.MemberNames) - { - modelState.AddModelError(memberName, vr.ErrorMessage); - } - } - } - - /// - /// This will check if any properties of the model are attributed with the RequiredForPersistenceAttribute attribute and if they are it will - /// check if that property validates, if it doesn't it means that the current model cannot be persisted because it doesn't have the necessary information - /// to be saved. - /// - /// - /// - /// - /// This is normally used for things like content creating when the name is empty since we cannot actually create a content item when the name is empty. - /// This is similar but different from the standard Required validator since we still allow content to be saved when validation fails but there are some - /// content fields that are absolutely mandatory for creating/saving. - /// - internal static bool ModelHasRequiredForPersistenceErrors(object model) - { - var requiredForPersistenceProperties = TypeDescriptor.GetProperties(model).Cast() - .Where(x => x.Attributes.Cast().Any(a => a is RequiredForPersistenceAttribute)); - - var validator = new RequiredForPersistenceAttribute(); - return requiredForPersistenceProperties.Any(p => !validator.IsValid(p.GetValue(model))); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/ExamineExtensions.cs b/src/Umbraco.Web/ExamineExtensions.cs index e75f9200c6..00471a44e1 100644 --- a/src/Umbraco.Web/ExamineExtensions.cs +++ b/src/Umbraco.Web/ExamineExtensions.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Examine; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Models; +using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.PublishedCache; @@ -13,40 +11,22 @@ namespace Umbraco.Web /// Extension methods for Examine /// public static class ExamineExtensions - { - public static PublishedContentSet ConvertSearchResultToPublishedContent(this IEnumerable results, - ContextualPublishedCache cache) - { - //TODO: The search result has already returned a result which SHOULD include all of the data to create an IPublishedContent, - // however this is currently not the case: - // http://examine.codeplex.com/workitem/10350 + { + public static IEnumerable ToPublishedSearchResults(this IEnumerable results, IPublishedCache cache) + { + var list = new List(); - var list = new List(); - var set = new PublishedContentSet(list); - - foreach (var result in results.OrderByDescending(x => x.Score)) - { - var content = cache.GetById(result.Id); - if (content == null) continue; // skip if this doesn't exist in the cache + foreach (var result in results.OrderByDescending(x => x.Score)) + { + if (!int.TryParse(result.Id, out var intId)) continue; //invalid + var content = cache.GetById(intId); + if (content == null) continue; // skip if this doesn't exist in the cache - // need to extend the content as we're going to add a property to it, - // and we should not ever do it to the content we get from the cache, - // precisely because it is cached and shared by all requests. + list.Add(new PublishedSearchResult(content, result.Score)); - // but we cannot wrap it because we need to respect the type that was - // returned by the cache, in case the cache can create real types. - // so we have to ask it to please extend itself. + } - list.Add(content); - var extend = set.MapContent(content); - - var property = new PropertyResult("examineScore", - result.Score, - PropertyResultType.CustomProperty); - extend.AddProperty(property); - } - - return set; - } - } -} \ No newline at end of file + return list; + } + } +} diff --git a/src/Umbraco.Web/ExamineStartup.cs b/src/Umbraco.Web/ExamineStartup.cs index eac3e20d87..f3a8bc3ef5 100644 --- a/src/Umbraco.Web/ExamineStartup.cs +++ b/src/Umbraco.Web/ExamineStartup.cs @@ -1,208 +1,212 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Examine; -using Examine.Config; -using Examine.LuceneEngine; -using Examine.LuceneEngine.Providers; -using Examine.Providers; -using Lucene.Net.Index; -using Lucene.Net.Store; -using Umbraco.Core; -using Umbraco.Core.Logging; -using UmbracoExamine; +// fixme - Examine is borked +// this cannot compile because it seems to require some changes that are not in Examine v2 +// this should *not* exist, see ExamineComponent instead, which handles everything about Examine -namespace Umbraco.Web -{ - /// - /// Used to configure Examine during startup for the web application - /// - internal class ExamineStartup - { - private readonly List _indexesToRebuild = new List(); - private readonly ApplicationContext _appCtx; - private readonly ProfilingLogger _profilingLogger; - private static bool _isConfigured = false; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using Examine; +//using Examine.Config; +//using Examine.LuceneEngine; +//using Examine.LuceneEngine.Providers; +//using Examine.Providers; +//using Lucene.Net.Index; +//using Lucene.Net.Store; +//using Umbraco.Core; +//using Umbraco.Core.Logging; +//using UmbracoExamine; - //this is used if we are not the MainDom, in which case we need to ensure that if indexes need rebuilding that this - //doesn't occur since that should only occur when we are MainDom - private bool _disableExamineIndexing = false; +//namespace Umbraco.Web +//{ +// /// +// /// Used to configure Examine during startup for the web application +// /// +// internal class ExamineStartup +// { +// private readonly List _indexesToRebuild = new List(); +// private readonly ApplicationContext _appCtx; +// private readonly ProfilingLogger _profilingLogger; +// private static bool _isConfigured = false; - public ExamineStartup(ApplicationContext appCtx) - { - _appCtx = appCtx; - _profilingLogger = appCtx.ProfilingLogger; - } +// //this is used if we are not the MainDom, in which case we need to ensure that if indexes need rebuilding that this +// //doesn't occur since that should only occur when we are MainDom +// private bool _disableExamineIndexing = false; - /// - /// Called during the initialize operation of the boot manager process - /// - public void Initialize() - { - //We want to manage Examine's appdomain shutdown sequence ourselves so first we'll disable Examine's default behavior - //and then we'll use MainDom to control Examine's shutdown - ExamineManager.DisableDefaultHostingEnvironmentRegistration(); +// public ExamineStartup(ApplicationContext appCtx) +// { +// _appCtx = appCtx; +// _profilingLogger = appCtx.ProfilingLogger; +// } - //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain - //terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock - //which simply checks the existence of the lock file - DirectoryTracker.DefaultLockFactory = d => - { - var simpleFsLockFactory = new NoPrefixSimpleFsLockFactory(d); - return simpleFsLockFactory; - }; +// /// +// /// Called during the initialize operation of the boot manager process +// /// +// public void Initialize() +// { +// //We want to manage Examine's appdomain shutdown sequence ourselves so first we'll disable Examine's default behavior +// //and then we'll use MainDom to control Examine's shutdown +// ExamineManager.DisableDefaultHostingEnvironmentRegistration(); - //This is basically a hack for this item: http://issues.umbraco.org/issue/U4-5976 - // when Examine initializes it will try to rebuild if the indexes are empty, however in many cases not all of Examine's - // event handlers will be assigned during bootup when the rebuilding starts which is a problem. So with the examine 0.1.58.2941 build - // it has an event we can subscribe to in order to cancel this rebuilding process, but what we'll do is cancel it and postpone the rebuilding until the - // boot process has completed. It's a hack but it works. - ExamineManager.Instance.BuildingEmptyIndexOnStartup += OnInstanceOnBuildingEmptyIndexOnStartup; +// //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain +// //terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock +// //which simply checks the existence of the lock file +// DirectoryTracker.DefaultLockFactory = d => +// { +// var simpleFsLockFactory = new NoPrefixSimpleFsLockFactory(d); +// return simpleFsLockFactory; +// }; - //let's deal with shutting down Examine with MainDom - var examineShutdownRegistered = _appCtx.MainDom.Register(() => - { - using (_profilingLogger.TraceDuration("Examine shutting down")) - { - //Due to the way Examine's own IRegisteredObject works, we'll first run it with immediate=false and then true so that - //it's correct subroutines are executed (otherwise we'd have to run this logic manually ourselves) - ExamineManager.Instance.Stop(false); - ExamineManager.Instance.Stop(true); - } - }); - if (examineShutdownRegistered) - { - _profilingLogger.Logger.Debug("Examine shutdown registered with MainDom"); - } - else - { - _profilingLogger.Logger.Debug("Examine shutdown not registered, this appdomain is not the MainDom"); +// //This is basically a hack for this item: http://issues.umbraco.org/issue/U4-5976 +// // when Examine initializes it will try to rebuild if the indexes are empty, however in many cases not all of Examine's +// // event handlers will be assigned during bootup when the rebuilding starts which is a problem. So with the examine 0.1.58.2941 build +// // it has an event we can subscribe to in order to cancel this rebuilding process, but what we'll do is cancel it and postpone the rebuilding until the +// // boot process has completed. It's a hack but it works. +// ExamineManager.Instance.BuildingEmptyIndexOnStartup += OnInstanceOnBuildingEmptyIndexOnStartup; - //if we could not register the shutdown examine ourselves, it means we are not maindom! in this case all of examine should be disabled - //from indexing anything on startup!! - _disableExamineIndexing = true; - Suspendable.ExamineEvents.SuspendIndexers(); - } - } +// //let's deal with shutting down Examine with MainDom +// var examineShutdownRegistered = _appCtx.MainDom.Register(() => +// { +// using (_profilingLogger.TraceDuration("Examine shutting down")) +// { +// //Due to the way Examine's own IRegisteredObject works, we'll first run it with immediate=false and then true so that +// //it's correct subroutines are executed (otherwise we'd have to run this logic manually ourselves) +// ExamineManager.Instance.Stop(false); +// ExamineManager.Instance.Stop(true); +// } +// }); +// if (examineShutdownRegistered) +// { +// _profilingLogger.Logger.Debug("Examine shutdown registered with MainDom"); +// } +// else +// { +// _profilingLogger.Logger.Debug("Examine shutdown not registered, this appdomain is not the MainDom"); - /// - /// Called during the Complete operation of the boot manager process - /// - public void Complete() - { - EnsureUnlockedAndConfigured(); +// //if we could not register the shutdown examine ourselves, it means we are not maindom! in this case all of examine should be disabled +// //from indexing anything on startup!! +// _disableExamineIndexing = true; +// Suspendable.ExamineEvents.SuspendIndexers(); +// } +// } - //Ok, now that everything is complete we'll check if we've stored any references to index that need rebuilding and run them - // (see the initialize method for notes) - we'll ensure we remove the event handler too in case examine manager doesn't actually - // initialize during startup, in which case we want it to rebuild the indexes itself. - ExamineManager.Instance.BuildingEmptyIndexOnStartup -= OnInstanceOnBuildingEmptyIndexOnStartup; +// /// +// /// Called during the Complete operation of the boot manager process +// /// +// public void Complete() +// { +// EnsureUnlockedAndConfigured(); - //don't do anything if we have disabled this - if (_disableExamineIndexing == false) - { - foreach (var indexer in _indexesToRebuild) - { - indexer.RebuildIndex(); - } - } - } +// //Ok, now that everything is complete we'll check if we've stored any references to index that need rebuilding and run them +// // (see the initialize method for notes) - we'll ensure we remove the event handler too in case examine manager doesn't actually +// // initialize during startup, in which case we want it to rebuild the indexes itself. +// ExamineManager.Instance.BuildingEmptyIndexOnStartup -= OnInstanceOnBuildingEmptyIndexOnStartup; - /// - /// Called to perform the rebuilding indexes on startup if the indexes don't exist - /// - public void RebuildIndexes() - { - //don't do anything if we have disabled this - if (_disableExamineIndexing) return; +// //don't do anything if we have disabled this +// if (_disableExamineIndexing == false) +// { +// foreach (var indexer in _indexesToRebuild) +// { +// indexer.RebuildIndex(); +// } +// } +// } - EnsureUnlockedAndConfigured(); +// /// +// /// Called to perform the rebuilding indexes on startup if the indexes don't exist +// /// +// public void RebuildIndexes() +// { +// //don't do anything if we have disabled this +// if (_disableExamineIndexing) return; - //If the developer has explicitly opted out of rebuilding indexes on startup then we - // should adhere to that and not do it, this means that if they are load balancing things will be - // out of sync if they are auto-scaling but there's not much we can do about that. - if (ExamineSettings.Instance.RebuildOnAppStart == false) return; +// EnsureUnlockedAndConfigured(); - foreach (var indexer in GetIndexesForColdBoot()) - { - indexer.RebuildIndex(); - } - } +// //If the developer has explicitly opted out of rebuilding indexes on startup then we +// // should adhere to that and not do it, this means that if they are load balancing things will be +// // out of sync if they are auto-scaling but there's not much we can do about that. +// if (ExamineSettings.Instance.RebuildOnAppStart == false) return; - /// - /// The method used to create indexes on a cold boot - /// - /// - /// A cold boot is when the server determines it will not (or cannot) process instructions in the cache table and - /// will rebuild it's own caches itself. - /// - public IEnumerable GetIndexesForColdBoot() - { - // NOTE: This is IMPORTANT! ... we don't want to rebuild any index that is already flagged to be re-indexed - // on startup based on our _indexesToRebuild variable and how Examine auto-rebuilds when indexes are empty. - // This callback is used above for the DatabaseServerMessenger startup options. +// foreach (var indexer in GetIndexesForColdBoot()) +// { +// indexer.RebuildIndex(); +// } +// } - // all indexes - IEnumerable indexes = ExamineManager.Instance.IndexProviderCollection; +// /// +// /// The method used to create indexes on a cold boot +// /// +// /// +// /// A cold boot is when the server determines it will not (or cannot) process instructions in the cache table and +// /// will rebuild it's own caches itself. +// /// +// public IEnumerable GetIndexesForColdBoot() +// { +// // NOTE: This is IMPORTANT! ... we don't want to rebuild any index that is already flagged to be re-indexed +// // on startup based on our _indexesToRebuild variable and how Examine auto-rebuilds when indexes are empty. +// // This callback is used above for the DatabaseServerMessenger startup options. - // except those that are already flagged - // and are processed in Complete() - if (_indexesToRebuild.Any()) - indexes = indexes.Except(_indexesToRebuild); +// // all indexes +// IEnumerable indexes = ExamineManager.Instance.IndexProviderCollection; - // return - foreach (var index in indexes) - yield return index; - } +// // except those that are already flagged +// // and are processed in Complete() +// if (_indexesToRebuild.Any()) +// indexes = indexes.Except(_indexesToRebuild); - /// - /// Must be called to configure each index and ensure it's unlocked before any indexing occurs - /// - /// - /// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before - /// either of these happens, we need to configure the indexes. - /// - private void EnsureUnlockedAndConfigured() - { - if (_isConfigured) return; +// // return +// foreach (var index in indexes) +// yield return index; +// } - _isConfigured = true; +// /// +// /// Must be called to configure each index and ensure it's unlocked before any indexing occurs +// /// +// /// +// /// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before +// /// either of these happens, we need to configure the indexes. +// /// +// private void EnsureUnlockedAndConfigured() +// { +// if (_isConfigured) return; - foreach (var luceneIndexer in ExamineManager.Instance.IndexProviderCollection.OfType()) - { - //We now need to disable waiting for indexing for Examine so that the appdomain is shutdown immediately and doesn't wait for pending - //indexing operations. We used to wait for indexing operations to complete but this can cause more problems than that is worth because - //that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems. - luceneIndexer.WaitForIndexQueueOnShutdown = false; +// _isConfigured = true; - //we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that - //the indexes are not operational unless MainDom is true so if _disableExamineIndexing is false then we should be in charge - if (_disableExamineIndexing == false) - { - var dir = luceneIndexer.GetLuceneDirectory(); - if (IndexWriter.IsLocked(dir)) - { - _profilingLogger.Logger.Info("Forcing index " + luceneIndexer.IndexSetName + " to be unlocked since it was left in a locked state"); - IndexWriter.Unlock(dir); - } - } - } - } +// foreach (var luceneIndexer in ExamineManager.Instance.IndexProviderCollection.OfType()) +// { +// //We now need to disable waiting for indexing for Examine so that the appdomain is shutdown immediately and doesn't wait for pending +// //indexing operations. We used to wait for indexing operations to complete but this can cause more problems than that is worth because +// //that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems. +// luceneIndexer.WaitForIndexQueueOnShutdown = false; - private void OnInstanceOnBuildingEmptyIndexOnStartup(object sender, BuildingEmptyIndexOnStartupEventArgs args) - { - //store the indexer that needs rebuilding because it's empty for when the boot process - // is complete and cancel this current event so the rebuild process doesn't start right now. - args.Cancel = true; - _indexesToRebuild.Add((BaseIndexProvider)args.Indexer); +// //we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that +// //the indexes are not operational unless MainDom is true so if _disableExamineIndexing is false then we should be in charge +// if (_disableExamineIndexing == false) +// { +// var dir = luceneIndexer.GetLuceneDirectory(); +// if (IndexWriter.IsLocked(dir)) +// { +// _profilingLogger.Logger.Info("Forcing index " + luceneIndexer.IndexSetName + " to be unlocked since it was left in a locked state"); +// IndexWriter.Unlock(dir); +// } +// } +// } +// } - //check if the index is rebuilding due to an error and log it - if (args.IsHealthy == false) - { - var baseIndex = args.Indexer as BaseIndexProvider; - var name = baseIndex != null ? baseIndex.Name : "[UKNOWN]"; +// private void OnInstanceOnBuildingEmptyIndexOnStartup(object sender, BuildingEmptyIndexOnStartupEventArgs args) +// { +// //store the indexer that needs rebuilding because it's empty for when the boot process +// // is complete and cancel this current event so the rebuild process doesn't start right now. +// args.Cancel = true; +// _indexesToRebuild.Add((BaseIndexProvider)args.Indexer); - _profilingLogger.Logger.Error(string.Format("The index {0} is rebuilding due to being unreadable/corrupt", name), args.UnhealthyException); - } - } - } -} \ No newline at end of file +// //check if the index is rebuilding due to an error and log it +// if (args.IsHealthy == false) +// { +// var baseIndex = args.Indexer as BaseIndexProvider; +// var name = baseIndex != null ? baseIndex.Name : "[UKNOWN]"; + +// _profilingLogger.Logger.Error(string.Format("The index {0} is rebuilding due to being unreadable/corrupt", name), args.UnhealthyException); +// } +// } +// } +//} diff --git a/src/Umbraco.Web/Extensions/UdiExtensions.cs b/src/Umbraco.Web/Extensions/UdiExtensions.cs deleted file mode 100644 index 3302f5538a..0000000000 --- a/src/Umbraco.Web/Extensions/UdiExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.ComponentModel; -using Umbraco.Core; -using Umbraco.Core.Models; - -namespace Umbraco.Web.Extensions -{ - [Obsolete("Use methods on UmbracoHelper instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static class UdiExtensions - { - [Obsolete("Use methods on UmbracoHelper instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IPublishedContent ToPublishedContent(this Udi udi) - { - var guidUdi = udi as GuidUdi; - if (guidUdi == null) return null; - - var umbracoType = Constants.UdiEntityType.ToUmbracoObjectType(guidUdi.EntityType); - - var umbracoHelper = new UmbracoHelper(UmbracoContext.Current); - var entityService = ApplicationContext.Current.Services.EntityService; - switch (umbracoType) - { - case UmbracoObjectTypes.Document: - return umbracoHelper.TypedContent(guidUdi.Guid); - case UmbracoObjectTypes.Media: - var mediaAttempt = entityService.GetIdForKey(guidUdi.Guid, umbracoType); - if (mediaAttempt.Success) - return umbracoHelper.TypedMedia(mediaAttempt.Result); - break; - case UmbracoObjectTypes.Member: - var memberAttempt = entityService.GetIdForKey(guidUdi.Guid, umbracoType); - if (memberAttempt.Success) - return umbracoHelper.TypedMember(memberAttempt.Result); - break; - } - - return null; - } - } -} diff --git a/src/Umbraco.Web/Features/DisabledFeatures.cs b/src/Umbraco.Web/Features/DisabledFeatures.cs index d771c101f4..62fb019c70 100644 --- a/src/Umbraco.Web/Features/DisabledFeatures.cs +++ b/src/Umbraco.Web/Features/DisabledFeatures.cs @@ -19,6 +19,17 @@ namespace Umbraco.Web.Features /// /// Gets the disabled controllers. /// - public TypeList Controllers { get; private set; } + public TypeList Controllers { get; } + + /// + /// Disables the device preview feature of previewing. + /// + public bool DisableDevicePreview { get; set; } + + /// + /// If true, all references to templates will be removed in the back office and routing + /// + public bool DisableTemplates { get; set; } + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Features/EnabledFeatures.cs b/src/Umbraco.Web/Features/EnabledFeatures.cs new file mode 100644 index 0000000000..7e734f6a84 --- /dev/null +++ b/src/Umbraco.Web/Features/EnabledFeatures.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Web.Features +{ + /// + /// Represents enabled features. + /// + internal class EnabledFeatures + { + /// + /// This allows us to inject a razor view into the Umbraco preview view to extend it + /// + /// + /// This is set to a virtual path of a razor view file + /// + public string PreviewExtendedView { get; set; } + } +} diff --git a/src/Umbraco.Web/Features/FeaturesResolver.cs b/src/Umbraco.Web/Features/FeaturesResolver.cs deleted file mode 100644 index cdb6fae9ea..0000000000 --- a/src/Umbraco.Web/Features/FeaturesResolver.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.Features -{ - internal class FeaturesResolver : SingleObjectResolverBase - { - public FeaturesResolver(UmbracoFeatures value) - : base(value) - { } - - /// - /// Sets the features. - /// - /// For developers, at application startup. - public void SetFeatures(UmbracoFeatures features) - { - Value = features; - } - - /// - /// Gets the features. - /// - public UmbracoFeatures Features - { - get { return Value; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Features/UmbracoFeatures.cs b/src/Umbraco.Web/Features/UmbracoFeatures.cs index 5dc64ff5e4..d1c3899271 100644 --- a/src/Umbraco.Web/Features/UmbracoFeatures.cs +++ b/src/Umbraco.Web/Features/UmbracoFeatures.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.Features /// /// Represents the Umbraco features. /// - internal class UmbracoFeatures + public class UmbracoFeatures { /// /// Initializes a new instance of the class. @@ -14,24 +14,23 @@ namespace Umbraco.Web.Features public UmbracoFeatures() { Disabled = new DisabledFeatures(); + Enabled = new EnabledFeatures(); } - - // note - // currently, the only thing a FeatureSet does is list disabled controllers, - // but eventually we could enable and disable more parts of Umbraco. and then - // we would need some logic to figure out what's enabled/disabled - hence it's - // better to use IsEnabled, where the logic would go, rather than directly - // accessing the Disabled collection. - + /// /// Gets the disabled features. /// - public DisabledFeatures Disabled { get; set; } + internal DisabledFeatures Disabled { get; } /// - /// Determines whether a feature is enabled. + /// Gets the enabled features. /// - public bool IsEnabled(Type feature) + internal EnabledFeatures Enabled { get; } + + /// + /// Determines whether a controller is enabled. + /// + internal bool IsControllerEnabled(Type feature) { if (typeof(UmbracoApiControllerBase).IsAssignableFrom(feature)) return Disabled.Controllers.Contains(feature) == false; @@ -39,4 +38,4 @@ namespace Umbraco.Web.Features throw new NotSupportedException("Not a supported feature type."); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/FormDataCollectionExtensions.cs b/src/Umbraco.Web/FormDataCollectionExtensions.cs index 632569abef..46fa4378ad 100644 --- a/src/Umbraco.Web/FormDataCollectionExtensions.cs +++ b/src/Umbraco.Web/FormDataCollectionExtensions.cs @@ -7,7 +7,7 @@ using Umbraco.Core; namespace Umbraco.Web { - + public static class FormDataCollectionExtensions { /// @@ -77,8 +77,8 @@ namespace Umbraco.Web if (string.IsNullOrEmpty(val)) return default(T); var converted = val.TryConvertTo(); - return converted.Success - ? converted.Result + return converted.Success + ? converted.Result : default(T); } } diff --git a/src/Umbraco.Web/FormlessPage.cs b/src/Umbraco.Web/FormlessPage.cs index 6c7a309a9b..c1f78cddae 100644 --- a/src/Umbraco.Web/FormlessPage.cs +++ b/src/Umbraco.Web/FormlessPage.cs @@ -1,20 +1,20 @@ -using System.Web.UI; +using System.Web.UI; namespace Umbraco.Web { - /// - /// A formless page for use with the rendering a control in a page via Server.Execute. - /// This ignores the check to check for a form control on the page. - /// - /// - /// UmbracoHelper currently uses this for rendering macros but could be used anywhere we want when rendering - /// a page with Server.Execute. - /// SD: I have a custom MVC engine that uses this in my own internal libs if we want to pull it out which is called ViewManager - /// and works really well for things like this. - /// - internal class FormlessPage : Page - { - public override void VerifyRenderingInServerForm(Control control) { } + /// + /// A formless page for use with the rendering a control in a page via Server.Execute. + /// This ignores the check to check for a form control on the page. + /// + /// + /// UmbracoHelper currently uses this for rendering macros but could be used anywhere we want when rendering + /// a page with Server.Execute. + /// SD: I have a custom MVC engine that uses this in my own internal libs if we want to pull it out which is called ViewManager + /// and works really well for things like this. + /// + internal class FormlessPage : Page + { + public override void VerifyRenderingInServerForm(Control control) { } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/GridTemplateExtensions.cs b/src/Umbraco.Web/GridTemplateExtensions.cs index cf267897d4..174ac3fc1f 100644 --- a/src/Umbraco.Web/GridTemplateExtensions.cs +++ b/src/Umbraco.Web/GridTemplateExtensions.cs @@ -10,20 +10,22 @@ using System.Web.Mvc; using Umbraco.Web.Templates; using System.IO; using System.Web.Routing; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Mvc; namespace Umbraco.Web { - + public static class GridTemplateExtensions { public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedProperty property, string framework = "bootstrap3") { - var asString = property.Value as string; + var asString = property.GetValue() as string; if (asString != null && string.IsNullOrEmpty(asString)) return new MvcHtmlString(string.Empty); var view = "Grid/" + framework; - return html.Partial(view, property.Value); + return html.Partial(view, property.GetValue()); } public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedContent contentItem) @@ -33,19 +35,19 @@ namespace Umbraco.Web public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedContent contentItem, string propertyAlias) { - Mandate.ParameterNotNullOrEmpty(propertyAlias, "propertyAlias"); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias)); return html.GetGridHtml(contentItem, propertyAlias, "bootstrap3"); } public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedContent contentItem, string propertyAlias, string framework) { - Mandate.ParameterNotNullOrEmpty(propertyAlias, "propertyAlias"); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias)); var view = "Grid/" + framework; var prop = contentItem.GetProperty(propertyAlias); if (prop == null) throw new NullReferenceException("No property type found with alias " + propertyAlias); - var model = prop.Value; + var model = prop.GetValue(); var asString = model as string; if (asString != null && string.IsNullOrEmpty(asString)) return new MvcHtmlString(string.Empty); @@ -55,11 +57,11 @@ namespace Umbraco.Web public static MvcHtmlString GetGridHtml(this IPublishedProperty property, HtmlHelper html, string framework = "bootstrap3") { - var asString = property.Value as string; + var asString = property.GetValue() as string; if (asString != null && string.IsNullOrEmpty(asString)) return new MvcHtmlString(string.Empty); var view = "Grid/" + framework; - return html.Partial(view, property.Value); + return html.Partial(view, property.GetValue()); } public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, HtmlHelper html) { @@ -67,18 +69,18 @@ namespace Umbraco.Web } public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, HtmlHelper html, string propertyAlias) { - Mandate.ParameterNotNullOrEmpty(propertyAlias, "propertyAlias"); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias)); return GetGridHtml(contentItem, html, propertyAlias, "bootstrap3"); } public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, HtmlHelper html, string propertyAlias, string framework) { - Mandate.ParameterNotNullOrEmpty(propertyAlias, "propertyAlias"); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias)); var view = "Grid/" + framework; var prop = contentItem.GetProperty(propertyAlias); if (prop == null) throw new NullReferenceException("No property type found with alias " + propertyAlias); - var model = prop.Value; + var model = prop.GetValue(); var asString = model as string; if (asString != null && string.IsNullOrEmpty(asString)) return new MvcHtmlString(string.Empty); @@ -90,10 +92,10 @@ namespace Umbraco.Web [Obsolete("This should not be used, GetGridHtml methods accepting HtmlHelper as a parameter or GetGridHtml extensions on HtmlHelper should be used instead")] public static MvcHtmlString GetGridHtml(this IPublishedProperty property, string framework = "bootstrap3") { - var asString = property.Value as string; + var asString = property.GetValue() as string; if (asString != null && string.IsNullOrEmpty(asString)) return new MvcHtmlString(string.Empty); - var htmlHelper = CreateHtmlHelper(property.Value); + var htmlHelper = CreateHtmlHelper(property.GetValue()); return htmlHelper.GetGridHtml(property, framework); } @@ -106,19 +108,19 @@ namespace Umbraco.Web [Obsolete("This should not be used, GetGridHtml methods accepting HtmlHelper as a parameter or GetGridHtml extensions on HtmlHelper should be used instead")] public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, string propertyAlias) { - Mandate.ParameterNotNullOrEmpty(propertyAlias, "propertyAlias"); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias)); - return GetGridHtml(contentItem, propertyAlias, "bootstrap3"); + return GetGridHtml(contentItem, propertyAlias, "bootstrap3"); } [Obsolete("This should not be used, GetGridHtml methods accepting HtmlHelper as a parameter or GetGridHtml extensions on HtmlHelper should be used instead")] public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, string propertyAlias, string framework) { - Mandate.ParameterNotNullOrEmpty(propertyAlias, "propertyAlias"); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias)); var prop = contentItem.GetProperty(propertyAlias); if (prop == null) throw new NullReferenceException("No property type found with alias " + propertyAlias); - var model = prop.Value; + var model = prop.GetValue(); var asString = model as string; if (asString != null && string.IsNullOrEmpty(asString)) return new MvcHtmlString(string.Empty); @@ -142,7 +144,7 @@ namespace Umbraco.Web private class FakeView : IView { public void Render(ViewContext viewContext, TextWriter writer) - { + { } } } diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs index da74465703..01863dac0c 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs @@ -10,7 +10,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Config public abstract class AbstractConfigCheck : HealthCheck { private readonly ConfigurationService _configurationService; - private readonly ILocalizedTextService _textService; + + protected ILocalizedTextService TextService { get; } /// /// Gets the config file path. @@ -50,27 +51,21 @@ namespace Umbraco.Web.HealthCheck.Checks.Config get { return false; } } - protected AbstractConfigCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + protected AbstractConfigCheck(ILocalizedTextService textService) { - _textService = healthCheckContext.ApplicationContext.Services.TextService; - _configurationService = new ConfigurationService(AbsoluteFilePath, XPath, _textService); + TextService = textService; + _configurationService = new ConfigurationService(AbsoluteFilePath, XPath, textService); } /// /// Gets the name of the file. /// - private string FileName - { - get { return Path.GetFileName(FilePath); } - } + private string FileName => Path.GetFileName(FilePath); /// /// Gets the absolute file path. /// - private string AbsoluteFilePath - { - get { return IOHelper.MapPath(FilePath); } - } + private string AbsoluteFilePath => IOHelper.MapPath(FilePath); /// /// Gets the message for when the check has succeeded. @@ -79,7 +74,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config { get { - return _textService.Localize("healthcheck/checkSuccessMessage", + return TextService.Localize("healthcheck/checkSuccessMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, XPath, AbsoluteFilePath }); } } @@ -92,9 +87,9 @@ namespace Umbraco.Web.HealthCheck.Checks.Config get { return ValueComparisonType == ValueComparisonType.ShouldEqual - ? _textService.Localize("healthcheck/checkErrorMessageDifferentExpectedValue", + ? TextService.Localize("healthcheck/checkErrorMessageDifferentExpectedValue", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, XPath, AbsoluteFilePath }) - : _textService.Localize("healthcheck/checkErrorMessageUnexpectedValue", + : TextService.Localize("healthcheck/checkErrorMessageUnexpectedValue", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, XPath, AbsoluteFilePath }); } } @@ -110,7 +105,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config var rectifiedValue = recommendedValue != null ? recommendedValue.Value : ProvidedValue; - return _textService.Localize("healthcheck/rectifySuccessMessage", + return TextService.Localize("healthcheck/rectifySuccessMessage", new[] { CurrentValue, @@ -124,18 +119,12 @@ namespace Umbraco.Web.HealthCheck.Checks.Config /// /// Gets a value indicating whether this check can be rectified automatically. /// - public virtual bool CanRectify - { - get { return ValueComparisonType == ValueComparisonType.ShouldEqual; } - } + public virtual bool CanRectify => ValueComparisonType == ValueComparisonType.ShouldEqual; /// /// Gets a value indicating whether this check can be rectified automatically if a value is provided. /// - public virtual bool CanRectifyWithValue - { - get { return ValueComparisonType == ValueComparisonType.ShouldNotEqual; } - } + public virtual bool CanRectifyWithValue => ValueComparisonType == ValueComparisonType.ShouldNotEqual; public override IEnumerable GetStatus() { @@ -167,7 +156,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config // Declare the action for rectifying the config value var rectifyAction = new HealthCheckAction("rectify", Id) { - Name = _textService.Localize("healthcheck/rectifyButton"), + Name = TextService.Localize("healthcheck/rectifyButton"), ValueRequired = CanRectifyWithValue, }; @@ -189,7 +178,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config public virtual HealthCheckStatus Rectify() { if (ValueComparisonType == ValueComparisonType.ShouldNotEqual) - throw new InvalidOperationException(_textService.Localize("healthcheck/cannotRectifyShouldNotEqual")); + throw new InvalidOperationException(TextService.Localize("healthcheck/cannotRectifyShouldNotEqual")); var recommendedValue = Values.First(v => v.IsRecommended).Value; return UpdateConfigurationValue(recommendedValue); @@ -203,10 +192,10 @@ namespace Umbraco.Web.HealthCheck.Checks.Config public virtual HealthCheckStatus Rectify(string value) { if (ValueComparisonType == ValueComparisonType.ShouldEqual) - throw new InvalidOperationException(_textService.Localize("healthcheck/cannotRectifyShouldEqualWithValue")); + throw new InvalidOperationException(TextService.Localize("healthcheck/cannotRectifyShouldEqualWithValue")); if (string.IsNullOrWhiteSpace(value)) - throw new InvalidOperationException(_textService.Localize("healthcheck/valueToRectifyNotProvided")); + throw new InvalidOperationException(TextService.Localize("healthcheck/valueToRectifyNotProvided")); // Need to track provided value in order to correctly put together the rectify message ProvidedValue = value; @@ -235,4 +224,4 @@ namespace Umbraco.Web.HealthCheck.Checks.Config : Rectify(action.ProvidedValue); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/AcceptableConfiguration.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/AcceptableConfiguration.cs index e7c3036991..b71bba1d14 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/AcceptableConfiguration.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/AcceptableConfiguration.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Web.HealthCheck.Checks.Config +namespace Umbraco.Web.HealthCheck.Checks.Config { public class AcceptableConfiguration { public string Value { get; set; } public bool IsRecommended { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs index 6612b77b26..951609f91f 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs @@ -8,57 +8,27 @@ namespace Umbraco.Web.HealthCheck.Checks.Config Group = "Live Environment")] public class CompilationDebugCheck : AbstractConfigCheck { - private readonly ILocalizedTextService _textService; + public CompilationDebugCheck(ILocalizedTextService textService) + : base(textService) + { } - public CompilationDebugCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) - { - _textService = healthCheckContext.ApplicationContext.Services.TextService; - } + public override string FilePath => "~/Web.config"; - public override string FilePath - { - get { return "~/Web.config"; } - } + public override string XPath => "/configuration/system.web/compilation/@debug"; - public override string XPath - { - get { return "/configuration/system.web/compilation/@debug"; } - } + public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual; - public override ValueComparisonType ValueComparisonType - { - get { return ValueComparisonType.ShouldEqual; } - } + public override bool ValidIfConfigMissing => true; - public override bool ValidIfConfigMissing + public override IEnumerable Values => new List { - get { return true; } - } + new AcceptableConfiguration { IsRecommended = true, Value = bool.FalseString.ToLower() } + }; - public override IEnumerable Values - { - get - { - return new List - { - new AcceptableConfiguration { IsRecommended = true, Value = bool.FalseString.ToLower() } - }; - } - } - - public override string CheckSuccessMessage - { - get { return _textService.Localize("healthcheck/compilationDebugCheckSuccessMessage"); } - } + public override string CheckSuccessMessage => TextService.Localize("healthcheck/compilationDebugCheckSuccessMessage"); - public override string CheckErrorMessage - { - get { return _textService.Localize("healthcheck/compilationDebugCheckErrorMessage"); } - } + public override string CheckErrorMessage => TextService.Localize("healthcheck/compilationDebugCheckErrorMessage"); - public override string RectifySuccessMessage - { - get { return _textService.Localize("healthcheck/compilationDebugCheckRectifySuccessMessage"); } - } + public override string RectifySuccessMessage => TextService.Localize("healthcheck/compilationDebugCheckRectifySuccessMessage"); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs index 439ef4cf72..c1d71d4180 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.IO; using System.Xml; using Umbraco.Core.Logging; using Umbraco.Core.Services; +using Umbraco.Web.Composing; namespace Umbraco.Web.HealthCheck.Checks.Config { @@ -25,7 +26,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config } /// - /// Gets a value from a given configuration file with the given XPath + /// Gets a value from a given configuration file with the given XPath /// public ConfigurationServiceResult GetConfigurationValue() { @@ -57,7 +58,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config } catch (Exception exception) { - LogHelper.Error("Error trying to get configuration value", exception); + Current.Logger.Error("Error trying to get configuration value", exception); return new ConfigurationServiceResult { Success = false, @@ -67,7 +68,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config } /// - /// Updates a value in a given configuration file with the given XPath + /// Updates a value in a given configuration file with the given XPath /// /// /// @@ -103,7 +104,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config } catch (Exception exception) { - LogHelper.Error("Error trying to update configuration", exception); + Current.Logger.Error("Error trying to update configuration", exception); return new ConfigurationServiceResult { Success = false, @@ -112,4 +113,4 @@ namespace Umbraco.Web.HealthCheck.Checks.Config } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationServiceResult.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationServiceResult.cs index e88492d6b2..3b1400da5b 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationServiceResult.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationServiceResult.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Web.HealthCheck.Checks.Config +namespace Umbraco.Web.HealthCheck.Checks.Config { public class ConfigurationServiceResult { public bool Success { get; set; } public string Result { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/CustomErrorsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/CustomErrorsCheck.cs index 3a42dfbc03..63986b6c62 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/CustomErrorsCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/CustomErrorsCheck.cs @@ -10,46 +10,27 @@ namespace Umbraco.Web.HealthCheck.Checks.Config Group = "Live Environment")] public class CustomErrorsCheck : AbstractConfigCheck { - private readonly ILocalizedTextService _textService; + public CustomErrorsCheck(ILocalizedTextService textService) + : base(textService) + { } - public CustomErrorsCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) - { - _textService = healthCheckContext.ApplicationContext.Services.TextService; - } + public override string FilePath => "~/Web.config"; - public override string FilePath - { - get { return "~/Web.config"; } - } + public override string XPath => "/configuration/system.web/customErrors/@mode"; - public override string XPath - { - get { return "/configuration/system.web/customErrors/@mode"; } - } + public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual; - public override ValueComparisonType ValueComparisonType + public override IEnumerable Values => new List { - get { return ValueComparisonType.ShouldEqual; } - } - - public override IEnumerable Values - { - get - { - return new List - { - new AcceptableConfiguration { IsRecommended = true, Value = CustomErrorsMode.RemoteOnly.ToString() }, - new AcceptableConfiguration { IsRecommended = false, Value = "On" } - }; - } - } + new AcceptableConfiguration { IsRecommended = true, Value = CustomErrorsMode.RemoteOnly.ToString() }, + new AcceptableConfiguration { IsRecommended = false, Value = "On" } + }; public override string CheckSuccessMessage { get { - return _textService.Localize("healthcheck/customErrorsCheckSuccessMessage", - new[] { CurrentValue }); + return TextService.Localize("healthcheck/customErrorsCheckSuccessMessage", new[] { CurrentValue }); } } @@ -57,7 +38,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config { get { - return _textService.Localize("healthcheck/customErrorsCheckErrorMessage", + return TextService.Localize("healthcheck/customErrorsCheckErrorMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); } } @@ -66,9 +47,9 @@ namespace Umbraco.Web.HealthCheck.Checks.Config { get { - return _textService.Localize("healthcheck/customErrorsCheckRectifySuccessMessage", + return TextService.Localize("healthcheck/customErrorsCheckRectifySuccessMessage", new[] { Values.First(v => v.IsRecommended).Value }); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/MacroErrorsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/MacroErrorsCheck.cs index 0fe37e11e9..09c041998e 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/MacroErrorsCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/MacroErrorsCheck.cs @@ -9,27 +9,15 @@ namespace Umbraco.Web.HealthCheck.Checks.Config Group = "Configuration")] public class MacroErrorsCheck : AbstractConfigCheck { - private readonly ILocalizedTextService _textService; + public MacroErrorsCheck(ILocalizedTextService textService) + : base(textService) + { } - public MacroErrorsCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) - { - _textService = healthCheckContext.ApplicationContext.Services.TextService; - } + public override string FilePath => "~/Config/umbracoSettings.config"; - public override string FilePath - { - get { return "~/Config/umbracoSettings.config"; } - } + public override string XPath => "/settings/content/MacroErrors"; - public override string XPath - { - get { return "/settings/content/MacroErrors"; } - } - - public override ValueComparisonType ValueComparisonType - { - get { return ValueComparisonType.ShouldEqual; } - } + public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual; public override IEnumerable Values { @@ -52,12 +40,12 @@ namespace Umbraco.Web.HealthCheck.Checks.Config return values; } } - + public override string CheckSuccessMessage { get { - return _textService.Localize("healthcheck/macroErrorModeCheckSuccessMessage", + return TextService.Localize("healthcheck/macroErrorModeCheckSuccessMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); } } @@ -66,7 +54,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config { get { - return _textService.Localize("healthcheck/macroErrorModeCheckErrorMessage", + return TextService.Localize("healthcheck/macroErrorModeCheckErrorMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); } } @@ -75,9 +63,9 @@ namespace Umbraco.Web.HealthCheck.Checks.Config { get { - return _textService.Localize("healthcheck/macroErrorModeCheckRectifySuccessMessage", + return TextService.Localize("healthcheck/macroErrorModeCheckRectifySuccessMessage", new[] { Values.First(v => v.IsRecommended).Value }); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/NotificationEmailCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/NotificationEmailCheck.cs index bcdb0ebc24..8c52a5686b 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/NotificationEmailCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/NotificationEmailCheck.cs @@ -8,48 +8,25 @@ namespace Umbraco.Web.HealthCheck.Checks.Config Group = "Configuration")] public class NotificationEmailCheck : AbstractConfigCheck { - private readonly ILocalizedTextService _textService; private const string DefaultFromEmail = "your@email.here"; - public NotificationEmailCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) - { - _textService = healthCheckContext.ApplicationContext.Services.TextService; - } + public NotificationEmailCheck(ILocalizedTextService textService) + : base(textService) + { } - public override string FilePath - { - get { return "~/Config/umbracoSettings.config"; } - } + public override string FilePath => "~/Config/umbracoSettings.config"; - public override string XPath - { - get { return "/settings/content/notifications/email"; } - } + public override string XPath => "/settings/content/notifications/email"; - public override ValueComparisonType ValueComparisonType - { - get { return ValueComparisonType.ShouldNotEqual; } - } + public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldNotEqual; - public override IEnumerable Values + public override IEnumerable Values => new List { - get - { - return new List - { - new AcceptableConfiguration { IsRecommended = false, Value = DefaultFromEmail } - }; - } - } + new AcceptableConfiguration { IsRecommended = false, Value = DefaultFromEmail } + }; - public override string CheckSuccessMessage - { - get { return _textService.Localize("healthcheck/notificationEmailsCheckSuccessMessage", new [] { CurrentValue } ); } - } + public override string CheckSuccessMessage => TextService.Localize("healthcheck/notificationEmailsCheckSuccessMessage", new [] { CurrentValue } ); - public override string CheckErrorMessage - { - get { return _textService.Localize("healthcheck/notificationEmailsCheckErrorMessage", new[] { DefaultFromEmail }); } - } + public override string CheckErrorMessage => TextService.Localize("healthcheck/notificationEmailsCheckErrorMessage", new[] { DefaultFromEmail }); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/TraceCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/TraceCheck.cs index d0e38815da..b8e4e51eb9 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/TraceCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/TraceCheck.cs @@ -8,52 +8,26 @@ namespace Umbraco.Web.HealthCheck.Checks.Config Group = "Live Environment")] public class TraceCheck : AbstractConfigCheck { - private readonly ILocalizedTextService _textService; - public TraceCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) - { - _textService = healthCheckContext.ApplicationContext.Services.TextService; - } + public TraceCheck(ILocalizedTextService textService) + : base(textService) + { } - public override string FilePath - { - get { return "~/Web.config"; } - } + public override string FilePath => "~/Web.config"; - public override string XPath - { - get { return "/configuration/system.web/trace/@enabled"; } - } + public override string XPath => "/configuration/system.web/trace/@enabled"; - public override ValueComparisonType ValueComparisonType - { - get { return ValueComparisonType.ShouldEqual; } - } + public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual; - public override IEnumerable Values + public override IEnumerable Values => new List { - get - { - return new List - { - new AcceptableConfiguration { IsRecommended = true, Value = bool.FalseString.ToLower() } - }; - } - } + new AcceptableConfiguration { IsRecommended = true, Value = bool.FalseString.ToLower() } + }; - public override string CheckSuccessMessage - { - get { return _textService.Localize("healthcheck/traceModeCheckSuccessMessage"); } - } + public override string CheckSuccessMessage => TextService.Localize("healthcheck/traceModeCheckSuccessMessage"); - public override string CheckErrorMessage - { - get { return _textService.Localize("healthcheck/traceModeCheckErrorMessage"); } - } + public override string CheckErrorMessage => TextService.Localize("healthcheck/traceModeCheckErrorMessage"); - public override string RectifySuccessMessage - { - get { return _textService.Localize("healthcheck/traceModeCheckRectifySuccessMessage"); } - } + public override string RectifySuccessMessage => TextService.Localize("healthcheck/traceModeCheckRectifySuccessMessage"); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs index 2b9f1aa2a2..4a467d7120 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs @@ -12,27 +12,16 @@ namespace Umbraco.Web.HealthCheck.Checks.Config public class TrySkipIisCustomErrorsCheck : AbstractConfigCheck { private readonly Version _serverVersion = HttpRuntime.IISVersion; - private readonly ILocalizedTextService _textService; - public TrySkipIisCustomErrorsCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) - { - _textService = healthCheckContext.ApplicationContext.Services.TextService; - } + public TrySkipIisCustomErrorsCheck(ILocalizedTextService textService) + : base(textService) + { } - public override string FilePath - { - get { return "~/Config/umbracoSettings.config"; } - } + public override string FilePath => "~/Config/umbracoSettings.config"; - public override string XPath - { - get { return "/settings/web.routing/@trySkipIisCustomErrors"; } - } + public override string XPath => "/settings/web.routing/@trySkipIisCustomErrors"; - public override ValueComparisonType ValueComparisonType - { - get { return ValueComparisonType.ShouldEqual; } - } + public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual; public override IEnumerable Values { @@ -50,7 +39,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config { get { - return _textService.Localize("healthcheck/trySkipIisCustomErrorsCheckSuccessMessage", + return TextService.Localize("healthcheck/trySkipIisCustomErrorsCheckSuccessMessage", new[] { Values.First(v => v.IsRecommended).Value, _serverVersion.ToString() }); } } @@ -59,7 +48,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config { get { - return _textService.Localize("healthcheck/trySkipIisCustomErrorsCheckErrorMessage", + return TextService.Localize("healthcheck/trySkipIisCustomErrorsCheckErrorMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, _serverVersion.ToString() }); } } @@ -68,9 +57,9 @@ namespace Umbraco.Web.HealthCheck.Checks.Config { get { - return _textService.Localize("healthcheck/trySkipIisCustomErrorsCheckRectifySuccessMessage", + return TextService.Localize("healthcheck/trySkipIisCustomErrorsCheckRectifySuccessMessage", new[] { Values.First(v => v.IsRecommended).Value, _serverVersion.ToString() }); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/ValueComparisonType.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/ValueComparisonType.cs index 88c8a94a09..8df40da9f9 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/ValueComparisonType.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/ValueComparisonType.cs @@ -5,4 +5,4 @@ ShouldEqual, ShouldNotEqual, } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs index 8b94271e29..c53793274f 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/XmlDataIntegrityHealthCheck.cs @@ -1,10 +1,8 @@ -using System; +using System; using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Composing; using Umbraco.Core.Services; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity { @@ -16,25 +14,24 @@ namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity "XML Data Integrity", Description = "This checks the data integrity for the xml structures for content, media and members that are stored in the cmsContentXml table. This does not check the data integrity of the xml cache file, only the xml structures stored in the database used to create the xml cache file.", Group = "Data Integrity")] + [HideFromTypeFinder] // only if running the Xml cache! added by XmlCacheComponent! public class XmlDataIntegrityHealthCheck : HealthCheck { private readonly ILocalizedTextService _textService; + private readonly PublishedCache.XmlPublishedCache.PublishedSnapshotService _publishedSnapshotService; private const string CheckContentXmlTableAction = "checkContentXmlTable"; private const string CheckMediaXmlTableAction = "checkMediaXmlTable"; private const string CheckMembersXmlTableAction = "checkMembersXmlTable"; - public XmlDataIntegrityHealthCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + public XmlDataIntegrityHealthCheck(ILocalizedTextService textService, IPublishedSnapshotService publishedSnapshotService) { - _sqlSyntax = HealthCheckContext.ApplicationContext.DatabaseContext.SqlSyntax; - _services = HealthCheckContext.ApplicationContext.Services; - _database = HealthCheckContext.ApplicationContext.DatabaseContext.Database; - _textService = healthCheckContext.ApplicationContext.Services.TextService; - } + _textService = textService; - private readonly ISqlSyntaxProvider _sqlSyntax; - private readonly ServiceContext _services; - private readonly UmbracoDatabase _database; + _publishedSnapshotService = publishedSnapshotService as PublishedCache.XmlPublishedCache.PublishedSnapshotService; + if (_publishedSnapshotService == null) + throw new NotSupportedException("Unsupported IPublishedSnapshotService, only the Xml one is supported."); + } /// /// Get the status for this health check @@ -56,13 +53,13 @@ namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity switch (action.Alias) { case CheckContentXmlTableAction: - _services.ContentService.RebuildXmlStructures(); + _publishedSnapshotService.RebuildContentAndPreviewXml(); return CheckContent(); case CheckMediaXmlTableAction: - _services.MediaService.RebuildXmlStructures(); + _publishedSnapshotService.RebuildMediaXml(); return CheckMedia(); case CheckMembersXmlTableAction: - _services.MemberService.RebuildXmlStructures(); + _publishedSnapshotService.RebuildMemberXml(); return CheckMembers(); default: throw new ArgumentOutOfRangeException(); @@ -71,128 +68,30 @@ namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity private HealthCheckStatus CheckMembers() { - var total = _services.MemberService.Count(); - var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); - var subQuery = new Sql() - .Select("Count(*)") - .From(_sqlSyntax) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == memberObjectType); - var totalXml = _database.ExecuteScalar(subQuery); - - var actions = new List(); - if (totalXml != total) - actions.Add(new HealthCheckAction(CheckMembersXmlTableAction, Id)); - - return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckMembers", new[] { totalXml.ToString(), total.ToString(), (total - totalXml).ToString() })) - { - ResultType = totalXml == total ? StatusResultType.Success : StatusResultType.Error, - Actions = actions - }; + return Check(_publishedSnapshotService.VerifyMemberXml(), CheckMembersXmlTableAction, "healthcheck/xmlDataIntegrityCheckMembers"); } - /// - /// Checks the cmsContentXml table to see if the number of entries for media matches the number of media entities - /// - /// - /// - /// Further to just counting the data integrity, this also checks if the XML stored in the cmsContentXml table contains the correct - /// GUID identifier. In older versions of Umbraco, the GUID is not persisted to the content cache, that will cause problems in newer - /// versions of Umbraco, so the xml table would need a rebuild. - /// private HealthCheckStatus CheckMedia() { - var total = _services.MediaService.CountNotTrashed(); - var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); - - //count entries - var countTotalSubQuery = new Sql() - .Select("Count(*)") - .From(_sqlSyntax) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == mediaObjectType); - var totalXml = _database.ExecuteScalar(countTotalSubQuery); - - //count invalid entries - var countNonGuidQuery = new Sql() - .Select("Count(*)") - .From(_sqlSyntax) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == mediaObjectType) - .Where(string.Format("{0}.{1} NOT LIKE '% key=\"%'", _sqlSyntax.GetQuotedTableName("cmsContentXml"), _sqlSyntax.GetQuotedColumnName("xml"))); - var totalNonGuidXml = _database.ExecuteScalar(countNonGuidQuery); - - var hasError = false; - var actions = new List(); - if (totalXml != total || totalNonGuidXml > 0) - { - //if the counts don't match - actions.Add(new HealthCheckAction(CheckMediaXmlTableAction, Id)); - hasError = true; - } - - return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckMedia", - new[] { totalXml.ToString(), total.ToString(), totalNonGuidXml.ToString() })) - { - ResultType = hasError == false - ? StatusResultType.Success - : StatusResultType.Error, - Actions = actions - }; + return Check(_publishedSnapshotService.VerifyMediaXml(), CheckMediaXmlTableAction, "healthcheck/xmlDataIntegrityCheckMedia"); } - /// - /// Checks the cmsContentXml table to see if the number of entries for content matches the number of published content entities - /// - /// - /// - /// Further to just counting the data integrity, this also checks if the XML stored in the cmsContentXml table contains the correct - /// GUID identifier. In older versions of Umbraco, the GUID is not persisted to the content cache, that will cause problems in newer - /// versions of Umbraco, so the xml table would need a rebuild. - /// private HealthCheckStatus CheckContent() { - var total = _services.ContentService.CountPublished(); - var documentObjectType = Guid.Parse(Constants.ObjectTypes.Document); + return Check(_publishedSnapshotService.VerifyContentAndPreviewXml(), CheckContentXmlTableAction, "healthcheck/xmlDataIntegrityCheckContent"); + } - //count entires - var countTotalSubQuery = new Sql() - .Select(string.Format("DISTINCT {0}.{1}", _sqlSyntax.GetQuotedTableName("cmsContentXml"), _sqlSyntax.GetQuotedColumnName("nodeId"))) - .From(_sqlSyntax) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, left => left.NodeId, right => right.NodeId); - var totalXml = _database.ExecuteScalar("SELECT COUNT(*) FROM (" + countTotalSubQuery.SQL + ") as tmp"); - - //count invalid entries - var countNonGuidQuery = new Sql() - .Select("Count(*)") - .From(_sqlSyntax) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == documentObjectType) - .Where(string.Format("{0}.{1} NOT LIKE '% key=\"%'", _sqlSyntax.GetQuotedTableName("cmsContentXml"), _sqlSyntax.GetQuotedColumnName("xml"))); - var totalNonGuidXml = _database.ExecuteScalar(countNonGuidQuery); - - var hasError = false; + private HealthCheckStatus Check(bool ok, string action, string text) + { var actions = new List(); - if (totalXml != total || totalNonGuidXml > 0) - { - //if the counts don't match - actions.Add(new HealthCheckAction(CheckContentXmlTableAction, Id)); - hasError = true; - } + if (ok == false) + actions.Add(new HealthCheckAction(action, Id)); - return new HealthCheckStatus(_textService.Localize("healthcheck/xmlDataIntegrityCheckContent", - new[] { totalXml.ToString(), total.ToString(), totalNonGuidXml.ToString() })) + return new HealthCheckStatus(_textService.Localize(text, new[] { ok ? "ok" : "not ok" })) { - ResultType = hasError == false - ? StatusResultType.Success - : StatusResultType.Error, + ResultType = ok ? StatusResultType.Success : StatusResultType.Error, Actions = actions }; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs index ff8c2646cc..86cef17b4c 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs @@ -28,9 +28,9 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions { private readonly ILocalizedTextService _textService; - public FolderAndFilePermissionsCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + public FolderAndFilePermissionsCheck(ILocalizedTextService textService) { - _textService = healthCheckContext.ApplicationContext.Services.TextService; + _textService = textService; } /// @@ -84,16 +84,16 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions }; // Run checks for required and optional paths for modify permission - List requiredFailedPaths; - List optionalFailedPaths; - var requiredPathCheckResult = FilePermissionHelper.TestDirectories(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out requiredFailedPaths); - var optionalPathCheckResult = FilePermissionHelper.TestDirectories(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out optionalFailedPaths); + var requiredPathCheckResult = FilePermissionHelper.EnsureDirectories( + GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out var requiredFailedPaths); + var optionalPathCheckResult = FilePermissionHelper.EnsureDirectories( + GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out var optionalFailedPaths); //now check the special folders - List requiredFailedPaths2; - List optionalFailedPaths2; - var requiredPathCheckResult2 = FilePermissionHelper.TestDirectories(GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Required), out requiredFailedPaths2, writeCausesRestart:true); - var optionalPathCheckResult2 = FilePermissionHelper.TestDirectories(GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Optional), out optionalFailedPaths2, writeCausesRestart: true); + var requiredPathCheckResult2 = FilePermissionHelper.EnsureDirectories( + GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Required), out var requiredFailedPaths2, writeCausesRestart:true); + var optionalPathCheckResult2 = FilePermissionHelper.EnsureDirectories( + GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Optional), out var optionalFailedPaths2, writeCausesRestart: true); requiredPathCheckResult = requiredPathCheckResult && requiredPathCheckResult2; optionalPathCheckResult = optionalPathCheckResult && optionalPathCheckResult2; @@ -115,10 +115,10 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions }; // Run checks for required and optional paths for modify permission - List requiredFailedPaths; - List optionalFailedPaths; - var requiredPathCheckResult = FilePermissionHelper.TestFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out requiredFailedPaths); - var optionalPathCheckResult = FilePermissionHelper.TestFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out optionalFailedPaths); + IEnumerable requiredFailedPaths; + IEnumerable optionalFailedPaths; + var requiredPathCheckResult = FilePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out requiredFailedPaths); + var optionalPathCheckResult = FilePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out optionalFailedPaths); return GetStatus(requiredPathCheckResult, requiredFailedPaths, optionalPathCheckResult, optionalFailedPaths, PermissionCheckFor.File); } @@ -133,7 +133,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions .ToArray(); } - private HealthCheckStatus GetStatus(bool requiredPathCheckResult, List requiredFailedPaths, + private HealthCheckStatus GetStatus(bool requiredPathCheckResult, IEnumerable requiredFailedPaths, bool optionalPathCheckResult, IEnumerable optionalFailedPaths, PermissionCheckFor checkingFor) { @@ -180,4 +180,4 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions return filePath.Replace(rootFolder, string.Empty); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs index 03ef48c5b5..20e211d16d 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using System.Xml.Linq; using System.Xml.XPath; using Umbraco.Core.Configuration; +using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Services; @@ -20,15 +21,19 @@ namespace Umbraco.Web.HealthCheck.Checks.Security public class ClickJackingCheck : HealthCheck { private readonly ILocalizedTextService _textService; + private readonly IRuntimeState _runtime; + private readonly IHttpContextAccessor _httpContextAccessor; private const string SetFrameOptionsHeaderInConfigActiobn = "setFrameOptionsHeaderInConfig"; private const string XFrameOptionsHeader = "X-Frame-Options"; private const string XFrameOptionsValue = "sameorigin"; // Note can't use "deny" as that would prevent Umbraco itself using IFRAMEs - public ClickJackingCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + public ClickJackingCheck(ILocalizedTextService textService, IRuntimeState runtime, IHttpContextAccessor httpContextAccessor) { - _textService = healthCheckContext.ApplicationContext.Services.TextService; + _textService = textService; + _runtime = runtime; + _httpContextAccessor = httpContextAccessor; } /// @@ -63,7 +68,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security var success = false; // Access the site home page and check for the click-jack protection header or meta tag - var url = HealthCheckContext.SiteUrl; + var url = _runtime.ApplicationUrl; var request = WebRequest.Create(url); request.Method = "GET"; try @@ -85,7 +90,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security } catch (Exception ex) { - message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { url, ex.Message }); + message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { url.ToString(), ex.Message }); } var actions = new List(); diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs index a808538a31..f49af6da7f 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net; using System.Web; using Umbraco.Core.Configuration; +using Umbraco.Core; using Umbraco.Core.Services; namespace Umbraco.Web.HealthCheck.Checks.Security @@ -16,10 +17,14 @@ namespace Umbraco.Web.HealthCheck.Checks.Security public class ExcessiveHeadersCheck : HealthCheck { private readonly ILocalizedTextService _textService; + private readonly IRuntimeState _runtime; + private readonly IHttpContextAccessor _httpContextAccessor; - public ExcessiveHeadersCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + public ExcessiveHeadersCheck(ILocalizedTextService textService, IRuntimeState runtime, IHttpContextAccessor httpContextAccessor) { - _textService = healthCheckContext.ApplicationContext.Services.TextService; + _textService = textService; + _runtime = runtime; + _httpContextAccessor = httpContextAccessor; } /// @@ -46,9 +51,9 @@ namespace Umbraco.Web.HealthCheck.Checks.Security { var message = string.Empty; var success = false; - var url = HealthCheckContext.SiteUrl; - - // Access the site home page and check for the headers + var url = _runtime.ApplicationUrl; + + // Access the site home page and check for the headers var request = WebRequest.Create(url); request.Method = "HEAD"; try @@ -66,7 +71,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security } catch (Exception ex) { - message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { url, ex.Message }); + message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { url.ToString(), ex.Message }); } var actions = new List(); @@ -78,4 +83,4 @@ namespace Umbraco.Web.HealthCheck.Checks.Security }; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs index 155730a56e..448b047b8b 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Net; using System.Security.Cryptography.X509Certificates; using System.Web; +using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Services; using Umbraco.Web.HealthCheck.Checks.Config; @@ -17,12 +19,16 @@ namespace Umbraco.Web.HealthCheck.Checks.Security public class HttpsCheck : HealthCheck { private readonly ILocalizedTextService _textService; + private readonly IRuntimeState _runtime; + private readonly IGlobalSettings _globalSettings; private const string FixHttpsSettingAction = "fixHttpsSetting"; - public HttpsCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + public HttpsCheck(ILocalizedTextService textService, IRuntimeState runtime, IGlobalSettings globalSettings) { - _textService = healthCheckContext.ApplicationContext.Services.TextService; + _textService = textService; + _runtime = runtime; + _globalSettings = globalSettings; } /// @@ -56,10 +62,10 @@ namespace Umbraco.Web.HealthCheck.Checks.Security var message = string.Empty; StatusResultType result; - // Attempt to access the site over HTTPS to see if it HTTPS is supported + // Attempt to access the site over HTTPS to see if it HTTPS is supported // and a valid certificate has been configured - var url = HealthCheckContext.SiteUrl.Replace("http:", "https:"); - var request = (HttpWebRequest)WebRequest.Create(url); + var url = _runtime.ApplicationUrl.ToString().Replace("http:", "https:"); + var request = (HttpWebRequest) WebRequest.Create(url); request.Method = "HEAD"; try @@ -126,7 +132,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security private HealthCheckStatus CheckIfCurrentSchemeIsHttps() { - var uri = new Uri(HealthCheckContext.SiteUrl); + var uri = _runtime.ApplicationUrl; var success = uri.Scheme == "https"; var actions = new List(); @@ -141,8 +147,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Security private HealthCheckStatus CheckHttpsConfigurationSetting() { - var httpsSettingEnabled = Core.Configuration.GlobalSettings.UseSSL; - var uri = new Uri(HealthCheckContext.SiteUrl); + var httpsSettingEnabled = _globalSettings.UseHttps; + var uri = _runtime.ApplicationUrl; var actions = new List(); string resultMessage; @@ -165,7 +171,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security new[] {httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not"}); resultType = httpsSettingEnabled ? StatusResultType.Success: StatusResultType.Error; } - + return new HealthCheckStatus(resultMessage) { @@ -197,4 +203,4 @@ namespace Umbraco.Web.HealthCheck.Checks.Security }; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/Checks/Services/SmtpCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Services/SmtpCheck.cs index 6a59bfd65e..8e3447c79f 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Services/SmtpCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Services/SmtpCheck.cs @@ -4,6 +4,7 @@ using System.IO; using System.Net.Configuration; using System.Net.Sockets; using System.Web.Configuration; +using Umbraco.Core; using Umbraco.Core.Services; namespace Umbraco.Web.HealthCheck.Checks.Services @@ -16,10 +17,12 @@ namespace Umbraco.Web.HealthCheck.Checks.Services public class SmtpCheck : HealthCheck { private readonly ILocalizedTextService _textService; + private readonly IRuntimeState _runtime; - public SmtpCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) + public SmtpCheck(ILocalizedTextService textService, IRuntimeState runtime) { - _textService = healthCheckContext.ApplicationContext.Services.TextService; + _textService = textService; + _runtime = runtime; } /// @@ -48,7 +51,9 @@ namespace Umbraco.Web.HealthCheck.Checks.Services var message = string.Empty; var success = false; - var config = WebConfigurationManager.OpenWebConfiguration(HealthCheckContext.ApplicationPath); + // appPath is the virtual application root path on the server + var appPath = ""; + var config = WebConfigurationManager.OpenWebConfiguration(_runtime.ApplicationVirtualPath); var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings"); if (settings == null) { @@ -106,4 +111,4 @@ namespace Umbraco.Web.HealthCheck.Checks.Services } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheck.cs b/src/Umbraco.Web/HealthCheck/HealthCheck.cs index 34158a66eb..2c8b0c3c84 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheck.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheck.cs @@ -1,35 +1,30 @@ using System; using System.Collections.Generic; using System.Runtime.Serialization; -using umbraco.interfaces; using Umbraco.Core; +using Umbraco.Core.Composing; namespace Umbraco.Web.HealthCheck { /// - /// The abstract health check class + /// Provides a base class for health checks. /// [DataContract(Name = "healtCheck", Namespace = "")] public abstract class HealthCheck : IDiscoverable { - protected HealthCheck(HealthCheckContext healthCheckContext) + protected HealthCheck() { - HealthCheckContext = healthCheckContext; //Fill in the metadata - var thisType = this.GetType(); + var thisType = GetType(); var meta = thisType.GetCustomAttribute(false); if (meta == null) - throw new InvalidOperationException( - string.Format("The health check {0} requires a {1}", thisType, typeof(HealthCheckAttribute))); + throw new InvalidOperationException($"The health check {thisType} requires a {typeof (HealthCheckAttribute)}"); Name = meta.Name; Description = meta.Description; Group = meta.Group; Id = meta.Id; } - [IgnoreDataMember] - public HealthCheckContext HealthCheckContext { get; private set; } - [DataMember(Name = "id")] public Guid Id { get; private set; } @@ -56,6 +51,5 @@ namespace Umbraco.Web.HealthCheck public abstract HealthCheckStatus ExecuteAction(HealthCheckAction action); //TODO: What else? - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckAction.cs b/src/Umbraco.Web/HealthCheck/HealthCheckAction.cs index 18931cf1dd..ce7d842e96 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckAction.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckAction.cs @@ -1,15 +1,14 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Services; +using Umbraco.Web.Composing; namespace Umbraco.Web.HealthCheck { [DataContract(Name = "healtCheckAction", Namespace = "")] public class HealthCheckAction { - private readonly ILocalizedTextService _textService; - /// /// Empty ctor used for serialization /// @@ -48,7 +47,6 @@ namespace Umbraco.Web.HealthCheck [DataMember(Name = "actionParameters")] public Dictionary ActionParameters { get; set; } - /// /// The name of the action - this is used to name the fix button /// @@ -78,4 +76,4 @@ namespace Umbraco.Web.HealthCheck [DataMember(Name = "providedValue")] public string ProvidedValue { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckAttribute.cs b/src/Umbraco.Web/HealthCheck/HealthCheckAttribute.cs index 885af8b3ba..35a8e022fb 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckAttribute.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Web.HealthCheck { @@ -7,7 +7,7 @@ namespace Umbraco.Web.HealthCheck /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class HealthCheckAttribute : Attribute - { + { public HealthCheckAttribute(string id, string name) { Id = new Guid(id); @@ -23,4 +23,4 @@ namespace Umbraco.Web.HealthCheck //TODO: Do we need more metadata? } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckCollection.cs b/src/Umbraco.Web/HealthCheck/HealthCheckCollection.cs new file mode 100644 index 0000000000..6de442b765 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HealthCheckCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.HealthCheck +{ + public class HealthCheckCollection : BuilderCollectionBase + { + public HealthCheckCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckContext.cs b/src/Umbraco.Web/HealthCheck/HealthCheckContext.cs deleted file mode 100644 index eb09eb5f59..0000000000 --- a/src/Umbraco.Web/HealthCheck/HealthCheckContext.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Web; -using Umbraco.Core; - -namespace Umbraco.Web.HealthCheck -{ - /// - /// Context exposing all services that could be required for health check classes to perform and/or fix their checks - /// - public class HealthCheckContext - { - private readonly HttpContextBase _httpContext; - - private readonly UmbracoContext _umbracoContext; - - public HealthCheckContext(HttpContextBase httpContext, UmbracoContext umbracoContext) - { - if (httpContext == null) throw new ArgumentNullException("httpContext"); - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - _httpContext = httpContext; - _umbracoContext = umbracoContext; - ApplicationContext = _umbracoContext.Application; - } - - public HealthCheckContext(ApplicationContext applicationContext) - { - ApplicationContext = applicationContext; - } - - public ApplicationContext ApplicationContext { get; private set; } - - public string SiteUrl - { - get - { - return _httpContext != null - ? _httpContext.Request.Url.GetLeftPart(UriPartial.Authority) - : ApplicationContext.UmbracoApplicationUrl.Replace("/umbraco", string.Empty); - } - } - - public string ApplicationPath - { - get - { - return _httpContext != null - ? _httpContext.Request.ApplicationPath - : "/"; - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckController.cs b/src/Umbraco.Web/HealthCheck/HealthCheckController.cs index 34d201d5fe..14642ac843 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckController.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckController.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration; using System.Linq; using System.Web.Http; +using Umbraco.Core.Logging; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Web.Editors; @@ -17,12 +18,14 @@ namespace Umbraco.Web.HealthCheck [UmbracoApplicationAuthorize(Core.Constants.Applications.Developer)] public class HealthCheckController : UmbracoAuthorizedJsonController { - private readonly IHealthCheckResolver _healthCheckResolver; + private readonly HealthCheckCollection _checks; private readonly IList _disabledCheckIds; + private readonly ILogger _logger; - public HealthCheckController() + public HealthCheckController(HealthCheckCollection checks, ILogger logger) { - _healthCheckResolver = HealthCheckResolver.Current; + _checks = checks ?? throw new ArgumentNullException(nameof(checks)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); var healthCheckConfig = UmbracoConfig.For.HealthCheck(); _disabledCheckIds = healthCheckConfig.DisabledChecks @@ -30,35 +33,13 @@ namespace Umbraco.Web.HealthCheck .ToList(); } - [Obsolete("Use the contructor specifying all parameters instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public HealthCheckController(IHealthCheckResolver healthCheckResolver) - { - _healthCheckResolver = healthCheckResolver; - var healthCheckConfig = UmbracoConfig.For.HealthCheck(); - _disabledCheckIds = healthCheckConfig.DisabledChecks - .Select(x => x.Id) - .ToList(); - } - - public HealthCheckController(IHealthCheckResolver healthCheckResolver, IHealthChecks healthCheckConfig) - { - if (healthCheckResolver == null) throw new ArgumentNullException("healthCheckResolver"); - if (healthCheckConfig == null) throw new ArgumentNullException("healthCheckConfig"); - - _healthCheckResolver = healthCheckResolver; - _disabledCheckIds = healthCheckConfig.DisabledChecks - .Select(x => x.Id) - .ToList(); - } - /// /// Gets a grouped list of health checks, but doesn't actively check the status of each health check. /// /// Returns a collection of anonymous objects representing each group. public object GetAllHealthChecks() { - var groups = _healthCheckResolver.HealthChecks + var groups = _checks .Where(x => _disabledCheckIds.Contains(x.Id) == false) .GroupBy(x => x.Group) .OrderBy(x => x.Key); @@ -82,7 +63,7 @@ namespace Umbraco.Web.HealthCheck public object GetStatus(Guid id) { var check = GetCheckById(id); - + try { //Core.Logging.LogHelper.Debug("Running health check: " + check.Name); @@ -90,7 +71,7 @@ namespace Umbraco.Web.HealthCheck } catch (Exception e) { - Core.Logging.LogHelper.Error("Exception in health check: " + check.Name, e); + _logger.Error("Exception in health check: " + check.Name, e); throw; } } @@ -104,13 +85,13 @@ namespace Umbraco.Web.HealthCheck private HealthCheck GetCheckById(Guid id) { - var check = _healthCheckResolver.HealthChecks + var check = _checks .Where(x => _disabledCheckIds.Contains(x.Id) == false) .FirstOrDefault(x => x.Id == id); - if (check == null) throw new InvalidOperationException(string.Format("No health check found with id {0}", id)); + if (check == null) throw new InvalidOperationException($"No health check found with id {id}"); return check; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodAttribute.cs b/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodAttribute.cs index 0b09e67839..d9da271d58 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodAttribute.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Web.HealthCheck { @@ -7,12 +7,12 @@ namespace Umbraco.Web.HealthCheck /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class HealthCheckNotificationMethodAttribute : Attribute - { + { public HealthCheckNotificationMethodAttribute(string alias) { Alias = alias; } - public string Alias { get; private set; } + public string Alias { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodCollection.cs b/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodCollection.cs new file mode 100644 index 0000000000..6c5f89e4bc --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodCollection.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; +using Umbraco.Web.HealthCheck.NotificationMethods; + +namespace Umbraco.Web.HealthCheck +{ + public class HealthCheckNotificationMethodCollection : BuilderCollectionBase + { + public HealthCheckNotificationMethodCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodCollectionBuilder.cs b/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodCollectionBuilder.cs new file mode 100644 index 0000000000..4f958da9a0 --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodCollectionBuilder.cs @@ -0,0 +1,15 @@ +using LightInject; +using Umbraco.Core.Composing; +using Umbraco.Web.HealthCheck.NotificationMethods; + +namespace Umbraco.Web.HealthCheck +{ + internal class HealthCheckNotificationMethodCollectionBuilder : LazyCollectionBuilderBase + { + public HealthCheckNotificationMethodCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override HealthCheckNotificationMethodCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodResolver.cs b/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodResolver.cs deleted file mode 100644 index e7c6326a89..0000000000 --- a/src/Umbraco.Web/HealthCheck/HealthCheckNotificationMethodResolver.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Configuration; -using System.Reflection; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using Umbraco.Web.HealthCheck.NotificationMethods; -using Umbraco.Core.Configuration.HealthChecks; - -namespace Umbraco.Web.HealthCheck -{ - - - /// - /// Resolves all health check instances - /// - /// - /// Each instance scoped to the lifespan of the http request - /// - internal class HealthCheckNotificationMethodResolver : LazyManyObjectsResolverBase, IHealthCheckNotificationMethodsResolver - { - public HealthCheckNotificationMethodResolver(ILogger logger, Func> lazyTypeList) - : base(new HealthCheckNotificationMethodServiceProvider(), logger, lazyTypeList, ObjectLifetimeScope.Application) - { - } - - /// - /// Returns all health check notification method instances - /// - public IEnumerable NotificationMethods - { - get { return Values; } - } - - /// - /// This will ctor the IHealthCheckNotificatationMethod instances - /// - private class HealthCheckNotificationMethodServiceProvider : IServiceProvider - { - public object GetService(Type serviceType) - { - var ctor = serviceType.GetConstructors().FirstOrDefault(); - if (ctor == null) - { - return null; - } - - // Load attribute from type in order to find alias for notification method - var attribute = serviceType.GetCustomAttributes(typeof(HealthCheckNotificationMethodAttribute), true) - .FirstOrDefault() as HealthCheckNotificationMethodAttribute; - if (attribute == null) - { - return null; - } - - // Using alias, get related configuration - var healthCheckConfig = UmbracoConfig.For.HealthCheck(); - var notificationMethods = healthCheckConfig.NotificationSettings.NotificationMethods; - var notificationMethod = notificationMethods[attribute.Alias]; - - - // Create array for constructor paramenters. Will consists of common ones that all notification methods have as well - // as those specific to this particular notification method. - var baseType = typeof(NotificationMethodBase); - var baseTypeCtor = baseType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).First(); - var baseTypeCtorParamNames = baseTypeCtor.GetParameters().Select(x => x.Name); - - List ctorParams; - - if (notificationMethod != null) - { - // configuration found so set ctorParams to config values - ctorParams = new List - { - notificationMethod.Enabled, - notificationMethod.FailureOnly, - notificationMethod.Verbosity - }; - ctorParams.AddRange(ctor.GetParameters() - .Where(x => baseTypeCtorParamNames.Contains(x.Name) == false) - .Select(x => notificationMethod.Settings[x.Name].Value)); - } - else - { - // no configuration found so set to default values, enabled = false - ctorParams = new List { false, false, HealthCheckNotificationVerbosity.Detailed }; - ctorParams.AddRange(ctor.GetParameters() - .Where(x => baseTypeCtorParamNames.Contains(x.Name) == false) - .Select(x => string.Empty)); - } - - // Instantiate the type with the constructor parameters - return Activator.CreateInstance(serviceType, ctorParams.ToArray()); - } - } - } -} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs b/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs deleted file mode 100644 index 96762f540d..0000000000 --- a/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.HealthCheck -{ - /// - /// Resolves all health check instances - /// - /// - /// Each instance scoped to the lifespan of the http request - /// - public class HealthCheckResolver : LazyManyObjectsResolverBase, IHealthCheckResolver - { - public HealthCheckResolver(ILogger logger, Func> lazyTypeList) - : base(new HealthCheckServiceProvider(), logger, lazyTypeList, ObjectLifetimeScope.Application) - { - } - - /// - /// Returns all health check instances - /// - public IEnumerable HealthChecks - { - get { return Values; } - } - - /// - /// This will ctor the HealthCheck instances - /// - /// - /// This is like a super crappy DI - in v8 we have real DI - /// - private class HealthCheckServiceProvider : IServiceProvider - { - public object GetService(Type serviceType) - { - var normalArgs = new[] { typeof(HealthCheckContext) }; - var found = serviceType.GetConstructor(normalArgs); - if (found != null) - { - var gotUmbracoContext = UmbracoContext.Current != null; - var healthCheckContext = gotUmbracoContext - ? new HealthCheckContext(new HttpContextWrapper(HttpContext.Current), UmbracoContext.Current) - : new HealthCheckContext(ApplicationContext.Current); - return found.Invoke(new object[] - { - healthCheckContext - }); - } - - //use normal ctor - return Activator.CreateInstance(serviceType); - } - } - } -} diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckResults.cs b/src/Umbraco.Web/HealthCheck/HealthCheckResults.cs index e5effbba7f..139072dcfe 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckResults.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckResults.cs @@ -2,7 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using MarkdownSharp; +using HeyRed.MarkdownSharp; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Logging; @@ -13,10 +14,12 @@ namespace Umbraco.Web.HealthCheck private readonly Dictionary> _results; public readonly bool AllChecksSuccessful; + private ILogger Logger => Current.Logger; // fixme inject + public HealthCheckResults(IEnumerable checks) { _results = checks.ToDictionary( - t => t.Name, + t => t.Name, t => { try { @@ -24,8 +27,8 @@ namespace Umbraco.Web.HealthCheck } catch (Exception ex) { - LogHelper.Error(string.Format("Error running scheduled health check: {0}", t.Name), ex); - var message = string.Format("Health check failed with exception: {0}. See logs for details.", ex.Message); + Logger.Error($"Error running scheduled health check: {t.Name}", ex); + var message = $"Health check failed with exception: {ex.Message}. See logs for details."; return new List { new HealthCheckStatus(message) @@ -33,9 +36,9 @@ namespace Umbraco.Web.HealthCheck ResultType = StatusResultType.Error } }; - } + } }); - + // find out if all checks pass or not AllChecksSuccessful = true; foreach (var result in _results) @@ -51,7 +54,7 @@ namespace Umbraco.Web.HealthCheck public void LogResults() { - LogHelper.Info("Scheduled health check results:"); + Logger.Info("Scheduled health check results:"); foreach (var result in _results) { var checkName = result.Key; @@ -59,16 +62,16 @@ namespace Umbraco.Web.HealthCheck var checkIsSuccess = result.Value.All(x => x.ResultType == StatusResultType.Success); if (checkIsSuccess) { - LogHelper.Info(string.Format(" Checks for '{0}' all completed succesfully.", checkName)); + Logger.Info($" Checks for '{checkName}' all completed succesfully."); } else { - LogHelper.Warn(string.Format(" Checks for '{0}' completed with errors.", checkName)); + Logger.Warn($" Checks for '{checkName}' completed with errors."); } foreach (var checkResult in checkResults) { - LogHelper.Info(string.Format(" Result: {0}, Message: '{1}'", checkResult.ResultType, checkResult.Message)); + Logger.Info($" Result: {checkResult.ResultType}, Message: '{checkResult.Message}'"); } } } diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckStatus.cs b/src/Umbraco.Web/HealthCheck/HealthCheckStatus.cs index c966cadfa7..445582788a 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckStatus.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckStatus.cs @@ -51,4 +51,4 @@ namespace Umbraco.Web.HealthCheck //TODO: What else? } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HealthCheck/HeathCheckCollectionBuilder.cs b/src/Umbraco.Web/HealthCheck/HeathCheckCollectionBuilder.cs new file mode 100644 index 0000000000..9528f9a9dd --- /dev/null +++ b/src/Umbraco.Web/HealthCheck/HeathCheckCollectionBuilder.cs @@ -0,0 +1,18 @@ +using LightInject; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.HealthCheck +{ + public class HealthCheckCollectionBuilder : LazyCollectionBuilderBase + { + public HealthCheckCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override HealthCheckCollectionBuilder This => this; + + // note: in v7 they were per-request, not sure why? + // the collection is injected into the controller & there's only 1 controller per request anyways + protected override ILifetime CollectionLifetime => null; // transient! + } +} diff --git a/src/Umbraco.Web/HealthCheck/IHealthCheckNotificationMethodsResolver.cs b/src/Umbraco.Web/HealthCheck/IHealthCheckNotificationMethodsResolver.cs deleted file mode 100644 index 21bdc7fcb4..0000000000 --- a/src/Umbraco.Web/HealthCheck/IHealthCheckNotificationMethodsResolver.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Web.HealthCheck.NotificationMethods; - -namespace Umbraco.Web.HealthCheck -{ - public interface IHealthCheckNotificationMethodsResolver - { - IEnumerable NotificationMethods { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/IHealthCheckResolver.cs b/src/Umbraco.Web/HealthCheck/IHealthCheckResolver.cs deleted file mode 100644 index 795b61198e..0000000000 --- a/src/Umbraco.Web/HealthCheck/IHealthCheckResolver.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Web.HealthCheck -{ - public interface IHealthCheckResolver - { - IEnumerable HealthChecks { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs index 998525c96a..a38a15608d 100644 --- a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs @@ -1,52 +1,35 @@ using System; using System.Net.Mail; +using System.Threading; using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Services; namespace Umbraco.Web.HealthCheck.NotificationMethods { [HealthCheckNotificationMethod("email")] - public class EmailNotificationMethod : NotificationMethodBase, IHealthCheckNotificatationMethod + public class EmailNotificationMethod : NotificationMethodBase { private readonly ILocalizedTextService _textService; - /// - /// Default constructor which is used in the provider model - /// - /// - /// - /// - /// - public EmailNotificationMethod(bool enabled, bool failureOnly, HealthCheckNotificationVerbosity verbosity, - string recipientEmail) - : this(enabled, failureOnly, verbosity, recipientEmail, ApplicationContext.Current.Services.TextService) + public EmailNotificationMethod(ILocalizedTextService textService) { - } + var recipientEmail = Settings["recipientEmail"]?.Value; + if (string.IsNullOrWhiteSpace(recipientEmail)) + { + Enabled = false; + return; + } - /// - /// Constructor that could be used for testing - /// - /// - /// - /// - /// - /// - internal EmailNotificationMethod(bool enabled, bool failureOnly, HealthCheckNotificationVerbosity verbosity, - string recipientEmail, ILocalizedTextService textService) - : base(enabled, failureOnly, verbosity) - { - if (textService == null) throw new ArgumentNullException("textService"); - _textService = textService; RecipientEmail = recipientEmail; - Verbosity = verbosity; + + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); } - public string RecipientEmail { get; private set; } + public string RecipientEmail { get; } - public async Task SendAsync(HealthCheckResults results) + public override async Task SendAsync(HealthCheckResults results, CancellationToken token) { if (ShouldSend(results) == false) { @@ -68,17 +51,23 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods var subject = _textService.Localize("healthcheck/scheduledHealthCheckEmailSubject"); var mailSender = new EmailSender(); - using (var mailMessage = new MailMessage(UmbracoConfig.For.UmbracoSettings().Content.NotificationEmailAddress, - RecipientEmail, - string.IsNullOrEmpty(subject) ? "Umbraco Health Check Status" : subject, - message) - { - IsBodyHtml = message.IsNullOrWhiteSpace() == false - && message.Contains("<") && message.Contains("(); + if (attribute == null) + { + Enabled = false; + return; + } + + var healthCheckConfig = UmbracoConfig.For.HealthCheck(); + var notificationMethods = healthCheckConfig.NotificationSettings.NotificationMethods; + var notificationMethod = notificationMethods[attribute.Alias]; + if (notificationMethod == null) + { + Enabled = false; + return; + } + + Enabled = notificationMethod.Enabled; + FailureOnly = notificationMethod.FailureOnly; + Verbosity = notificationMethod.Verbosity; + Settings = notificationMethod.Settings; } public bool Enabled { get; protected set; } @@ -17,19 +40,13 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods public HealthCheckNotificationVerbosity Verbosity { get; protected set; } + public IReadOnlyDictionary Settings { get; } + protected bool ShouldSend(HealthCheckResults results) { - if (Enabled == false) - { - return false; - } - - if (FailureOnly && results.AllChecksSuccessful) - { - return false; - } - - return true; + return Enabled && (!FailureOnly || !results.AllChecksSuccessful); } + + public abstract Task SendAsync(HealthCheckResults results, CancellationToken token); } } diff --git a/src/Umbraco.Web/HealthCheck/StatusResultType.cs b/src/Umbraco.Web/HealthCheck/StatusResultType.cs index 8e5fbb4cc9..c6bd50f247 100644 --- a/src/Umbraco.Web/HealthCheck/StatusResultType.cs +++ b/src/Umbraco.Web/HealthCheck/StatusResultType.cs @@ -7,4 +7,4 @@ Error, Info } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs index db59f22f64..26a1d97f67 100644 --- a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs @@ -1,52 +1,43 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; using System.Web.Mvc; -using ClientDependency.Core.Config; using Microsoft.Owin.Security; using Newtonsoft.Json; -using Umbraco.Core; -using Umbraco.Core.Configuration; +using Umbraco.Core.Composing; using Umbraco.Web.Editors; +using Umbraco.Web.Features; using Umbraco.Web.Models; namespace Umbraco.Web { - using Umbraco.Core.Configuration; + using Core.Configuration; /// /// HtmlHelper extensions for the back office /// public static class HtmlHelperBackOfficeExtensions { - [Obsolete("Use the overload with all required parameters instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IHtmlString BareMinimumServerVariablesScript(this HtmlHelper html, UrlHelper uri, string externalLoginsUrl) - { - return html.BareMinimumServerVariablesScript(uri, ApplicationContext.Current, externalLoginsUrl); - } - /// /// Outputs a script tag containing the bare minimum (non secure) server vars for use with the angular app /// /// /// - /// /// /// The post url used to sign in with external logins - this can change depending on for what service the external login is service. /// Example: normal back office login or authenticating upgrade login /// + /// + /// /// /// /// These are the bare minimal server variables that are required for the application to start without being authenticated, /// we will load the rest of the server vars after the user is authenticated. /// - public static IHtmlString BareMinimumServerVariablesScript(this HtmlHelper html, UrlHelper uri, ApplicationContext appCtx, string externalLoginsUrl) + public static IHtmlString BareMinimumServerVariablesScript(this HtmlHelper html, UrlHelper uri, string externalLoginsUrl, UmbracoFeatures features, IGlobalSettings globalSettings) { - var serverVars = new BackOfficeServerVariables(uri, appCtx, UmbracoConfig.For.UmbracoSettings()); + var serverVars = new BackOfficeServerVariables(uri, Current.RuntimeState, features, globalSettings); var minVars = serverVars.BareMinimumServerVariables(); var str = @""; return html.Raw(str); - } + } /// /// Used to render the script that will pass in the angular "externalLoginInfo" service/value on page load @@ -122,7 +113,7 @@ namespace Umbraco.Web sb.AppendLine(@"app.value(""resetPasswordCodeInfo"", {"); - sb.AppendLine(@"errors: errors,"); + sb.AppendLine(@"errors: errors,"); sb.Append(@"resetCodeModel: "); sb.AppendLine(JsonConvert.SerializeObject(resetCodeModel)); sb.AppendLine(@"});"); @@ -130,4 +121,4 @@ namespace Umbraco.Web return html.Raw(sb.ToString()); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 3062613b6b..dd34dea6b5 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -9,22 +9,22 @@ using System.Web.Mvc.Html; using System.Web.Routing; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Dynamics; +using Umbraco.Core.Exceptions; using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Profiling; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; using Umbraco.Web.Models; using Umbraco.Web.Mvc; +using Umbraco.Web.Security; using Constants = Umbraco.Core.Constants; -using Member = umbraco.cms.businesslogic.member.Member; namespace Umbraco.Web { /// - /// HtmlHelper extensions for use in templates - /// - public static class HtmlHelperRenderExtensions - { + /// HtmlHelper extensions for use in templates + /// + public static class HtmlHelperRenderExtensions + { /// /// Renders the markup for the profiler /// @@ -32,7 +32,7 @@ namespace Umbraco.Web /// public static IHtmlString RenderProfiler(this HtmlHelper helper) { - return new HtmlString(ProfilerResolver.Current.Profiler.Render()); + return new HtmlString(Current.Profiler.Render()); } /// @@ -47,13 +47,13 @@ namespace Umbraco.Web public static MvcHtmlString AreaPartial(this HtmlHelper helper, string partial, string area, object model = null, ViewDataDictionary viewData = null) { var originalArea = helper.ViewContext.RouteData.DataTokens["area"]; - helper.ViewContext.RouteData.DataTokens["area"] = area; + helper.ViewContext.RouteData.DataTokens["area"] = area; var result = helper.Partial(partial, model, viewData); helper.ViewContext.RouteData.DataTokens["area"] = originalArea; return result; } - /// + /// /// Will render the preview badge when in preview mode which is not required ever unless the MVC page you are /// using does not inherit from UmbracoTemplatePage /// @@ -77,84 +77,85 @@ namespace Umbraco.Web } - public static IHtmlString CachedPartial( - this HtmlHelper htmlHelper, - string partialViewName, - object model, - int cachedSeconds, - bool cacheByPage = false, - bool cacheByMember = false, - ViewDataDictionary viewData = null, - Func contextualKeyBuilder = null) - { - var cacheKey = new StringBuilder(partialViewName); - if (cacheByPage) - { - if (UmbracoContext.Current == null) - { - throw new InvalidOperationException("Cannot cache by page if the UmbracoContext has not been initialized, this parameter can only be used in the context of an Umbraco request"); - } - cacheKey.AppendFormat("{0}-", UmbracoContext.Current.PageId); - } - if (cacheByMember) - { - var currentMember = Member.GetCurrentMember(); - cacheKey.AppendFormat("m{0}-", currentMember == null ? 0 : currentMember.Id); - } - if (contextualKeyBuilder != null) - { - var contextualKey = contextualKeyBuilder(model, viewData); + public static IHtmlString CachedPartial( + this HtmlHelper htmlHelper, + string partialViewName, + object model, + int cachedSeconds, + bool cacheByPage = false, + bool cacheByMember = false, + ViewDataDictionary viewData = null, + Func contextualKeyBuilder = null) + { + var cacheKey = new StringBuilder(partialViewName); + if (cacheByPage) + { + if (UmbracoContext.Current == null) + { + throw new InvalidOperationException("Cannot cache by page if the UmbracoContext has not been initialized, this parameter can only be used in the context of an Umbraco request"); + } + cacheKey.AppendFormat("{0}-", UmbracoContext.Current.PageId); + } + if (cacheByMember) + { + var helper = new MembershipHelper(Current.UmbracoContext); + var currentMember = helper.GetCurrentMember(); + cacheKey.AppendFormat("m{0}-", currentMember == null ? 0 : currentMember.Id); + } + if (contextualKeyBuilder != null) + { + var contextualKey = contextualKeyBuilder(model, viewData); cacheKey.AppendFormat("c{0}-", contextualKey); - } - return ApplicationContext.Current.ApplicationCache.CachedPartialView(htmlHelper, partialViewName, model, cachedSeconds, cacheKey.ToString(), viewData); - } + } + return Current.ApplicationCache.CachedPartialView(htmlHelper, partialViewName, model, cachedSeconds, cacheKey.ToString(), viewData); + } - public static MvcHtmlString EditorFor(this HtmlHelper htmlHelper, string templateName = "", string htmlFieldName = "", object additionalViewData = null) - where T : new() - { - var model = new T(); - var typedHelper = new HtmlHelper( - htmlHelper.ViewContext.CopyWithModel(model), - htmlHelper.ViewDataContainer.CopyWithModel(model)); + public static MvcHtmlString EditorFor(this HtmlHelper htmlHelper, string templateName = "", string htmlFieldName = "", object additionalViewData = null) + where T : new() + { + var model = new T(); + var typedHelper = new HtmlHelper( + htmlHelper.ViewContext.CopyWithModel(model), + htmlHelper.ViewDataContainer.CopyWithModel(model)); - return typedHelper.EditorFor(x => model, templateName, htmlFieldName, additionalViewData); - } + return typedHelper.EditorFor(x => model, templateName, htmlFieldName, additionalViewData); + } - /// - /// A validation summary that lets you pass in a prefix so that the summary only displays for elements - /// containing the prefix. This allows you to have more than on validation summary on a page. - /// - /// - /// - /// - /// - /// - /// - public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, - string prefix = "", - bool excludePropertyErrors = false, - string message = "", - IDictionary htmlAttributes = null) - { - if (prefix.IsNullOrWhiteSpace()) - { - return htmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); - } + /// + /// A validation summary that lets you pass in a prefix so that the summary only displays for elements + /// containing the prefix. This allows you to have more than on validation summary on a page. + /// + /// + /// + /// + /// + /// + /// + public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, + string prefix = "", + bool excludePropertyErrors = false, + string message = "", + IDictionary htmlAttributes = null) + { + if (prefix.IsNullOrWhiteSpace()) + { + return htmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); + } - //if there's a prefix applied, we need to create a new html helper with a filtered ModelState collection so that it only looks for - //specific model state with the prefix. - var filteredHtmlHelper = new HtmlHelper(htmlHelper.ViewContext, htmlHelper.ViewDataContainer.FilterContainer(prefix)); - return filteredHtmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); - } + //if there's a prefix applied, we need to create a new html helper with a filtered ModelState collection so that it only looks for + //specific model state with the prefix. + var filteredHtmlHelper = new HtmlHelper(htmlHelper.ViewContext, htmlHelper.ViewDataContainer.FilterContainer(prefix)); + return filteredHtmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); + } - /// - /// Returns the result of a child action of a strongly typed SurfaceController - /// - /// - /// - /// - /// - public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName) + /// + /// Returns the result of a child action of a strongly typed SurfaceController + /// + /// + /// + /// + /// + public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName) where T : SurfaceController { return htmlHelper.Action(actionName, typeof(T)); @@ -170,13 +171,12 @@ namespace Umbraco.Web /// public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName, Type surfaceType) { - Mandate.ParameterNotNull(surfaceType, "surfaceType"); - Mandate.ParameterNotNullOrEmpty(actionName, "actionName"); + if (surfaceType == null) throw new ArgumentNullException(nameof(surfaceType)); + if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName)); var routeVals = new RouteValueDictionary(new {area = ""}); - var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers - .SingleOrDefault(x => x == surfaceType); + var surfaceController = Current.SurfaceControllerTypes.SingleOrDefault(x => x == surfaceType); if (surfaceController == null) throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); var metaData = PluginController.GetMetadata(surfaceController); @@ -189,7 +189,7 @@ namespace Umbraco.Web } else { - routeVals.Add("area", metaData.AreaName); + routeVals.Add("area", metaData.AreaName); } } @@ -268,47 +268,47 @@ namespace Umbraco.Web /// Used for rendering out the Form for BeginUmbracoForm /// internal class UmbracoForm : MvcForm - { - /// - /// Creates an UmbracoForm - /// - /// - /// - /// - /// - /// - /// - public UmbracoForm( - ViewContext viewContext, - string controllerName, - string controllerAction, - string area, + { + /// + /// Creates an UmbracoForm + /// + /// + /// + /// + /// + /// + /// + public UmbracoForm( + ViewContext viewContext, + string controllerName, + string controllerAction, + string area, FormMethod method, - object additionalRouteVals = null) - : base(viewContext) - { - _viewContext = viewContext; - _method = method; + object additionalRouteVals = null) + : base(viewContext) + { + _viewContext = viewContext; + _method = method; _encryptedString = UmbracoHelper.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); - } + } - private readonly ViewContext _viewContext; - private readonly FormMethod _method; - private bool _disposed; - private readonly string _encryptedString; + private readonly ViewContext _viewContext; + private readonly FormMethod _method; + private bool _disposed; + private readonly string _encryptedString; - protected override void Dispose(bool disposing) - { - if (this._disposed) - return; - this._disposed = true; + protected override void Dispose(bool disposing) + { + if (this._disposed) + return; + this._disposed = true; //write out the hidden surface form routes _viewContext.Writer.Write(""); - base.Dispose(disposing); - } - } + base.Dispose(disposing); + } + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller @@ -323,17 +323,17 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, null, new Dictionary(), method); } - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName) - { - return html.BeginUmbracoForm(action, controllerName, null, new Dictionary()); - } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName) + { + return html.BeginUmbracoForm(action, controllerName, null, new Dictionary()); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller @@ -349,18 +349,18 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary(), method); } - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals) - { - return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary()); - } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary()); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller @@ -381,20 +381,20 @@ namespace Umbraco.Web } /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - object htmlAttributes) - { + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + object htmlAttributes) + { return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); - } + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller @@ -411,30 +411,30 @@ namespace Umbraco.Web IDictionary htmlAttributes, FormMethod method) { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + if (string.IsNullOrWhiteSpace(action)) throw new ArgumentNullOrEmptyException(nameof(action)); + if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes, method); } /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - IDictionary htmlAttributes) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + IDictionary htmlAttributes) + { + if (string.IsNullOrWhiteSpace(action)) throw new ArgumentNullOrEmptyException(nameof(action)); + if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); - return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes); - } + return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin @@ -449,17 +449,17 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary(), method); } - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType) - { - return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary()); - } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType) + { + return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary()); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin @@ -475,18 +475,18 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, typeof(T), method); } - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T)); - } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T)); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin @@ -503,19 +503,19 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary(), method); } - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals) - { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary()); - } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary()); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin @@ -532,19 +532,19 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, method); } - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals); - } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin @@ -565,20 +565,20 @@ namespace Umbraco.Web } /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - object htmlAttributes) - { + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + object htmlAttributes) + { return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); - } + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin @@ -600,21 +600,21 @@ namespace Umbraco.Web } /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - object htmlAttributes) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); - } + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + object htmlAttributes) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin @@ -631,13 +631,12 @@ namespace Umbraco.Web IDictionary htmlAttributes, FormMethod method) { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNull(surfaceType, "surfaceType"); + if (string.IsNullOrWhiteSpace(action)) throw new ArgumentNullOrEmptyException(nameof(action)); + if (surfaceType == null) throw new ArgumentNullException(nameof(surfaceType)); var area = ""; - var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers - .SingleOrDefault(x => x == surfaceType); + var surfaceController = Current.SurfaceControllerTypes.SingleOrDefault(x => x == surfaceType); if (surfaceController == null) throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); var metaData = PluginController.GetMetadata(surfaceController); @@ -650,17 +649,17 @@ namespace Umbraco.Web } /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - IDictionary htmlAttributes) + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + IDictionary htmlAttributes) { return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes, FormMethod.Post); } @@ -685,21 +684,21 @@ namespace Umbraco.Web } /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - IDictionary htmlAttributes) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); - } + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + IDictionary htmlAttributes) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin @@ -715,18 +714,18 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary(), method); } - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area) - { - return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary()); - } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area) + { + return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary()); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin @@ -744,153 +743,153 @@ namespace Umbraco.Web IDictionary htmlAttributes, FormMethod method) { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + if (string.IsNullOrEmpty(action)) throw new ArgumentNullOrEmptyException(nameof(action)); + if (string.IsNullOrEmpty(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); var formAction = UmbracoContext.Current.OriginalRequestUrl.PathAndQuery; return html.RenderForm(formAction, method, htmlAttributes, controllerName, action, area, additionalRouteVals); } /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, - object additionalRouteVals, - IDictionary htmlAttributes) + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, + object additionalRouteVals, + IDictionary htmlAttributes) { return html.BeginUmbracoForm(action, controllerName, area, additionalRouteVals, htmlAttributes, FormMethod.Post); } - /// - /// This renders out the form for us - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// This code is pretty much the same as the underlying MVC code that writes out the form - /// - private static MvcForm RenderForm(this HtmlHelper htmlHelper, - string formAction, - FormMethod method, - IDictionary htmlAttributes, - string surfaceController, - string surfaceAction, - string area, - object additionalRouteVals = null) - { + /// + /// This renders out the form for us + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// This code is pretty much the same as the underlying MVC code that writes out the form + /// + private static MvcForm RenderForm(this HtmlHelper htmlHelper, + string formAction, + FormMethod method, + IDictionary htmlAttributes, + string surfaceController, + string surfaceAction, + string area, + object additionalRouteVals = null) + { - //ensure that the multipart/form-data is added to the html attributes - if (htmlAttributes.ContainsKey("enctype") == false) - { - htmlAttributes.Add("enctype", "multipart/form-data"); - } + //ensure that the multipart/form-data is added to the html attributes + if (htmlAttributes.ContainsKey("enctype") == false) + { + htmlAttributes.Add("enctype", "multipart/form-data"); + } - var tagBuilder = new TagBuilder("form"); - tagBuilder.MergeAttributes(htmlAttributes); - // action is implicitly generated, so htmlAttributes take precedence. - tagBuilder.MergeAttribute("action", formAction); - // method is an explicit parameter, so it takes precedence over the htmlAttributes. - tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true); - var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled == false; - if (traditionalJavascriptEnabled) - { - // forms must have an ID for client validation - tagBuilder.GenerateId("form" + Guid.NewGuid().ToString("N")); - } - htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); + var tagBuilder = new TagBuilder("form"); + tagBuilder.MergeAttributes(htmlAttributes); + // action is implicitly generated, so htmlAttributes take precedence. + tagBuilder.MergeAttribute("action", formAction); + // method is an explicit parameter, so it takes precedence over the htmlAttributes. + tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true); + var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled == false; + if (traditionalJavascriptEnabled) + { + // forms must have an ID for client validation + tagBuilder.GenerateId("form" + Guid.NewGuid().ToString("N")); + } + htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); - //new UmbracoForm: - var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, method, additionalRouteVals); + //new UmbracoForm: + var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, method, additionalRouteVals); - if (traditionalJavascriptEnabled) - { - htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"]; - } - return theForm; - } + if (traditionalJavascriptEnabled) + { + htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"]; + } + return theForm; + } - #endregion - - #region Wrap + #endregion - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, params IHtmlTagWrapper[] children) - { - var item = html.Wrap(tag, innerText, (object)null); - foreach (var child in children) - { - item.AddChild(child); - } - return item; - } + #region Wrap - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - string innerText = null; - if (inner != null && inner.GetType() != typeof(DynamicNull)) - { - innerText = string.Format("{0}", inner); - } - var item = html.Wrap(tag, innerText, anonymousAttributes); - foreach (var child in children) - { - item.AddChild(child); - } - return item; - } - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner) - { - string innerText = null; - if (inner != null && inner.GetType() != typeof(DynamicNull)) - { - innerText = string.Format("{0}", inner); - } - return html.Wrap(tag, innerText, (object)null); - } + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, params IHtmlTagWrapper[] children) + { + var item = html.Wrap(tag, innerText, (object)null); + foreach (var child in children) + { + item.AddChild(child); + } + return item; + } - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - var wrap = new HtmlTagWrapper(tag); - if (anonymousAttributes != null) - { - wrap.ReflectAttributesFromAnonymousType(anonymousAttributes); - } - if (!string.IsNullOrWhiteSpace(innerText)) - { - wrap.AddChild(new HtmlTagWrapperTextNode(innerText)); - } - foreach (var child in children) - { - wrap.AddChild(child); - } - return wrap; - } + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + string innerText = null; + if (inner != null) + { + innerText = string.Format("{0}", inner); + } + var item = html.Wrap(tag, innerText, anonymousAttributes); + foreach (var child in children) + { + item.AddChild(child); + } + return item; + } + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner) + { + string innerText = null; + if (inner != null) + { + innerText = string.Format("{0}", inner); + } + return html.Wrap(tag, innerText, (object)null); + } - public static HtmlTagWrapper Wrap(this HtmlHelper html, bool visible, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - var item = html.Wrap(tag, innerText, anonymousAttributes, children); - item.Visible = visible; - return item; - } + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + var wrap = new HtmlTagWrapper(tag); + if (anonymousAttributes != null) + { + wrap.ReflectAttributesFromAnonymousType(anonymousAttributes); + } + if (!string.IsNullOrWhiteSpace(innerText)) + { + wrap.AddChild(new HtmlTagWrapperTextNode(innerText)); + } + foreach (var child in children) + { + wrap.AddChild(child); + } + return wrap; + } + + public static HtmlTagWrapper Wrap(this HtmlHelper html, bool visible, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + var item = html.Wrap(tag, innerText, anonymousAttributes, children); + item.Visible = visible; + return item; + } #endregion #region canvasdesigner - public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, + public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, UrlHelper url, UmbracoContext umbCtx) { @@ -908,7 +907,7 @@ namespace Umbraco.Web UrlHelper url, UmbracoContext umbCtx, string canvasdesignerConfigPath, string canvasdesignerPalettesPath) { - + var umbracoPath = url.Content(SystemDirectories.Umbraco); string previewLink = @"" + @@ -920,18 +919,18 @@ namespace Umbraco.Web string noPreviewLinks = @""; // Get page value - int pageId = umbCtx.PublishedContentRequest.UmbracoPage.PageID; - string[] path = umbCtx.PublishedContentRequest.UmbracoPage.SplitPath; + int pageId = umbCtx.PublishedRequest.UmbracoPage.PageID; + string[] path = umbCtx.PublishedRequest.UmbracoPage.SplitPath; string result = string.Empty; string cssPath = CanvasDesignerUtility.GetStylesheetPath(path, false); if (umbCtx.InPreviewMode) { - canvasdesignerConfigPath = string.IsNullOrEmpty(canvasdesignerConfigPath) == false - ? canvasdesignerConfigPath + canvasdesignerConfigPath = string.IsNullOrEmpty(canvasdesignerConfigPath) == false + ? canvasdesignerConfigPath : string.Format("{0}/js/canvasdesigner.config.js", umbracoPath); - canvasdesignerPalettesPath = string.IsNullOrEmpty(canvasdesignerPalettesPath) == false - ? canvasdesignerPalettesPath + canvasdesignerPalettesPath = string.IsNullOrEmpty(canvasdesignerPalettesPath) == false + ? canvasdesignerPalettesPath : string.Format("{0}/js/canvasdesigner.palettes.js", umbracoPath); if (string.IsNullOrEmpty(cssPath) == false) diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs index 2caeb012bc..26ee4c6557 100644 --- a/src/Umbraco.Web/HtmlStringUtilities.cs +++ b/src/Umbraco.Web/HtmlStringUtilities.cs @@ -11,11 +11,8 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web { /// - /// Utility class for working with strings and HTML in views + /// Provides utility methods for UmbracoHelper for working with strings and html in views. /// - /// - /// The UmbracoHelper uses this class for it's string methods - /// public sealed class HtmlStringUtilities { /// @@ -32,7 +29,7 @@ namespace Umbraco.Web { var doc = new HtmlDocument(); doc.LoadHtml("

" + html + "

"); - + var targets = new List(); var nodes = doc.DocumentNode.FirstChild.SelectNodes(".//*"); @@ -61,29 +58,36 @@ namespace Umbraco.Web return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml.Replace(" ", " ")); } - internal string Join(string seperator, params object[] args) + internal string Join(string separator, params object[] args) { - var results = args.Where(arg => arg != null && arg.GetType() != typeof(TIgnore)).Select(arg => string.Format("{0}", arg)).Where(sArg => !string.IsNullOrWhiteSpace(sArg)).ToList(); - return string.Join(seperator, results); + var results = args + .Where(x => x != null) + .Select(x => x.ToString()) + .Where(x => string.IsNullOrWhiteSpace(x) == false); + return string.Join(separator, results); } - internal string Concatenate(params object[] args) + internal string Concatenate(params object[] args) { - var result = new StringBuilder(); - foreach (var sArg in args.Where(arg => arg != null && arg.GetType() != typeof(TIgnore)).Select(arg => string.Format("{0}", arg)).Where(sArg => !string.IsNullOrWhiteSpace(sArg))) + var sb = new StringBuilder(); + foreach (var arg in args + .Where(x => x != null) + .Select(x => x.ToString()) + .Where(x => string.IsNullOrWhiteSpace(x) == false)) { - result.Append(sArg); + sb.Append(arg); } - return result.ToString(); + return sb.ToString(); } - internal string Coalesce(params object[] args) + internal string Coalesce(params object[] args) { - foreach (var sArg in args.Where(arg => arg != null && arg.GetType() != typeof(TIgnore)).Select(arg => string.Format("{0}", arg)).Where(sArg => !string.IsNullOrWhiteSpace(sArg))) - { - return sArg; - } - return string.Empty; + var arg = args + .Where(x => x != null) + .Select(x => x.ToString()) + .FirstOrDefault(x => string.IsNullOrWhiteSpace(x) == false); + + return arg ?? string.Empty; } public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) diff --git a/src/Umbraco.Web/HttpContextUmbracoContextAccessor.cs b/src/Umbraco.Web/HttpContextUmbracoContextAccessor.cs new file mode 100644 index 0000000000..5a05ee85ff --- /dev/null +++ b/src/Umbraco.Web/HttpContextUmbracoContextAccessor.cs @@ -0,0 +1,32 @@ +using System; + +namespace Umbraco.Web +{ + internal class HttpContextUmbracoContextAccessor : IUmbracoContextAccessor + { + private const string HttpContextItemKey = "Umbraco.Web.UmbracoContext"; + private readonly IHttpContextAccessor _httpContextAccessor; + + public HttpContextUmbracoContextAccessor(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public UmbracoContext UmbracoContext + { + get + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) throw new Exception("oops:httpContext"); + return (UmbracoContext) httpContext.Items[HttpContextItemKey]; + } + + set + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) throw new Exception("oops:httpContext"); + httpContext.Items[HttpContextItemKey] = value; + } + } + } +} diff --git a/src/Umbraco.Web/HttpCookieExtensions.cs b/src/Umbraco.Web/HttpCookieExtensions.cs index d2133927a0..60c807ed66 100644 --- a/src/Umbraco.Web/HttpCookieExtensions.cs +++ b/src/Umbraco.Web/HttpCookieExtensions.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web ///
/// /// This should 100% supercede the StateManager.Cookies - /// + /// internal static class HttpCookieExtensions { /// @@ -23,7 +23,7 @@ namespace Umbraco.Web /// /// /// - /// Adapted from: https://stackoverflow.com/a/29057304/5018 because there's an issue with .NET WebApi cookie parsing logic + /// Adapted from: https://stackoverflow.com/a/29057304/5018 because there's an issue with .NET WebApi cookie parsing logic /// when using requestHeaders.GetCookies() when an invalid cookie name is present. /// public static string GetCookieValue(this HttpRequestHeaders requestHeaders, string cookieName) @@ -32,7 +32,7 @@ namespace Umbraco.Web { if (header.Key.Equals("Cookie", StringComparison.InvariantCultureIgnoreCase) == false) continue; - + var cookiesHeaderValue = header.Value.FirstOrDefault(); if (cookiesHeaderValue == null) return null; @@ -48,7 +48,7 @@ namespace Umbraco.Web } return null; - } + } /// /// Removes the cookie from the request and the response if it exists @@ -74,6 +74,16 @@ namespace Umbraco.Web } } + /// + /// Removes the cookie from the request and the response if it exists + /// + /// + /// + public static void ExpireCookie(this HttpContext http, string cookieName) + { + new HttpContextWrapper(http).ExpireCookie(cookieName); + } + public static string GetPreviewCookieValue(this HttpRequestMessage request) { var cookie = request.Headers.GetCookies(Constants.Web.PreviewCookieName).FirstOrDefault(); @@ -87,6 +97,16 @@ namespace Umbraco.Web return null; } + public static string GetPreviewCookieValue(this HttpRequestBase request) + { + return request.GetCookieValue(Constants.Web.PreviewCookieName); + } + + public static string GetPreviewCookieValue(this HttpRequest request) + { + return new HttpRequestWrapper(request).GetPreviewCookieValue(); + } + /// /// Does a preview cookie exist ? /// @@ -97,6 +117,16 @@ namespace Umbraco.Web return request.Cookies[Constants.Web.PreviewCookieName] != null; } + /// + /// Does a preview cookie exist ? + /// + /// + /// + public static bool HasPreviewCookie(this HttpRequest request) + { + return new HttpRequestWrapper(request).HasPreviewCookie(); + } + /// /// Does a preview cookie exist ? /// @@ -107,6 +137,36 @@ namespace Umbraco.Web return request.Cookies[Constants.Web.PreviewCookieName] != null; } + /// + /// Returns the cookie's string value + /// + /// + /// + /// + public static string GetCookieValue(this HttpRequestBase request, string cookieName) + { + var cookie = request.Cookies.Get(cookieName); + if (cookie != null) + { + if (cookie.Value.IsNullOrWhiteSpace() == false) + { + return cookie.Value; + } + } + return null; + } + + /// + /// Returns the cookie's string value + /// + /// + /// + /// + public static string GetCookieValue(this HttpRequest request, string cookieName) + { + return new HttpRequestWrapper(request).GetCookieValue(cookieName); + } + /// /// Does a cookie exist with the specified key ? /// @@ -118,6 +178,17 @@ namespace Umbraco.Web return request.Cookies[key] != null; } + /// + /// Does a cookie exist with the specified key ? + /// + /// + /// + /// + public static bool HasCookie(this HttpRequest request, string key) + { + return request.Cookies[key] != null; + } + /// /// Is there a cookie with the key supplied and does it have a value that is not empty /// @@ -130,5 +201,16 @@ namespace Umbraco.Web && request.Cookies[key].Value != null && request.Cookies[key].Value.IsNullOrWhiteSpace() == false; } + + /// + /// Is there a cookie with the key supplied and does it have a value that is not empty + /// + /// + /// + /// + public static bool HasCookieValue(this HttpRequest request, string key) + { + return new HttpRequestWrapper(request).HasCookieValue(key); + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HttpRequestExtensions.cs b/src/Umbraco.Web/HttpRequestExtensions.cs index d7f9e409c8..677e85b2b2 100644 --- a/src/Umbraco.Web/HttpRequestExtensions.cs +++ b/src/Umbraco.Web/HttpRequestExtensions.cs @@ -7,77 +7,77 @@ using Umbraco.Core; namespace Umbraco.Web { /// - /// Extension methods for the HttpRequest and HttpRequestBase objects - /// - public static class HttpRequestExtensions - { + /// Extension methods for the HttpRequest and HttpRequestBase objects + /// + public static class HttpRequestExtensions + { /// /// Extracts the value from the query string and cleans it to prevent xss attacks. /// /// /// - /// + /// public static string CleanForXss(this HttpRequest request, string key) { var item = request.GetItemAsString(key); return item.CleanForXss(); } - /// - /// Safely get a request item as string, if the item does not exist, an empty string is returned. - /// - /// - /// - /// The value to return if the key is not found in the collection - /// - public static string GetItemAsString(this HttpRequest request, string key, string valueIfNotFound = "") - { - return new HttpRequestWrapper(request).GetItemAsString(key, valueIfNotFound); - } + /// + /// Safely get a request item as string, if the item does not exist, an empty string is returned. + /// + /// + /// + /// The value to return if the key is not found in the collection + /// + public static string GetItemAsString(this HttpRequest request, string key, string valueIfNotFound = "") + { + return new HttpRequestWrapper(request).GetItemAsString(key, valueIfNotFound); + } - /// - /// Safely get a request item as string, if the item does not exist, an empty string is returned. - /// - /// - /// - /// The value to return if the key is not found in the collection - /// - public static string GetItemAsString(this HttpRequestBase request, string key, string valueIfNotFound = "") - { - var val = request[key]; - return !val.IsNullOrWhiteSpace() ? val : valueIfNotFound; - } + /// + /// Safely get a request item as string, if the item does not exist, an empty string is returned. + /// + /// + /// + /// The value to return if the key is not found in the collection + /// + public static string GetItemAsString(this HttpRequestBase request, string key, string valueIfNotFound = "") + { + var val = request[key]; + return !val.IsNullOrWhiteSpace() ? val : valueIfNotFound; + } - /// - /// Safely get the item from the query string and convert it to type 'T', otherwise will return default(T). - /// - /// - /// - /// - /// - public static T GetItemAs(this HttpRequestBase request, string key) - { - var val = request[key]; - var whitespaceCheck = !val.IsNullOrWhiteSpace() ? val : string.Empty; - if (whitespaceCheck.IsNullOrWhiteSpace()) - return (T) typeof (T).GetDefaultValue(); - var attempt = val.TryConvertTo(); - if (attempt.Success) - return attempt.Result; - return (T)typeof(T).GetDefaultValue(); - } + /// + /// Safely get the item from the query string and convert it to type 'T', otherwise will return default(T). + /// + /// + /// + /// + /// + public static T GetItemAs(this HttpRequestBase request, string key) + { + var val = request[key]; + var whitespaceCheck = !val.IsNullOrWhiteSpace() ? val : string.Empty; + if (whitespaceCheck.IsNullOrWhiteSpace()) + return (T) typeof (T).GetDefaultValue(); + var attempt = val.TryConvertTo(); + if (attempt.Success) + return attempt.Result; + return (T)typeof(T).GetDefaultValue(); + } - /// - /// Safely get the item from the query string and convert it to type 'T', otherwise will return default(T). - /// - /// - /// - /// - /// - public static T GetItemAs(this HttpRequest request, string key) - { - return new HttpRequestWrapper(request).GetItemAs(key); - } + /// + /// Safely get the item from the query string and convert it to type 'T', otherwise will return default(T). + /// + /// + /// + /// + /// + public static T GetItemAs(this HttpRequest request, string key) + { + return new HttpRequestWrapper(request).GetItemAs(key); + } - } + } } diff --git a/src/Umbraco.Web/HttpUrlHelperExtensions.cs b/src/Umbraco.Web/HttpUrlHelperExtensions.cs index d3e87a6bf7..113d7b3351 100644 --- a/src/Umbraco.Web/HttpUrlHelperExtensions.cs +++ b/src/Umbraco.Web/HttpUrlHelperExtensions.cs @@ -1,8 +1,10 @@ -using System; +using System; using System.Linq; using System.Linq.Expressions; using System.Web.Http.Routing; using Umbraco.Core; +using Umbraco.Core.Exceptions; +using Umbraco.Web.Composing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -36,7 +38,7 @@ namespace Umbraco.Web if (methodParams.Any() == false) { - return url.GetUmbracoApiService(method.Name); + return url.GetUmbracoApiService(method.Name); } return url.GetUmbracoApiService(method.Name, methodParams.Values.First()); } @@ -51,12 +53,12 @@ namespace Umbraco.Web /// public static string GetUmbracoApiService(this UrlHelper url, string actionName, Type apiControllerType, object id = null) { - Mandate.ParameterNotNullOrEmpty(actionName, "actionName"); - Mandate.ParameterNotNull(apiControllerType, "apiControllerType"); + if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName)); + if (apiControllerType == null) throw new ArgumentNullException(nameof(apiControllerType)); var area = ""; - var apiController = UmbracoApiControllerResolver.Current.RegisteredUmbracoApiControllers + var apiController = Current.UmbracoApiControllerTypes .SingleOrDefault(x => x == apiControllerType); if (apiController == null) throw new InvalidOperationException("Could not find the umbraco api controller of type " + apiControllerType.FullName); @@ -93,8 +95,8 @@ namespace Umbraco.Web /// public static string GetUmbracoApiService(this UrlHelper url, string actionName, string controllerName, string area, object id = null) { - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - Mandate.ParameterNotNullOrEmpty(actionName, "actionName"); + if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName)); + if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); string routeName; if (area.IsNullOrWhiteSpace()) @@ -139,7 +141,7 @@ namespace Umbraco.Web public static string GetUmbracoApiServiceBaseUrl(this UrlHelper url, Expression> methodSelector) where T : UmbracoApiController { - var method = Core.ExpressionHelper.GetMethodInfo(methodSelector); + var method = ExpressionHelper.GetMethodInfo(methodSelector); if (method == null) { throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) + " or the result "); @@ -147,4 +149,4 @@ namespace Umbraco.Web return url.GetUmbracoApiService(method.Name).TrimEnd(method.Name); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/HybridAccessorBase.cs b/src/Umbraco.Web/HybridAccessorBase.cs new file mode 100644 index 0000000000..669b929fd6 --- /dev/null +++ b/src/Umbraco.Web/HybridAccessorBase.cs @@ -0,0 +1,93 @@ +using System; +using System.Runtime.Remoting.Messaging; +using Umbraco.Core; + +namespace Umbraco.Web +{ + internal abstract class HybridAccessorBase + where T : class + { + // ReSharper disable StaticMemberInGenericType + private static readonly object Locker = new object(); + private static bool _registered; + // ReSharper restore StaticMemberInGenericType + + private readonly IHttpContextAccessor _httpContextAccessor; + + protected abstract string ItemKey { get; } + + // read + // http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html + // http://stackoverflow.com/questions/14176028/why-does-logicalcallcontext-not-work-with-async + // http://stackoverflow.com/questions/854976/will-values-in-my-threadstatic-variables-still-be-there-when-cycled-via-threadpo + // https://msdn.microsoft.com/en-us/library/dd642243.aspx?f=255&MSPPError=-2147217396 ThreadLocal + // http://stackoverflow.com/questions/29001266/cleaning-up-callcontext-in-tpl clearing call context + // + // anything that is ThreadStatic will stay with the thread and NOT flow in async threads + // the only thing that flows is the logical call context (safe in 4.5+) + + // no! + //[ThreadStatic] + //private static T _value; + + // yes! flows with async! + private T NonContextValue + { + get { return (T) CallContext.LogicalGetData(ItemKey); } + set + { + if (value == null) CallContext.FreeNamedDataSlot(ItemKey); + else CallContext.LogicalSetData(ItemKey, value); + } + } + + protected HybridAccessorBase(IHttpContextAccessor httpContextAccessor) + { + if (httpContextAccessor == null) throw new ArgumentNullException(nameof(httpContextAccessor)); + _httpContextAccessor = httpContextAccessor; + + lock (Locker) + { + // register the itemKey once with SafeCallContext + if (_registered) return; + _registered = true; + } + + // ReSharper disable once VirtualMemberCallInConstructor + var itemKey = ItemKey; // virtual + SafeCallContext.Register(() => + { + var value = CallContext.LogicalGetData(itemKey); + CallContext.FreeNamedDataSlot(itemKey); + return value; + }, o => + { + if (o == null) return; + var value = o as T; + if (value == null) throw new ArgumentException($"Expected type {typeof(T).FullName}, got {o.GetType().FullName}", nameof(o)); + CallContext.LogicalSetData(itemKey, value); + }); + } + + protected T Value + { + get + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) return NonContextValue; + return (T) httpContext.Items[ItemKey]; + } + + set + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) + NonContextValue = value; + else if (value == null) + httpContext.Items.Remove(ItemKey); + else + httpContext.Items[ItemKey] = value; + } + } + } +} diff --git a/src/Umbraco.Web/HybridEventMessagesAccessor.cs b/src/Umbraco.Web/HybridEventMessagesAccessor.cs new file mode 100644 index 0000000000..fddde403d8 --- /dev/null +++ b/src/Umbraco.Web/HybridEventMessagesAccessor.cs @@ -0,0 +1,19 @@ +using Umbraco.Core.Events; + +namespace Umbraco.Web +{ + internal class HybridEventMessagesAccessor : HybridAccessorBase, IEventMessagesAccessor + { + protected override string ItemKey => "Umbraco.Core.Events.HybridEventMessagesAccessor"; + + public HybridEventMessagesAccessor(IHttpContextAccessor httpContextAccessor) + : base(httpContextAccessor) + { } + + public EventMessages EventMessages + { + get { return Value; } + set { Value = value; } + } + } +} diff --git a/src/Umbraco.Web/HybridUmbracoContextAccessor.cs b/src/Umbraco.Web/HybridUmbracoContextAccessor.cs new file mode 100644 index 0000000000..e8a07b0f4c --- /dev/null +++ b/src/Umbraco.Web/HybridUmbracoContextAccessor.cs @@ -0,0 +1,17 @@ +namespace Umbraco.Web +{ + internal class HybridUmbracoContextAccessor : HybridAccessorBase, IUmbracoContextAccessor + { + public HybridUmbracoContextAccessor(IHttpContextAccessor httpContextAccessor) + : base(httpContextAccessor) + { } + + protected override string ItemKey => "Umbraco.Web.HybridUmbracoContextAccessor"; + + public UmbracoContext UmbracoContext + { + get { return Value; } + set { Value = value; } + } + } +} diff --git a/src/Umbraco.Web/IDynamicPublishedContentQuery.cs b/src/Umbraco.Web/IDynamicPublishedContentQuery.cs deleted file mode 100644 index 7c95fd7e2e..0000000000 --- a/src/Umbraco.Web/IDynamicPublishedContentQuery.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.XPath; -using Umbraco.Core.Xml; - -namespace Umbraco.Web -{ - /// - /// Query methods used for accessing content dynamically in templates - /// - public interface IDynamicPublishedContentQuery - { - dynamic Content(int id); - dynamic Content(Guid id); - dynamic ContentSingleAtXPath(string xpath, params XPathVariable[] vars); - dynamic ContentSingleAtXPath(XPathExpression xpath, params XPathVariable[] vars); - dynamic Content(IEnumerable ids); - dynamic Content(IEnumerable ids); - dynamic ContentAtXPath(string xpath, params XPathVariable[] vars); - dynamic ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); - dynamic ContentAtRoot(); - - // note: we CANNOT implement Media by Guid in v7 without break-changing IPublishedCache, - // since we don't support XPath navigation of the media tree. - - dynamic Media(int id); - //dynamic Media(Guid id); - dynamic Media(IEnumerable ids); - //dynamic Media(IEnumerable ids); - dynamic MediaAtRoot(); - - /// - /// Searches content - /// - /// - /// - /// - /// - dynamic Search(string term, bool useWildCards = true, string searchProvider = null); - - /// - /// Searhes content - /// - /// - /// - /// - dynamic Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null); - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/IHttpContextAccessor.cs b/src/Umbraco.Web/IHttpContextAccessor.cs index 4b5a8ed884..a00632803d 100644 --- a/src/Umbraco.Web/IHttpContextAccessor.cs +++ b/src/Umbraco.Web/IHttpContextAccessor.cs @@ -1,11 +1,9 @@ +using System.Web; + namespace Umbraco.Web { - /// - /// Used to retrieve the HttpContext - /// - /// - /// NOTE: This has a singleton lifespan - /// - public interface IHttpContextAccessor : Core.IHttpContextAccessor - { } -} \ No newline at end of file + public interface IHttpContextAccessor + { + HttpContext HttpContext { get; set; } + } +} diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs new file mode 100644 index 0000000000..055dcbe181 --- /dev/null +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Xml.XPath; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Xml; + +namespace Umbraco.Web +{ + using Examine = global::Examine; + + /// + /// Query methods used for accessing strongly typed content in templates + /// + public interface IPublishedContentQuery + { + IPublishedContent Content(int id); + IPublishedContent Content(Guid id); + IPublishedContent Content(Udi id); + IPublishedContent ContentSingleAtXPath(string xpath, params XPathVariable[] vars); + IEnumerable Content(IEnumerable ids); + IEnumerable Content(IEnumerable ids); + IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars); + IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); + IEnumerable ContentAtRoot(); + + IPublishedContent Media(int id); + IPublishedContent Media(Guid id); + IPublishedContent Media(Udi id); + IEnumerable Media(IEnumerable ids); + IEnumerable Media(IEnumerable ids); + IEnumerable MediaAtRoot(); + + /// + /// Searches content. + /// + IEnumerable Search(string term, bool useWildCards = true, string indexName = null); + + /// + /// Searches content. + /// + IEnumerable Search(int skip, int take, out int totalRecords, string term, bool useWildCards = true, string indexName = null); + + /// + /// Searches content. + /// + IEnumerable Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.ISearcher searchProvider = null); + + /// + /// Searches content. + /// + IEnumerable Search(int skip, int take, out int totalRecords, Examine.SearchCriteria.ISearchCriteria criteria, Examine.ISearcher searchProvider = null); + } +} diff --git a/src/Umbraco.Web/ITagQuery.cs b/src/Umbraco.Web/ITagQuery.cs index 48098e1904..3d0757a695 100644 --- a/src/Umbraco.Web/ITagQuery.cs +++ b/src/Umbraco.Web/ITagQuery.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Models; namespace Umbraco.Web @@ -79,4 +80,4 @@ namespace Umbraco.Web /// IEnumerable GetTagsForEntity(int contentId, string tagGroup = null); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/ITypedPublishedContentQuery.cs b/src/Umbraco.Web/ITypedPublishedContentQuery.cs deleted file mode 100644 index f075727b5c..0000000000 --- a/src/Umbraco.Web/ITypedPublishedContentQuery.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.XPath; -using Umbraco.Core.Models; -using Umbraco.Core.Xml; - -namespace Umbraco.Web -{ - /// - /// Query methods used for accessing strongly typed content in templates - /// - public interface ITypedPublishedContentQuery - { - /// - /// Gets a content item from the cache - /// - /// - /// - IPublishedContent TypedContent(int id); - - /// - /// Gets a content item from the cache - /// - /// - /// - IPublishedContent TypedContent(Guid id); - - IPublishedContent TypedContentSingleAtXPath(string xpath, params XPathVariable[] vars); - IEnumerable TypedContent(IEnumerable ids); - IEnumerable TypedContent(IEnumerable ids); - IEnumerable TypedContentAtXPath(string xpath, params XPathVariable[] vars); - IEnumerable TypedContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); - IEnumerable TypedContentAtRoot(); - - // TODO: we CANNOT implement TypedMedia by Guid in v7 without break-changing IPublishedCache, since we don't support XPath navigation of the media tree. - // surely there is a way we can support this without XPath, it's needed so we can query properly by UDI - - IPublishedContent TypedMedia(int id); - //IPublishedContent TypedMedia(Guid id); - IEnumerable TypedMedia(IEnumerable ids); - //IEnumerable TypedMedia(IEnumerable ids); - IEnumerable TypedMediaAtRoot(); - - /// - /// Searches content - /// - /// - /// - /// - /// - IEnumerable TypedSearch(string term, bool useWildCards = true, string searchProvider = null); - - /// - /// Searches content - /// - /// - /// - /// - /// - /// - /// - /// - IEnumerable TypedSearch(int skip, int take, out int totalRecords, string term, bool useWildCards = true, string searchProvider = null); - - /// - /// Searhes content - /// - /// - /// - /// - IEnumerable TypedSearch(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null); - - /// - /// Searhes content - /// - /// - /// - /// - /// - /// - /// - IEnumerable TypedSearch(int skip, int take, out int totalrecords, Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null); - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/IUmbracoComponentRenderer.cs b/src/Umbraco.Web/IUmbracoComponentRenderer.cs index aaff577491..dc91cacdc0 100644 --- a/src/Umbraco.Web/IUmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/IUmbracoComponentRenderer.cs @@ -1,6 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Web; -using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web { @@ -39,33 +39,5 @@ namespace Umbraco.Web /// The parameters. /// IHtmlString RenderMacro(string alias, IDictionary parameters); - - /// - /// Renders an field to the template - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - //// - /// - IHtmlString Field(IPublishedContent currentPage, string fieldAlias, - string altFieldAlias = "", string altText = "", string insertBefore = "", string insertAfter = "", - bool recursive = false, bool convertLineBreaks = false, bool removeParagraphTags = false, - RenderFieldCaseType casing = RenderFieldCaseType.Unchanged, - RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, - bool formatAsDate = false, - bool formatAsDateWithTime = false, - string formatAsDateWithTimeSeparator = ""); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/IUmbracoContextAccessor.cs b/src/Umbraco.Web/IUmbracoContextAccessor.cs index 8111606602..74df940865 100644 --- a/src/Umbraco.Web/IUmbracoContextAccessor.cs +++ b/src/Umbraco.Web/IUmbracoContextAccessor.cs @@ -1,13 +1,10 @@ -namespace Umbraco.Web +namespace Umbraco.Web { /// - /// Used to retrieve the Umbraco context + /// Provides access to UmbracoContext. /// - /// - /// NOTE: This has a singleton lifespan - /// public interface IUmbracoContextAccessor { - UmbracoContext Value { get; } + UmbracoContext UmbracoContext { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/ImageCropperBaseExtensions.cs b/src/Umbraco.Web/ImageCropperBaseExtensions.cs deleted file mode 100644 index f30437705e..0000000000 --- a/src/Umbraco.Web/ImageCropperBaseExtensions.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Web.Models; - -namespace Umbraco.Web -{ - internal static class ImageCropperBaseExtensions - { - internal static ImageCropDataSet DeserializeToCropDataSet(this string json) - { - var imageCrops = new ImageCropDataSet(); - if (json.DetectIsJson()) - { - try - { - imageCrops = JsonConvert.DeserializeObject(json, new JsonSerializerSettings - { - Culture = CultureInfo.InvariantCulture, - FloatParseHandling = FloatParseHandling.Decimal - }); - } - catch (Exception ex) - { - LogHelper.Error(typeof(ImageCropperBaseExtensions), "Could not parse the json string: " + json, ex); - } - } - - return imageCrops; - } - - internal static ImageCropData GetCrop(this ImageCropDataSet dataset, string cropAlias) - { - if (dataset == null || dataset.Crops == null || !dataset.Crops.Any()) - return null; - - return dataset.Crops.GetCrop(cropAlias); - } - - internal static ImageCropData GetCrop(this IEnumerable dataset, string cropAlias) - { - var imageCropDatas = dataset.ToArray(); - if (dataset == null || imageCropDatas.Any() == false) - return null; - - if (string.IsNullOrEmpty(cropAlias)) - return imageCropDatas.FirstOrDefault(); - - return imageCropDatas.FirstOrDefault(x => x.Alias.ToLowerInvariant() == cropAlias.ToLowerInvariant()); - } - - internal static string GetCropBaseUrl(this ImageCropDataSet cropDataSet, string cropAlias, bool preferFocalPoint) - { - var cropUrl = new StringBuilder(); - - var crop = cropDataSet.GetCrop(cropAlias); - - // if crop alias has been specified but not found in the Json we should return null - if (string.IsNullOrEmpty(cropAlias) == false && crop == null) - { - return null; - } - - if ((preferFocalPoint && cropDataSet.HasFocalPoint()) || (crop != null && crop.Coordinates == null && cropDataSet.HasFocalPoint()) || (string.IsNullOrEmpty(cropAlias) && cropDataSet.HasFocalPoint())) - { - cropUrl.Append("?center=" + cropDataSet.FocalPoint.Top.ToString(CultureInfo.InvariantCulture) + "," + cropDataSet.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); - cropUrl.Append("&mode=crop"); - } - else if (crop != null && crop.Coordinates != null && preferFocalPoint == false) - { - cropUrl.Append("?crop="); - cropUrl.Append(crop.Coordinates.X1.ToString(CultureInfo.InvariantCulture)).Append(","); - cropUrl.Append(crop.Coordinates.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); - cropUrl.Append(crop.Coordinates.X2.ToString(CultureInfo.InvariantCulture)).Append(","); - cropUrl.Append(crop.Coordinates.Y2.ToString(CultureInfo.InvariantCulture)); - cropUrl.Append("&cropmode=percentage"); - } - else - { - cropUrl.Append("?anchor=center"); - cropUrl.Append("&mode=crop"); - } - - return cropUrl.ToString(); - } - } -} diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index 3517da0e49..ed134d2e58 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -2,8 +2,12 @@ using Newtonsoft.Json.Linq; using System.Globalization; using System.Text; +using Newtonsoft.Json; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Web.Models; namespace Umbraco.Web @@ -99,7 +103,7 @@ namespace Umbraco.Web /// /// /// If the image should be upscaled to requested dimensions - /// + /// /// /// The . /// @@ -114,7 +118,7 @@ namespace Umbraco.Web ImageCropAnchor? imageCropAnchor = null, bool preferFocalPoint = false, bool useCropDimensions = false, - bool cacheBuster = true, + bool cacheBuster = true, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, bool upScale = true) @@ -125,12 +129,12 @@ namespace Umbraco.Web if (mediaItem.HasProperty(propertyAlias) == false || mediaItem.HasValue(propertyAlias) == false) return string.Empty; - + //get the default obj from the value converter - var cropperValue = mediaItem.GetPropertyValue(propertyAlias); + var cropperValue = mediaItem.Value(propertyAlias); //is it strongly typed? - var stronglyTyped = cropperValue as ImageCropDataSet; + var stronglyTyped = cropperValue as ImageCropperValue; string mediaItemUrl; if (stronglyTyped != null) { @@ -144,7 +148,7 @@ namespace Umbraco.Web var jobj = cropperValue as JObject; if (jobj != null) { - stronglyTyped = jobj.ToObject(); + stronglyTyped = jobj.ToObject(); mediaItemUrl = stronglyTyped.Src; return GetCropUrl( mediaItemUrl, stronglyTyped, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, @@ -207,7 +211,7 @@ namespace Umbraco.Web /// /// /// If the image should be upscaled to requested dimensions - /// + /// /// /// The . /// @@ -222,17 +226,17 @@ namespace Umbraco.Web ImageCropAnchor? imageCropAnchor = null, bool preferFocalPoint = false, bool useCropDimensions = false, - string cacheBusterValue = null, + string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, bool upScale = true) { if (string.IsNullOrEmpty(imageUrl)) return string.Empty; - ImageCropDataSet cropDataSet = null; + ImageCropperValue cropDataSet = null; if (string.IsNullOrEmpty(imageCropperValue) == false && imageCropperValue.DetectIsJson() && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) { - cropDataSet = imageCropperValue.DeserializeToCropDataSet(); + cropDataSet = imageCropperValue.DeserializeImageCropperValue(); } return GetCropUrl( imageUrl, cropDataSet, width, height, cropAlias, quality, imageCropMode, @@ -286,13 +290,13 @@ namespace Umbraco.Web /// /// /// If the image should be upscaled to requested dimensions - /// + /// /// /// The . /// public static string GetCropUrl( this string imageUrl, - ImageCropDataSet cropDataSet, + ImageCropperValue cropDataSet, int? width = null, int? height = null, string cropAlias = null, @@ -314,17 +318,12 @@ namespace Umbraco.Web { var crop = cropDataSet.GetCrop(cropAlias); - imageProcessorUrl.Append(cropDataSet.Src); - - var cropBaseUrl = cropDataSet.GetCropBaseUrl(cropAlias, preferFocalPoint); - if (cropBaseUrl != null) - { - imageProcessorUrl.Append(cropBaseUrl); - } - else - { + // if a crop was specified, but not found, return null + if (crop == null && !string.IsNullOrWhiteSpace(cropAlias)) return null; - } + + imageProcessorUrl.Append(cropDataSet.Src); + cropDataSet.AppendCropBaseUrl(imageProcessorUrl, crop, string.IsNullOrWhiteSpace(cropAlias), preferFocalPoint); if (crop != null & useCropDimensions) { @@ -365,7 +364,7 @@ namespace Umbraco.Web var hasFormat = furtherOptions != null && furtherOptions.InvariantContains("&format="); - //Only put quality here, if we don't have a format specified. + //Only put quality here, if we don't have a format specified. //Otherwise we need to put quality at the end to avoid it being overridden by the format. if (quality != null && hasFormat == false) { @@ -432,5 +431,27 @@ namespace Umbraco.Web return string.Empty; } + + internal static ImageCropperValue DeserializeImageCropperValue(this string json) + { + var imageCrops = new ImageCropperValue(); + if (json.DetectIsJson()) + { + try + { + imageCrops = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + Culture = CultureInfo.InvariantCulture, + FloatParseHandling = FloatParseHandling.Decimal + }); + } + catch (Exception ex) + { + Current.Logger.Error(typeof(ImageCropperTemplateExtensions), "Could not parse the json string: " + json, ex); + } + } + + return imageCrops; + } } } diff --git a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs index 421822d8b6..96377e7cb7 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs @@ -1,17 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Net.Http; using System.Reflection; -using System.Text; using System.Web.Http; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; +using Umbraco.Core.Migrations.Install; using Umbraco.Web.Install.Models; using Umbraco.Web.WebApi; @@ -21,48 +17,37 @@ namespace Umbraco.Web.Install.Controllers [HttpInstallAuthorize] public class InstallApiController : ApiController { - public InstallApiController() - : this(UmbracoContext.Current) - { + private readonly DatabaseBuilder _databaseBuilder; + private readonly ProfilingLogger _proflog; + private readonly InstallStepCollection _installSteps; + private readonly ILogger _logger; - } - - public InstallApiController(UmbracoContext umbracoContext) + public InstallApiController(UmbracoContext umbracoContext, DatabaseBuilder databaseBuilder, ProfilingLogger proflog, InstallHelper installHelper, InstallStepCollection installSteps) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - UmbracoContext = umbracoContext; + UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); + _databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder)); + _proflog = proflog ?? throw new ArgumentNullException(nameof(proflog)); + _installSteps = installSteps; + InstallHelper = installHelper; + _logger = _proflog.Logger; } /// - /// Returns the current UmbracoContext + /// Gets the Umbraco context. /// - public UmbracoContext UmbracoContext { get; private set; } + public UmbracoContext UmbracoContext { get; } - public ApplicationContext ApplicationContext - { - get { return UmbracoContext.Application; } - } - - private InstallHelper _helper; - internal InstallHelper InstallHelper - { - get - { - return _helper ?? (_helper = new InstallHelper(UmbracoContext)); - } - } + internal InstallHelper InstallHelper { get; } public bool PostValidateDatabaseConnection(DatabaseModel model) { - var dbHelper = new DatabaseHelper(); - var canConnect = dbHelper.CheckConnection(model, ApplicationContext); + var canConnect = _databaseBuilder.CheckConnection(model.DatabaseType.ToString(), model.ConnectionString, model.Server, model.DatabaseName, model.Login, model.Password, model.IntegratedAuth); return canConnect; } /// - /// Gets the install setup + /// Gets the install setup. /// - /// public InstallSetup GetSetup() { var setup = new InstallSetup(); @@ -71,7 +56,7 @@ namespace Umbraco.Web.Install.Controllers var steps = new List(); - var installSteps = InstallHelper.GetStepsForCurrentInstallType().ToArray(); + var installSteps = _installSteps.GetStepsForCurrentInstallType().ToArray(); //only get the steps that are targetting the current install type steps.AddRange(installSteps); @@ -81,21 +66,19 @@ namespace Umbraco.Web.Install.Controllers return setup; } - + public IEnumerable GetPackages() { - var installHelper = new InstallHelper(UmbracoContext); - var starterKits = installHelper.GetStarterKits(); + var starterKits = InstallHelper.GetStarterKits(); return starterKits; } /// - /// Does the install + /// Installs. /// - /// public InstallProgressResultModel PostPerformInstall(InstallInstructions installModel) { - if (installModel == null) throw new ArgumentNullException("installModel"); + if (installModel == null) throw new ArgumentNullException(nameof(installModel)); var status = InstallStatusTracker.GetStatus().ToArray(); //there won't be any statuses returned if the app pool has restarted so we need to re-read from file. @@ -108,22 +91,18 @@ namespace Umbraco.Web.Install.Controllers var queue = new Queue(status.Where(x => x.IsComplete == false)); while (queue.Count > 0) { - var stepStatus = queue.Dequeue(); - - var step = InstallHelper.GetAllSteps().Single(x => x.Name == stepStatus.Name); + var item = queue.Dequeue(); + var step = _installSteps.GetAllSteps().Single(x => x.Name == item.Name); - JToken instruction = null; - //If this step has any instructions then extract them - if (installModel.Instructions.Any(x => x.Key == step.Name)) - { - instruction = installModel.Instructions[step.Name]; - } - - //If this step doesn't require execution then continue to the next one, this is just a fail-safe check. + // if this step has any instructions then extract them + JToken instruction; + installModel.Instructions.TryGetValue(item.Name, out instruction); // else null + + // if this step doesn't require execution then continue to the next one, this is just a fail-safe check. if (StepRequiresExecution(step, instruction) == false) { - //set this as complete and continue - InstallStatusTracker.SetComplete(installModel.InstallId, stepStatus.Name, null); + // set this as complete and continue + InstallStatusTracker.SetComplete(installModel.InstallId, item.Name); continue; } @@ -131,13 +110,13 @@ namespace Umbraco.Web.Install.Controllers { var setupData = ExecuteStep(step, instruction); - //update the status - InstallStatusTracker.SetComplete(installModel.InstallId, step.Name, setupData != null ? setupData.SavedStepData : null); + // update the status + InstallStatusTracker.SetComplete(installModel.InstallId, step.Name, setupData?.SavedStepData); - //Determine's the next step in the queue and dequeue's any items that don't need to execute - var nextStep = IterateNextRequiredStep(step, queue, installModel.InstallId, installModel); - - //check if there's a custom view to return for this step + // determine's the next step in the queue and dequeue's any items that don't need to execute + var nextStep = IterateSteps(step, queue, installModel.InstallId, installModel); + + // check if there's a custom view to return for this step if (setupData != null && setupData.View.IsNullOrWhiteSpace() == false) { return new InstallProgressResultModel(false, step.Name, nextStep, setupData.View, setupData.ViewModel); @@ -148,7 +127,7 @@ namespace Umbraco.Web.Install.Controllers catch (Exception ex) { - LogHelper.Error("An error occurred during installation step " + step.Name, ex); + _logger.Error("An error occurred during installation step " + step.Name, ex); if (ex is TargetInvocationException && ex.InnerException != null) { @@ -188,101 +167,81 @@ namespace Umbraco.Web.Install.Controllers /// /// /// - private string IterateNextRequiredStep(InstallSetupStep current, Queue queue, Guid installId, InstallInstructions installModel) + private string IterateSteps(InstallSetupStep current, Queue queue, Guid installId, InstallInstructions installModel) { - if (queue.Count > 0) + while (queue.Count > 0) { - var next = queue.Peek(); - var step = InstallHelper.GetAllSteps().Single(x => x.Name == next.Name); + var item = queue.Peek(); - //If the current step restarts the app pool then we must simply return the next one in the queue, - // we cannot peek ahead as the next step might rely on the app restart and therefore RequiresExecution - // will rely on that too. + // if the current step restarts the app pool then we must simply return the next one in the queue, + // we cannot peek ahead as the next step might rely on the app restart and therefore RequiresExecution + // will rely on that too. if (current.PerformsAppRestart) - { - return step.Name; - } + return item.Name; - JToken instruction = null; - //If this step has any instructions then extract them - if (installModel.Instructions.Any(x => x.Key == step.Name)) - { - instruction = installModel.Instructions[step.Name]; - } + var step = _installSteps.GetAllSteps().Single(x => x.Name == item.Name); - //if the step requires execution then return it's name + // if this step has any instructions then extract them + JToken instruction; + installModel.Instructions.TryGetValue(item.Name, out instruction); // else null + + // if the step requires execution then return its name if (StepRequiresExecution(step, instruction)) - { return step.Name; - } - //this step no longer requires execution, this could be due to a new config change during installation, - // so we'll dequeue this one from the queue and recurse + // no longer requires execution, could be due to a new config change during installation + // dequeue queue.Dequeue(); - //set this as complete - InstallStatusTracker.SetComplete(installId, step.Name, null); + // complete + InstallStatusTracker.SetComplete(installId, step.Name); - //recurse - return IterateNextRequiredStep(step, queue, installId, installModel); + // and continue + current = step; } - //there is no more steps return string.Empty; } - /// - /// Check if the step requires execution - /// - /// - /// - /// + // determines whether the step requires execution internal bool StepRequiresExecution(InstallSetupStep step, JToken instruction) { - var model = instruction == null ? null : instruction.ToObject(step.StepType); + var model = instruction?.ToObject(step.StepType); var genericStepType = typeof(InstallSetupStep<>); Type[] typeArgs = { step.StepType }; var typedStepType = genericStepType.MakeGenericType(typeArgs); try { var method = typedStepType.GetMethods().Single(x => x.Name == "RequiresExecution"); - return (bool)method.Invoke(step, new object[] { model }); + return (bool) method.Invoke(step, new[] { model }); } catch (Exception ex) { - LogHelper.Error("Checking if step requires execution (" + step.Name + ") failed.", ex); + _logger.Error("Checking if step requires execution (" + step.Name + ") failed.", ex); throw; } } + // executes the step internal InstallSetupResult ExecuteStep(InstallSetupStep step, JToken instruction) { - using (ApplicationContext.ProfilingLogger.TraceDuration("Executing installation step: " + step.Name, "Step completed")) + using (_proflog.TraceDuration("Executing installation step: " + step.Name, "Step completed")) { - var model = instruction == null ? null : instruction.ToObject(step.StepType); + var model = instruction?.ToObject(step.StepType); var genericStepType = typeof(InstallSetupStep<>); Type[] typeArgs = { step.StepType }; var typedStepType = genericStepType.MakeGenericType(typeArgs); try { var method = typedStepType.GetMethods().Single(x => x.Name == "Execute"); - return (InstallSetupResult)method.Invoke(step, new object[] { model }); + return (InstallSetupResult) method.Invoke(step, new[] { model }); } catch (Exception ex) { - LogHelper.Error("Installation step " + step.Name + " failed.", ex); + _logger.Error("Installation step " + step.Name + " failed.", ex); throw; } } } - - private HttpResponseMessage Json(object jsonObject, HttpStatusCode status) - { - var response = Request.CreateResponse(status); - var json = JObject.FromObject(jsonObject); - response.Content = new StringContent(json.ToString(), Encoding.UTF8, "application/json"); - return response; - } - } } diff --git a/src/Umbraco.Web/Install/Controllers/InstallController.cs b/src/Umbraco.Web/Install/Controllers/InstallController.cs index 7e3336e1e8..b886c459ab 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallController.cs @@ -1,12 +1,10 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Install; using Umbraco.Web.Security; namespace Umbraco.Web.Install.Controllers @@ -17,36 +15,36 @@ namespace Umbraco.Web.Install.Controllers /// /// NOTE: All views must have their full paths as we do not have a custom view engine for the installation views! /// - [InstallAuthorizeAttribute] + [InstallAuthorize] public class InstallController : Controller { private readonly UmbracoContext _umbracoContext; + private readonly InstallHelper _installHelper; + private readonly IRuntimeState _runtime; + private readonly ILogger _logger; + private readonly IGlobalSettings _globalSettings; - public InstallController() - : this(UmbracoContext.Current) - { - - } - - public InstallController(UmbracoContext umbracoContext) - { - _umbracoContext = umbracoContext; - } - + public InstallController(UmbracoContext umbracoContext, InstallHelper installHelper, IRuntimeState runtime, ILogger logger, IGlobalSettings globalSettings) + { + _umbracoContext = umbracoContext; + _installHelper = installHelper; + _runtime = runtime; + _logger = logger; + _globalSettings = globalSettings; + } [HttpGet] public ActionResult Index() { - if (ApplicationContext.Current.IsConfigured) - { - return Redirect(SystemDirectories.Umbraco.EnsureEndsWith('/')); - } + if (_runtime.Level == RuntimeLevel.Run) + return Redirect(SystemDirectories.Umbraco.EnsureEndsWith('/')); - if (ApplicationContext.Current.IsUpgrading) + if (_runtime.Level == RuntimeLevel.Upgrade) { // Update ClientDependency version - var clientDependencyConfig = new ClientDependencyConfiguration(ApplicationContext.Current.ProfilingLogger.Logger); - var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber(); + var clientDependencyConfig = new ClientDependencyConfiguration(_logger); + var clientDependencyUpdated = clientDependencyConfig.UpdateVersionNumber( + UmbracoVersion.SemanticVersion, DateTime.UtcNow, "yyyyMMdd"); // Delete ClientDependency temp directories to make sure we get fresh caches var clientDependencyTempFilesDeleted = clientDependencyConfig.ClearTempFiles(HttpContext); @@ -59,20 +57,17 @@ namespace Umbraco.Web.Install.Controllers return Redirect(SystemDirectories.Umbraco + "/AuthorizeUpgrade?redir=" + Server.UrlEncode(Request.RawUrl)); } } - - //gen the install base url + // gen the install base url ViewBag.InstallApiBaseUrl = Url.GetUmbracoApiService("GetSetup", "InstallApi", "UmbracoInstall").TrimEnd("GetSetup"); - - //get the base umbraco folder + + // get the base umbraco folder ViewBag.UmbracoBaseFolder = IOHelper.ResolveUrl(SystemDirectories.Umbraco); - InstallHelper ih = new InstallHelper(_umbracoContext); - ih.InstallStatus(false, ""); + _installHelper.InstallStatus(false, ""); - //always ensure full path (see NOTE in the class remarks) - return View(GlobalSettings.Path.EnsureEndsWith('/') + "install/views/index.cshtml"); + // always ensure full path (see NOTE in the class remarks) + return View(_globalSettings.Path.EnsureEndsWith('/') + "install/views/index.cshtml"); } - } } diff --git a/src/Umbraco.Web/Install/Controllers/InstallPackageController.cs b/src/Umbraco.Web/Install/Controllers/InstallPackageController.cs index e73302dc02..eb7d02053b 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallPackageController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallPackageController.cs @@ -8,48 +8,40 @@ using System.Web.Http; using Newtonsoft.Json.Linq; using umbraco; using Umbraco.Core; +using Umbraco.Web.Cache; using Umbraco.Core.Configuration; +using Umbraco.Web.Composing; using Umbraco.Web.Install.Models; using Umbraco.Web.WebApi; namespace Umbraco.Web.Install.Controllers { /// - /// A controller for the installation process regarding packages - /// - /// - /// Currently this is used for web services however we should/could eventually migrate the whole installer to MVC as it - /// is a bit of a mess currently. - /// - [HttpInstallAuthorize] + /// A controller for the installation process regarding packages + /// + /// + /// Currently this is used for web services however we should/could eventually migrate the whole installer to MVC as it + /// is a bit of a mess currently. + /// + [HttpInstallAuthorize] [AngularJsonOnlyConfiguration] [Obsolete("This is only used for the legacy way of installing starter kits in the back office")] public class InstallPackageController : ApiController - { - private readonly ApplicationContext _applicationContext; + { + public InstallPackageController() + { } - public InstallPackageController() - : this(ApplicationContext.Current) - { - - } + private const string RepoGuid = "65194810-1f85-11dd-bd0b-0800200c9a66"; - public InstallPackageController(ApplicationContext applicationContext) - { - _applicationContext = applicationContext; - } - - private const string RepoGuid = "65194810-1f85-11dd-bd0b-0800200c9a66"; - - /// - /// Empty action, useful for retrieving the base url for this controller - /// - /// - [HttpGet] + /// + /// Empty action, useful for retrieving the base url for this controller + /// + /// + [HttpGet] public HttpResponseMessage Index() - { - throw new NotImplementedException(); - } + { + throw new NotImplementedException(); + } /// /// Connects to the repo, downloads the package and creates the manifest @@ -59,7 +51,7 @@ namespace Umbraco.Web.Install.Controllers [HttpPost] public HttpResponseMessage DownloadPackageFiles(InstallPackageModel model) { - var packageFile = _applicationContext.Services.PackagingService.FetchPackageFile( + var packageFile = Current.Services.PackagingService.FetchPackageFile( model.KitGuid, UmbracoVersion.Current, UmbracoContext.Current.Security.CurrentUser.Id); @@ -67,118 +59,120 @@ namespace Umbraco.Web.Install.Controllers var installer = new global::umbraco.cms.businesslogic.packager.Installer(UmbracoContext.Current.Security.CurrentUser.Id); var tempFile = installer.Import(packageFile); - installer.LoadConfig(tempFile); + installer.LoadConfig(tempFile); var pId = installer.CreateManifest(tempFile, model.KitGuid.ToString(), RepoGuid); - return Json(new - { - success = true, - manifestId = pId, - packageFile = tempFile, - percentage = 10, - message = "Downloading starter kit files..." - }, HttpStatusCode.OK); - } + return Json(new + { + success = true, + manifestId = pId, + packageFile = tempFile, + percentage = 10, + message = "Downloading starter kit files..." + }, HttpStatusCode.OK); + } - /// - /// Installs the files in the package - /// - /// - [HttpPost] + /// + /// Installs the files in the package + /// + /// + [HttpPost] public HttpResponseMessage InstallPackageFiles(InstallPackageModel model) - { + { model.PackageFile = HttpUtility.UrlDecode(model.PackageFile); var installer = new global::umbraco.cms.businesslogic.packager.Installer(UmbracoContext.Current.Security.CurrentUser.Id); installer.LoadConfig(model.PackageFile); installer.InstallFiles(model.ManifestId, model.PackageFile); - return Json(new - { - success = true, + return Json(new + { + success = true, model.ManifestId, model.PackageFile, - percentage = 20, - message = "Installing starter kit files" - }, HttpStatusCode.OK); - } + percentage = 20, + message = "Installing starter kit files" + }, HttpStatusCode.OK); + } - /// - /// Ensures the app pool is restarted - /// - /// - [HttpPost] + /// + /// Ensures the app pool is restarted + /// + /// + [HttpPost] public HttpResponseMessage RestartAppPool() - { - _applicationContext.RestartApplicationPool(Request.TryGetHttpContext().Result); - return Json(new - { - success = true, - percentage = 25, - message = "Installing starter kit files" - }, HttpStatusCode.OK); - } - - /// - /// Checks if the app pool has completed restarted - /// - /// - [HttpPost] - public HttpResponseMessage CheckAppPoolRestart() - { - if (Request.TryGetHttpContext().Result.Application.AllKeys.Contains("AppPoolRestarting")) - { - return Request.CreateResponse(HttpStatusCode.BadRequest); - } - - return Json(new - { - percentage = 30, + { + Current.RestartAppPool(Request.TryGetHttpContext().Result); + return Json(new + { success = true, - }, HttpStatusCode.OK); - } + percentage = 25, + message = "Installing starter kit files" + }, HttpStatusCode.OK); + } - /// - /// Installs the business logic portion of the package after app restart - /// - /// - [HttpPost] + /// + /// Checks if the app pool has completed restarted + /// + /// + [HttpPost] + public HttpResponseMessage CheckAppPoolRestart() + { + if (Request.TryGetHttpContext().Result.Application.AllKeys.Contains("AppPoolRestarting")) + { + return Request.CreateResponse(HttpStatusCode.BadRequest); + } + + return Json(new + { + percentage = 30, + success = true, + }, HttpStatusCode.OK); + } + + /// + /// Installs the business logic portion of the package after app restart + /// + /// + [HttpPost] public HttpResponseMessage InstallBusinessLogic(InstallPackageModel model) - { + { model.PackageFile = HttpUtility.UrlDecode(model.PackageFile); var installer = new global::umbraco.cms.businesslogic.packager.Installer(UmbracoContext.Current.Security.CurrentUser.Id); installer.LoadConfig(model.PackageFile); installer.InstallBusinessLogic(model.ManifestId, model.PackageFile); - return Json(new - { - success = true, + return Json(new + { + success = true, model.ManifestId, model.PackageFile, - percentage = 70, - message = "Installing starter kit files" - }, HttpStatusCode.OK); - } + percentage = 70, + message = "Installing starter kit files" + }, HttpStatusCode.OK); + } - /// - /// Cleans up the package installation - /// - /// - [HttpPost] + /// + /// Cleans up the package installation + /// + /// + [HttpPost] public HttpResponseMessage CleanupInstallation(InstallPackageModel model) - { + { model.PackageFile = HttpUtility.UrlDecode(model.PackageFile); var installer = new global::umbraco.cms.businesslogic.packager.Installer(UmbracoContext.Current.Security.CurrentUser.Id); installer.LoadConfig(model.PackageFile); installer.InstallCleanUp(model.ManifestId, model.PackageFile); - library.RefreshContent(); + // library.RefreshContent is obsolete, would need to RefreshAll... snapshot, + // but it should be managed automatically by services and caches! + //DistributedCache.Instance.RefreshAll...(); - return Json(new - { - success = true, + return Json(new + { + success = true, model.ManifestId, model.PackageFile, - percentage = 100, - message = "Starter kit has been installed" - }, HttpStatusCode.OK); - } + percentage = 100, + message = "Starter kit has been installed" + }, HttpStatusCode.OK); + } private HttpResponseMessage Json(object jsonObject, HttpStatusCode status) { @@ -187,6 +181,6 @@ namespace Umbraco.Web.Install.Controllers response.Content = new StringContent(json.ToString(), Encoding.UTF8, "application/json"); return response; } - } + } } diff --git a/src/Umbraco.Web/Install/DatabaseHelper.cs b/src/Umbraco.Web/Install/DatabaseHelper.cs deleted file mode 100644 index cb2263628b..0000000000 --- a/src/Umbraco.Web/Install/DatabaseHelper.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core; -using Umbraco.Core.Persistence; -using Umbraco.Web.Install.Models; - -namespace Umbraco.Web.Install -{ - internal class DatabaseHelper - { - internal bool CheckConnection(DatabaseModel database, ApplicationContext applicationContext) - { - string connectionString; - DatabaseProviders provider; - var dbContext = applicationContext.DatabaseContext; - - if (database.ConnectionString.IsNullOrWhiteSpace() == false) - { - connectionString = database.ConnectionString; - provider = DbConnectionExtensions.DetectProviderFromConnectionString(connectionString); - } - else if (database.DatabaseType == DatabaseType.SqlCe) - { - //we do not test this connection - return true; - //connectionString = dbContext.GetEmbeddedDatabaseConnectionString(); - } - else if (database.IntegratedAuth) - { - connectionString = dbContext.GetIntegratedSecurityDatabaseConnectionString( - database.Server, database.DatabaseName); - provider = DatabaseProviders.SqlServer;; - } - else - { - string providerName; - connectionString = dbContext.GetDatabaseConnectionString( - database.Server, database.DatabaseName, database.Login, database.Password, - database.DatabaseType.ToString(), - out providerName); - - provider = database.DatabaseType == DatabaseType.MySql ? DatabaseProviders.MySql : DatabaseProviders.SqlServer; - } - - return DbConnectionExtensions.IsConnectionAvailable(connectionString, provider); - } - } -} diff --git a/src/Umbraco.Web/Install/FilePermissionHelper.cs b/src/Umbraco.Web/Install/FilePermissionHelper.cs index a91bd6d306..b9252c9e65 100644 --- a/src/Umbraco.Web/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Web/Install/FilePermissionHelper.cs @@ -1,44 +1,47 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Web; using System.IO; using System.Security.AccessControl; using Umbraco.Core.IO; -using umbraco; +using Umbraco.Web.Composing; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.Install { internal class FilePermissionHelper { - internal static readonly string[] PermissionDirs = { SystemDirectories.Css, SystemDirectories.Config, SystemDirectories.Data, SystemDirectories.Media, SystemDirectories.Masterpages, SystemDirectories.Xslt, SystemDirectories.UserControls, SystemDirectories.Preview }; - internal static readonly string[] PermissionFiles = { }; - internal static readonly string[] PackagesPermissionsDirs = { SystemDirectories.Bin, SystemDirectories.Umbraco, SystemDirectories.UserControls, SystemDirectories.Packages }; + // ensure that these directories exist and Umbraco can write to them + private static readonly string[] PermissionDirs = { SystemDirectories.Css, SystemDirectories.Config, SystemDirectories.Data, SystemDirectories.Media, SystemDirectories.Masterpages, SystemDirectories.Xslt, SystemDirectories.UserControls, SystemDirectories.Preview }; + private static readonly string[] PackagesPermissionsDirs = { SystemDirectories.Bin, SystemDirectories.Umbraco, SystemDirectories.UserControls, SystemDirectories.Packages }; - public static bool RunFilePermissionTestSuite(out Dictionary> errorReport) + // ensure Umbraco can write to these files (the directories must exist) + private static readonly string[] PermissionFiles = { }; + + public static bool RunFilePermissionTestSuite(out Dictionary> report) { - errorReport = new Dictionary>(); + report = new Dictionary>(); - List errors; + IEnumerable errors; - if (TestDirectories(PermissionDirs, out errors) == false) - errorReport["Folder creation failed"] = errors.ToList(); + if (EnsureDirectories(PermissionDirs, out errors) == false) + report["Folder creation failed"] = errors.ToList(); - if (TestDirectories(PackagesPermissionsDirs, out errors) == false) - errorReport["File writing for packages failed"] = errors.ToList(); + if (EnsureDirectories(PackagesPermissionsDirs, out errors) == false) + report["File writing for packages failed"] = errors.ToList(); - if (TestFiles(PermissionFiles, out errors) == false) - errorReport["File writing failed"] = errors.ToList(); + if (EnsureFiles(PermissionFiles, out errors) == false) + report["File writing failed"] = errors.ToList(); - if (TestContentXml(out errors) == false) - errorReport["Cache file writing failed"] = errors.ToList(); + if (TestPublishedSnapshotService(out errors) == false) + report["Published snapshot environment check failed"] = errors.ToList(); - if (TestFolderCreation(SystemDirectories.Media, out errors) == false) - errorReport["Media folder creation failed"] = errors.ToList(); + if (EnsureCanCreateSubDirectory(SystemDirectories.Media, out errors) == false) + report["Media folder creation failed"] = errors.ToList(); - return errorReport.Any() == false; + return report.Count == 0; } - + /// /// This will test the directories for write access /// @@ -50,97 +53,83 @@ namespace Umbraco.Web.Install /// reliable but we cannot write a file since it will cause an app domain restart. /// /// - public static bool TestDirectories(string[] directories, out List errorReport, bool writeCausesRestart = false) + public static bool EnsureDirectories(string[] dirs, out IEnumerable errors, bool writeCausesRestart = false) { - errorReport = new List(); - bool succes = true; - foreach (string dir in directories) + List temp = null; + var success = true; + foreach (var dir in dirs) { - if (Directory.Exists(dir) == false) continue; + // we don't want to create/ship unnecessary directories, so + // here we just ensure we can access the directory, not create it + var tryAccess = TryAccessDirectory(dir, !writeCausesRestart); + if (tryAccess) continue; - var folder = IOHelper.MapPath(dir); - - var result = writeCausesRestart - ? HasWritePermissionOnDir(folder) - : SaveAndDeleteFile(Path.Combine(folder, "configWizardPermissionTest.txt")); - - if (result == false) - { - succes = false; - errorReport.Add(dir); - } + if (temp == null) temp = new List(); + temp.Add(dir); + success = false; } - return succes; + errors = success ? Enumerable.Empty() : temp; + return success; } - public static bool TestFiles(string[] files, out List errorReport) + public static bool EnsureFiles(string[] files, out IEnumerable errors) { - errorReport = new List(); - bool succes = true; - foreach (string file in files) + List temp = null; + var success = true; + foreach (var file in files) { - bool result = OpenFileForWrite(IOHelper.MapPath(file)); - if (result == false) - { - errorReport.Add(file); - succes = false; - } + var canWrite = TryWriteFile(file); + if (canWrite) continue; + + if (temp == null) temp = new List(); + temp.Add(file); + success = false; } - return succes; + errors = success ? Enumerable.Empty() : temp; + return success; } - public static bool TestFolderCreation(string folder, out List errorReport) + public static bool EnsureCanCreateSubDirectory(string dir, out IEnumerable errors) { - errorReport = new List(); - try - { - string tempDir = IOHelper.MapPath(folder + "/testCreatedByConfigWizard"); - Directory.CreateDirectory(tempDir); - Directory.Delete(tempDir); - return true; - } - catch - { - errorReport.Add(folder); - return false; - } + return EnsureCanCreateSubDirectories(new[] { dir }, out errors); } - public static bool TestContentXml(out List errorReport) + public static bool EnsureCanCreateSubDirectories(IEnumerable dirs, out IEnumerable errors) { - errorReport = new List(); - // Test creating/saving/deleting a file in the same location as the content xml file - // NOTE: We cannot modify the xml file directly because a background thread is responsible for - // that and we might get lock issues. - try + List temp = null; + var success = true; + foreach (var dir in dirs) { - var xmlFile = content.GetUmbracoXmlDiskFileName() + ".tmp"; - SaveAndDeleteFile(xmlFile); - return true; - } - catch - { - errorReport.Add(SystemFiles.ContentCacheXml); - return false; + var canCreate = TryCreateSubDirectory(dir); + if (canCreate) continue; + + if (temp == null) temp = new List(); + temp.Add(dir); + success = false; } + + errors = success ? Enumerable.Empty() : temp; + return success; } - private static bool SaveAndDeleteFile(string file) + public static bool TestPublishedSnapshotService(out IEnumerable errors) + { + var publishedSnapshotService = Current.PublishedSnapshotService; + return publishedSnapshotService.EnsureEnvironment(out errors); + } + + // tries to create a sub-directory + // if successful, the sub-directory is deleted + // creates the directory if needed - does not delete it + private static bool TryCreateSubDirectory(string dir) { try { - //first check if the directory of the file exists, and if not try to create that first. - FileInfo fi = new FileInfo(file); - if (fi.Directory.Exists == false) - { - fi.Directory.Create(); - } - - File.WriteAllText(file, - "This file has been created by the umbraco configuration wizard. It is safe to delete it!"); - File.Delete(file); + var path = IOHelper.MapPath(dir + "/" + CreateRandomName()); + Directory.CreateDirectory(path); + Directory.Delete(path); return true; } catch @@ -149,7 +138,65 @@ namespace Umbraco.Web.Install } } - private static bool HasWritePermissionOnDir(string path) + // tries to create a file + // if successful, the file is deleted + // creates the directory if needed - does not delete it + public static bool TryCreateDirectory(string dir) + { + try + { + var dirPath = IOHelper.MapPath(dir); + + if (Directory.Exists(dirPath) == false) + Directory.CreateDirectory(dirPath); + + var filePath = dirPath + "/" + CreateRandomName() + ".tmp"; + File.WriteAllText(filePath, "This is an Umbraco internal test file. It is safe to delete it."); + File.Delete(filePath); + return true; + } + catch + { + return false; + } + } + + // tries to create a file + // if successful, the file is deleted + // + // or + // + // use the ACL APIs to avoid creating files + // + // if the directory does not exist, do nothing & success + public static bool TryAccessDirectory(string dir, bool canWrite) + { + try + { + var dirPath = IOHelper.MapPath(dir); + + if (Directory.Exists(dirPath) == false) + return true; + + if (canWrite) + { + var filePath = dirPath + "/" + CreateRandomName() + ".tmp"; + File.WriteAllText(filePath, "This is an Umbraco internal test file. It is safe to delete it."); + File.Delete(filePath); + return true; + } + else + { + return HasWritePermission(dirPath); + } + } + catch + { + return false; + } + } + + private static bool HasWritePermission(string path) { var writeAllow = false; var writeDeny = false; @@ -164,12 +211,12 @@ namespace Umbraco.Web.Install return false; } catch (Exception e) - { - //This is not 100% accurate btw because it could turn out that the current user doesn't - //have access to read the current permissions but does have write access. + { + //This is not 100% accurate btw because it could turn out that the current user doesn't + //have access to read the current permissions but does have write access. //I think this is an edge case however return false; - } + } foreach (FileSystemAccessRule rule in accessRules) { @@ -185,17 +232,25 @@ namespace Umbraco.Web.Install return writeAllow && writeDeny == false; } - private static bool OpenFileForWrite(string file) + // tries to write into a file + // fails if the directory does not exist + private static bool TryWriteFile(string file) { try { - File.AppendText(file).Close(); + var path = IOHelper.MapPath(file); + File.AppendText(path).Close(); + return true; } catch { return false; } - return true; + } + + private static string CreateRandomName() + { + return "umbraco-test." + Guid.NewGuid().ToString("N").Substring(0, 8); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/HttpInstallAuthorizeAttribute.cs b/src/Umbraco.Web/Install/HttpInstallAuthorizeAttribute.cs index 4559a42740..68acdd6cc8 100644 --- a/src/Umbraco.Web/Install/HttpInstallAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Install/HttpInstallAuthorizeAttribute.cs @@ -1,73 +1,65 @@ -using System; -using System.Web; +using System; using System.Web.Http; using System.Web.Http.Controllers; using Umbraco.Core; -using Umbraco.Web.Security; -using umbraco.BasePages; using Umbraco.Core.Logging; +using Umbraco.Web.Composing; namespace Umbraco.Web.Install { - /// - /// Ensures authorization occurs for the installer if it has already completed. If install has not yet occured - /// then the authorization is successful - /// - internal class HttpInstallAuthorizeAttribute : AuthorizeAttribute - { - private readonly ApplicationContext _applicationContext; + /// + /// Ensures authorization occurs for the installer if it has already completed. + /// If install has not yet occured then the authorization is successful. + /// + internal class HttpInstallAuthorizeAttribute : AuthorizeAttribute + { + // todo - cannot inject UmbracoContext nor RuntimeState in the attribute, read: + // http://stackoverflow.com/questions/30096903/dependency-injection-inside-a-filterattribute-in-asp-net-mvc-6 + // https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=98 - don't do it! + // http://blog.ploeh.dk/2014/06/13/passive-attributes/ - passive attributes + // http://xunitpatterns.com/Humble%20Object.html - humble objects + // + // so... either access them via Current service locator, OR use an action filter alongside this attribute (see articles). + // the second solution is nicer BUT for the time being, let's use the first (simpler). + private readonly UmbracoContext _umbracoContext; + private readonly IRuntimeState _runtimeState; - private ApplicationContext GetApplicationContext() - { - return _applicationContext ?? ApplicationContext.Current; - } + private IRuntimeState RuntimeState => _runtimeState ?? Current.RuntimeState; - private UmbracoContext GetUmbracoContext() - { - return _umbracoContext ?? UmbracoContext.Current; - } + private UmbracoContext UmbracoContext => _umbracoContext ?? Current.UmbracoContext; /// /// THIS SHOULD BE ONLY USED FOR UNIT TESTS /// /// - public HttpInstallAuthorizeAttribute(UmbracoContext umbracoContext) + /// + public HttpInstallAuthorizeAttribute(UmbracoContext umbracoContext, IRuntimeState runtimeState) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); _umbracoContext = umbracoContext; - _applicationContext = _umbracoContext.Application; + _runtimeState = runtimeState; } public HttpInstallAuthorizeAttribute() - { - } + { } - protected override bool IsAuthorized(HttpActionContext actionContext) - { + protected override bool IsAuthorized(HttpActionContext actionContext) + { try { - //if its not configured then we can continue - if (GetApplicationContext().IsConfigured == false) - { - return true; - } - var umbCtx = GetUmbracoContext(); - - //otherwise we need to ensure that a user is logged in - var isLoggedIn = GetUmbracoContext().Security.ValidateCurrentUser(); - if (isLoggedIn) - { - return true; - } - return false; + // if not configured (install or upgrade) then we can continue + // otherwise we need to ensure that a user is logged in + return RuntimeState.Level == RuntimeLevel.Install + || RuntimeState.Level == RuntimeLevel.Upgrade + || UmbracoContext.Security.ValidateCurrentUser(); } catch (Exception ex) { - LogHelper.Error("An error occurred determining authorization", ex); + Current.Logger.Error("An error occurred determining authorization", ex); return false; } - } - - } -} \ No newline at end of file + } + } +} diff --git a/src/Umbraco.Web/Install/InstallAuthorizeAttribute.cs b/src/Umbraco.Web/Install/InstallAuthorizeAttribute.cs index ecaf2d6be1..238bb7c6eb 100644 --- a/src/Umbraco.Web/Install/InstallAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Install/InstallAuthorizeAttribute.cs @@ -2,46 +2,40 @@ using System.Web; using System.Web.Mvc; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.IO; -using Umbraco.Web.Security; -using umbraco.BasePages; +using Umbraco.Web.Composing; namespace Umbraco.Web.Install { /// - /// Ensures authorization occurs for the installer if it has already completed. If install has not yet occured - /// then the authorization is successful + /// Ensures authorization occurs for the installer if it has already completed. + /// If install has not yet occured then the authorization is successful /// internal class InstallAuthorizeAttribute : AuthorizeAttribute { - private readonly ApplicationContext _applicationContext; + // see note in HttpInstallAuthorizeAttribute private readonly UmbracoContext _umbracoContext; + private readonly IRuntimeState _runtimeState; - private ApplicationContext GetApplicationContext() - { - return _applicationContext ?? ApplicationContext.Current; - } + private IRuntimeState RuntimeState => _runtimeState ?? Current.RuntimeState; - private UmbracoContext GetUmbracoContext() - { - return _umbracoContext ?? UmbracoContext.Current; - } + private UmbracoContext UmbracoContext => _umbracoContext ?? Current.UmbracoContext; /// /// THIS SHOULD BE ONLY USED FOR UNIT TESTS /// /// - public InstallAuthorizeAttribute(UmbracoContext umbracoContext) + /// + public InstallAuthorizeAttribute(UmbracoContext umbracoContext, IRuntimeState runtimeState) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); _umbracoContext = umbracoContext; - _applicationContext = _umbracoContext.Application; + _runtimeState = runtimeState; } public InstallAuthorizeAttribute() - { - } + { } /// /// Ensures that the user must be logged in or that the application is not configured just yet. @@ -50,24 +44,15 @@ namespace Umbraco.Web.Install /// protected override bool AuthorizeCore(HttpContextBase httpContext) { - if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); try { - //if its not configured then we can continue - if (!GetApplicationContext().IsConfigured) - { - return true; - } - var umbCtx = GetUmbracoContext(); - //otherwise we need to ensure that a user is logged in - var isLoggedIn = GetUmbracoContext().Security.ValidateCurrentUser(); - if (isLoggedIn) - { - return true; - } - - return false; + // if not configured (install or upgrade) then we can continue + // otherwise we need to ensure that a user is logged in + return RuntimeState.Level == RuntimeLevel.Install + || RuntimeState.Level == RuntimeLevel.Upgrade + || UmbracoContext.Security.ValidateCurrentUser(); } catch (Exception) { @@ -81,8 +66,7 @@ namespace Umbraco.Web.Install /// protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { - filterContext.Result = new RedirectResult(SystemDirectories.Umbraco.EnsureEndsWith('/')); + filterContext.Result = new RedirectResult(SystemDirectories.Umbraco.EnsureEndsWith('/')); } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/InstallHelper.cs b/src/Umbraco.Web/Install/InstallHelper.cs index b2c3d1213d..2e7e26aa13 100644 --- a/src/Umbraco.Web/Install/InstallHelper.cs +++ b/src/Umbraco.Web/Install/InstallHelper.cs @@ -6,65 +6,40 @@ using System.IO; using System.Linq; using System.Net.Http; using System.Web; -using System.Web.Script.Serialization; -using System.Web.UI; +using LightInject; using Semver; -using umbraco.BusinessLogic; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Install; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Web.Cache; +using Umbraco.Web.Composing; using Umbraco.Web.Install.InstallSteps; using Umbraco.Web.Install.Models; - namespace Umbraco.Web.Install { - internal class InstallHelper + public sealed class InstallHelper { - private readonly UmbracoContext _umbContext; + private readonly DatabaseBuilder _databaseBuilder; + private readonly HttpContextBase _httpContext; + private readonly ILogger _logger; + private readonly IGlobalSettings _globalSettings; private InstallationType? _installationType; - internal InstallHelper(UmbracoContext umbContext) + public InstallHelper(UmbracoContext umbracoContext, + DatabaseBuilder databaseBuilder, + ILogger logger, IGlobalSettings globalSettings) { - _umbContext = umbContext; - } - - /// - /// Get the installer steps - /// - /// - /// - /// The step order returned here is how they will appear on the front-end if they have views assigned - /// - public IEnumerable GetAllSteps() - { - return new List - { - new NewInstallStep(_umbContext.HttpContext, _umbContext.Application), - new UpgradeStep(_umbContext.Application), - new FilePermissionsStep(), - new MajorVersion7UpgradeReport(_umbContext.Application), - new Version73FileCleanup(_umbContext.HttpContext, _umbContext.Application.ProfilingLogger.Logger), - new DatabaseConfigureStep(_umbContext.Application), - new ConfigureMachineKey(_umbContext.Application), - new DatabaseInstallStep(_umbContext.Application), - new DatabaseUpgradeStep(_umbContext.Application), - new StarterKitDownloadStep(_umbContext.Application, _umbContext.Security, _umbContext.HttpContext), - new StarterKitInstallStep(_umbContext.Application, _umbContext.HttpContext), - new StarterKitCleanupStep(_umbContext.Application), - new SetUmbracoVersionStep(_umbContext.Application, _umbContext.HttpContext), - }; - } - - /// - /// Returns the steps that are used only for the current installation type - /// - /// - public IEnumerable GetStepsForCurrentInstallType() - { - return GetAllSteps().Where(x => x.InstallTypeTarget.HasFlag(GetInstallationType())); + _httpContext = umbracoContext.HttpContext; + _logger = logger; + _globalSettings = globalSettings; + _databaseBuilder = databaseBuilder; } public InstallationType GetInstallationType() @@ -72,7 +47,7 @@ namespace Umbraco.Web.Install return _installationType ?? (_installationType = IsBrandNewInstall ? InstallationType.NewInstall : InstallationType.Upgrade).Value; } - internal void DeleteLegacyInstaller() + internal static void DeleteLegacyInstaller() { if (Directory.Exists(IOHelper.MapPath(SystemDirectories.Install))) { @@ -101,27 +76,32 @@ namespace Umbraco.Web.Install { try { - string userAgent = _umbContext.HttpContext.Request.UserAgent; + var userAgent = _httpContext.Request.UserAgent; // Check for current install Id var installId = Guid.NewGuid(); - var installCookie = new StateHelper.Cookies.Cookie("umb_installId", 1); - if (string.IsNullOrEmpty(installCookie.GetValue()) == false) + + var installCookie = _httpContext.Request.GetCookieValue(Constants.Web.InstallerCookieName); + if (string.IsNullOrEmpty(installCookie) == false) { - if (Guid.TryParse(installCookie.GetValue(), out installId)) + if (Guid.TryParse(installCookie, out installId)) { // check that it's a valid Guid if (installId == Guid.Empty) installId = Guid.NewGuid(); } } - installCookie.SetValue(installId.ToString()); + _httpContext.Response.Cookies.Set(new HttpCookie(Constants.Web.InstallerCookieName, "1")); - string dbProvider = string.Empty; + var dbProvider = string.Empty; if (IsBrandNewInstall == false) - dbProvider = ApplicationContext.Current.DatabaseContext.DatabaseProvider.ToString(); + { + // we don't have DatabaseProvider anymore... doing it differently + //dbProvider = ApplicationContext.Current.DatabaseContext.DatabaseProvider.ToString(); + dbProvider = GetDbProviderString(Current.SqlContext); + } - org.umbraco.update.CheckForUpgrade check = new org.umbraco.update.CheckForUpgrade(); + var check = new org.umbraco.update.CheckForUpgrade(); check.Install(installId, IsBrandNewInstall == false, isCompleted, @@ -136,10 +116,29 @@ namespace Umbraco.Web.Install } catch (Exception ex) { - LogHelper.Error("An error occurred in InstallStatus trying to check upgrades", ex); + _logger.Error("An error occurred in InstallStatus trying to check upgrades", ex); } } + internal static string GetDbProviderString(ISqlContext sqlContext) + { + var dbProvider = string.Empty; + + // we don't have DatabaseProvider anymore... + //dbProvider = ApplicationContext.Current.DatabaseContext.DatabaseProvider.ToString(); + // + // doing it differently + var syntax = sqlContext.SqlSyntax; + if (syntax is SqlCeSyntaxProvider) + dbProvider = "SqlServerCE"; + else if (syntax is MySqlSyntaxProvider) + dbProvider = "MySql"; + else if (syntax is SqlServerSyntaxProvider) + dbProvider = (syntax as SqlServerSyntaxProvider).ServerVersion.IsAzure ? "SqlAzure" : "SqlServer"; + + return dbProvider; + } + /// /// Checks if this is a brand new install meaning that there is no configured version and there is no configured database connection /// @@ -148,8 +147,8 @@ namespace Umbraco.Web.Install get { var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; - if (GlobalSettings.ConfigurationStatus.IsNullOrWhiteSpace() - && _umbContext.Application.DatabaseContext.IsConnectionStringConfigured(databaseSettings) == false) + if (_globalSettings.ConfigurationStatus.IsNullOrWhiteSpace() + && _databaseBuilder.IsConnectionStringConfigured(databaseSettings) == false) { //no version or conn string configured, must be a brand new install return true; @@ -157,35 +156,13 @@ namespace Umbraco.Web.Install //now we have to check if this is really a new install, the db might be configured and might contain data - if (_umbContext.Application.DatabaseContext.IsConnectionStringConfigured(databaseSettings) == false - || _umbContext.Application.DatabaseContext.IsDatabaseConfigured == false) + if (_databaseBuilder.IsConnectionStringConfigured(databaseSettings) == false + || _databaseBuilder.IsDatabaseConfigured == false) { return true; } - //check if we have the default user configured already - var result = _umbContext.Application.DatabaseContext.Database.ExecuteScalar( - "SELECT COUNT(*) FROM umbracoUser WHERE id=0 AND userPassword='default'"); - if (result == 1) - { - //the user has not been configured - //this is always true on UaaS, need to check if there's multiple users too - var usersResult = _umbContext.Application.DatabaseContext.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoUser"); - return usersResult == 1; - } - - // //check if there are any content types configured, if there isn't then we will consider this a new install - // result = _umbContext.Application.DatabaseContext.Database.ExecuteScalar( - // @"SELECT COUNT(*) FROM cmsContentType - // INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id - // WHERE umbracoNode.nodeObjectType = @contentType", new {contentType = Constants.ObjectTypes.DocumentType}); - // if (result == 0) - // { - // //no content types have been created - // return true; - // } - - return false; + return _databaseBuilder.HasSomeNonDefaultUser() == false; } } @@ -195,8 +172,7 @@ namespace Umbraco.Web.Install try { - var requestUri = string.Format("http://our.umbraco.org/webapi/StarterKit/Get/?umbracoVersion={0}", - UmbracoVersion.Current); + var requestUri = $"http://our.umbraco.org/webapi/StarterKit/Get/?umbracoVersion={UmbracoVersion.Current}"; using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri)) using (var httpClient = new HttpClient()) @@ -207,10 +183,10 @@ namespace Umbraco.Web.Install } catch (AggregateException ex) { - LogHelper.Error("Could not download list of available starter kits", ex); + _logger.Error("Could not download list of available starter kits", ex); } return packages; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/InstallStatusTracker.cs b/src/Umbraco.Web/Install/InstallStatusTracker.cs index 971c64d2ed..7944100648 100644 --- a/src/Umbraco.Web/Install/InstallStatusTracker.cs +++ b/src/Umbraco.Web/Install/InstallStatusTracker.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Umbraco.Core; +using Umbraco.Core.Collections; using Umbraco.Core.IO; using Umbraco.Web.Install.Models; @@ -37,7 +38,7 @@ namespace Umbraco.Web.Install } public static void ClearFiles() - { + { var dir = IOHelper.MapPath("~/App_Data/TEMP/Install/"); if (Directory.Exists(dir)) { @@ -130,7 +131,7 @@ namespace Umbraco.Web.Install trackingItem.AdditionalData = additionalData; } trackingItem.IsComplete = true; - + //save the file var file = GetFile(installId); var serialized = JsonConvert.SerializeObject(new List(_steps)); @@ -140,6 +141,6 @@ namespace Umbraco.Web.Install public static IEnumerable GetStatus() { return new List(_steps).OrderBy(x => x.ServerOrder); - } + } } } diff --git a/src/Umbraco.Web/Install/InstallStepCollection.cs b/src/Umbraco.Web/Install/InstallStepCollection.cs new file mode 100644 index 0000000000..22e084ede3 --- /dev/null +++ b/src/Umbraco.Web/Install/InstallStepCollection.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Web.Install.Models; + +namespace Umbraco.Web.Install +{ + public sealed class InstallStepCollection + { + private readonly InstallHelper _installHelper; + private readonly IEnumerable _orderedInstallerSteps; + + public InstallStepCollection(InstallHelper installHelper, IEnumerable orderedInstallerSteps) + { + _installHelper = installHelper; + _orderedInstallerSteps = orderedInstallerSteps; + } + + + /// + /// Get the installer steps + /// + /// + /// + /// The step order returned here is how they will appear on the front-end if they have views assigned + /// + public IEnumerable GetAllSteps() + { + return _orderedInstallerSteps; + } + + /// + /// Returns the steps that are used only for the current installation type + /// + /// + public IEnumerable GetStepsForCurrentInstallType() + { + return GetAllSteps().Where(x => x.InstallTypeTarget.HasFlag(_installHelper.GetInstallationType())); + } + } +} diff --git a/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs b/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs index e4038df4ab..de53fd60cf 100644 --- a/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs +++ b/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs @@ -1,9 +1,6 @@ -using System; -using System.Configuration; -using System.Linq; +using System.Linq; using System.Web.Configuration; using System.Xml.Linq; -using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Security; using Umbraco.Web.Install.Models; @@ -16,26 +13,15 @@ namespace Umbraco.Web.Install.InstallSteps PerformsAppRestart = true)] internal class ConfigureMachineKey : InstallSetupStep { - private readonly ApplicationContext _appContext; - - public ConfigureMachineKey(ApplicationContext appContext) - { - if (appContext == null) throw new ArgumentNullException("appContext"); - _appContext = appContext; - } - - public override string View - { - get { return HasMachineKey() == false ? base.View : ""; } - } + public override string View => HasMachineKey() == false ? base.View : ""; /// /// Don't display the view or execute if a machine key already exists /// /// - private bool HasMachineKey() + private static bool HasMachineKey() { - var section = (MachineKeySection)WebConfigurationManager.GetSection("system.web/machineKey"); + var section = (MachineKeySection) WebConfigurationManager.GetSection("system.web/machineKey"); return section.ElementInformation.Source != null; } @@ -49,7 +35,7 @@ namespace Umbraco.Web.Install.InstallSteps if (model.HasValue && model.Value == false) return null; //install the machine key - var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root)); + var fileName = IOHelper.MapPath($"{SystemDirectories.Root}/web.config"); var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); var systemWeb = xml.Root.DescendantsAndSelf("system.web").Single(); @@ -72,4 +58,4 @@ namespace Umbraco.Web.Install.InstallSteps return HasMachineKey() == false; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs index 9f74596ca2..aab42dca9e 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; using System.Configuration; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; +using Umbraco.Core.Migrations.Install; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps @@ -19,11 +13,12 @@ namespace Umbraco.Web.Install.InstallSteps PerformsAppRestart = true)] internal class DatabaseConfigureStep : InstallSetupStep { - private readonly ApplicationContext _applicationContext; + private readonly DatabaseBuilder _databaseBuilder; + private readonly ILogger _logger; - public DatabaseConfigureStep(ApplicationContext applicationContext) + public DatabaseConfigureStep(DatabaseBuilder databaseBuilder) { - _applicationContext = applicationContext; + _databaseBuilder = databaseBuilder; } public override InstallSetupResult Execute(DatabaseModel database) @@ -34,9 +29,7 @@ namespace Umbraco.Web.Install.InstallSteps database = new DatabaseModel(); } - var dbHelper = new DatabaseHelper(); - - if (dbHelper.CheckConnection(database, _applicationContext) == false) + if (_databaseBuilder.CheckConnection(database.DatabaseType.ToString(), database.ConnectionString, database.Server, database.DatabaseName, database.Login, database.Password, database.IntegratedAuth) == false) { throw new InstallException("Could not connect to the database"); } @@ -46,35 +39,30 @@ namespace Umbraco.Web.Install.InstallSteps private void ConfigureConnection(DatabaseModel database) { - var dbContext = _applicationContext.DatabaseContext; if (database.ConnectionString.IsNullOrWhiteSpace() == false) { - dbContext.ConfigureDatabaseConnection(database.ConnectionString); + _databaseBuilder.ConfigureDatabaseConnection(database.ConnectionString); } else if (database.DatabaseType == DatabaseType.SqlCe) { - dbContext.ConfigureEmbeddedDatabaseConnection(); + _databaseBuilder.ConfigureEmbeddedDatabaseConnection(); } else if (database.IntegratedAuth) { - dbContext.ConfigureIntegratedSecurityDatabaseConnection( - database.Server, database.DatabaseName); + _databaseBuilder.ConfigureIntegratedSecurityDatabaseConnection(database.Server, database.DatabaseName); } else { var password = database.Password.Replace("&", "&").Replace(">", ">").Replace("<", "<").Replace("\"", """).Replace("'", "''"); password = string.Format("'{0}'", password); - dbContext.ConfigureDatabaseConnection( + _databaseBuilder.ConfigureDatabaseConnection( database.Server, database.DatabaseName, database.Login, password, database.DatabaseType.ToString()); } } - public override string View - { - get { return ShouldDisplayView() ? base.View : ""; } - } + public override string View => ShouldDisplayView() ? base.View : ""; public override bool RequiresExecution(DatabaseModel model) { @@ -86,18 +74,18 @@ namespace Umbraco.Web.Install.InstallSteps //If the connection string is already present in web.config we don't need to show the settings page and we jump to installing/upgrading. var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; - if (_applicationContext.DatabaseContext.IsConnectionStringConfigured(databaseSettings)) + if (_databaseBuilder.IsConnectionStringConfigured(databaseSettings)) { try { //Since a connection string was present we verify the db can connect and query - var result = _applicationContext.DatabaseContext.ValidateDatabaseSchema(); + var result = _databaseBuilder.ValidateDatabaseSchema(); result.DetermineInstalledVersion(); return false; } catch (Exception ex) { - _applicationContext.ProfilingLogger.Logger.Error("An error occurred, reconfiguring...", ex); + _logger.Error("An error occurred, reconfiguring...", ex); //something went wrong, could not connect so probably need to reconfigure return true; } diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs index b75a310546..068f3069bd 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Configuration; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Install; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps @@ -12,40 +13,43 @@ namespace Umbraco.Web.Install.InstallSteps "DatabaseInstall", 11, "")] internal class DatabaseInstallStep : InstallSetupStep { - private readonly ApplicationContext _applicationContext; + private readonly DatabaseBuilder _databaseBuilder; + private readonly IRuntimeState _runtime; + private readonly ILogger _logger; - public DatabaseInstallStep(ApplicationContext applicationContext) + public DatabaseInstallStep(DatabaseBuilder databaseBuilder, IRuntimeState runtime, ILogger logger) { - _applicationContext = applicationContext; + _databaseBuilder = databaseBuilder; + _runtime = runtime; + _logger = logger; } public override InstallSetupResult Execute(object model) { - var result = _applicationContext.DatabaseContext.CreateDatabaseSchemaAndData(_applicationContext); + if (_runtime.Level == RuntimeLevel.Run) + throw new Exception("Umbraco is already configured!"); + + var result = _databaseBuilder.CreateDatabaseSchemaAndData(); if (result.Success == false) { throw new InstallException("The database failed to install. ERROR: " + result.Message); } - + if (result.RequiresUpgrade == false) { - HandleConnectionStrings(); + HandleConnectionStrings(_logger); return null; } - else - { - //upgrade is required so set the flag for the next step - return new InstallSetupResult(new Dictionary - { - {"upgrade", true} - }); - } - + //upgrade is required so set the flag for the next step + return new InstallSetupResult(new Dictionary + { + {"upgrade", true} + }); } - internal static void HandleConnectionStrings() + internal static void HandleConnectionStrings(ILogger logger) { // Remove legacy umbracoDbDsn configuration setting if it exists and connectionstring also exists if (ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName] != null) @@ -55,7 +59,7 @@ namespace Umbraco.Web.Install.InstallSteps else { var ex = new ArgumentNullException(string.Format("ConfigurationManager.ConnectionStrings[{0}]", Constants.System.UmbracoConnectionName), "Install / upgrade did not complete successfully, umbracoDbDSN was not set in the connectionStrings section"); - LogHelper.Error("", ex); + logger.Error("", ex); throw ex; } } @@ -65,4 +69,4 @@ namespace Umbraco.Web.Install.InstallSteps return true; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs index c3ae313898..c078caf906 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs @@ -1,10 +1,9 @@ -using System; -using System.Collections.Generic; +using System; using System.Configuration; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Install; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps @@ -13,31 +12,35 @@ namespace Umbraco.Web.Install.InstallSteps "DatabaseUpgrade", 12, "")] internal class DatabaseUpgradeStep : InstallSetupStep { - private readonly ApplicationContext _applicationContext; - - public DatabaseUpgradeStep(ApplicationContext applicationContext) + private readonly DatabaseBuilder _databaseBuilder; + private readonly IRuntimeState _runtime; + private readonly ILogger _logger; + + public DatabaseUpgradeStep(DatabaseBuilder databaseBuilder, IRuntimeState runtime, ILogger logger) { - _applicationContext = applicationContext; + _databaseBuilder = databaseBuilder; + _runtime = runtime; + _logger = logger; } public override InstallSetupResult Execute(object model) { - var installSteps = InstallStatusTracker.GetStatus().ToArray(); + var installSteps = InstallStatusTracker.GetStatus().ToArray(); var previousStep = installSteps.Single(x => x.Name == "DatabaseInstall"); var upgrade = previousStep.AdditionalData.ContainsKey("upgrade"); if (upgrade) { - LogHelper.Info("Running 'Upgrade' service"); + _logger.Info("Running 'Upgrade' service"); - var result = _applicationContext.DatabaseContext.UpgradeSchemaAndData(_applicationContext.Services.MigrationEntryService); + var result = _databaseBuilder.UpgradeSchemaAndData(); if (result.Success == false) { throw new InstallException("The database failed to upgrade. ERROR: " + result.Message); } - DatabaseInstallStep.HandleConnectionStrings(); + DatabaseInstallStep.HandleConnectionStrings(_logger); } return null; @@ -46,10 +49,8 @@ namespace Umbraco.Web.Install.InstallSteps public override bool RequiresExecution(object model) { //if it's properly configured (i.e. the versions match) then no upgrade necessary - if (_applicationContext.IsConfigured) - { + if (_runtime.Level == RuntimeLevel.Run) return false; - } var installSteps = InstallStatusTracker.GetStatus().ToArray(); //this step relies on the previous one completed - because it has stored some information we need @@ -57,13 +58,13 @@ namespace Umbraco.Web.Install.InstallSteps { return false; } - + var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; - if (_applicationContext.DatabaseContext.IsConnectionStringConfigured(databaseSettings)) + if (_databaseBuilder.IsConnectionStringConfigured(databaseSettings)) { //Since a connection string was present we verify whether this is an upgrade or an empty db - var result = _applicationContext.DatabaseContext.ValidateDatabaseSchema(); + var result = _databaseBuilder.ValidateDatabaseSchema(); var determinedVersion = result.DetermineInstalledVersion(); if (determinedVersion.Equals(new Version(0, 0, 0))) @@ -80,4 +81,4 @@ namespace Umbraco.Web.Install.InstallSteps return false; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/InstallSteps/FilePermissionsStep.cs b/src/Umbraco.Web/Install/InstallSteps/FilePermissionsStep.cs index 37b12798cb..6d07190b4f 100644 --- a/src/Umbraco.Web/Install/InstallSteps/FilePermissionsStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/FilePermissionsStep.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using umbraco; @@ -15,17 +15,13 @@ namespace Umbraco.Web.Install.InstallSteps { public override InstallSetupResult Execute(object model) { - //first validate file permissions - var permissionsOk = true; - Dictionary> reportParts; - - permissionsOk = FilePermissionHelper.RunFilePermissionTestSuite(out reportParts); + // validate file permissions + Dictionary> report; + var permissionsOk = FilePermissionHelper.RunFilePermissionTestSuite(out report); if (permissionsOk == false) - { - throw new InstallException("Permission check failed", "permissionsreport", new { errors = reportParts }); - } - + throw new InstallException("Permission check failed", "permissionsreport", new { errors = report }); + return null; } @@ -34,4 +30,4 @@ namespace Umbraco.Web.Install.InstallSteps return true; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/InstallSteps/MajorVersion7UpgradeReport.cs b/src/Umbraco.Web/Install/InstallSteps/MajorVersion7UpgradeReport.cs deleted file mode 100644 index e7ee95dadb..0000000000 --- a/src/Umbraco.Web/Install/InstallSteps/MajorVersion7UpgradeReport.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Install.Models; - -namespace Umbraco.Web.Install.InstallSteps -{ - [InstallSetupStep(InstallationType.Upgrade, - "MajorVersion7UpgradeReport", 1, "")] - internal class MajorVersion7UpgradeReport : InstallSetupStep - { - private readonly ApplicationContext _applicationContext; - - public MajorVersion7UpgradeReport(ApplicationContext applicationContext) - { - _applicationContext = applicationContext; - } - - public override InstallSetupResult Execute(object model) - { - //we cannot run this step if the db is not configured. - if (_applicationContext.DatabaseContext.IsDatabaseConfigured == false) - { - return null; - } - - var result = _applicationContext.DatabaseContext.ValidateDatabaseSchema(); - var determinedVersion = result.DetermineInstalledVersion(); - - return new InstallSetupResult("version7upgradereport", - new - { - currentVersion = determinedVersion.ToString(), - newVersion = UmbracoVersion.Current.ToString(), - errors = CreateReport() - }); - } - - public override bool RequiresExecution(object model) - { - //if it's configured, then no need to run - if (_applicationContext.IsConfigured) - { - return false; - } - - try - { - //we cannot run this step if the db is not configured. - if (_applicationContext.DatabaseContext.IsDatabaseConfigured == false) - { - return false; - } - } - catch (InvalidOperationException) - { - //if there is no db context - return false; - } - - var result = _applicationContext.DatabaseContext.ValidateDatabaseSchema(); - var determinedVersion = result.DetermineInstalledVersion(); - if ((string.IsNullOrWhiteSpace(GlobalSettings.ConfigurationStatus) == false || determinedVersion.Equals(new Version(0, 0, 0)) == false) - && UmbracoVersion.Current.Major > determinedVersion.Major) - { - //it's an upgrade to a major version so we're gonna show this step if there are issues - - var report = CreateReport(); - return report.Any(); - } - - return false; - } - - private IEnumerable CreateReport() - { - var errorReport = new List(); - - var sql = new Sql(); - sql - .Select( - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumn("cmsDataType", "controlId"), - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumn("umbracoNode", "text")) - .From(SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsDataType")) - .InnerJoin(SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("umbracoNode")) - .On( - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumn("cmsDataType", "nodeId") + " = " + - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumn("umbracoNode", "id")); - - var list = _applicationContext.DatabaseContext.Database.Fetch(sql); - foreach (var item in list) - { - Guid legacyId = item.controlId; - //check for a map entry - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(legacyId); - if (alias != null) - { - //check that the new property editor exists with that alias - var editor = PropertyEditorResolver.Current.GetByAlias(alias); - if (editor == null) - { - errorReport.Add(string.Format("Property Editor with ID '{0}' (assigned to Data Type '{1}') has a valid GUID -> Alias map but no property editor was found. It will be replaced with a Readonly/Label property editor.", item.controlId, item.text)); - } - } - else - { - errorReport.Add(string.Format("Property Editor with ID '{0}' (assigned to Data Type '{1}') does not have a valid GUID -> Alias map. It will be replaced with a Readonly/Label property editor.", item.controlId, item.text)); - } - } - - return errorReport; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs index 2d0daf1aa9..e55203ec9f 100644 --- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Configuration; @@ -6,7 +6,9 @@ using System.Web; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Migrations.Install; using Umbraco.Core.Persistence; +using Umbraco.Core.Services; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps @@ -15,21 +17,24 @@ namespace Umbraco.Web.Install.InstallSteps /// This is the first UI step for a brand new install /// /// - /// By default this will show the user view which is the most basic information to configure a new install, but if an install get's interupted because of an - /// error, etc... and the end-user refreshes the installer then we cannot show the user screen because they've already entered that information so instead we'll + /// By default this will show the user view which is the most basic information to configure a new install, but if an install get's interupted because of an + /// error, etc... and the end-user refreshes the installer then we cannot show the user screen because they've already entered that information so instead we'll /// display a simple continue installation view. /// - [InstallSetupStep(InstallationType.NewInstall, - "User", 20, "")] + [InstallSetupStep(InstallationType.NewInstall, "User", 20, "")] internal class NewInstallStep : InstallSetupStep { private readonly HttpContextBase _http; - private readonly ApplicationContext _applicationContext; + private readonly IUserService _userService; + private readonly DatabaseBuilder _databaseBuilder; + private readonly IGlobalSettings _globalSettings; - public NewInstallStep(HttpContextBase http, ApplicationContext applicationContext) + public NewInstallStep(HttpContextBase http, IUserService userService, DatabaseBuilder databaseBuilder, IGlobalSettings globalSettings) { _http = http; - _applicationContext = applicationContext; + _userService = userService; + _databaseBuilder = databaseBuilder; + _globalSettings = globalSettings; } //TODO: Change all logic in this step to use ASP.NET Identity NOT MembershipProviders @@ -44,16 +49,16 @@ namespace Umbraco.Web.Install.InstallSteps public override InstallSetupResult Execute(UserModel user) { - var admin = _applicationContext.Services.UserService.GetUserById(0); + var admin = _userService.GetUserById(Constants.Security.SuperId); if (admin == null) { - throw new InvalidOperationException("Could not find the admi user!"); + throw new InvalidOperationException("Could not find the super user!"); } - var membershipUser = CurrentProvider.GetUser(0, true); + var membershipUser = CurrentProvider.GetUser(Constants.Security.SuperId, true); if (membershipUser == null) { - throw new InvalidOperationException("No user found in membership provider with id of 0"); + throw new InvalidOperationException($"No user found in membership provider with id of {Constants.Security.SuperId}."); } try @@ -73,7 +78,7 @@ namespace Umbraco.Web.Install.InstallSteps admin.Name = user.Name.Trim(); admin.Username = user.Email.Trim(); - _applicationContext.Services.UserService.Save(admin); + _userService.Save(admin); if (user.SubscribeToNewsLetter) @@ -111,41 +116,28 @@ namespace Umbraco.Web.Install.InstallSteps { get { return RequiresExecution(null) //the user UI - ? "user" + ? "user" //the continue install UI : "continueinstall"; } } - + public override bool RequiresExecution(UserModel model) { //now we have to check if this is really a new install, the db might be configured and might contain data var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; - //if there's already a version then there should def be a user but in some cases someone may have + //if there's already a version then there should def be a user but in some cases someone may have // left a version number in there but cleared out their db conn string, in that case, it's really a new install. - if (GlobalSettings.ConfigurationStatus.IsNullOrWhiteSpace() == false && databaseSettings != null) return false; + if (_globalSettings.ConfigurationStatus.IsNullOrWhiteSpace() == false && databaseSettings != null) return false; - if (_applicationContext.DatabaseContext.IsConnectionStringConfigured(databaseSettings) - && _applicationContext.DatabaseContext.IsDatabaseConfigured) - { - //check if we have the default user configured already - var result = _applicationContext.DatabaseContext.Database.ExecuteScalar( - "SELECT COUNT(*) FROM umbracoUser WHERE id=0 AND userPassword='default'"); - if (result == 1) - { - //the user has not been configured - return true; - } - return false; - } - else - { - // In this one case when it's a brand new install and nothing has been configured, make sure the - // back office cookie is cleared so there's no old cookies lying around causing problems - _http.ExpireCookie(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName); + if (_databaseBuilder.IsConnectionStringConfigured(databaseSettings) && _databaseBuilder.IsDatabaseConfigured) + return _databaseBuilder.HasSomeNonDefaultUser() == false; + + // In this one case when it's a brand new install and nothing has been configured, make sure the + // back office cookie is cleared so there's no old cookies lying around causing problems + _http.ExpireCookie(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName); return true; - } } } } diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs index 0de405ebef..0cac3fe91f 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Web; +using System.Web; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Services; using Umbraco.Web.Cache; +using Umbraco.Web.Composing; using Umbraco.Web.Install.Models; using Umbraco.Web.Security; -using GlobalSettings = umbraco.GlobalSettings; + namespace Umbraco.Web.Install.InstallSteps { @@ -16,42 +16,45 @@ namespace Umbraco.Web.Install.InstallSteps PerformsAppRestart = true)] internal class SetUmbracoVersionStep : InstallSetupStep { - private readonly ApplicationContext _applicationContext; private readonly HttpContextBase _httpContext; + private readonly InstallHelper _installHelper; + private readonly IGlobalSettings _globalSettings; + private readonly IUserService _userService; + private readonly DistributedCache _distributedCache; - public SetUmbracoVersionStep(ApplicationContext applicationContext, HttpContextBase httpContext) + public SetUmbracoVersionStep(HttpContextBase httpContext, InstallHelper installHelper, IGlobalSettings globalSettings, IUserService userService, DistributedCache distributedCache) { - _applicationContext = applicationContext; _httpContext = httpContext; + _installHelper = installHelper; + _globalSettings = globalSettings; + _userService = userService; + _distributedCache = distributedCache; } public override InstallSetupResult Execute(object model) { - var ih = new InstallHelper(UmbracoContext.Current); - //During a new install we'll log the default user in (which is id = 0). // During an upgrade, the user will already need to be logged in in order to run the installer. - var security = new WebSecurity(_httpContext, _applicationContext); + var security = new WebSecurity(_httpContext, _userService, _globalSettings); //we do this check here because for upgrades the user will already be logged in, for brand new installs, // they will not be logged in, however we cannot check the current installation status because it will tell // us that it is in 'upgrade' because we already have a database conn configured and a database. - if (security.IsAuthenticated() == false && GlobalSettings.ConfigurationStatus.IsNullOrWhiteSpace()) + if (security.IsAuthenticated() == false && _globalSettings.ConfigurationStatus.IsNullOrWhiteSpace()) { - security.PerformLogin(0); + security.PerformLogin(-1); } - //This is synonymous with library.RefreshContent() - but we don't want to use library - // for anything anymore so welll use the method that it is wrapping. This will just make sure - // the correct xml structure exists in the xml cache file. This is required by some upgrade scripts - // that may modify the cmsContentXml table directly. - DistributedCache.Instance.RefreshAllPageCache(); + // Some upgrade scripts "may modify the database (cmsContentXml...) tables directly" - not sure + // that is still true but the idea is that after an upgrade we want to reset the local published snapshot, on + // all LB nodes of course, so we need to use the distributed cache, and refresh everything. + _distributedCache.RefreshAllPublishedSnapshot(); // Update configurationStatus - GlobalSettings.ConfigurationStatus = UmbracoVersion.GetSemanticVersion().ToSemanticString(); - - //reports the ended install - ih.InstallStatus(true, ""); + _globalSettings.ConfigurationStatus = UmbracoVersion.SemanticVersion.ToSemanticString(); + + //reports the ended install + _installHelper.InstallStatus(true, ""); return null; } @@ -61,4 +64,4 @@ namespace Umbraco.Web.Install.InstallSteps return true; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/InstallSteps/StarterKitCleanupStep.cs b/src/Umbraco.Web/Install/InstallSteps/StarterKitCleanupStep.cs index 05d0e7b8fa..5738f2a67c 100644 --- a/src/Umbraco.Web/Install/InstallSteps/StarterKitCleanupStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/StarterKitCleanupStep.cs @@ -1,25 +1,15 @@ -using System; -using System.Collections.Generic; +using System; using System.Linq; using System.Web; -using umbraco; using umbraco.cms.businesslogic.packager; -using Umbraco.Core; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps { - [InstallSetupStep(InstallationType.NewInstall, + [InstallSetupStep(InstallationType.NewInstall, "StarterKitCleanup", 32, "Almost done")] internal class StarterKitCleanupStep : InstallSetupStep { - private readonly ApplicationContext _applicationContext; - - public StarterKitCleanupStep(ApplicationContext applicationContext) - { - _applicationContext = applicationContext; - } - public override InstallSetupResult Execute(object model) { var installSteps = InstallStatusTracker.GetStatus().ToArray(); @@ -28,7 +18,7 @@ namespace Umbraco.Web.Install.InstallSteps var packageFile = (string)previousStep.AdditionalData["packageFile"]; CleanupInstallation(manifestId, packageFile); - + return null; } @@ -39,7 +29,9 @@ namespace Umbraco.Web.Install.InstallSteps installer.LoadConfig(packageFile); installer.InstallCleanUp(manifestId, packageFile); - library.RefreshContent(); + // library.RefreshContent is obsolete, would need to RefreshAll... snapshot, + // but it should be managed automatically by services and caches! + //DistributedCache.Instance.RefreshAll...(); } public override bool RequiresExecution(object model) @@ -54,4 +46,4 @@ namespace Umbraco.Web.Install.InstallSteps return true; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/InstallSteps/StarterKitDownloadStep.cs b/src/Umbraco.Web/Install/InstallSteps/StarterKitDownloadStep.cs index fd0c15bd61..b8ae600dba 100644 --- a/src/Umbraco.Web/Install/InstallSteps/StarterKitDownloadStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/StarterKitDownloadStep.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Web; using umbraco.cms.businesslogic.packager; -using Umbraco.Core; +using Umbraco.Core.Services; using Umbraco.Core.Configuration; +using Umbraco.Web.Composing; using Umbraco.Web.Install.Models; using Umbraco.Web.Security; @@ -15,15 +16,15 @@ namespace Umbraco.Web.Install.InstallSteps PerformsAppRestart = true)] internal class StarterKitDownloadStep : InstallSetupStep { - private readonly ApplicationContext _applicationContext; - private readonly WebSecurity _security; - private readonly HttpContextBase _httpContext; + private readonly InstallHelper _installHelper; + private readonly UmbracoContext _umbracoContext; + private readonly IContentService _contentService; - public StarterKitDownloadStep(ApplicationContext applicationContext, WebSecurity security, HttpContextBase httpContext) + public StarterKitDownloadStep(IContentService contentService, InstallHelper installHelper, UmbracoContext umbracoContext) { - _applicationContext = applicationContext; - _security = security; - _httpContext = httpContext; + _installHelper = installHelper; + _umbracoContext = umbracoContext; + _contentService = contentService; } private const string RepoGuid = "65194810-1f85-11dd-bd0b-0800200c9a66"; @@ -33,8 +34,7 @@ namespace Umbraco.Web.Install.InstallSteps //if there is no value assigned then use the default starter kit if (starterKitId.HasValue == false) { - var installHelper = new InstallHelper(UmbracoContext.Current); - var starterKits = installHelper.GetStarterKits().FirstOrDefault(); + var starterKits = _installHelper.GetStarterKits().FirstOrDefault(); if (starterKits != null) starterKitId = starterKits.Id; else @@ -49,7 +49,7 @@ namespace Umbraco.Web.Install.InstallSteps var result = DownloadPackageFiles(starterKitId.Value); - _applicationContext.RestartApplicationPool(_httpContext); + Current.RestartAppPool(); return new InstallSetupResult(new Dictionary { @@ -59,11 +59,11 @@ namespace Umbraco.Web.Install.InstallSteps } private Tuple DownloadPackageFiles(Guid kitGuid) - { + { var installer = new Installer(); //Go get the package file from the package repo - var packageFile = _applicationContext.Services.PackagingService.FetchPackageFile(kitGuid, UmbracoVersion.Current, _security.GetUserId()); + var packageFile = Current.Services.PackagingService.FetchPackageFile(kitGuid, UmbracoVersion.Current, _umbracoContext.Security.GetUserId().ResultOr(0)); var tempFile = installer.Import(packageFile); installer.LoadConfig(tempFile); @@ -99,10 +99,10 @@ namespace Umbraco.Web.Install.InstallSteps if (InstalledPackage.GetAllInstalledPackages().Count > 0) return false; - if (_applicationContext.Services.ContentService.GetRootContent().Any()) + if (_contentService.GetRootContent().Any()) return false; return true; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/InstallSteps/StarterKitInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/StarterKitInstallStep.cs index e206ed559a..844a27a576 100644 --- a/src/Umbraco.Web/Install/InstallSteps/StarterKitInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/StarterKitInstallStep.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Generic; +using System; using System.Linq; using System.Web; using umbraco.cms.businesslogic.packager; -using Umbraco.Core; +using Umbraco.Web.Composing; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps @@ -13,26 +12,24 @@ namespace Umbraco.Web.Install.InstallSteps PerformsAppRestart = true)] internal class StarterKitInstallStep : InstallSetupStep { - private readonly ApplicationContext _applicationContext; private readonly HttpContextBase _httContext; - public StarterKitInstallStep(ApplicationContext applicationContext, HttpContextBase httContext) + public StarterKitInstallStep(HttpContextBase httContext) { - _applicationContext = applicationContext; _httContext = httContext; } public override InstallSetupResult Execute(object model) { - var installSteps = InstallStatusTracker.GetStatus().ToArray(); + var installSteps = InstallStatusTracker.GetStatus().ToArray(); var previousStep = installSteps.Single(x => x.Name == "StarterKitDownload"); var manifestId = Convert.ToInt32(previousStep.AdditionalData["manifestId"]); var packageFile = (string)previousStep.AdditionalData["packageFile"]; InstallBusinessLogic(manifestId, packageFile); - _applicationContext.RestartApplicationPool(_httContext); + Current.RestartAppPool(_httContext); return null; } @@ -42,11 +39,11 @@ namespace Umbraco.Web.Install.InstallSteps packageFile = HttpUtility.UrlDecode(packageFile); var installer = new Installer(); installer.LoadConfig(packageFile); - installer.InstallBusinessLogic(manifestId, packageFile); + installer.InstallBusinessLogic(manifestId, packageFile); } public override bool RequiresExecution(object model) - { + { var installSteps = InstallStatusTracker.GetStatus().ToArray(); //this step relies on the preious one completed - because it has stored some information we need if (installSteps.Any(x => x.Name == "StarterKitDownload" && x.AdditionalData.ContainsKey("manifestId")) == false) @@ -57,4 +54,4 @@ namespace Umbraco.Web.Install.InstallSteps return true; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs b/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs index 902cae8096..7e3bd57524 100644 --- a/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs @@ -1,7 +1,5 @@ -using System; -using System.Linq; -using Semver; -using Umbraco.Core; +using System; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Web.Install.Models; @@ -10,44 +8,36 @@ namespace Umbraco.Web.Install.InstallSteps /// /// This step is purely here to show the button to commence the upgrade /// - [InstallSetupStep(InstallationType.Upgrade, - "Upgrade", "upgrade", 1, "Upgrading Umbraco to the latest and greatest version.")] + [InstallSetupStep(InstallationType.Upgrade, "Upgrade", "upgrade", 1, "Upgrading Umbraco to the latest and greatest version.")] internal class UpgradeStep : InstallSetupStep { - private readonly ApplicationContext _appCtx; + public override bool RequiresExecution(object model) => true; - public UpgradeStep(ApplicationContext appCtx) - { - _appCtx = appCtx; - } - - public override bool RequiresExecution(object model) - { - return true; - } - - public override InstallSetupResult Execute(object model) - { - return null; - } + public override InstallSetupResult Execute(object model) => null; public override object ViewModel { get { - var currentVersion = _appCtx.CurrentVersion().GetVersion(3).ToString(); - var newVersion = UmbracoVersion.Current.ToString(); - var reportUrl = string.Format("https://our.umbraco.org/contribute/releases/compare?from={0}&to={1}¬es=1", currentVersion, newVersion); + var currentVersion = UmbracoVersion.Local.ToString(); + var newVersion = UmbracoVersion.SemanticVersion.ToString(); - return new + string FormatGuidState(string value) { - currentVersion = currentVersion, - newVersion = newVersion, - reportUrl = reportUrl - }; + if (string.IsNullOrWhiteSpace(value)) value = "unknown"; + else if (Guid.TryParse(value, out var currentStateGuid)) + value = currentStateGuid.ToString("N").Substring(0, 8); + return value; + } + var state = Current.RuntimeState; // fixme inject + var currentState = FormatGuidState(state.CurrentMigrationState); + var newState = FormatGuidState(state.FinalMigrationState); + + var reportUrl = $"https://our.umbraco.org/contribute/releases/compare?from={currentVersion}&to={newVersion}¬es=1"; + + return new { currentVersion, newVersion, currentState, newState, reportUrl }; } } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/InstallSteps/Version73FileCleanup.cs b/src/Umbraco.Web/Install/InstallSteps/Version73FileCleanup.cs deleted file mode 100644 index f15463dc5f..0000000000 --- a/src/Umbraco.Web/Install/InstallSteps/Version73FileCleanup.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Web; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Web.Install.Models; - -namespace Umbraco.Web.Install.InstallSteps -{ - [InstallSetupStep(InstallationType.Upgrade, - "Version73FileCleanup", 2, - "Performing some housecleaning...", - PerformsAppRestart = true)] - internal class Version73FileCleanup : InstallSetupStep - { - private readonly HttpContextBase _httpContext; - private readonly ILogger _logger; - - public Version73FileCleanup(HttpContextBase httpContext, ILogger logger) - { - _httpContext = httpContext; - _logger = logger; - } - - /// - /// The step execution method - /// - /// - /// - public override InstallSetupResult Execute(object model) - { - //first cleanup all web.configs - - var root = new DirectoryInfo(_httpContext.Server.MapPath("~/")); - ProcessWebConfigs(root); - - //now remove the dll - - var bin = root.GetDirectories("bin", SearchOption.TopDirectoryOnly); - if (bin.Length == 1) - { - var dll = bin[0].GetFiles("Microsoft.Web.Mvc.FixedDisplayModes.dll", SearchOption.TopDirectoryOnly); - if (dll.Length == 1) - { - _logger.Info("Deleting non-compatible and no longer used DLL: {0}", () => dll[0].FullName); - File.Delete(dll[0].FullName); - } - } - - return null; - } - - /// - /// Determines if this step needs to execute based on the current state of the application and/or install process - /// - /// - public override bool RequiresExecution(object model) - { - return UmbracoVersion.Current == Version.Parse("7.3.0"); - } - - private void ProcessWebConfigs(DirectoryInfo dir) - { - //Do the processing of files - var found = dir.GetFiles("web.config", SearchOption.AllDirectories); - - foreach (var configFile in found) - { - var fileName = configFile.FullName; - _logger.Info("Cleaning up web.config file: {0}", () => fileName); - - var contents = File.ReadAllText(fileName); - contents = _microsoftWebHelpers.Replace(contents, string.Empty); - contents = _webPagesRazorVersion.Replace(contents, "$1=3.0.0.0"); - contents = _mvcVersion.Replace(contents, "$1=5.2.3.0"); - using (var writer = new StreamWriter(configFile.FullName, false)) - { - writer.Write(contents); - } - } - } - - private readonly Regex _microsoftWebHelpers = new Regex(@"", RegexOptions.Compiled); - private readonly Regex _webPagesRazorVersion = new Regex(@"(System\.Web\.WebPages\.Razor,\s*?Version)(=2\.0\.0\.0)", RegexOptions.Compiled); - private readonly Regex _mvcVersion = new Regex(@"(System\.Web\.Mvc,\s*?Version)(=4\.0\.0\.0)", RegexOptions.Compiled); - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Install/Models/DatabaseModel.cs b/src/Umbraco.Web/Install/Models/DatabaseModel.cs index 74bf2935e5..4b09534f39 100644 --- a/src/Umbraco.Web/Install/Models/DatabaseModel.cs +++ b/src/Umbraco.Web/Install/Models/DatabaseModel.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Web.Install.Models { @@ -32,4 +32,4 @@ namespace Umbraco.Web.Install.Models [DataMember(Name = "connectionString")] public string ConnectionString { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/Models/DatabaseType.cs b/src/Umbraco.Web/Install/Models/DatabaseType.cs index b66de5496b..3f7379eca5 100644 --- a/src/Umbraco.Web/Install/Models/DatabaseType.cs +++ b/src/Umbraco.Web/Install/Models/DatabaseType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Web.Install.Models +namespace Umbraco.Web.Install.Models { public enum DatabaseType { @@ -8,4 +8,4 @@ namespace Umbraco.Web.Install.Models SqlAzure, Custom } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/Models/InstallInstructions.cs b/src/Umbraco.Web/Install/Models/InstallInstructions.cs index f0a8907cdc..da4dfb1671 100644 --- a/src/Umbraco.Web/Install/Models/InstallInstructions.cs +++ b/src/Umbraco.Web/Install/Models/InstallInstructions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; using Newtonsoft.Json.Linq; @@ -14,4 +14,4 @@ namespace Umbraco.Web.Install.Models [DataMember(Name = "installId")] public Guid InstallId { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/Models/InstallProgressResultModel.cs b/src/Umbraco.Web/Install/Models/InstallProgressResultModel.cs index 6ebffd4ffe..7ea3b3375b 100644 --- a/src/Umbraco.Web/Install/Models/InstallProgressResultModel.cs +++ b/src/Umbraco.Web/Install/Models/InstallProgressResultModel.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Web.Install.Models { @@ -39,4 +39,4 @@ namespace Umbraco.Web.Install.Models [DataMember(Name = "model")] public object ViewModel { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/Models/InstallSetup.cs b/src/Umbraco.Web/Install/Models/InstallSetup.cs index 55f66f13d6..f61e301a09 100644 --- a/src/Umbraco.Web/Install/Models/InstallSetup.cs +++ b/src/Umbraco.Web/Install/Models/InstallSetup.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -18,9 +18,9 @@ namespace Umbraco.Web.Install.Models [DataMember(Name = "installId")] public Guid InstallId { get; private set; } - + [DataMember(Name = "steps")] - public IEnumerable Steps { get; set; } + public IEnumerable Steps { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/Models/InstallSetupResult.cs b/src/Umbraco.Web/Install/Models/InstallSetupResult.cs index e04f3c166a..4ef4b70f51 100644 --- a/src/Umbraco.Web/Install/Models/InstallSetupResult.cs +++ b/src/Umbraco.Web/Install/Models/InstallSetupResult.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Web.Install.Models { @@ -8,7 +8,7 @@ namespace Umbraco.Web.Install.Models public class InstallSetupResult { public InstallSetupResult() - { + { } public InstallSetupResult(IDictionary savedStepData, string view, object viewModel = null) @@ -44,4 +44,4 @@ namespace Umbraco.Web.Install.Models /// public object ViewModel { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/Models/InstallSetupStep.cs b/src/Umbraco.Web/Install/Models/InstallSetupStep.cs index 3cfa2659e2..36cf838a82 100644 --- a/src/Umbraco.Web/Install/Models/InstallSetupStep.cs +++ b/src/Umbraco.Web/Install/Models/InstallSetupStep.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.Serialization; using Umbraco.Core; @@ -71,9 +71,9 @@ namespace Umbraco.Web.Install.Models [IgnoreDataMember] public bool PerformsAppRestart { get; private set; } - + /// - /// Defines what order this step needs to execute on the server side since the + /// Defines what order this step needs to execute on the server side since the /// steps might be shown out of order on the front-end /// [DataMember(Name = "serverOrder")] @@ -84,7 +84,7 @@ namespace Umbraco.Web.Install.Models /// [IgnoreDataMember] public abstract Type StepType { get; } - + [IgnoreDataMember] public bool HasUIElement { @@ -92,4 +92,4 @@ namespace Umbraco.Web.Install.Models } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/Models/InstallSetupStepAttribute.cs b/src/Umbraco.Web/Install/Models/InstallSetupStepAttribute.cs index 3fb99a21bb..9cecdacf99 100644 --- a/src/Umbraco.Web/Install/Models/InstallSetupStepAttribute.cs +++ b/src/Umbraco.Web/Install/Models/InstallSetupStepAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.RegularExpressions; namespace Umbraco.Web.Install.Models @@ -42,4 +42,4 @@ namespace Umbraco.Web.Install.Models /// public bool PerformsAppRestart { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/Models/InstallTrackingItem.cs b/src/Umbraco.Web/Install/Models/InstallTrackingItem.cs index 360f58e0e7..1b1985dd1e 100644 --- a/src/Umbraco.Web/Install/Models/InstallTrackingItem.cs +++ b/src/Umbraco.Web/Install/Models/InstallTrackingItem.cs @@ -35,4 +35,4 @@ namespace Umbraco.Web.Install.Models return Name.GetHashCode(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/Models/InstallationType.cs b/src/Umbraco.Web/Install/Models/InstallationType.cs index cfedb8b850..b0202a4c3a 100644 --- a/src/Umbraco.Web/Install/Models/InstallationType.cs +++ b/src/Umbraco.Web/Install/Models/InstallationType.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Web.Install.Models { @@ -8,4 +8,4 @@ namespace Umbraco.Web.Install.Models NewInstall = 1 << 0, // 1 Upgrade = 1 << 1, // 2 } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/Models/UserModel.cs b/src/Umbraco.Web/Install/Models/UserModel.cs index 82baf9ba56..aed397a7d9 100644 --- a/src/Umbraco.Web/Install/Models/UserModel.cs +++ b/src/Umbraco.Web/Install/Models/UserModel.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Web.Install.Models { @@ -7,14 +7,14 @@ namespace Umbraco.Web.Install.Models { [DataMember(Name = "name")] public string Name { get; set; } - + [DataMember(Name = "email")] public string Email { get; set; } - + [DataMember(Name = "password")] public string Password { get; set; } [DataMember(Name = "subscribeToNewsLetter")] public bool SubscribeToNewsLetter { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Install/UmbracoInstallArea.cs b/src/Umbraco.Web/Install/UmbracoInstallArea.cs index 789fe854f4..2b44abd4ab 100644 --- a/src/Umbraco.Web/Install/UmbracoInstallArea.cs +++ b/src/Umbraco.Web/Install/UmbracoInstallArea.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Install /// /// /// - /// By using the context to register the routes it means that the area is already applied to them all + /// By using the context to register the routes it means that the area is already applied to them all /// and that the namespaces searched for the controllers are ONLY the ones specified. /// public override void RegisterArea(AreaRegistrationContext context) @@ -52,4 +52,4 @@ namespace Umbraco.Web.Install get { return "UmbracoInstall"; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/LightInjectExtensions.cs b/src/Umbraco.Web/LightInjectExtensions.cs new file mode 100644 index 0000000000..580dc4117d --- /dev/null +++ b/src/Umbraco.Web/LightInjectExtensions.cs @@ -0,0 +1,49 @@ +using System.Reflection; +using System.Web.Http.Controllers; +using System.Web.Mvc; +using LightInject; +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web +{ + internal static class LightInjectExtensions + { + /// + /// Registers all IControllers using the TypeLoader for scanning and caching found instances for the calling assembly + /// + /// + /// + /// + public static void RegisterMvcControllers(this IServiceRegistry container, TypeLoader typeLoader, Assembly assembly) + { + //TODO: We've already scanned for UmbracoApiControllers and SurfaceControllers - should we scan again + // for all controllers? Seems like we should just do this once and then filter. That said here we are + // only scanning our own single assembly. Hrm. + + container.RegisterControllers(typeLoader, assembly); + } + + /// + /// Registers all IHttpController using the TypeLoader for scanning and caching found instances for the calling assembly + /// + /// + /// + /// + public static void RegisterApiControllers(this IServiceRegistry container, TypeLoader typeLoader, Assembly assembly) + { + //TODO: We've already scanned for UmbracoApiControllers and SurfaceControllers - should we scan again + // for all controllers? Seems like we should just do this once and then filter. That said here we are + // only scanning our own single assembly. Hrm. + + container.RegisterControllers(typeLoader, assembly); + } + + private static void RegisterControllers(this IServiceRegistry container, TypeLoader typeLoader, Assembly assembly) + { + var types = typeLoader.GetTypes(specificAssemblies: new[] { assembly }); + foreach (var type in types) + container.Register(type, new PerRequestLifeTime()); + } + } +} diff --git a/src/Umbraco.Web/Macros/MacroContent.cs b/src/Umbraco.Web/Macros/MacroContent.cs new file mode 100644 index 0000000000..2e7680ebe0 --- /dev/null +++ b/src/Umbraco.Web/Macros/MacroContent.cs @@ -0,0 +1,53 @@ +using System; +using System.IO; +using System.Web.UI; + +namespace Umbraco.Web.Macros +{ + // represents the content of a macro + public class MacroContent + { + // gets or sets the text content + public string Text { get; set; } + + // gets or sets the control content + public Control Control { get; set; } + public string ControlId { get; set; } + + // gets or sets the date the content was generated + public DateTime Date { get; set; } = DateTime.Now; + + // a value indicating whether the content is empty + public bool IsEmpty => Text == null && Control == null; + + // a value indicating whether the content is pure text (no control) + public bool IsText => Control == null; + + // a value indicating whether the content is a control + public bool IsControl => Control != null; + + // gets an empty macro content + public static MacroContent Empty { get; } = new MacroContent(); + + // gets the macro content as a string + // ie executes the control if any + public string GetAsText() + { + if (Control == null) return Text ?? string.Empty; + + using (var textWriter = new StringWriter()) + using (var htmlWriter = new HtmlTextWriter(textWriter)) + { + Control.RenderControl(htmlWriter); + return textWriter.ToString(); + } + } + + // gets the macro content as a control + // ie wraps the text in a control if needed + public Control GetAsControl() + { + return Control ?? new LiteralControl(Text ?? string.Empty); + } + } +} diff --git a/src/Umbraco.Web/Macros/MacroModel.cs b/src/Umbraco.Web/Macros/MacroModel.cs new file mode 100644 index 0000000000..4239dbc827 --- /dev/null +++ b/src/Umbraco.Web/Macros/MacroModel.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Web.Macros +{ + public class MacroModel + { + public int Id { get; set; } + + public string Name { get; set; } + + public string Alias { get; set; } + + public string MacroControlIdentifier { get; set; } + + public MacroTypes MacroType { get; set; } + + // that one was for CustomControls which are gone in v8 + //public string TypeAssembly { get; set; } + + public string TypeName { get; set; } + + public string Xslt { get; set; } + + public string ScriptName { get; set; } + + public string ScriptCode { get; set; } + + public string ScriptLanguage { get; set; } + + public int CacheDuration { get; set; } + + public bool CacheByPage { get; set; } + + public bool CacheByMember { get; set; } + + public bool RenderInEditor { get; set; } + + public string CacheIdentifier { get; set; } + + public List Properties { get; } = new List(); + + public MacroModel() + { } + + public MacroModel(IMacro macro) + { + if (macro == null) return; + + Id = macro.Id; + Name = macro.Name; + Alias = macro.Alias; + //TypeAssembly = macro.ControlAssembly; + TypeName = macro.ControlType; + Xslt = macro.XsltPath; + ScriptName = macro.ScriptPath; + CacheDuration = macro.CacheDuration; + CacheByPage = macro.CacheByPage; + CacheByMember = macro.CacheByMember; + RenderInEditor = macro.UseInEditor; + + foreach (var prop in macro.Properties) + Properties.Add(new MacroPropertyModel(prop.Alias, string.Empty, prop.EditorAlias)); + + // can convert enums + MacroType = MacroService.GetMacroType(macro); + } + } +} diff --git a/src/Umbraco.Web/Macros/MacroPropertyModel.cs b/src/Umbraco.Web/Macros/MacroPropertyModel.cs new file mode 100644 index 0000000000..0621155be7 --- /dev/null +++ b/src/Umbraco.Web/Macros/MacroPropertyModel.cs @@ -0,0 +1,27 @@ +namespace Umbraco.Web.Macros +{ + public class MacroPropertyModel + { + public string Key { get; set; } + + public string Value { get; set; } + + public string Type { get; set; } + + public MacroPropertyModel() + { } + + public MacroPropertyModel(string key, string value) + { + Key = key; + Value = value; + } + + public MacroPropertyModel(string key, string value, string type) + { + Key = key; + Value = value; + Type = type; + } + } +} diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs new file mode 100644 index 0000000000..9692b73b52 --- /dev/null +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -0,0 +1,756 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; +using System.Web.Caching; +using System.Web.Hosting; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Macros; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Composing; + +namespace Umbraco.Web.Macros +{ + public class MacroRenderer + { + private readonly ProfilingLogger _plogger; + + // todo: there are many more things that would need to be injected in here + + public MacroRenderer(ProfilingLogger plogger) + { + _plogger = plogger; + } + + // probably can do better - just porting from v7 + public IList Exceptions { get; } = new List(); + + #region MacroContent cache + + // gets this macro content cache identifier + private static string GetContentCacheIdentifier(MacroModel model, int pageId) + { + var id = new StringBuilder(); + + var alias = string.IsNullOrEmpty(model.ScriptCode) + ? model.Alias + : GenerateCacheKeyFromCode(model.ScriptCode); + id.AppendFormat("{0}-", alias); + + if (model.CacheByPage) + id.AppendFormat("{0}-", pageId); + + if (model.CacheByMember) + { + object key = 0; + + if (HttpContext.Current.User.Identity.IsAuthenticated) + { + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + var member = Core.Security.MembershipProviderExtensions.GetCurrentUser(provider); + key = member?.ProviderUserKey ?? 0; + } + + id.AppendFormat("m{0}-", key); + } + + foreach (var value in model.Properties.Select(x => x.Value)) + id.AppendFormat("{0}-", value.Length <= 255 ? value : value.Substring(0, 255)); + + return id.ToString(); + } + + private static string GenerateCacheKeyFromCode(string input) + { + if (string.IsNullOrEmpty(input)) throw new ArgumentNullException(nameof(input)); + + // step 1, calculate MD5 hash from input + var md5 = MD5.Create(); + var inputBytes = Encoding.ASCII.GetBytes(input); + var hash = md5.ComputeHash(inputBytes); + + // step 2, convert byte array to hex string + var sb = new StringBuilder(); + foreach (var h in hash) sb.Append(h.ToString("X2")); + return sb.ToString(); + } + + // gets this macro content from the cache + // ensuring that it is appropriate to use the cache + private static MacroContent GetMacroContentFromCache(MacroModel model) + { + // only if cache is enabled + if (UmbracoContext.Current.InPreviewMode || model.CacheDuration <= 0) return null; + + var cache = Current.ApplicationCache.RuntimeCache; + var macroContent = cache.GetCacheItem(CacheKeys.MacroContentCacheKey + model.CacheIdentifier); + + if (macroContent == null) return null; + + Current.Logger.Debug(() => $"Macro content loaded from cache \"{model.CacheIdentifier}\"."); + + // ensure that the source has not changed + // note: does not handle dependencies, and never has + var macroSource = GetMacroFile(model); // null if macro is not file-based + if (macroSource != null) + { + if (macroSource.Exists == false) + { + Current.Logger.Debug("Macro source does not exist anymore, ignore cache."); + return null; + } + + if (macroContent.Date < macroSource.LastWriteTime) + { + Current.Logger.Debug("Macro source has changed, ignore cache."); + return null; + } + } + + // this is legacy and I'm not sure what exactly it is supposed to do + if (macroContent.Control != null) + macroContent.Control.ID = macroContent.ControlId; + + return macroContent; + } + + // stores macro content into the cache + private static void AddMacroContentToCache(MacroModel model, MacroContent macroContent) + { + // only if cache is enabled + if (UmbracoContext.Current.InPreviewMode || model.CacheDuration <= 0) return; + + // just make sure... + if (macroContent == null) return; + + // do not cache if it should cache by member and there's not member + if (model.CacheByMember) + { + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + var member = Core.Security.MembershipProviderExtensions.GetCurrentUser(provider); + var key = member?.ProviderUserKey; + if (key == null) return; + } + + // scripts and xslt can be cached as strings but not controls + // as page events (Page_Load) wouldn't be hit -- yet caching + // controls is a bad idea, it can lead to plenty of issues ?! + // eg with IDs... + + // this is legacy and I'm not sure what exactly it is supposed to do + if (macroContent.Control != null) + macroContent.ControlId = macroContent.Control.ID; + + // remember when we cache the content + macroContent.Date = DateTime.Now; + + var cache = Current.ApplicationCache.RuntimeCache; + cache.InsertCacheItem( + CacheKeys.MacroContentCacheKey + model.CacheIdentifier, + () => macroContent, + new TimeSpan(0, 0, model.CacheDuration), + priority: CacheItemPriority.NotRemovable + ); + + Current.Logger.Debug(() => $"Macro content saved to cache \"{model.CacheIdentifier}\"."); + } + + // gets the macro source file name + // null if the macro is not file-based + internal static string GetMacroFileName(MacroModel model) + { + string filename; + + switch (model.MacroType) + { + case MacroTypes.Xslt: + filename = SystemDirectories.Xslt.EnsureEndsWith('/') + model.Xslt; + break; + //case MacroTypes.Script: + // // was "~/macroScripts/" + // filename = SystemDirectories.MacroScripts.EnsureEndsWith('/') + model.ScriptName; + // break; + case MacroTypes.PartialView: + filename = model.ScriptName; //partial views are saved with their full virtual path + break; + case MacroTypes.UserControl: + filename = model.TypeName; //user controls are saved with their full virtual path + break; + //case MacroTypes.Script: + //case MacroTypes.CustomControl: + //case MacroTypes.Unknown: + default: + // not file-based, or not supported + filename = null; + break; + } + + return filename; + } + + // gets the macro source file + // null if macro is not file-based + internal static FileInfo GetMacroFile(MacroModel model) + { + var filename = GetMacroFileName(model); + if (filename == null) return null; + + var mapped = HostingEnvironment.MapPath(filename); + if (mapped == null) return null; + + var file = new FileInfo(mapped); + return file.Exists ? file : null; + } + + #endregion + + #region MacroModel properties + + // updates the model properties values according to the attributes + private static void UpdateMacroModelProperties(MacroModel model, Hashtable attributes) + { + foreach (var prop in model.Properties) + { + var key = prop.Key.ToLowerInvariant(); + prop.Value = attributes.ContainsKey(key) + ? attributes[key]?.ToString() ?? string.Empty + : string.Empty; + } + } + + // generates the model properties according to the attributes + public static void GenerateMacroModelPropertiesFromAttributes(MacroModel model, Hashtable attributes) + { + foreach (string key in attributes.Keys) + model.Properties.Add(new MacroPropertyModel(key, attributes[key].ToString())); + } + + #endregion + + #region Render/Execute + + // still, this is ugly. The macro should have a Content property + // referring to IPublishedContent we're rendering the macro against, + // this is all soooo convoluted ;-( + + public MacroContent Render(MacroModel macro, Hashtable pageElements, int pageId, Hashtable attributes) + { + UpdateMacroModelProperties(macro, attributes); + return Render(macro, pageElements, pageId); + } + + public MacroContent Render(MacroModel macro, Hashtable pageElements, int pageId) + { + // trigger MacroRendering event so that the model can be manipulated before rendering + OnMacroRendering(new MacroRenderingEventArgs(pageElements, pageId)); + + var macroInfo = $"Render Macro: {macro.Name}, type: {macro.MacroType}, cache: {macro.CacheDuration}"; + using (_plogger.DebugDuration(macroInfo, "Rendered Macro.")) + { + // parse macro parameters ie replace the special [#key], [$key], etc. syntaxes + foreach (var prop in macro.Properties) + prop.Value = ParseAttribute(pageElements, prop.Value); + + macro.CacheIdentifier = GetContentCacheIdentifier(macro, pageId); + + // get the macro from cache if it is there + var macroContent = GetMacroContentFromCache(macro); + + // macroContent.IsEmpty may be true, meaning the macro produces no output, + // but still can be cached because its execution did not trigger any error. + // so we need to actually render, only if macroContent is null + if (macroContent != null) + return macroContent; + + // this will take care of errors + // it may throw, if we actually want to throw, so better not + // catch anything here and let the exception be thrown + var attempt = ExecuteMacroOfType(macro); + + // by convention ExecuteMacroByType must either throw or return a result + // just check to avoid internal errors + macroContent = attempt.Result; + if (macroContent == null) + throw new Exception("Internal error, ExecuteMacroOfType returned no content."); + + // add to cache if render is successful + // content may be empty but that's not an issue + if (attempt.Success) + { + // write to cache (if appropriate) + AddMacroContentToCache(macro, macroContent); + } + + return macroContent; + } + } + + /// + /// Executes a macro of a given type. + /// + private Attempt ExecuteMacroWithErrorWrapper(MacroModel macro, string msgIn, string msgOut, Func getMacroContent, Func msgErr) + { + using (_plogger.DebugDuration(msgIn, msgOut)) + { + return ExecuteProfileMacroWithErrorWrapper(macro, msgIn, getMacroContent, msgErr); + } + } + + /// + /// Executes a macro of a given type. + /// + private Attempt ExecuteProfileMacroWithErrorWrapper(MacroModel macro, string msgIn, Func getMacroContent, Func msgErr) + { + try + { + return Attempt.Succeed(getMacroContent()); + } + catch (Exception e) + { + Exceptions.Add(e); + + _plogger.Logger.Warn(e, "Failed " + msgIn); + + var macroErrorEventArgs = new MacroErrorEventArgs + { + Name = macro.Name, + Alias = macro.Alias, + ItemKey = macro.ScriptName, + Exception = e, + Behaviour = UmbracoConfig.For.UmbracoSettings().Content.MacroErrorBehaviour + }; + + OnError(macroErrorEventArgs); + + switch (macroErrorEventArgs.Behaviour) + { + case MacroErrorBehaviour.Inline: + // do not throw, eat the exception, display the trace error message + return Attempt.Fail(new MacroContent { Text = msgErr() }, e); + case MacroErrorBehaviour.Silent: + // do not throw, eat the exception, do not display anything + return Attempt.Fail(new MacroContent { Text = string.Empty }, e); + case MacroErrorBehaviour.Content: + // do not throw, eat the exception, display the custom content + return Attempt.Fail(new MacroContent { Text = macroErrorEventArgs.Html ?? string.Empty }, e); + //case MacroErrorBehaviour.Throw: + default: + // see http://issues.umbraco.org/issue/U4-497 at the end + // throw the original exception + throw; + } + } + } + + /// + /// Executes a macro. + /// + /// Returns an attempt that is successful if the macro ran successfully. If the macro failed + /// to run properly, the attempt fails, though it may contain a content. But for instance that content + /// should not be cached. In that case the attempt may also contain an exception. + private Attempt ExecuteMacroOfType(MacroModel model) + { + // ensure that we are running against a published node (ie available in XML) + // that may not be the case if the macro is embedded in a RTE of an unpublished document + + if (UmbracoContext.Current.PublishedRequest == null + || UmbracoContext.Current.PublishedRequest.HasPublishedContent == false) + return Attempt.Fail(new MacroContent { Text = "[macro]" }); + + var textService = Current.Services.TextService; + + switch (model.MacroType) + { + case MacroTypes.PartialView: + return ExecuteMacroWithErrorWrapper(model, + $"Executing PartialView: TypeName=\"{model.TypeName}\", ScriptName=\"{model.ScriptName}\".", + "Executed PartialView.", + () => ExecutePartialView(model), + () => textService.Localize("errors/macroErrorLoadingPartialView", new[] { model.ScriptName })); + + //case MacroTypes.Script: + // return ExecuteMacroWithErrorWrapper(model, + // "Executing Script: " + (string.IsNullOrWhiteSpace(model.ScriptCode) + // ? "ScriptName=\"" + model.ScriptName + "\"" + // : "Inline, Language=\"" + model.ScriptLanguage + "\""), + // "Executed Script.", + // () => ExecuteScript(model), + // () => textService.Localize("errors/macroErrorLoadingMacroEngineScript", new[] { model.ScriptName })); + + case MacroTypes.Xslt: + return ExecuteMacroWithErrorWrapper(model, + $"Executing Xslt: TypeName=\"{model.TypeName}\", ScriptName=\"{model.Xslt}\".", + "Executed Xslt.", + () => ExecuteXslt(model, _plogger), + // cannot diff. between reading & parsing... bah + () => textService.Localize("errors/macroErrorParsingXSLTFile", new[] { model.Xslt })); + + case MacroTypes.UserControl: + return ExecuteMacroWithErrorWrapper(model, + $"Loading UserControl: TypeName=\"{model.TypeName}\".", + "Loaded UserControl.", + () => ExecuteUserControl(model), + () => textService.Localize("errors/macroErrorLoadingUsercontrol", new[] { model.TypeName })); + + //case MacroTypes.Script: + default: + return ExecuteMacroWithErrorWrapper(model, + $"Execute macro with unsupported type \"{model.MacroType}\".", + "Executed.", + () => { throw new Exception("Unsupported macro type."); }, + () => textService.Localize("errors/macroErrorUnsupportedType")); + } + } + + // raised when a macro triggers an error + public static event TypedEventHandler Error; + + protected void OnError(MacroErrorEventArgs e) + { + Error?.Invoke(this, e); + } + + // raised before the macro renders, allowing devs to modify it + public static event TypedEventHandler MacroRendering; + + protected void OnMacroRendering(MacroRenderingEventArgs e) + { + MacroRendering?.Invoke(this, e); + } + + #endregion + + #region Execute engines + + /// + /// Renders a PartialView Macro. + /// + /// The text output of the macro execution. + private static MacroContent ExecutePartialView(MacroModel macro) + { + var engine = new PartialViewMacroEngine(); + var content = UmbracoContext.Current.PublishedRequest.PublishedContent; + return engine.Execute(macro, content); + } + + /// + /// Renders an Xslt Macro. + /// + /// The text output of the macro execution. + public static MacroContent ExecuteXslt(MacroModel macro, ProfilingLogger plogger) + { + var engine = new XsltMacroEngine(plogger); + return engine.Execute(macro); + } + + public static MacroContent ExecuteUserControl(MacroModel macro) + { + // add tilde for v4 defined macros + if (string.IsNullOrEmpty(macro.TypeName) == false + && macro.TypeName.StartsWith("~") == false) + macro.TypeName = "~/" + macro.TypeName; + + var engine = new UserControlMacroEngine(); + return engine.Execute(macro); + } + + #endregion + + #region Execution helpers + + // parses attribute value looking for [@requestKey], [%sessionKey], [#pageElement], [$recursiveValue] + // supports fallbacks eg "[@requestKey],[%sessionKey],1234" + public static string ParseAttribute(IDictionary pageElements, string attributeValue) + { + + // check for potential querystring/cookie variables + attributeValue = attributeValue.Trim(); + if (attributeValue.StartsWith("[") == false) + return attributeValue; + + var tokens = attributeValue.Split(',').Select(x => x.Trim()).ToArray(); + + // ensure we only process valid input ie each token must be [?x] and not eg a json array + // like [1,2,3] which we don't want to parse - however the last one can be a literal, so + // don't check on the last one which can be just anything - check all previous tokens + + char[] validTypes = { '@', '%', '#', '$' }; + if (tokens.Take(tokens.Length - 1).Any(x => + x.Length < 4 // ie "[?x]".Length - too short + || x[0] != '[' // starts with [ + || x[x.Length - 1] != ']' // ends with ] + || validTypes.Contains(x[1]) == false)) + { + return attributeValue; + } + + var context = HttpContext.Current; + + foreach (var token in tokens) + { + var isToken = token.Length > 4 && token[0] == '[' && token[token.Length - 1] == ']' && validTypes.Contains(token[1]); + + if (isToken == false) + { + // anything that is not a token is a value, use it + attributeValue = token; + break; + } + + var type = token[1]; + var name = token.Substring(2, token.Length - 3); + + switch (type) + { + case '@': + attributeValue = context?.Request[name]; + break; + case '%': + attributeValue = context?.Session[name]?.ToString(); + if (string.IsNullOrEmpty(attributeValue)) + attributeValue = context?.Request.GetCookieValue(name); + break; + case '#': + if (pageElements == null) pageElements = GetPageElements(); + attributeValue = pageElements[name]?.ToString(); + break; + case '$': + if (pageElements == null) pageElements = GetPageElements(); + attributeValue = pageElements[name]?.ToString(); + if (string.IsNullOrEmpty(attributeValue)) + attributeValue = ParseAttributeOnParents(pageElements, name); + break; + } + + attributeValue = attributeValue?.Trim(); + if (string.IsNullOrEmpty(attributeValue) == false) + break; // got a value, use it + } + + return attributeValue; + } + + private static string ParseAttributeOnParents(IDictionary pageElements, string name) + { + // this was, and still is, an ugly piece of nonsense + + var value = string.Empty; + var cache = UmbracoContext.Current.ContentCache; // should be injected + + var splitpath = (string[])pageElements["splitpath"]; + for (var i = splitpath.Length - 1; i > 0; i--) // at 0 we have root (-1) + { + var content = cache.GetById(int.Parse(splitpath[i])); + if (content == null) continue; + value = content.Value(name)?.ToString(); + if (string.IsNullOrEmpty(value) == false) break; + } + + return value; + } + + private static IDictionary GetPageElements() + { + IDictionary pageElements = null; + if (HttpContext.Current.Items["pageElements"] != null) + pageElements = (IDictionary)HttpContext.Current.Items["pageElements"]; + return pageElements; + } + + #endregion + + #region RTE macros + + public static string RenderMacroStartTag(Hashtable attributes, int pageId, Guid versionId) + { + var div = "
"; + + return div; + } + + private static string EncodeMacroAttribute(string attributeContents) + { + // replace linebreaks + attributeContents = attributeContents.Replace("\n", "\\n").Replace("\r", "\\r"); + + // replace quotes + attributeContents = attributeContents.Replace("\"", """); + + // replace tag start/ends + attributeContents = attributeContents.Replace("<", "<").Replace(">", ">"); + + return attributeContents; + } + + public static string RenderMacroEndTag() + { + return "
"; + } + + private static readonly Regex HrefRegex = new Regex("href=\"([^\"]*)\"", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + public static string GetRenderedMacro(int macroId, Hashtable elements, Hashtable attributes, int pageId, IMacroService macroService, ProfilingLogger plogger) + { + var m = macroService.GetById(macroId); + if (m == null) return string.Empty; + var model = new MacroModel(m); + + // get as text, will render the control if any + var renderer = new MacroRenderer(plogger); + var macroContent = renderer.Render(model, elements, pageId); + var text = macroContent.GetAsText(); + + // remove hrefs + text = HrefRegex.Replace(text, match => "href=\"javascript:void(0)\""); + + return text; + } + + public static string MacroContentByHttp(int pageId, Guid pageVersion, Hashtable attributes, IMacroService macroService) + { + // though... we only support FullTrust now? + if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted) + return "Cannot render macro content in the rich text editor when the application is running in a Partial Trust environment"; + + var tempAlias = attributes["macroalias"]?.ToString() ?? attributes["macroAlias"].ToString(); + + var m = macroService.GetByAlias(tempAlias); + if (m == null) return string.Empty; + var macro = new MacroModel(m); + if (macro.RenderInEditor == false) + return ShowNoMacroContent(macro); + + var querystring = $"umbPageId={pageId}&umbVersionId={pageVersion}"; + var ide = attributes.GetEnumerator(); + while (ide.MoveNext()) + querystring += $"&umb_{ide.Key}={HttpContext.Current.Server.UrlEncode((ide.Value ?? String.Empty).ToString())}"; + + // create a new 'HttpWebRequest' object to the mentioned URL. + var useSsl = UmbracoConfig.For.GlobalSettings().UseHttps; + var protocol = useSsl ? "https" : "http"; + var currentRequest = HttpContext.Current.Request; + var serverVars = currentRequest.ServerVariables; + var umbracoDir = IOHelper.ResolveUrl(SystemDirectories.Umbraco); + var url = $"{protocol}://{serverVars["SERVER_NAME"]}:{serverVars["SERVER_PORT"]}{umbracoDir}/macroResultWrapper.aspx?{querystring}"; + + var myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); + + // allows for validation of SSL conversations (to bypass SSL errors in debug mode!) + ServicePointManager.ServerCertificateValidationCallback += ValidateRemoteCertificate; + + // propagate the user's context + // TODO: this is the worst thing ever. + // also will not work if people decide to put their own custom auth system in place. + var inCookie = currentRequest.Cookies[UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName]; + if (inCookie == null) throw new NullReferenceException("No auth cookie found"); + var cookie = new Cookie(inCookie.Name, inCookie.Value, inCookie.Path, serverVars["SERVER_NAME"]); + myHttpWebRequest.CookieContainer = new CookieContainer(); + myHttpWebRequest.CookieContainer.Add(cookie); + + // assign the response object of 'HttpWebRequest' to a 'HttpWebResponse' variable. + HttpWebResponse myHttpWebResponse = null; + var text = string.Empty; + try + { + myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); + if (myHttpWebResponse.StatusCode == HttpStatusCode.OK) + { + var streamResponse = myHttpWebResponse.GetResponseStream(); + if (streamResponse == null) + throw new Exception("Internal error, no response stream."); + var streamRead = new StreamReader(streamResponse); + var readBuff = new char[256]; + var count = streamRead.Read(readBuff, 0, 256); + while (count > 0) + { + var outputData = new string(readBuff, 0, count); + text += outputData; + count = streamRead.Read(readBuff, 0, 256); + } + + streamResponse.Close(); + streamRead.Close(); + + // find the content of a form + const string grabStart = ""; + const string grabEnd = ""; + + var grabStartPos = text.InvariantIndexOf(grabStart) + grabStart.Length; + var grabEndPos = text.InvariantIndexOf(grabEnd) - grabStartPos; + text = text.Substring(grabStartPos, grabEndPos); + } + else + { + text = ShowNoMacroContent(macro); + } + } + catch (Exception) + { + text = ShowNoMacroContent(macro); + } + finally + { + // release the HttpWebResponse Resource. + myHttpWebResponse?.Close(); + } + + return text.Replace("\n", string.Empty).Replace("\r", string.Empty); + } + + private static string ShowNoMacroContent(MacroModel model) + { + var name = HttpUtility.HtmlEncode(model.Name); // safe + return $"{name}
No macro content available for WYSIWYG editing
"; + } + + private static bool ValidateRemoteCertificate( + object sender, + X509Certificate certificate, + X509Chain chain, + SslPolicyErrors policyErrors + ) + { + // allow any old dodgy certificate in debug mode + return GlobalSettings.DebugMode || policyErrors == SslPolicyErrors.None; + } + + #endregion + } + + public class MacroRenderingEventArgs : EventArgs + { + public MacroRenderingEventArgs(Hashtable pageElements, int pageId) + { + PageElements = pageElements; + PageId = pageId; + } + + public int PageId { get; } + + public Hashtable PageElements { get; } + } +} diff --git a/src/Umbraco.Web/Macros/PartialViewMacroController.cs b/src/Umbraco.Web/Macros/PartialViewMacroController.cs index df6a939632..cf31de3bac 100644 --- a/src/Umbraco.Web/Macros/PartialViewMacroController.cs +++ b/src/Umbraco.Web/Macros/PartialViewMacroController.cs @@ -1,11 +1,8 @@ -using System.Collections.Generic; -using System.Web.Mvc; -using Umbraco.Core.Models; +using System.Web.Mvc; using Umbraco.Web.Models; using Umbraco.Web.Mvc; -using umbraco.cms.businesslogic.macro; -using umbraco.interfaces; using System.Linq; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Macros { @@ -40,4 +37,4 @@ namespace Umbraco.Web.Macros return PartialView(_macro.ScriptName, model); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs index 10d105883f..4488dee2ec 100644 --- a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs +++ b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs @@ -1,47 +1,35 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; using System.Web; using System.Web.Mvc; using System.Web.Routing; using System.Web.WebPages; -using Umbraco.Core.IO; -using umbraco.cms.businesslogic.macro; -using Umbraco.Core.Models; -using umbraco.interfaces; -using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Core; -using System.Web.Mvc.Html; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Macros { /// - /// A macro engine using MVC Partial Views to execute + /// A macro engine using MVC Partial Views to execute. /// - public class PartialViewMacroEngine : IMacroEngine + public class PartialViewMacroEngine { private readonly Func _getHttpContext; private readonly Func _getUmbracoContext; - public const string EngineName = "Partial View Macro Engine"; - public PartialViewMacroEngine() { _getHttpContext = () => { if (HttpContext.Current == null) - throw new InvalidOperationException("The " + this.GetType() + " cannot execute with a null HttpContext.Current reference"); + throw new InvalidOperationException($"The {GetType()} cannot execute with a null HttpContext.Current reference."); return new HttpContextWrapper(HttpContext.Current); }; _getUmbracoContext = () => { if (UmbracoContext.Current == null) - throw new InvalidOperationException("The " + this.GetType() + " cannot execute with a null UmbracoContext.Current reference"); + throw new InvalidOperationException($"The {GetType()} cannot execute with a null UmbracoContext.Current reference."); return UmbracoContext.Current; }; } @@ -57,34 +45,7 @@ namespace Umbraco.Web.Macros _getUmbracoContext = () => umbracoContext; } - public string Name - { - get { return EngineName; } - } - - //NOTE: We do not return any supported extensions because we don't want the MacroEngineFactory to return this - // macro engine when searching for engines via extension. Those types of engines are reserved for files that are - // stored in the ~/macroScripts folder and each engine must support unique extensions. This is a total Hack until - // we rewrite how macro engines work. - public IEnumerable SupportedExtensions - { - get { return Enumerable.Empty(); } - } - - //NOTE: We do not return any supported extensions because we don't want the MacroEngineFactory to return this - // macro engine when searching for engines via extension. Those types of engines are reserved for files that are - // stored in the ~/macroScripts folder and each engine must support unique extensions. This is a total Hack until - // we rewrite how macro engines work. - public IEnumerable SupportedUIExtensions - { - get { return Enumerable.Empty(); } - } - public Dictionary SupportedProperties - { - get { throw new NotSupportedException(); } - } - - public bool Validate(string code, string tempFileName, INode currentPage, out string errorMessage) + public bool Validate(string code, string tempFileName, IPublishedContent currentPage, out string errorMessage) { var temp = GetVirtualPathFromPhysicalPath(tempFileName); try @@ -100,35 +61,22 @@ namespace Umbraco.Web.Macros return true; } - public string Execute(MacroModel macro, INode node) + public MacroContent Execute(MacroModel macro, IPublishedContent content) { - if (node == null) return string.Empty; + if (macro == null) throw new ArgumentNullException(nameof(macro)); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (macro.ScriptName.IsNullOrWhiteSpace()) throw new ArgumentException("The ScriptName property of the macro object cannot be null or empty"); - var umbCtx = _getUmbracoContext(); - //NOTE: This is a bit of a nasty hack to check if the INode is actually already based on an IPublishedContent - // (will be the case when using LegacyConvertedNode ) - return Execute(macro, - (node is IPublishedContent) - ? (IPublishedContent)node - : umbCtx.ContentCache.GetById(node.Id)); - } - - public string Execute(MacroModel macro, IPublishedContent content) - { - if (macro == null) throw new ArgumentNullException("macro"); - if (content == null) throw new ArgumentNullException("content"); - if (macro.ScriptName.IsNullOrWhiteSpace()) throw new ArgumentException("The ScriptName property of the macro object cannot be null or empty"); - var http = _getHttpContext(); var umbCtx = _getUmbracoContext(); var routeVals = new RouteData(); routeVals.Values.Add("controller", "PartialViewMacro"); routeVals.Values.Add("action", "Index"); - routeVals.DataTokens.Add(Umbraco.Core.Constants.Web.UmbracoContextDataToken, umbCtx); //required for UmbracoViewPage + routeVals.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx); //required for UmbracoViewPage - //lets render this controller as a child action - var viewContext = new ViewContext {ViewData = new ViewDataDictionary()};; - //try and extract the current view context from the route values, this would be set in the UmbracoViewPage or in + //lets render this controller as a child action + var viewContext = new ViewContext { ViewData = new ViewDataDictionary() }; + //try and extract the current view context from the route values, this would be set in the UmbracoViewPage or in // the UmbracoPageResult if POSTing to an MVC controller but rendering in Webforms if (http.Request.RequestContext.RouteData.DataTokens.ContainsKey(Mvc.Constants.DataTokenCurrentViewContext)) { @@ -141,20 +89,20 @@ namespace Umbraco.Web.Macros using (var controller = new PartialViewMacroController(macro, content)) { controller.ViewData = viewContext.ViewData; - - controller.ControllerContext = new ControllerContext(request, controller); + + controller.ControllerContext = new ControllerContext(request, controller); //call the action to render var result = controller.Index(); - output = controller.RenderViewResultAsString(result); + output = controller.RenderViewResultAsString(result); } - return output; + return new MacroContent { Text = output }; } private string GetVirtualPathFromPhysicalPath(string physicalPath) { - string rootpath = _getHttpContext().Server.MapPath("~/"); + var rootpath = _getHttpContext().Server.MapPath("~/"); physicalPath = physicalPath.Replace(rootpath, ""); physicalPath = physicalPath.Replace("\\", "/"); return "~/" + physicalPath; diff --git a/src/Umbraco.Web/Macros/PartialViewMacroPage.cs b/src/Umbraco.Web/Macros/PartialViewMacroPage.cs index 8f5c11b044..a50fcc3149 100644 --- a/src/Umbraco.Web/Macros/PartialViewMacroPage.cs +++ b/src/Umbraco.Web/Macros/PartialViewMacroPage.cs @@ -1,4 +1,4 @@ -using System.Web.Mvc; +using System.Web.Mvc; using Umbraco.Core.Models; using Umbraco.Web.Models; using Umbraco.Web.Mvc; @@ -9,20 +9,5 @@ namespace Umbraco.Web.Macros /// The base view class that PartialViewMacro views need to inherit from /// public abstract class PartialViewMacroPage : UmbracoViewPage - { - protected override void InitializePage() - { - base.InitializePage(); - //set the model to the current node if it is not set, this is generally not the case - if (Model != null) - { - CurrentPage = Model.Content.AsDynamic(); - } - } - - /// - /// Returns the a DynamicPublishedContent object - /// - public dynamic CurrentPage { get; private set; } - } -} \ No newline at end of file + { } +} diff --git a/src/Umbraco.Web/Macros/UserControlMacroEngine.cs b/src/Umbraco.Web/Macros/UserControlMacroEngine.cs new file mode 100644 index 0000000000..790425f649 --- /dev/null +++ b/src/Umbraco.Web/Macros/UserControlMacroEngine.cs @@ -0,0 +1,91 @@ +using System; +using System.IO; +using System.Web; +using System.Web.UI; +using umbraco; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Web.Composing; + +namespace Umbraco.Web.Macros +{ + class UserControlMacroEngine + { + public MacroContent Execute(MacroModel model) + { + var filename = model.TypeName; + + // ensure the file exists + var path = IOHelper.FindFile(filename); + if (File.Exists(IOHelper.MapPath(path)) == false) + throw new Exception($"Failed to load control, file {filename} does not exist."); + + // load the control + var control = (UserControl)new UserControl().LoadControl(path); + control.ID = string.IsNullOrEmpty(model.MacroControlIdentifier) + ? GetControlUniqueId(filename) + : model.MacroControlIdentifier; + + // initialize the control + // note: we are not setting the 'CurrentNode' property on the control anymore, + // as that was an INode which is gone in v8. Use UmbracoContext to access the + // current content. + Current.Logger.Info($"Loaded control \"{filename}\" with ID \"{control.ID}\"."); + UpdateControlProperties(control, model); + + return new MacroContent { Control = control }; + } + + private static string GetControlUniqueId(string filename) + { + const string key = "MacroControlUniqueId"; + + var x = 0; + + if (HttpContext.Current != null) + { + if (HttpContext.Current.Items.Contains(key)) + x = (int)HttpContext.Current.Items[key]; + x += 1; + HttpContext.Current.Items[key] = x; + } + + return $"{Path.GetFileNameWithoutExtension(filename)}_{x}"; + } + + // set the control properties according to the model properties ie parameters + internal static void UpdateControlProperties(Control control, MacroModel model) + { + var type = control.GetType(); + + foreach (var modelProperty in model.Properties) + { + var controlProperty = type.GetProperty(modelProperty.Key); + if (controlProperty == null) + { + Current.Logger.Warn($"Control property \"{modelProperty.Key}\" doesn't exist or isn't accessible, skip."); + continue; + } + + var tryConvert = modelProperty.Value.TryConvertTo(controlProperty.PropertyType); + if (tryConvert.Success) + { + try + { + controlProperty.SetValue(control, tryConvert.Result, null); + Current.Logger.Debug($"Set property \"{modelProperty.Key}\" value \"{modelProperty.Value}\"."); + } + catch (Exception e) + { + Current.Logger.Warn(e, $"Failed to set property \"{modelProperty.Key}\" value \"{modelProperty.Value}\"."); + } + } + else + { + Current.Logger.Warn($"Failed to set property \"{modelProperty.Key}\" value \"{modelProperty.Value}\"."); + } + } + } + } +} diff --git a/src/Umbraco.Web/Macros/XsltMacroEngine.cs b/src/Umbraco.Web/Macros/XsltMacroEngine.cs new file mode 100644 index 0000000000..10fd6b5305 --- /dev/null +++ b/src/Umbraco.Web/Macros/XsltMacroEngine.cs @@ -0,0 +1,687 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Web; +using System.Web.Caching; +using System.Xml; +using System.Xml.XPath; +using System.Xml.Xsl; +using umbraco; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Xml.XPath; +using Umbraco.Web.Composing; +using Umbraco.Web.Templates; + +namespace Umbraco.Web.Macros +{ + /// + /// A macro engine using XSLT to execute. + /// + public class XsltMacroEngine + { + private readonly Func _getHttpContext; + private readonly Func _getUmbracoContext; + private readonly ProfilingLogger _plogger; + + public XsltMacroEngine(ProfilingLogger plogger) + { + _plogger = plogger; + + _getHttpContext = () => + { + if (HttpContext.Current == null) + throw new InvalidOperationException("The Xslt Macro Engine cannot execute with a null HttpContext.Current reference"); + return new HttpContextWrapper(HttpContext.Current); + }; + + _getUmbracoContext = () => + { + if (UmbracoContext.Current == null) + throw new InvalidOperationException("The Xslt Macro Engine cannot execute with a null UmbracoContext.Current reference"); + return UmbracoContext.Current; + }; + } + + #region Execute Xslt + + // executes the macro, relying on GetXsltTransform + // will pick XmlDocument or Navigator mode depending on the capabilities of the published caches + public MacroContent Execute(MacroModel model) + { + var hasCode = string.IsNullOrWhiteSpace(model.ScriptCode) == false; + var hasXslt = string.IsNullOrWhiteSpace(model.Xslt) == false; + + if (hasXslt == false && hasCode == false) + { + Current.Logger.Warn("Xslt is empty"); + return MacroContent.Empty; + } + + if (hasCode && model.ScriptLanguage.InvariantEquals("xslt") == false) + { + Current.Logger.Warn("Unsupported script language \"" + model.ScriptLanguage + "\"."); + return MacroContent.Empty; + } + + var msg = "Executing Xslt: " + (hasCode ? "Inline." : "Xslt=\"" + model.Xslt + "\"."); + using (_plogger.DebugDuration(msg, "ExecutedXslt.")) + { + // need these two here for error reporting + MacroNavigator macroNavigator = null; + XmlDocument macroXml = null; + + IXPathNavigable macroNavigable; + IXPathNavigable contentNavigable; + + var xmlCache = UmbracoContext.Current.ContentCache as PublishedCache.XmlPublishedCache.PublishedContentCache; + + if (xmlCache == null) + { + // a different cache + // inheriting from NavigableNavigator is required + + var contentNavigator = UmbracoContext.Current.ContentCache.CreateNavigator() as NavigableNavigator; + var mediaNavigator = UmbracoContext.Current.MediaCache.CreateNavigator() as NavigableNavigator; + + if (contentNavigator == null || mediaNavigator == null) + throw new Exception("Published caches XPathNavigator do not inherit from NavigableNavigator."); + + var parameters = new List(); + foreach (var prop in model.Properties) + AddMacroParameter(parameters, contentNavigator, mediaNavigator, prop.Key, prop.Type, prop.Value); + + macroNavigable = macroNavigator = new MacroNavigator(parameters); + contentNavigable = UmbracoContext.Current.ContentCache; + } + else + { + // the original XML cache + // render the macro on top of the Xml document + + var umbracoXml = xmlCache.GetXml(UmbracoContext.Current.InPreviewMode); + + macroXml = new XmlDocument(); + macroXml.LoadXml(""); + + foreach (var prop in model.Properties) + AddMacroXmlNode(umbracoXml, macroXml, prop.Key, prop.Type, prop.Value); + + macroNavigable = macroXml; + contentNavigable = umbracoXml; + } + + // this is somewhat ugly but eh... + var httpContext = _getHttpContext(); + if (httpContext.Request.QueryString["umbDebug"] != null && GlobalSettings.DebugMode) + { + var outerXml = macroXml?.OuterXml ?? macroNavigator.OuterXml; + var text = $"
Debug from {model.Name}
{HttpUtility.HtmlEncode(outerXml)}
"; + return new MacroContent { Text = text }; + } + + // get the transform + XslCompiledTransform transform; + if (hasCode) + { + try + { + using (var sreader = new StringReader(model.ScriptCode)) + using (var xreader = new XmlTextReader(sreader)) + { + transform = GetXsltTransform(xreader, GlobalSettings.DebugMode); + } + } + catch (Exception e) + { + throw new Exception("Failed to parse inline Xslt.", e); + } + } + else + { + try + { + transform = GetCachedXsltTransform(model.Xslt); + } + catch (Exception e) + { + throw new Exception($"Failed to read Xslt file \"{model.Xslt}\".", e); + } + } + + using (_plogger.DebugDuration("Performing transformation.", "Performed transformation.")) + { + try + { + var transformed = XsltTransform(_plogger, macroNavigable, contentNavigable, transform); + var text = TemplateUtilities.ResolveUrlsFromTextString(transformed); + return new MacroContent { Text = text }; + } + catch (Exception e) + { + throw new Exception($"Failed to exec Xslt file \"{model.Xslt}\".", e); + } + } + } + } + + /// + /// Adds the XSLT extension namespaces to the XSLT header using + /// {0} as the container for the namespace references and + /// {1} as the container for the exclude-result-prefixes + /// + /// The XSLT + /// The XSLT with {0} and {1} replaced. + /// This is done here because it needs the engine's XSLT extensions. + public static string AddXsltExtensionsToHeader(string xslt) + { + var namespaceList = new StringBuilder(); + var namespaceDeclaractions = new StringBuilder(); + foreach (var extension in GetXsltExtensions()) + { + namespaceList.Append(extension.Key).Append(' '); + namespaceDeclaractions.AppendFormat("xmlns:{0}=\"urn:{0}\" ", extension.Key); + } + + // parse xslt + xslt = xslt.Replace("{0}", namespaceDeclaractions.ToString()); + xslt = xslt.Replace("{1}", namespaceList.ToString()); + return xslt; + } + + public static string TestXsltTransform(ProfilingLogger plogger, string xsltText, int currentPageId = -1) + { + IXPathNavigable macroNavigable; + IXPathNavigable contentNavigable; + + var xmlCache = UmbracoContext.Current.ContentCache as PublishedCache.XmlPublishedCache.PublishedContentCache; + + var xslParameters = new Dictionary(); + xslParameters["currentPage"] = UmbracoContext.Current.ContentCache + .CreateNavigator() + .Select(currentPageId > 0 ? ("//* [@id=" + currentPageId + "]") : "//* [@parentID=-1]"); + + if (xmlCache == null) + { + // a different cache + // inheriting from NavigableNavigator is required + + var contentNavigator = UmbracoContext.Current.ContentCache.CreateNavigator() as NavigableNavigator; + if (contentNavigator == null) + throw new Exception("Published caches XPathNavigator do not inherit from NavigableNavigator."); + + var parameters = new List(); + macroNavigable = new MacroNavigator(parameters); + contentNavigable = UmbracoContext.Current.ContentCache; + } + else + { + // the original XML cache + // render the macro on top of the Xml document + + var umbracoXml = xmlCache.GetXml(UmbracoContext.Current.InPreviewMode); + + var macroXml = new XmlDocument(); + macroXml.LoadXml(""); + + macroNavigable = macroXml; + contentNavigable = umbracoXml; + } + + // for a test, do not try...catch + // but let the exceptions be thrown + + XslCompiledTransform transform; + using (var reader = new XmlTextReader(new StringReader(xsltText))) + { + transform = GetXsltTransform(reader, true); + } + var transformed = XsltTransform(plogger, macroNavigable, contentNavigable, transform, xslParameters); + + return transformed; + } + + public static string ExecuteItemRenderer(ProfilingLogger plogger, XslCompiledTransform transform, string itemData) + { + IXPathNavigable macroNavigable; + IXPathNavigable contentNavigable; + + var xmlCache = UmbracoContext.Current.ContentCache as PublishedCache.XmlPublishedCache.PublishedContentCache; + + if (xmlCache == null) + { + // a different cache + // inheriting from NavigableNavigator is required + + var contentNavigator = UmbracoContext.Current.ContentCache.CreateNavigator() as NavigableNavigator; + var mediaNavigator = UmbracoContext.Current.MediaCache.CreateNavigator() as NavigableNavigator; + + if (contentNavigator == null || mediaNavigator == null) + throw new Exception("Published caches XPathNavigator do not inherit from NavigableNavigator."); + + var parameters = new List(); + + macroNavigable = new MacroNavigator(parameters); + contentNavigable = UmbracoContext.Current.ContentCache; + } + else + { + // the original XML cache + // render the macro on top of the Xml document + + var umbracoXml = xmlCache.GetXml(UmbracoContext.Current.InPreviewMode); + + var macroXml = new XmlDocument(); + macroXml.LoadXml(""); + + macroNavigable = macroXml; + contentNavigable = umbracoXml; + } + + var xslParameters = new Dictionary { { "itemData", itemData } }; + return XsltTransform(plogger, macroNavigable, contentNavigable, transform, xslParameters); + } + + #endregion + + #region XsltTransform + + // running on the XML cache, document mode + // add parameters to the root node + // note: contains legacy dirty code + private static void AddMacroXmlNode(XmlDocument umbracoXml, XmlDocument macroXml, + string macroPropertyAlias, string macroPropertyType, string macroPropertyValue) + { + var macroXmlNode = macroXml.CreateNode(XmlNodeType.Element, macroPropertyAlias, string.Empty); + + // if no value is passed, then use the current "pageID" as value + var contentId = macroPropertyValue == string.Empty ? UmbracoContext.Current.PageId.ToString() : macroPropertyValue; + + Current.Logger.Info($"Xslt node adding search start ({macroPropertyAlias},{macroPropertyValue})"); + + switch (macroPropertyType) + { + case "contentTree": + var nodeId = macroXml.CreateAttribute("nodeID"); + nodeId.Value = contentId; + macroXmlNode.Attributes.SetNamedItem(nodeId); + + // Get subs + try + { + macroXmlNode.AppendChild(macroXml.ImportNode(umbracoXml.GetElementById(contentId), true)); + } + catch + { } + break; + + case "contentCurrent": + var importNode = macroPropertyValue == string.Empty + ? umbracoXml.GetElementById(contentId) + : umbracoXml.GetElementById(macroPropertyValue); + + var currentNode = macroXml.ImportNode(importNode, true); + + // remove all sub content nodes + foreach (XmlNode n in currentNode.SelectNodes("*[@isDoc]")) + currentNode.RemoveChild(n); + + macroXmlNode.AppendChild(currentNode); + + break; + + case "contentSubs": // disable that one, it does not work anyway... + //x.LoadXml(""); + //x.FirstChild.AppendChild(x.ImportNode(umbracoXml.GetElementById(contentId), true)); + //macroXmlNode.InnerXml = TransformMacroXml(x, "macroGetSubs.xsl"); + break; + + case "contentAll": + macroXmlNode.AppendChild(macroXml.ImportNode(umbracoXml.DocumentElement, true)); + break; + + case "contentRandom": + XmlNode source = umbracoXml.GetElementById(contentId); + if (source != null) + { + var sourceList = source.SelectNodes("*[@isDoc]"); + if (sourceList.Count > 0) + { + int rndNumber; + var r = library.GetRandom(); + lock (r) + { + rndNumber = r.Next(sourceList.Count); + } + var node = macroXml.ImportNode(sourceList[rndNumber], true); + // remove all sub content nodes + foreach (XmlNode n in node.SelectNodes("*[@isDoc]")) + node.RemoveChild(n); + + macroXmlNode.AppendChild(node); + } + else + Current.Logger.Warn("Error adding random node - parent (" + macroPropertyValue + ") doesn't have children!"); + } + else + Current.Logger.Warn("Error adding random node - parent (" + macroPropertyValue + ") doesn't exists!"); + break; + + case "mediaCurrent": + if (string.IsNullOrEmpty(macroPropertyValue) == false) + { + //var c = new global::umbraco.cms.businesslogic.Content(int.Parse(macroPropertyValue)); + //macroXmlNode.AppendChild(macroXml.ImportNode(c.ToXml(global::umbraco.content.Instance.XmlContent, false), true)); + var nav = UmbracoContext.Current.MediaCache.CreateNodeNavigator(int.Parse(macroPropertyValue), false); + if (nav != null) + macroXmlNode.AppendChild(macroXml.ReadNode(nav.ReadSubtree())); + } + break; + + default: + macroXmlNode.InnerText = HttpUtility.HtmlDecode(macroPropertyValue); + break; + } + macroXml.FirstChild.AppendChild(macroXmlNode); + } + + // running on a navigable cache, navigable mode + // add parameters to the macro parameters collection + private static void AddMacroParameter(ICollection parameters, + NavigableNavigator contentNavigator, NavigableNavigator mediaNavigator, + string macroPropertyAlias, string macroPropertyType, string macroPropertyValue) + { + // if no value is passed, then use the current "pageID" as value + var contentId = macroPropertyValue == string.Empty ? UmbracoContext.Current.PageId.ToString() : macroPropertyValue; + + Current.Logger.Info($"Xslt node adding search start ({macroPropertyAlias},{macroPropertyValue})"); + + // beware! do not use the raw content- or media- navigators, but clones !! + + switch (macroPropertyType) + { + case "contentTree": + parameters.Add(new MacroNavigator.MacroParameter( + macroPropertyAlias, + contentNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty + attributes: new Dictionary { { "nodeID", contentId } })); + + break; + + case "contentPicker": + parameters.Add(new MacroNavigator.MacroParameter( + macroPropertyAlias, + contentNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty + 0)); + break; + + case "contentSubs": + parameters.Add(new MacroNavigator.MacroParameter( + macroPropertyAlias, + contentNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty + 1)); + break; + + case "contentAll": + parameters.Add(new MacroNavigator.MacroParameter(macroPropertyAlias, contentNavigator.Clone())); + break; + + case "contentRandom": + var nav = contentNavigator.Clone(); + if (nav.MoveToId(contentId)) + { + var descendantIterator = nav.Select("./* [@isDoc]"); + if (descendantIterator.MoveNext()) + { + // not empty - and won't change + var descendantCount = descendantIterator.Count; + + int index; + var r = library.GetRandom(); + lock (r) + { + index = r.Next(descendantCount); + } + + while (index > 0 && descendantIterator.MoveNext()) + index--; + + var node = descendantIterator.Current.UnderlyingObject as INavigableContent; + if (node != null) + { + nav = contentNavigator.CloneWithNewRoot(node.Id); + parameters.Add(new MacroNavigator.MacroParameter(macroPropertyAlias, nav, 0)); + } + else + throw new InvalidOperationException("Iterator contains non-INavigableContent elements."); + } + else + Current.Logger.Warn("Error adding random node - parent (" + macroPropertyValue + ") doesn't have children!"); + } + else + Current.Logger.Warn("Error adding random node - parent (" + macroPropertyValue + ") doesn't exists!"); + break; + + case "mediaCurrent": + parameters.Add(new MacroNavigator.MacroParameter( + macroPropertyAlias, + mediaNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty + 0)); + break; + + default: + parameters.Add(new MacroNavigator.MacroParameter(macroPropertyAlias, HttpUtility.HtmlDecode(macroPropertyValue))); + break; + } + } + + // gets the result of the xslt transform + private static string XsltTransform(ProfilingLogger plogger, IXPathNavigable macroNavigable, IXPathNavigable contentNavigable, + XslCompiledTransform xslt, IDictionary xslParameters = null) + { + TextWriter tw = new StringWriter(); + + XsltArgumentList xslArgs; + using (plogger.DebugDuration("Adding Xslt extensions", "Added Xslt extensions")) + { + xslArgs = GetXsltArgumentListWithExtensions(); + var lib = new library(); + xslArgs.AddExtensionObject("urn:umbraco.library", lib); + } + + // add parameters + if (xslParameters == null || xslParameters.ContainsKey("currentPage") == false) + { + // note: "PageId" is a legacy stuff that might be != from what's in current PublishedContentRequest + var currentPageId = UmbracoContext.Current.PageId; + var current = contentNavigable.CreateNavigator().Select("//* [@id=" + currentPageId + "]"); + xslArgs.AddParam("currentPage", string.Empty, current); + } + if (xslParameters != null) + foreach (var parameter in xslParameters) + xslArgs.AddParam(parameter.Key, string.Empty, parameter.Value); + + // transform + using (plogger.DebugDuration("Executing Xslt transform", "Executed Xslt transform")) + { + xslt.Transform(macroNavigable, xslArgs, tw); + } + + return tw.ToString(); + } + + #endregion + + #region Manage transforms + + private static XslCompiledTransform GetCachedXsltTransform(string filename) + { + //TODO: SD: Do we really need to cache this?? + var filepath = IOHelper.MapPath(SystemDirectories.Xslt.EnsureEndsWith('/') + filename); + return Current.ApplicationCache.GetCacheItem( + CacheKeys.MacroXsltCacheKey + filename, + CacheItemPriority.Default, + new CacheDependency(filepath), + () => + { + using (var xslReader = new XmlTextReader(filepath)) + { + return GetXsltTransform(xslReader, GlobalSettings.DebugMode); + } + }); + } + + public static XslCompiledTransform GetXsltTransform(XmlTextReader xslReader, bool debugMode) + { + var transform = new XslCompiledTransform(debugMode); + var xslResolver = new XmlUrlResolver + { + Credentials = CredentialCache.DefaultCredentials + }; + + xslReader.EntityHandling = EntityHandling.ExpandEntities; + xslReader.DtdProcessing = DtdProcessing.Parse; + + try + { + transform.Load(xslReader, XsltSettings.TrustedXslt, xslResolver); + } + finally + { + xslReader.Close(); + } + + return transform; + } + + #endregion + + #region Manage extensions + + /* + private static readonly string XsltExtensionsConfig = + IOHelper.MapPath(SystemDirectories.Config + "/xsltExtensions.config"); + + private static readonly Func XsltExtensionsDependency = + () => new CacheDependency(XsltExtensionsConfig); + */ + + // creates and return an Xslt argument list with all Xslt extensions. + public static XsltArgumentList GetXsltArgumentListWithExtensions() + { + var xslArgs = new XsltArgumentList(); + + foreach (var extension in GetXsltExtensions()) + { + var extensionNamespace = "urn:" + extension.Key; + xslArgs.AddExtensionObject(extensionNamespace, extension.Value); + Current.Logger.Info($"Extension added: {extensionNamespace}, {extension.Value.GetType().Name}"); + } + + return xslArgs; + } + + /* + // gets the collection of all XSLT extensions for macros + // ie predefined, configured in the config file, and marked with the attribute + public static Dictionary GetCachedXsltExtensions() + { + // We could cache the extensions in a static variable but then the cache + // would not be refreshed when the .config file is modified. An application + // restart would be required. Better use the cache and add a dependency. + + // SD: The only reason the above statement might be true is because the xslt extension .config file is not a + // real config file!! if it was, we wouldn't have this issue. Having these in a static variable would be preferred! + // If you modify a config file, the app restarts and thus all static variables are reset. + // Having this stuff in cache just adds to the gigantic amount of cache data and will cause more cache turnover to happen. + + return ApplicationContext.Current.ApplicationCache.GetCacheItem( + "UmbracoXsltExtensions", + CacheItemPriority.NotRemovable, // NH 4.7.1, Changing to NotRemovable + null, // no refresh action + XsltExtensionsDependency(), // depends on the .config file + TimeSpan.FromDays(1), // expires in 1 day (?) + GetXsltExtensions); + } + */ + + // actually gets the collection of all XSLT extensions for macros + // ie predefined, configured in the config file, and marked with the attribute + public static Dictionary GetXsltExtensions() + { + return Current.XsltExtensions + .ToDictionary(x => x.Namespace, x => x.ExtensionObject); + + /* + // initialize the collection + // there is no "predefined" extensions anymore + var extensions = new Dictionary(); + + // Load the XSLT extensions configuration + var xsltExt = new XmlDocument(); + xsltExt.Load(XsltExtensionsConfig); + + // get the configured types + var extensionsNode = xsltExt.SelectSingleNode("/XsltExtensions"); + if (extensionsNode != null) + foreach (var attributes in extensionsNode.Cast() + .Where(x => x.NodeType == XmlNodeType.Element) + .Select(x => x.Attributes)) + { + Debug.Assert(attributes["assembly"] != null, "Extension attribute 'assembly' not specified."); + Debug.Assert(attributes["type"] != null, "Extension attribute 'type' not specified."); + Debug.Assert(attributes["alias"] != null, "Extension attribute 'alias' not specified."); + + // load the extension assembly + var extensionFile = IOHelper.MapPath(string.Format("{0}/{1}.dll", + SystemDirectories.Bin, attributes["assembly"].Value)); + + Assembly extensionAssembly; + try + { + extensionAssembly = Assembly.LoadFrom(extensionFile); + } + catch (Exception ex) + { + throw new Exception( + String.Format("Could not load assembly {0} for XSLT extension {1}. Please check config/xsltExtensions.config.", + extensionFile, attributes["alias"].Value), ex); + } + + // load the extension type + var extensionType = extensionAssembly.GetType(attributes["type"].Value); + if (extensionType == null) + throw new Exception( + String.Format("Could not load type {0} ({1}) for XSLT extension {2}. Please check config/xsltExtensions.config.", + attributes["type"].Value, extensionFile, attributes["alias"].Value)); + + // create an instance and add it to the extensions list + extensions.Add(attributes["alias"].Value, Activator.CreateInstance(extensionType)); + } + + // get types marked with XsltExtension attribute + var foundExtensions = TypeLoader.Current.ResolveXsltExtensions(); + foreach (var xsltType in foundExtensions) + { + var attributes = xsltType.GetCustomAttributes(true); + var xsltTypeName = xsltType.FullName; + foreach (var ns in attributes + .Select(attribute => string.IsNullOrEmpty(attribute.Namespace) ? attribute.Namespace : xsltTypeName)) + { + extensions.Add(ns, Activator.CreateInstance(xsltType)); + } + } + + return extensions; + */ + } + + #endregion + } +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs b/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs index 2f8ec85075..933dc1a204 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs @@ -79,4 +79,4 @@ namespace Umbraco.Web.Media.EmbedProviders } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/AbstractProvider.cs b/src/Umbraco.Web/Media/EmbedProviders/AbstractProvider.cs index 20c94c06d1..62f08b48f0 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/AbstractProvider.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/AbstractProvider.cs @@ -11,4 +11,4 @@ namespace Umbraco.Web.Media.EmbedProviders public abstract string GetMarkup(string url, int maxWidth, int maxHeight); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/Flickr.cs b/src/Umbraco.Web/Media/EmbedProviders/Flickr.cs index 944513ee4f..7d6c887d4a 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/Flickr.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/Flickr.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Web; @@ -22,4 +22,4 @@ namespace Umbraco.Web.Media.EmbedProviders imageUrl, imageWidth, imageHeight, HttpUtility.HtmlEncode(imageTitle)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/OEmbedPhoto.cs b/src/Umbraco.Web/Media/EmbedProviders/OEmbedPhoto.cs index acc75cb876..26dbf38d31 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/OEmbedPhoto.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/OEmbedPhoto.cs @@ -19,4 +19,4 @@ namespace Umbraco.Web.Media.EmbedProviders imageUrl, imageWidth, imageHeight, HttpUtility.HtmlEncode(imageTitle)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/OEmbedRich.cs b/src/Umbraco.Web/Media/EmbedProviders/OEmbedRich.cs index f93a4d799b..70ee29ed19 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/OEmbedRich.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/OEmbedRich.cs @@ -3,4 +3,4 @@ public class OEmbedRich : OEmbedVideo { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/OEmbedVideo.cs b/src/Umbraco.Web/Media/EmbedProviders/OEmbedVideo.cs index 3fb0b3d1e0..a4350e8681 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/OEmbedVideo.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/OEmbedVideo.cs @@ -12,4 +12,4 @@ namespace Umbraco.Web.Media.EmbedProviders return GetXmlProperty(doc, "/oembed/html"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/Settings/Dictionary.cs b/src/Umbraco.Web/Media/EmbedProviders/Settings/Dictionary.cs index 7d40d9c866..c259b4997a 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/Settings/Dictionary.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/Settings/Dictionary.cs @@ -11,4 +11,4 @@ namespace Umbraco.Web.Media.EmbedProviders.Settings return settingNode.ChildNodes.Cast().ToDictionary(item => item.Attributes != null ? item.Attributes["name"].Value : null, item => item.InnerText); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/Settings/String.cs b/src/Umbraco.Web/Media/EmbedProviders/Settings/String.cs index 9b6c162cc9..6dd501eb66 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/Settings/String.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/Settings/String.cs @@ -10,4 +10,4 @@ namespace Umbraco.Web.Media.EmbedProviders.Settings return settingNode.InnerText; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/EmbedProviders/Twitgoo.cs b/src/Umbraco.Web/Media/EmbedProviders/Twitgoo.cs index a28dd97809..efd8d9712f 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/Twitgoo.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/Twitgoo.cs @@ -20,4 +20,4 @@ namespace Umbraco.Web.Media.EmbedProviders img.Value); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/ImageUrl.cs b/src/Umbraco.Web/Media/ImageUrl.cs index 2af7fa8b9b..3606af5eda 100644 --- a/src/Umbraco.Web/Media/ImageUrl.cs +++ b/src/Umbraco.Web/Media/ImageUrl.cs @@ -8,6 +8,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Media; using umbraco; +using Umbraco.Web.Composing; namespace Umbraco.Web.Media { @@ -49,7 +50,7 @@ namespace Umbraco.Web.Media else { var p = UmbracoContext.Current.ContentCache.GetById(nodeId.GetValueOrDefault()); - var v = p.GetPropertyValue(field); + var v = p.Value(field); fieldValue = v == null ? string.Empty : v.ToString(); } } @@ -83,14 +84,14 @@ namespace Umbraco.Web.Media private static IImageUrlProvider GetProvider(string provider) { - return ImageUrlProviderResolver.Current.GetProvider(provider); + return Current.ImageUrlProviders[provider]; } private static object GetContentFromCache(int nodeIdInt, string field) { - var content = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( - string.Format("{0}{1}_{2}", CacheKeys.ContentItemCacheKey, nodeIdInt.ToString(CultureInfo.InvariantCulture), field)); + var content = Current.ApplicationCache.RuntimeCache.GetCacheItem( + string.Format("{0}{1}_{2}", CacheKeys.ContentItemCacheKey, nodeIdInt.ToString(CultureInfo.InvariantCulture), field)); return content; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/ImageUrlProviderCollection.cs b/src/Umbraco.Web/Media/ImageUrlProviderCollection.cs new file mode 100644 index 0000000000..5d20fbda4f --- /dev/null +++ b/src/Umbraco.Web/Media/ImageUrlProviderCollection.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Media; +using Umbraco.Web.Media.ImageUrlProviders; + +namespace Umbraco.Web.Media +{ + // fixme - kill entirely we should not use this anymore + internal class ImageUrlProviderCollection : BuilderCollectionBase + { + public ImageUrlProviderCollection(IEnumerable items) + : base(items) + { } + + public IImageUrlProvider this[string name] + { + get + { + if (string.IsNullOrWhiteSpace(name)) name = ImageUrlProvider.DefaultName; + var provider = this.FirstOrDefault(x => x.Name.InvariantEquals(name)); + if (provider == null) throw new InvalidOperationException($"No provider exists with name \"{name}\"."); + return provider; + } + } + } +} diff --git a/src/Umbraco.Web/Media/ImageUrlProviderCollectionBuilder.cs b/src/Umbraco.Web/Media/ImageUrlProviderCollectionBuilder.cs new file mode 100644 index 0000000000..79fb489e40 --- /dev/null +++ b/src/Umbraco.Web/Media/ImageUrlProviderCollectionBuilder.cs @@ -0,0 +1,14 @@ +using LightInject; +using Umbraco.Core.Composing; +using Umbraco.Core.Media; + +namespace Umbraco.Web.Media +{ + internal class ImageUrlProviderCollectionBuilder : OrderedCollectionBuilderBase + { + public ImageUrlProviderCollectionBuilder(IServiceContainer container) : base(container) + {} + + protected override ImageUrlProviderCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Web/Media/ImageUrlProviderResolver.cs b/src/Umbraco.Web/Media/ImageUrlProviderResolver.cs deleted file mode 100644 index dfed61e69d..0000000000 --- a/src/Umbraco.Web/Media/ImageUrlProviderResolver.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Media; -using Umbraco.Core.ObjectResolution; -using Umbraco.Web.Media.ImageUrlProviders; - -namespace Umbraco.Web.Media -{ - [Obsolete("IImageUrlProvider is no longer used and will be removed in future versions")] - internal sealed class ImageUrlProviderResolver : LazyManyObjectsResolverBase - { - internal ImageUrlProviderResolver(IServiceProvider serviceProvider, ILogger logger, Func> value) - : base(serviceProvider, logger, value) - { - } - - public IImageUrlProvider GetProvider(string provider) - { - return string.IsNullOrEmpty(provider) ? - Values.First(v => v.Name.Equals(ImageUrlProvider.DefaultName, StringComparison.InvariantCultureIgnoreCase)) : - Values.First(v => v.Name.Equals(provider, StringComparison.InvariantCultureIgnoreCase)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Media/ImageUrlProviders/ImageUrlProvider.cs b/src/Umbraco.Web/Media/ImageUrlProviders/ImageUrlProvider.cs index a8d541cebb..fa2209b278 100644 --- a/src/Umbraco.Web/Media/ImageUrlProviders/ImageUrlProvider.cs +++ b/src/Umbraco.Web/Media/ImageUrlProviders/ImageUrlProvider.cs @@ -66,7 +66,7 @@ namespace Umbraco.Web.Media.ImageUrlProviders { var lastIndexOf = filename.LastIndexOf('.'); var name = filename.Substring(0, lastIndexOf); - + //var extension = filename.Substring(lastIndexOf, filename.Length - lastIndexOf); //Built in cropper currently always uses jpg as an extension @@ -80,9 +80,7 @@ namespace Umbraco.Web.Media.ImageUrlProviders private static string GetProperty(XPathNodeIterator nodeIterator, string fileProp) { - var xpath = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema - ? string.Format(".//data[@alias = '{0}']", fileProp) - : string.Format(".//{0}", fileProp); + var xpath = string.Format(".//{0}", fileProp); var file = string.Empty; var selectSingleNode = nodeIterator.Current.SelectSingleNode(xpath); @@ -92,4 +90,4 @@ namespace Umbraco.Web.Media.ImageUrlProviders return file; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs b/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs index 8699592467..ea3a13a3c2 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/FileExtensionIconThumbnailProvider.cs @@ -3,12 +3,12 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Composing; using Umbraco.Core.IO; namespace Umbraco.Web.Media.ThumbnailProviders { - [Weight(2000)] + [Weight(2000)] public class FileExtensionIconThumbnailProvider : AbstractThumbnailProvider { protected override IEnumerable SupportedExtensions diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs index 56fe6306e3..4fea0a6b5f 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/ImageThumbnailProvider.cs @@ -4,15 +4,21 @@ using System.IO; using System.Linq; using System.Text; using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Composing; using Umbraco.Core.IO; namespace Umbraco.Web.Media.ThumbnailProviders { - [Weight(1000)] + [Weight(1000)] public class ImageThumbnailProvider : AbstractThumbnailProvider - { + { + private readonly MediaFileSystem _mediaFileSystem; + + public ImageThumbnailProvider(MediaFileSystem mediaFileSystem) + { + _mediaFileSystem = mediaFileSystem; + } + protected override IEnumerable SupportedExtensions { get { return new List { ".jpeg", ".jpg", ".gif", ".bmp", ".png", ".tiff", ".tif" }; } @@ -37,7 +43,7 @@ namespace Umbraco.Web.Media.ThumbnailProviders try { - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + var fs = _mediaFileSystem; var relativeThumbPath = fs.GetRelativePath(tmpThumbUrl); if (!fs.FileExists(relativeThumbPath)) return false; diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs b/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs index 6112d60471..0ecaaf8ff3 100644 --- a/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs +++ b/src/Umbraco.Web/Media/ThumbnailProviders/MediaTypeIconThumbnailProvider.cs @@ -3,15 +3,15 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Composing; using Umbraco.Core.IO; namespace Umbraco.Web.Media.ThumbnailProviders { - [Weight(3000)] + [Weight(3000)] public class MediaTypeIconThumbnailProvider : AbstractThumbnailProvider { - + protected override IEnumerable SupportedExtensions { get { return new List { "*" }; } diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProviderCollection.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProviderCollection.cs new file mode 100644 index 0000000000..1668677649 --- /dev/null +++ b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProviderCollection.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Media; + +namespace Umbraco.Web.Media.ThumbnailProviders +{ + // fixme - kill entirely in v8 thumbs should be generated by ImageProcessor + public class ThumbnailProviderCollection : BuilderCollectionBase + { + public ThumbnailProviderCollection(IEnumerable items) + : base(items) + { } + + public string GetThumbnailUrl(string fileUrl) + { + var provider = this.FirstOrDefault(x => x.CanProvideThumbnail(fileUrl)); + return provider != null ? provider.GetThumbnailUrl(fileUrl) : string.Empty; + } + } +} diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProviderCollectionBuilder.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProviderCollectionBuilder.cs new file mode 100644 index 0000000000..e5925ac4e7 --- /dev/null +++ b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProviderCollectionBuilder.cs @@ -0,0 +1,15 @@ +using LightInject; +using Umbraco.Core.Composing; +using Umbraco.Core.Media; + +namespace Umbraco.Web.Media.ThumbnailProviders +{ + public class ThumbnailProviderCollectionBuilder : WeightedCollectionBuilderBase + { + public ThumbnailProviderCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override ThumbnailProviderCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs deleted file mode 100644 index f172dc3b7e..0000000000 --- a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Media; -using Umbraco.Core.ObjectResolution; -using umbraco.BusinessLogic; -using umbraco.BusinessLogic.Utils; - -namespace Umbraco.Web.Media.ThumbnailProviders -{ - [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")] - public sealed class ThumbnailProvidersResolver : LazyManyObjectsResolverBase - { - /// - /// Constructor - /// - /// - /// - /// - internal ThumbnailProvidersResolver(IServiceProvider serviceProvider, ILogger logger, Func> providers) - : base(serviceProvider, logger, providers) - { - - } - - /// - /// Return the providers - /// - public IEnumerable Providers - { - get { return GetSortedValues(); } - } - - public string GetThumbnailUrl(string fileUrl) - { - var provider = Providers.FirstOrDefault(x => x.CanProvideThumbnail(fileUrl)); - return provider != null ? provider.GetThumbnailUrl(fileUrl) : string.Empty; - } - - } -} diff --git a/src/Umbraco.Web/MembershipProviderExtensions.cs b/src/Umbraco.Web/MembershipProviderExtensions.cs index 0aec00eb76..614950db59 100644 --- a/src/Umbraco.Web/MembershipProviderExtensions.cs +++ b/src/Umbraco.Web/MembershipProviderExtensions.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web { var baseProvider = membershipProvider as MembershipProviderBase; - var canReset = membershipProvider.CanResetPassword(userService); + var canReset = membershipProvider.CanResetPassword(userService); return new Dictionary { @@ -32,7 +32,7 @@ namespace Umbraco.Web {"minNonAlphaNumericChars", membershipProvider.MinRequiredNonAlphanumericCharacters} //TODO: Inject the other parameters in here to change the behavior of this control - based on the membership provider settings. }; - } + } } } diff --git a/src/Umbraco.Web/Migrations/ClearCsrfCookiesAfterUpgrade.cs b/src/Umbraco.Web/Migrations/ClearCsrfCookiesAfterUpgrade.cs new file mode 100644 index 0000000000..75ba97097b --- /dev/null +++ b/src/Umbraco.Web/Migrations/ClearCsrfCookiesAfterUpgrade.cs @@ -0,0 +1,26 @@ +using System.Web; +using Semver; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations; +using Umbraco.Core.Scoping; +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.Web.Migrations +{ + /// + /// After upgrade we clear out the csrf tokens + /// + public class ClearCsrfCookiesAfterUpgrade : IPostMigration + { + public void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger) + { + if (name != Constants.System.UmbracoUpgradePlanName) return; + if (HttpContext.Current == null) return; + + var http = new HttpContextWrapper(HttpContext.Current); + http.ExpireCookie(AngularAntiForgeryHelper.AngularCookieName); + http.ExpireCookie(AngularAntiForgeryHelper.CsrfValidationCookieName); + } + } +} diff --git a/src/Umbraco.Web/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs b/src/Umbraco.Web/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs new file mode 100644 index 0000000000..04007a9a46 --- /dev/null +++ b/src/Umbraco.Web/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs @@ -0,0 +1,50 @@ +using Semver; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations; +using Umbraco.Core.Scoping; + +namespace Umbraco.Web.Migrations +{ + /// + /// This will execute after upgrading to remove any xml cache for media that are currently in the bin + /// + /// + /// This will execute for specific versions - + /// + /// * If current is less than or equal to 7.0.0 + /// + public class ClearMediaXmlCacheForDeletedItemsAfterUpgrade : IPostMigration + { + private readonly ILogger _logger; + + public ClearMediaXmlCacheForDeletedItemsAfterUpgrade(ILogger logger) + { + _logger = logger; + } + + public void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger) + { + if (name != Constants.System.UmbracoUpgradePlanName) return; + + var target70 = new SemVersion(7 /*, 0, 0*/); + + if (originVersion <= target70) + { + //This query is structured to work with MySql, SQLCE and SqlServer: + // http://issues.umbraco.org/issue/U4-3876 + + var syntax = scope.SqlContext.SqlSyntax; + + var sql = @"DELETE FROM cmsContentXml WHERE nodeId IN + (SELECT nodeId FROM (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml + INNER JOIN umbracoNode ON cmsContentXml.nodeId = umbracoNode.id + WHERE nodeObjectType = '" + Constants.ObjectTypes.Media + "' AND " + syntax.GetQuotedColumnName("path") + " LIKE '%-21%') x)"; + + var count = scope.Database.Execute(sql); + + _logger.Info("Cleared " + count + " items from the media xml cache that were trashed and not meant to be there"); + } + } + } +} diff --git a/src/Umbraco.Web/Migrations/EnsureListViewDataTypeIsCreated.cs b/src/Umbraco.Web/Migrations/EnsureListViewDataTypeIsCreated.cs new file mode 100644 index 0000000000..9632737515 --- /dev/null +++ b/src/Umbraco.Web/Migrations/EnsureListViewDataTypeIsCreated.cs @@ -0,0 +1,71 @@ +using System; +using NPoco; +using Semver; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Scoping; + +namespace Umbraco.Web.Migrations +{ + /// + /// Creates the built in list view data types + /// + public class EnsureDefaultListViewDataTypesCreated : IPostMigration + { + public void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger) + { + if (name != Constants.System.UmbracoUpgradePlanName) return; + + var target720 = new SemVersion(7, 2, 0); + + if (originVersion > target720) + return; + + var syntax = scope.SqlContext.SqlSyntax; + + try + { + //Turn on identity insert if db provider is not mysql + if (syntax.SupportsIdentityInsert()) + scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", syntax.GetQuotedTableName("umbracoNode")))); + + if (scope.Database.Exists(Constants.DataTypes.DefaultContentListView)) + { + //If this already exists then just exit + return; + } + + scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + scope.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + } + finally + { + //Turn off identity insert if db provider is not mysql + if (syntax.SupportsIdentityInsert()) + scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF;", syntax.GetQuotedTableName("umbracoNode")))); + } + + try + { + //Turn on identity insert if db provider is not mysql + if (syntax.SupportsIdentityInsert()) + scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", syntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DataType)))); + + const string memberListViewConfiguration = "{\"pageSize\":10,\"orderBy\":Name,\"orderDirection\":asc,includeProperties:[{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]}"; + + scope.Database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultContentListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar" }); + scope.Database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultMediaListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar" }); + scope.Database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultMembersListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", Configuration = memberListViewConfiguration }); + } + finally + { + //Turn off identity insert if db provider is not mysql + if (syntax.SupportsIdentityInsert()) + scope.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF;", syntax.GetQuotedTableName(Constants.DatabaseSchema.Tables.DataType)))); + } + } + } +} diff --git a/src/Umbraco.Web/Migrations/OverwriteStylesheetFilesFromTempFiles.cs b/src/Umbraco.Web/Migrations/OverwriteStylesheetFilesFromTempFiles.cs new file mode 100644 index 0000000000..88c4972517 --- /dev/null +++ b/src/Umbraco.Web/Migrations/OverwriteStylesheetFilesFromTempFiles.cs @@ -0,0 +1,53 @@ +using System.IO; +using Semver; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations; +using Umbraco.Core.Migrations.Upgrade; +using Umbraco.Core.Scoping; + +namespace Umbraco.Web.Migrations +{ + + /// + /// When upgrading version 7.3 the migration MigrateStylesheetDataToFile will execute but we don't want to overwrite the developers + /// files during the migration since other parts of the migration might fail. So once the migration is complete, we'll then copy over the temp + /// files that this migration created over top of the developer's files. We'll also create a backup of their files. + /// + public sealed class OverwriteStylesheetFilesFromTempFiles : IPostMigration + { + public void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger) + { + if (name != Constants.System.UmbracoUpgradePlanName) return; + + var target73 = new SemVersion(7, 3, 0); + + if (originVersion <= target73) + { + var tempCssFolder = IOHelper.MapPath("~/App_Data/TEMP/CssMigration/"); + var cssFolder = IOHelper.MapPath("~/css"); + if (Directory.Exists(tempCssFolder)) + { + var files = Directory.GetFiles(tempCssFolder, "*.css", SearchOption.AllDirectories); + foreach (var file in files) + { + var relativePath = file.TrimStart(tempCssFolder).TrimStart("\\"); + var cssFilePath = Path.Combine(cssFolder, relativePath); + if (File.Exists(cssFilePath)) + { + //backup + var targetPath = Path.Combine(tempCssFolder, relativePath.EnsureEndsWith(".bak")); + logger.Info($"CSS file is being backed up from {cssFilePath}, to {targetPath} before being migrated to new format"); + File.Copy(cssFilePath, targetPath, true); + } + + //ensure the sub folder eixts + Directory.CreateDirectory(Path.GetDirectoryName(cssFilePath)); + File.Copy(file, cssFilePath, true); + } + } + } + } + } +} diff --git a/src/Umbraco.Web/Migrations/RebuildXmlCachesAfterUpgrade.cs b/src/Umbraco.Web/Migrations/RebuildXmlCachesAfterUpgrade.cs new file mode 100644 index 0000000000..2181eea367 --- /dev/null +++ b/src/Umbraco.Web/Migrations/RebuildXmlCachesAfterUpgrade.cs @@ -0,0 +1,57 @@ +using System; +using Semver; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations; +using Umbraco.Core.Scoping; +using Umbraco.Web.Composing; +using Umbraco.Web.PublishedCache.XmlPublishedCache; + +namespace Umbraco.Web.Migrations +{ + /// + /// Rebuilds the Xml caches after upgrading. + /// This will execute after upgrading to rebuild the xml cache + /// + /// + /// This cannot execute as part of a DB migration since it needs access to services and repositories. + /// Executes for: + /// - Media Xml : if current is less than, or equal to, 7.0.0 (superceeded by the next rule) + /// - Media & Content Xml : if current is less than, or equal to, 7.3.0 - because 7.3.0 adds .Key to cached items + /// + /// + public class RebuildXmlCachesAfterUpgrade : IPostMigration + { + public void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger) + { + if (name != Constants.System.UmbracoUpgradePlanName) return; + + var v730 = new SemVersion(new Version(7, 3, 0)); + + var doMedia = originVersion < v730; + var doContent = originVersion < v730; + + if (doMedia) + { + // fixme - maintain - for backward compatibility?! or replace with...?! + //var mediaService = (MediaService) ApplicationContext.Current.Services.MediaService; + //mediaService.RebuildXmlStructures(); + + var svc = Current.PublishedSnapshotService as PublishedSnapshotService; + svc?.RebuildMediaXml(); + + // note: not re-indexing medias? + } + + if (doContent) + { + // fixme - maintain - for backward compatibility?! or replace with...?! + //var contentService = (ContentService) ApplicationContext.Current.Services.ContentService; + //contentService.RebuildXmlStructures(); + + var svc = Current.PublishedSnapshotService as PublishedSnapshotService; + svc?.RebuildContentAndPreviewXml(); + } + } + } +} diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs index a36f3c89f6..ceba154842 100644 --- a/src/Umbraco.Web/ModelStateExtensions.cs +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -6,50 +6,50 @@ using System.Web.Mvc; namespace Umbraco.Web { - internal static class ModelStateExtensions - { + internal static class ModelStateExtensions + { - /// - /// Merges ModelState that has names matching the prefix - /// - /// - /// - /// - public static void Merge(this ModelStateDictionary state, ModelStateDictionary dictionary, string prefix) - { - if (dictionary == null) - return; - foreach (var keyValuePair in dictionary + /// + /// Merges ModelState that has names matching the prefix + /// + /// + /// + /// + public static void Merge(this ModelStateDictionary state, ModelStateDictionary dictionary, string prefix) + { + if (dictionary == null) + return; + foreach (var keyValuePair in dictionary //It can either equal the prefix exactly (model level errors) or start with the prefix. (property level errors) .Where(keyValuePair => keyValuePair.Key == prefix || keyValuePair.Key.StartsWith(prefix + "."))) - { - state[keyValuePair.Key] = keyValuePair.Value; - } - } + { + state[keyValuePair.Key] = keyValuePair.Value; + } + } - /// - /// Checks if there are any model errors on any fields containing the prefix - /// - /// - /// - /// - public static bool IsValid(this ModelStateDictionary state, string prefix) - { - return state.Where(v => v.Key.StartsWith(prefix + ".")).All(v => !v.Value.Errors.Any()); - } + /// + /// Checks if there are any model errors on any fields containing the prefix + /// + /// + /// + /// + public static bool IsValid(this ModelStateDictionary state, string prefix) + { + return state.Where(v => v.Key.StartsWith(prefix + ".")).All(v => !v.Value.Errors.Any()); + } - //NOTE: we used this alot in v5 when we had editors in MVC, this was really handy for knockout editors using JS + //NOTE: we used this alot in v5 when we had editors in MVC, this was really handy for knockout editors using JS - ///// - ///// Adds an error to the model state that has to do with data validation, this is generally used for JSON responses - ///// - ///// - ///// - //public static void AddDataValidationError(this ModelStateDictionary state, string errorMessage) - //{ - // state.AddModelError("DataValidation", errorMessage); - //} + ///// + ///// Adds an error to the model state that has to do with data validation, this is generally used for JSON responses + ///// + ///// + ///// + //public static void AddDataValidationError(this ModelStateDictionary state, string errorMessage) + //{ + // state.AddModelError("DataValidation", errorMessage); + //} /// /// Adds the error to model state correctly for a property so we can use it on the client side. @@ -59,22 +59,22 @@ namespace Umbraco.Web /// internal static void AddPropertyError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, ValidationResult result, string propertyAlias) { - //if there are no member names supplied then we assume that the validation message is for the overall property - // not a sub field on the property editor - if (result.MemberNames.Any() == false) + modelState.AddValidationError(result, "_Properties", propertyAlias); + } + + internal static void AddValidationError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, ValidationResult result, string prefix, string owner) + { + // if there are assigned member names, we combine the member name with the owner name + // so that we can try to match it up to a real field. otherwise, we assume that the + // validation message is for the overall owner. + var withNames = false; + foreach (var memberName in result.MemberNames) { - //add a model state error for the entire property - modelState.AddModelError(string.Format("{0}.{1}", "_Properties", propertyAlias), result.ErrorMessage); - } - else - { - //there's assigned field names so we'll combine the field name with the property name - // so that we can try to match it up to a real sub field of this editor - foreach (var field in result.MemberNames) - { - modelState.AddModelError(string.Format("{0}.{1}.{2}", "_Properties", propertyAlias, field), result.ErrorMessage); - } + modelState.AddModelError($"{prefix}.{owner}.{memberName}", result.ErrorMessage); + withNames = true; } + if (!withNames) + modelState.AddModelError($"{prefix}.{owner}", result.ErrorMessage); } public static IDictionary ToErrorDictionary(this System.Web.Http.ModelBinding.ModelStateDictionary modelState) @@ -92,31 +92,31 @@ namespace Umbraco.Web return modelStateError; } - /// - /// Serializes the ModelState to JSON for JavaScript to interrogate the errors - /// - /// - /// - public static JsonResult ToJsonErrors(this ModelStateDictionary state) - { - return new JsonResult - { - Data = new - { - success = state.IsValid.ToString().ToLower(), - failureType = "ValidationError", - validationErrors = from e in state - where e.Value.Errors.Count > 0 - select new - { - name = e.Key, - errors = e.Value.Errors.Select(x => x.ErrorMessage) - .Concat( - e.Value.Errors.Where(x => x.Exception != null).Select(x => x.Exception.Message)) - } - } - }; - } + /// + /// Serializes the ModelState to JSON for JavaScript to interrogate the errors + /// + /// + /// + public static JsonResult ToJsonErrors(this ModelStateDictionary state) + { + return new JsonResult + { + Data = new + { + success = state.IsValid.ToString().ToLower(), + failureType = "ValidationError", + validationErrors = from e in state + where e.Value.Errors.Count > 0 + select new + { + name = e.Key, + errors = e.Value.Errors.Select(x => x.ErrorMessage) + .Concat( + e.Value.Errors.Where(x => x.Exception != null).Select(x => x.Exception.Message)) + } + } + }; + } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/Models/BackOfficeTour.cs b/src/Umbraco.Web/Models/BackOfficeTour.cs index 78a4cd1897..268d5667f4 100644 --- a/src/Umbraco.Web/Models/BackOfficeTour.cs +++ b/src/Umbraco.Web/Models/BackOfficeTour.cs @@ -9,6 +9,11 @@ namespace Umbraco.Web.Models [DataContract(Name = "tour", Namespace = "")] public class BackOfficeTour { + public BackOfficeTour() + { + RequiredSections = new List(); + } + [DataMember(Name = "name")] public string Name { get; set; } [DataMember(Name = "alias")] diff --git a/src/Umbraco.Web/Models/BackOfficeTourFile.cs b/src/Umbraco.Web/Models/BackOfficeTourFile.cs index 7291a89ff4..6840171f48 100644 --- a/src/Umbraco.Web/Models/BackOfficeTourFile.cs +++ b/src/Umbraco.Web/Models/BackOfficeTourFile.cs @@ -9,6 +9,11 @@ namespace Umbraco.Web.Models [DataContract(Name = "tourFile", Namespace = "")] public class BackOfficeTourFile { + public BackOfficeTourFile() + { + Tours = new List(); + } + /// /// The file name for the tour /// @@ -27,4 +32,4 @@ namespace Umbraco.Web.Models [DataMember(Name = "tours")] public IEnumerable Tours { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/BackOfficeTourFilter.cs b/src/Umbraco.Web/Models/BackOfficeTourFilter.cs deleted file mode 100644 index 994cdb6d29..0000000000 --- a/src/Umbraco.Web/Models/BackOfficeTourFilter.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Text.RegularExpressions; - -namespace Umbraco.Web.Models -{ - public class BackOfficeTourFilter - { - public Regex PluginName { get; private set; } - public Regex TourFileName { get; private set; } - public Regex TourAlias { get; private set; } - - /// - /// Create a filter to filter out a whole plugin's tours - /// - /// - /// - public static BackOfficeTourFilter FilterPlugin(Regex pluginName) - { - return new BackOfficeTourFilter(pluginName, null, null); - } - - /// - /// Create a filter to filter out a whole tour file - /// - /// - /// - public static BackOfficeTourFilter FilterFile(Regex tourFileName) - { - return new BackOfficeTourFilter(null, tourFileName, null); - } - - /// - /// Create a filter to filter out a tour alias, this will filter out the same alias found in all files - /// - /// - /// - public static BackOfficeTourFilter FilterAlias(Regex tourAlias) - { - return new BackOfficeTourFilter(null, null, tourAlias); - } - - /// - /// Constructor to create a tour filter - /// - /// Value to filter out tours by a plugin, can be null - /// Value to filter out a tour file, can be null - /// Value to filter out a tour alias, can be null - /// - /// Depending on what is null will depend on how the filter is applied. - /// If pluginName is not NULL and it's matched then we check if tourFileName is not NULL and it's matched then we check tour alias is not NULL and then match it, - /// if any steps is NULL then the filters upstream are applied. - /// Example, pluginName = "hello", tourFileName="stuff", tourAlias=NULL = we will filter out the tour file "stuff" from the plugin "hello" but not from other plugins if the same file name exists. - /// Example, tourAlias="test.*" = we will filter out all tour aliases that start with the word "test" regardless of the plugin or file name - /// - public BackOfficeTourFilter(Regex pluginName, Regex tourFileName, Regex tourAlias) - { - PluginName = pluginName; - TourFileName = tourFileName; - TourAlias = tourAlias; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ChangingPasswordModel.cs b/src/Umbraco.Web/Models/ChangingPasswordModel.cs index f2004bb36d..33f14d97b2 100644 --- a/src/Umbraco.Web/Models/ChangingPasswordModel.cs +++ b/src/Umbraco.Web/Models/ChangingPasswordModel.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.Models public string NewPassword { get; set; } /// - /// The old password - used to change a password when: EnablePasswordRetrieval = false + /// The old password - used to change a password when: EnablePasswordRetrieval = false /// [DataMember(Name = "oldPassword")] public string OldPassword { get; set; } @@ -39,7 +39,7 @@ namespace Umbraco.Web.Models public bool? Reset { get; set; } /// - /// The password answer - required for reset when: RequiresQuestionAndAnswer = true + /// The password answer - required for reset when: RequiresQuestionAndAnswer = true /// [DataMember(Name = "answer")] public string Answer { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/AssignedContentPermissions.cs b/src/Umbraco.Web/Models/ContentEditing/AssignedContentPermissions.cs index dc071052cc..655a984da0 100644 --- a/src/Umbraco.Web/Models/ContentEditing/AssignedContentPermissions.cs +++ b/src/Umbraco.Web/Models/ContentEditing/AssignedContentPermissions.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing @@ -18,4 +18,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "permissions")] public IDictionary> AssignedPermissions { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/AssignedUserGroupPermissions.cs b/src/Umbraco.Web/Models/ContentEditing/AssignedUserGroupPermissions.cs index 5428de883f..13366a9596 100644 --- a/src/Umbraco.Web/Models/ContentEditing/AssignedUserGroupPermissions.cs +++ b/src/Umbraco.Web/Models/ContentEditing/AssignedUserGroupPermissions.cs @@ -35,4 +35,4 @@ namespace Umbraco.Web.Models.ContentEditing return result; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/AuditLog.cs b/src/Umbraco.Web/Models/ContentEditing/AuditLog.cs index 48c489135a..e908359f93 100644 --- a/src/Umbraco.Web/Models/ContentEditing/AuditLog.cs +++ b/src/Umbraco.Web/Models/ContentEditing/AuditLog.cs @@ -1,8 +1,5 @@ using System; using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing { @@ -16,19 +13,18 @@ namespace Umbraco.Web.Models.ContentEditing public string UserName { get; set; } [DataMember(Name = "userAvatars")] - public string[] UserAvatars { get; set; } + public string[] UserAvatars { get; set; } [DataMember(Name = "nodeId")] public int NodeId { get; set; } [DataMember(Name = "timestamp")] public DateTime Timestamp { get; set; } - - [JsonConverter(typeof(StringEnumConverter))] + [DataMember(Name = "logType")] - public AuditType LogType { get; set; } + public string LogType { get; set; } [DataMember(Name = "comment")] public string Comment { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/AuditLogType.cs b/src/Umbraco.Web/Models/ContentEditing/AuditLogType.cs deleted file mode 100644 index 5907f10eee..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/AuditLogType.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Web.Models.ContentEditing -{ - [Obsolete("Use Umbraco.Core.Models.AuditType instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public enum AuditLogType - { - /// - /// Used when new nodes are added - /// - New, - /// - /// Used when nodes are saved - /// - Save, - /// - /// Used when nodes are opened - /// - Open, - /// - /// Used when nodes are deleted - /// - Delete, - /// - /// Used when nodes are published - /// - Publish, - /// - /// Used when nodes are send to publishing - /// - SendToPublish, - /// - /// Used when nodes are unpublished - /// - UnPublish, - /// - /// Used when nodes are moved - /// - Move, - /// - /// Used when nodes are copied - /// - Copy, - /// - /// Used when nodes are assígned a domain - /// - AssignDomain, - /// - /// Used when public access are changed for a node - /// - PublicAccess, - /// - /// Used when nodes are sorted - /// - Sort, - /// - /// Used when a notification are send to a user - /// - Notify, - - /// - /// Used when a node's content is rolled back to a previous version - /// - RollBack, - /// - /// Used when a package is installed - /// - PackagerInstall, - /// - /// Used when a package is uninstalled - /// - PackagerUninstall, - /// - /// Used when a node is send to translation - /// - SendToTranslate - - } -} diff --git a/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs b/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs new file mode 100644 index 0000000000..f8633acdd0 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// The model representing Previewing of a content item from the back office + /// + public class BackOfficePreview + { + public string PreviewExtendedHeaderView { get; set; } + + //TODO: We could potentially have a 'footer' view + public bool DisableDevicePreview { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/CodeFileDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/CodeFileDisplay.cs index 34ad507836..2e66b270c9 100644 --- a/src/Umbraco.Web/Models/ContentEditing/CodeFileDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/CodeFileDisplay.cs @@ -29,8 +29,8 @@ namespace Umbraco.Web.Models.ContentEditing /// Path represents the path used by the backoffice tree /// For files stored on disk, this is a urlencoded, comma seperated /// path to the file, always starting with -1. - /// - /// -1,Partials,Parials%2FFolder,Partials%2FFolder%2FFile.cshtml + /// + /// -1,Partials,Parials%2FFolder,Partials%2FFolder%2FFile.cshtml /// [DataMember(Name = "path")] [ReadOnly(true)] @@ -66,7 +66,7 @@ namespace Umbraco.Web.Models.ContentEditing if (Name.ContainsAny(illegalChars)) { yield return new ValidationResult( - "The file name cannot contain illegal characters", + "The file name cannot contain illegal characters", new[] { "Name" }); } else if (System.IO.Path.GetFileNameWithoutExtension(Name).IsNullOrWhiteSpace()) diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentBaseItemSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentBaseItemSave.cs index 0a0aaf204b..3a28793ac7 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentBaseItemSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentBaseItemSave.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; namespace Umbraco.Web.Models.ContentEditing { @@ -9,12 +10,12 @@ namespace Umbraco.Web.Models.ContentEditing /// A model representing a content base item to be saved /// [DataContract(Name = "content", Namespace = "")] - public abstract class ContentBaseItemSave : ContentItemBasic, IHaveUploadedFiles - where TPersisted : IContentBase + public abstract class ContentBaseItemSave : ContentItemBasic, IHaveUploadedFiles + where TPersisted : IContentBase { protected ContentBaseItemSave() { - UploadedFiles = new List(); + UploadedFiles = new List(); } /// @@ -25,6 +26,6 @@ namespace Umbraco.Web.Models.ContentEditing public ContentSaveAction Action { get; set; } [IgnoreDataMember] - public List UploadedFiles { get; private set; } + public List UploadedFiles { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs index 18229d94d8..c04dd9ab8d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs @@ -14,8 +14,7 @@ namespace Umbraco.Web.Models.ContentEditing /// [DataContract(Name = "content", Namespace = "")] public class ContentItemBasic : EntityBasic - { - + { [DataMember(Name = "updateDate")] public DateTime UpdateDate { get; set; } @@ -24,9 +23,12 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "published")] public bool Published { get; set; } - - [DataMember(Name = "hasPublishedVersion")] - public bool HasPublishedVersion { get; set; } + + /// + /// Determines if the content item is a draft + /// + [DataMember(Name = "edited")] + public bool Edited { get; set; } [DataMember(Name = "owner")] public UserProfile Owner { get; set; } @@ -40,7 +42,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } - + protected bool Equals(ContentItemBasic other) { return Id == other.Id; @@ -79,8 +81,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "properties")] public virtual IEnumerable Properties { - get { return _properties; } - set { _properties = value; } + get => _properties; + set => _properties = value; } /// @@ -105,4 +107,4 @@ namespace Umbraco.Web.Models.ContentEditing } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs index 1e59aa1c93..937414cf6a 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs @@ -7,7 +7,7 @@ namespace Umbraco.Web.Models.ContentEditing { /// /// A model representing a content item to be displayed in the back office - /// + /// [DataContract(Name = "content", Namespace = "")] public class ContentItemDisplay : ListViewAwareContentItemDisplayBase { @@ -23,8 +23,14 @@ namespace Umbraco.Web.Models.ContentEditing public DateTime? ReleaseDate { get; set; } [DataMember(Name = "removeDate")] - public DateTime? ExpireDate { get; set; } + public DateTime? ExpireDate { get; set; } + /// + /// Represents the variant info for a content item + /// + [DataMember(Name = "variants")] + public IEnumerable Variants { get; set; } + [DataMember(Name = "template")] public string TemplateAlias { get; set; } @@ -58,4 +64,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "isBlueprint")] public bool IsBlueprint { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs index 87de7b435c..04a1e37f95 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel; using System.Runtime.Serialization; using Umbraco.Core.Models; @@ -6,7 +6,7 @@ using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing { public abstract class ContentItemDisplayBase : TabbedContentItem, INotificationModel, IErrorModel - where T : ContentPropertyBasic + where T : ContentPropertyBasic where TPersisted : IContentBase { protected ContentItemDisplayBase() @@ -41,7 +41,7 @@ namespace Umbraco.Web.Models.ContentEditing /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the /// updated model. - /// + /// /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. /// [DataMember(Name = "ModelState")] diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDto.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDto.cs index 2bd7f60660..a94eb5883d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDto.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDto.cs @@ -6,9 +6,9 @@ namespace Umbraco.Web.Models.ContentEditing /// /// Represents a content item from the database including all of the required data that we need to work with such as data type data /// - internal class ContentItemDto : ContentItemBasic + internal class ContentItemDto : ContentItemBasic where TPersisted : IContentBase { - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemFile.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemFile.cs deleted file mode 100644 index 2800d410f4..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemFile.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// Represents an uploaded file for a particular property - /// - public class ContentItemFile - { - /// - /// The property alias associated with the file - /// - public string PropertyAlias { get; set; } - - /// - /// The original file name - /// - public string FileName { get; set; } - - /// - /// The file path for the uploaded file for where the MultipartFormDataStreamProvider has saved the temp file - /// - public string TempFilePath { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs index 6c4eab8a31..ca410100ec 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.Serialization; using Newtonsoft.Json; using Umbraco.Core.Models; @@ -9,8 +10,14 @@ namespace Umbraco.Web.Models.ContentEditing /// A model representing a content item to be saved /// [DataContract(Name = "content", Namespace = "")] - public class ContentItemSave : ContentBaseItemSave - { + public class ContentItemSave : ContentBaseItemSave + { + /// + /// The language Id for the content variation being saved + /// + [DataMember(Name = "languageId")] + public int? LanguageId { get; set; } //TODO: Change this to ContentVariationPublish, but this will all change anyways when we can edit all variants at once + /// /// The template alias to save /// @@ -22,6 +29,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "expireDate")] public DateTime? ExpireDate { get; set; } - + + /// + /// Indicates that these variations should also be published + /// + [DataMember(Name = "publishVariations")] + public IEnumerable PublishVariations { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs index f317771333..199b32da9b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Models.ContentEditing public class ContentPropertyBasic { /// - /// This is the cmsPropertyData ID + /// This is the PropertyData ID /// /// /// This is not really used for anything @@ -30,12 +30,17 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "editor", IsRequired = false)] public string Editor { get; set; } + /// + /// Flags the property to denote that it can contain sensitive data + /// + [DataMember(Name = "isSensitive", IsRequired = false)] + public bool IsSensitive { get; set; } /// /// Used internally during model mapping /// [IgnoreDataMember] - internal PropertyEditor PropertyEditor { get; set; } + internal IDataEditor PropertyEditor { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs index 343b018000..8a09c62333 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs @@ -34,6 +34,9 @@ namespace Umbraco.Web.Models.ContentEditing public bool HideLabel { get; set; } [DataMember(Name = "validation")] - public PropertyTypeValidation Validation { get; set; } + public PropertyTypeValidation Validation { get; set; } + + [DataMember(Name = "readonly")] + public bool Readonly { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs index 6d2a7e6993..e80c76eb7b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs @@ -10,16 +10,10 @@ namespace Umbraco.Web.Models.ContentEditing /// internal class ContentPropertyDto : ContentPropertyBasic { - public IDataTypeDefinition DataType { get; set; } + public IDataType DataType { get; set; } public string Label { get; set; } public string Description { get; set; } public bool IsRequired { get; set; } public string ValidationRegExp { get; set; } - - /// - /// The current pre-values for this property - /// - [JsonIgnore] - internal PreValueCollection PreValues { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentRedirectUrl.cs b/src/Umbraco.Web/Models/ContentEditing/ContentRedirectUrl.cs index 38fdb265b8..805a6d60be 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentRedirectUrl.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentRedirectUrl.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing @@ -21,4 +21,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "contentId")] public int ContentId { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentSaveAction.cs b/src/Umbraco.Web/Models/ContentEditing/ContentSaveAction.cs index ed54588ccc..17cd82bd01 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentSaveAction.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentSaveAction.cs @@ -35,4 +35,4 @@ /// SendPublishNew = 5 } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentSortOrder.cs b/src/Umbraco.Web/Models/ContentEditing/ContentSortOrder.cs index 41df1abe27..d869f4eea2 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentSortOrder.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentSortOrder.cs @@ -31,5 +31,5 @@ namespace Umbraco.Web.Models.ContentEditing public int[] IdSortOrder { get; set; } } - + } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs index 938c7de657..f98f3bbc17 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeBasic.cs @@ -15,21 +15,21 @@ namespace Umbraco.Web.Models.ContentEditing /// /// /// Generally used to return the minimal amount of data about a content type - /// + /// [DataContract(Name = "contentType", Namespace = "")] public class ContentTypeBasic : EntityBasic { public ContentTypeBasic() { Blueprints = new Dictionary(); - } + } /// /// Overridden to apply our own validation attributes since this is not always required for other classes - /// - [Required] - [RegularExpression(@"^([a-zA-Z]\w.*)$", ErrorMessage = "Invalid alias")] - [DataMember(Name = "alias")] + /// + [Required] + [RegularExpression(@"^([a-zA-Z]\w.*)$", ErrorMessage = "Invalid alias")] + [DataMember(Name = "alias")] public override string Alias { get; set; } [DataMember(Name = "updateDate")] @@ -75,7 +75,7 @@ namespace Umbraco.Web.Models.ContentEditing { return IconIsClass ? string.Empty - : string.Format("{0}images/umbraco/{1}", GlobalSettings.Path.EnsureEndsWith("/"), Icon); + : string.Format("{0}images/umbraco/{1}", UmbracoConfig.For.GlobalSettings().Path.EnsureEndsWith("/"), Icon); } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs index fb50fdec5c..f37fb833ec 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs @@ -1,23 +1,23 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Models.Validation; - -namespace Umbraco.Web.Models.ContentEditing -{ - public abstract class ContentTypeCompositionDisplay : ContentTypeBasic, INotificationModel - { - protected ContentTypeCompositionDisplay() - { - //initialize collections so at least their never null +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Models.Validation; + +namespace Umbraco.Web.Models.ContentEditing +{ + public abstract class ContentTypeCompositionDisplay : ContentTypeBasic, INotificationModel + { + protected ContentTypeCompositionDisplay() + { + //initialize collections so at least their never null AllowedContentTypes = new List(); - CompositeContentTypes = new List(); - Notifications = new List(); + CompositeContentTypes = new List(); + Notifications = new List(); } //name, alias, icon, thumb, desc, inherited from basic @@ -28,8 +28,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "listViewEditorName")] [ReadOnly(true)] - public string ListViewEditorName { get; set; } - + public string ListViewEditorName { get; set; } + //Allowed child types [DataMember(Name = "allowedContentTypes")] public IEnumerable AllowedContentTypes { get; set; } @@ -43,15 +43,15 @@ namespace Umbraco.Web.Models.ContentEditing public IEnumerable LockedCompositeContentTypes { get; set; } [DataMember(Name = "allowAsRoot")] - public bool AllowAsRoot { get; set; } - + public bool AllowAsRoot { get; set; } + /// /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. /// [DataMember(Name = "notifications")] [ReadOnly(true)] - public List Notifications { get; private set; } - + public List Notifications { get; private set; } + /// /// This is used for validation of a content item. /// @@ -59,27 +59,27 @@ namespace Umbraco.Web.Models.ContentEditing /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the /// updated model. - /// + /// /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. /// [DataMember(Name = "ModelState")] [ReadOnly(true)] - public IDictionary Errors { get; set; } - } - - [DataContract(Name = "contentType", Namespace = "")] - public abstract class ContentTypeCompositionDisplay : ContentTypeCompositionDisplay - where TPropertyTypeDisplay : PropertyTypeDisplay - { - protected ContentTypeCompositionDisplay() - { - //initialize collections so at least their never null - Groups = new List>(); + public IDictionary Errors { get; set; } + } + + [DataContract(Name = "contentType", Namespace = "")] + public abstract class ContentTypeCompositionDisplay : ContentTypeCompositionDisplay + where TPropertyTypeDisplay : PropertyTypeDisplay + { + protected ContentTypeCompositionDisplay() + { + //initialize collections so at least their never null + Groups = new List>(); } - - //Tabs - [DataMember(Name = "groups")] + + //Tabs + [DataMember(Name = "groups")] public IEnumerable> Groups { get; set; } - - } -} + + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs index 62e1979076..3f3efa15d8 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; @@ -56,7 +56,7 @@ namespace Umbraco.Web.Models.ContentEditing { Groups = new List>(); } - + //Tabs [DataMember(Name = "groups")] public IEnumerable> Groups { get; set; } @@ -89,7 +89,7 @@ namespace Umbraco.Web.Models.ContentEditing { //we need to return the field name with an index so it's wired up correctly var lastProperty = duplicateProperties.Last().Last(); - var propertyGroup = Groups.Single(x => x.Properties.Contains(lastProperty)); + var propertyGroup = Groups.Single(x => x.Properties.Contains(lastProperty)); yield return new ValidationResult("Duplicate property aliases not allowed: " + lastProperty.Alias, new[] { @@ -99,4 +99,4 @@ namespace Umbraco.Web.Models.ContentEditing } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs new file mode 100644 index 0000000000..83fb3b03f3 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ContentVariation.cs @@ -0,0 +1,48 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents the variant info for a content item + /// + [DataContract(Name = "contentVariant", Namespace = "")] + public class ContentVariation + { + [DataMember(Name = "language", IsRequired = true)] + [Required] + public Language Language { get; set; } + + [DataMember(Name = "segment")] + public string Segment { get; set; } + + /// + /// The content name of the variant + /// + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "state")] + public string PublishedState { get; set; } + + /// + /// Determines if the content variant for this culture has been created + /// + [DataMember(Name = "exists")] + public bool Exists { get; set; } + + /// + /// Determines if this is the variant currently being edited + /// + [DataMember(Name = "current")] + public bool IsCurrent { get; set; } + + /// + /// If the variant is a required variant for validation purposes + /// + [DataMember(Name = "mandatory")] + public bool Mandatory { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariationPublish.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariationPublish.cs new file mode 100644 index 0000000000..aefc487e7c --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ContentVariationPublish.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Used to indicate which additional variants to publish when a content item is published + /// + public class ContentVariationPublish + { + [DataMember(Name = "languageId", IsRequired = true)] + [Required] + public int LanguageId { get; set; } + + [DataMember(Name = "segment")] + public string Segment { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/DataTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/DataTypeBasic.cs index 1165ec8fd6..31c4dcc6e8 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DataTypeBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DataTypeBasic.cs @@ -1,27 +1,27 @@ -using System.ComponentModel; -using System.Runtime.Serialization; - -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// The basic data type information - /// - [DataContract(Name = "dataType", Namespace = "")] - public class DataTypeBasic : EntityBasic - { - /// - /// Whether or not this is a system data type, in which case it cannot be deleted - /// - [DataMember(Name = "isSystem")] - [ReadOnly(true)] - public bool IsSystemDataType { get; set; } - - [DataMember(Name = "group")] - [ReadOnly(true)] +using System.ComponentModel; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// The basic data type information + /// + [DataContract(Name = "dataType", Namespace = "")] + public class DataTypeBasic : EntityBasic + { + /// + /// Whether or not this is a system data type, in which case it cannot be deleted + /// + [DataMember(Name = "isSystem")] + [ReadOnly(true)] + public bool IsSystemDataType { get; set; } + + [DataMember(Name = "group")] + [ReadOnly(true)] public string Group { get; set; } [DataMember(Name = "hasPrevalues")] [ReadOnly(true)] - public bool HasPrevalues { get; set; } - } -} \ No newline at end of file + public bool HasPrevalues { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/DataTypeConfigurationFieldDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/DataTypeConfigurationFieldDisplay.cs new file mode 100644 index 0000000000..022746c7ec --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/DataTypeConfigurationFieldDisplay.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents a datatype configuration field model for editing. + /// + [DataContract(Name = "preValue", Namespace = "")] + public class DataTypeConfigurationFieldDisplay : DataTypeConfigurationFieldSave + { + /// + /// The name to display for this pre-value field + /// + [DataMember(Name = "label", IsRequired = true)] + public string Name { get; set; } + + /// + /// The description to display for this pre-value field + /// + [DataMember(Name = "description")] + public string Description { get; set; } + + /// + /// Specifies whether to hide the label for the pre-value + /// + [DataMember(Name = "hideLabel")] + public bool HideLabel { get; set; } + + /// + /// The view to render for the field + /// + [DataMember(Name = "view", IsRequired = true)] + public string View { get; set; } + + /// + /// This allows for custom configuration to be injected into the pre-value editor + /// + [DataMember(Name = "config")] + public IDictionary Config { get; set; } + + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/DataTypeConfigurationFieldSave.cs b/src/Umbraco.Web/Models/ContentEditing/DataTypeConfigurationFieldSave.cs new file mode 100644 index 0000000000..85984df281 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/DataTypeConfigurationFieldSave.cs @@ -0,0 +1,24 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json.Linq; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents a datatype configuration field model for editing. + /// + [DataContract(Name = "preValue", Namespace = "")] + public class DataTypeConfigurationFieldSave + { + /// + /// Gets or sets the configuration field key. + /// + [DataMember(Name = "key", IsRequired = true)] + public string Key { get; set; } + + /// + /// Gets or sets the configuration field value. + /// + [DataMember(Name = "value", IsRequired = true)] + public object Value { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs index 795d334e71..767b7acabf 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs @@ -27,13 +27,13 @@ namespace Umbraco.Web.Models.ContentEditing public IEnumerable AvailableEditors { get; set; } [DataMember(Name = "preValues")] - public IEnumerable PreValues { get; set; } + public IEnumerable PreValues { get; set; } /// /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. /// [DataMember(Name = "notifications")] public List Notifications { get; private set; } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs index 9b6c0df110..bb391a51a9 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs @@ -1,44 +1,51 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -using Newtonsoft.Json; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.Models.ContentEditing { + /// + /// Represents a datatype model for editing. + /// [DataContract(Name = "dataType", Namespace = "")] public class DataTypeSave : EntityBasic { /// - /// The action to perform when saving this data type + /// Gets or sets the action to perform. /// /// - /// If either of the Publish actions are specified an exception will be thrown. + /// Some values (publish) are illegal here. /// [DataMember(Name = "action", IsRequired = true)] [Required] public ContentSaveAction Action { get; set; } + /// + /// Gets or sets the datatype editor. + /// [DataMember(Name = "selectedEditor", IsRequired = true)] [Required] - public string SelectedEditor { get; set; } + public string EditorAlias { get; set; } + /// + /// Gets or sets the datatype configuration fields. + /// [DataMember(Name = "preValues")] - public IEnumerable PreValues { get; set; } + public IEnumerable ConfigurationFields { get; set; } /// - /// The real persisted data type + /// Gets or sets the persisted data type. /// [IgnoreDataMember] - internal IDataTypeDefinition PersistedDataType { get; set; } + internal IDataType PersistedDataType { get; set; } /// - /// The PropertyEditor assigned + /// Gets or sets the property editor. /// [IgnoreDataMember] - internal PropertyEditor PropertyEditor { get; set; } + internal IDataEditor PropertyEditor { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs index 65641e7f63..7965b90ae0 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs @@ -9,17 +9,17 @@ namespace Umbraco.Web.Models.ContentEditing public DocumentTypeDisplay() { //initialize collections so at least their never null - AllowedTemplates = new List(); + AllowedTemplates = new List(); } //name, alias, icon, thumb, desc, inherited from the content type // Templates [DataMember(Name = "allowedTemplates")] - public IEnumerable AllowedTemplates { get; set; } + public IEnumerable AllowedTemplates { get; set; } [DataMember(Name = "defaultTemplate")] public EntityBasic DefaultTemplate { get; set; } - + } } diff --git a/src/Umbraco.Web/Models/ContentEditing/DocumentTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeSave.cs index cdf20dba8d..3f163f860f 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DocumentTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeSave.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; @@ -11,13 +11,13 @@ namespace Umbraco.Web.Models.ContentEditing /// [DataContract(Name = "contentType", Namespace = "")] public class DocumentTypeSave : ContentTypeSave - { + { /// /// The list of allowed templates to assign (template alias) /// [DataMember(Name = "allowedTemplates")] public IEnumerable AllowedTemplates { get; set; } - + /// /// The default template to assign (template alias) /// @@ -40,4 +40,4 @@ namespace Umbraco.Web.Models.ContentEditing } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs index 12afceea05..0c26332fa6 100644 --- a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs @@ -62,12 +62,12 @@ namespace Umbraco.Web.Models.ContentEditing /// [DataMember(Name = "path")] public string Path { get; set; } - + /// /// A collection of extra data that is available for this specific entity/entity type /// [DataMember(Name = "metaData")] [ReadOnly(true)] - public IDictionary AdditionalData { get; private set; } + public IDictionary AdditionalData { get; private set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs b/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs index 61d61f2108..38442f6da5 100644 --- a/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs +++ b/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Web.Models.ContentEditing +namespace Umbraco.Web.Models.ContentEditing { public class GetAvailableCompositionsFilter { @@ -17,4 +17,4 @@ namespace Umbraco.Web.Models.ContentEditing /// public string[] FilterContentTypes { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/IErrorModel.cs b/src/Umbraco.Web/Models/ContentEditing/IErrorModel.cs index 9e267aac33..1388b4dd05 100644 --- a/src/Umbraco.Web/Models/ContentEditing/IErrorModel.cs +++ b/src/Umbraco.Web/Models/ContentEditing/IErrorModel.cs @@ -14,4 +14,4 @@ namespace Umbraco.Web.Models.ContentEditing /// IDictionary Errors { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/IHaveUploadedFiles.cs b/src/Umbraco.Web/Models/ContentEditing/IHaveUploadedFiles.cs index 8366c9b8c6..b66e78cb00 100644 --- a/src/Umbraco.Web/Models/ContentEditing/IHaveUploadedFiles.cs +++ b/src/Umbraco.Web/Models/ContentEditing/IHaveUploadedFiles.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; +using Umbraco.Core.Models.Editors; namespace Umbraco.Web.Models.ContentEditing { public interface IHaveUploadedFiles { - List UploadedFiles { get; } + List UploadedFiles { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/INotificationModel.cs b/src/Umbraco.Web/Models/ContentEditing/INotificationModel.cs index 401ced3c91..20b5c0c4e6 100644 --- a/src/Umbraco.Web/Models/ContentEditing/INotificationModel.cs +++ b/src/Umbraco.Web/Models/ContentEditing/INotificationModel.cs @@ -11,4 +11,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "notifications")] List Notifications { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/InstalledPackageModel.cs b/src/Umbraco.Web/Models/ContentEditing/InstalledPackageModel.cs index 3b1bf69faf..9161f972bd 100644 --- a/src/Umbraco.Web/Models/ContentEditing/InstalledPackageModel.cs +++ b/src/Umbraco.Web/Models/ContentEditing/InstalledPackageModel.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing @@ -33,4 +33,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "iconUrl")] public string IconUrl { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/Language.cs b/src/Umbraco.Web/Models/ContentEditing/Language.cs new file mode 100644 index 0000000000..f78d2bd28f --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/Language.cs @@ -0,0 +1,28 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "language", Namespace = "")] + public class Language + { + [DataMember(Name = "id")] + public int Id { get; set; } + + [DataMember(Name = "culture", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string IsoCode { get; set; } + + [DataMember(Name = "name")] + [ReadOnly(true)] + public string Name { get; set; } + + [DataMember(Name = "isDefault")] + public bool IsDefaultVariantLanguage { get; set; } + + [DataMember(Name = "isMandatory")] + public bool Mandatory { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs b/src/Umbraco.Web/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs index f0cb59cbba..0982389a4c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing @@ -22,10 +22,10 @@ namespace Umbraco.Web.Models.ContentEditing /// Property for the entity's individual tree node URL /// /// - /// This is required if the item is a child of a list view since the tree won't actually be loaded, + /// This is required if the item is a child of a list view since the tree won't actually be loaded, /// so the app will need to go fetch the individual tree node in order to be able to load it's action list (menu) /// [DataMember(Name = "treeNodeUrl")] public string TreeNodeUrl { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MacroParameter.cs b/src/Umbraco.Web/Models/ContentEditing/MacroParameter.cs index 2ca9d0fa90..96ece40a0f 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MacroParameter.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MacroParameter.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } - + /// /// The editor view to render for this parameter /// diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs index b383dbb224..069ae25547 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.Models.ContentEditing { /// /// A model representing a content item to be displayed in the back office - /// + /// [DataContract(Name = "content", Namespace = "")] public class MediaItemDisplay : ListViewAwareContentItemDisplayBase { @@ -16,4 +16,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "mediaLink")] public string MediaLink { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaItemSave.cs b/src/Umbraco.Web/Models/ContentEditing/MediaItemSave.cs index 1a14b4b0fa..334c0d1ddd 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MediaItemSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MediaItemSave.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing @@ -11,4 +11,4 @@ namespace Umbraco.Web.Models.ContentEditing { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MediaTypeDisplay.cs index 7063613d66..40227184db 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MediaTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MediaTypeDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { @@ -7,4 +7,4 @@ namespace Umbraco.Web.Models.ContentEditing { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/MediaTypeSave.cs index e20eb23125..3e3a64740b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MediaTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MediaTypeSave.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { @@ -9,4 +9,4 @@ namespace Umbraco.Web.Models.ContentEditing public class MediaTypeSave : ContentTypeSave { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberBasic.cs b/src/Umbraco.Web/Models/ContentEditing/MemberBasic.cs index a85aa8c89f..ceb16ed748 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberBasic.cs @@ -14,4 +14,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "email")] public string Email { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs index 77eec943fc..729b1b119c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs @@ -28,11 +28,11 @@ namespace Umbraco.Web.Models.ContentEditing /// /// This is used to indicate how to map the membership provider properties to the save model, this mapping - /// will change if a developer has opted to have custom member property aliases specified in their membership provider config, + /// will change if a developer has opted to have custom member property aliases specified in their membership provider config, /// or if we are editing a member that is not an Umbraco member (custom provider) /// [DataMember(Name = "fieldConfig")] public IDictionary MemberProviderFieldMapping { get; set; } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberGroupDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberGroupDisplay.cs new file mode 100644 index 0000000000..6453fdb7a4 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/MemberGroupDisplay.cs @@ -0,0 +1,21 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + using System.Collections.Generic; + + [DataContract(Name = "memberGroup", Namespace = "")] + public class MemberGroupDisplay : EntityBasic, INotificationModel + { + public MemberGroupDisplay() + { + Notifications = new List(); + } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberGroupSave.cs b/src/Umbraco.Web/Models/ContentEditing/MemberGroupSave.cs new file mode 100644 index 0000000000..ed5c8de40f --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/MemberGroupSave.cs @@ -0,0 +1,9 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "memberGroup", Namespace = "")] + public class MemberGroupSave : EntityBasic + { + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberListDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberListDisplay.cs index 749035d1d4..c46d5d71fb 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberListDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberListDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing @@ -10,4 +10,4 @@ namespace Umbraco.Web.Models.ContentEditing public class MemberListDisplay : ContentItemDisplayBase { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeBasic.cs index dfe98461dd..f3fe7a262c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeBasic.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { @@ -13,5 +13,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "memberCanEdit")] public bool MemberCanEditProperty { get; set; } + + [DataMember(Name = "isSensitiveData")] + public bool IsSensitiveData { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeDisplay.cs index 8f7d4be310..e5612f5247 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { @@ -10,5 +10,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "memberCanEdit")] public bool MemberCanEditProperty { get; set; } + + [DataMember(Name = "isSensitiveData")] + public bool IsSensitiveData { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberSave.cs b/src/Umbraco.Web/Models/ContentEditing/MemberSave.cs index fac73e802d..3b1f3cc048 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberSave.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Models.ContentEditing /// public class MemberSave : ContentBaseItemSave { - + [DataMember(Name = "username", IsRequired = true)] [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] public string Username { get; set; } @@ -20,13 +20,13 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "email", IsRequired = true)] [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] public string Email { get; set; } - + [DataMember(Name = "password")] public ChangingPasswordModel Password { get; set; } - + [DataMember(Name = "memberGroups")] public IEnumerable Groups { get; set; } - + [DataMember(Name = "comments")] public string Comments { get; set; } @@ -36,6 +36,6 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "isApproved")] public bool IsApproved { get; set; } - //TODO: Need to add question / answer support + //TODO: Need to add question / answer support } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberTypeDisplay.cs index e6092a10f4..26d06c5d1c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberTypeDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { @@ -7,4 +7,4 @@ namespace Umbraco.Web.Models.ContentEditing { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/MemberTypeSave.cs index 98bfbf0fce..be3d294714 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberTypeSave.cs @@ -1,10 +1,10 @@ -namespace Umbraco.Web.Models.ContentEditing +namespace Umbraco.Web.Models.ContentEditing { /// /// Model used to save a member type /// public class MemberTypeSave : ContentTypeSave { - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs b/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs index d981ee58dc..14163d73f0 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs @@ -54,4 +54,4 @@ namespace Umbraco.Web.Models.ContentEditing }); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ModelWithNotifications.cs b/src/Umbraco.Web/Models/ContentEditing/ModelWithNotifications.cs index 4d34962f20..20bfb4947d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ModelWithNotifications.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ModelWithNotifications.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing @@ -28,4 +28,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "notifications")] public List Notifications { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs b/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs index a387876a45..dcd4a4f037 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MoveOrCopy.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; diff --git a/src/Umbraco.Web/Models/ContentEditing/Notification.cs b/src/Umbraco.Web/Models/ContentEditing/Notification.cs index 0f042b6cbc..0305348c7c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/Notification.cs +++ b/src/Umbraco.Web/Models/ContentEditing/Notification.cs @@ -8,9 +8,9 @@ namespace Umbraco.Web.Models.ContentEditing { public Notification() { - + } - + public Notification(string header, string message, SpeechBubbleIcon notificationType) { Header = header; @@ -25,4 +25,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "type")] public SpeechBubbleIcon NotificationType { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/Permission.cs b/src/Umbraco.Web/Models/ContentEditing/Permission.cs index 9c40dc0aa6..7b444e5959 100644 --- a/src/Umbraco.Web/Models/ContentEditing/Permission.cs +++ b/src/Umbraco.Web/Models/ContentEditing/Permission.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing @@ -35,4 +35,4 @@ namespace Umbraco.Web.Models.ContentEditing return this.MemberwiseClone(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs b/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs index 7793787670..4f3e5dbb18 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs @@ -1,10 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; +using Umbraco.Core.Models.Editors; namespace Umbraco.Web.Models.ContentEditing { /// - /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the + /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the /// temporary files that were created. /// [DataContract] @@ -12,12 +13,12 @@ namespace Umbraco.Web.Models.ContentEditing { public PostedFiles() { - UploadedFiles = new List(); + UploadedFiles = new List(); Notifications = new List(); } - public List UploadedFiles { get; private set; } + public List UploadedFiles { get; private set; } [DataMember(Name = "notifications")] public List Notifications { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs deleted file mode 100644 index 6879701bde..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// Defines a pre value editable field for a data type - /// - [DataContract(Name = "preValue", Namespace = "")] - public class PreValueFieldDisplay : PreValueFieldSave - { - - /// - /// The name to display for this pre-value field - /// - [DataMember(Name = "label", IsRequired = true)] - public string Name { get; set; } - - /// - /// The description to display for this pre-value field - /// - [DataMember(Name = "description")] - public string Description { get; set; } - - /// - /// Specifies whether to hide the label for the pre-value - /// - [DataMember(Name = "hideLabel")] - public bool HideLabel { get; set; } - - /// - /// The view to render for the field - /// - [DataMember(Name = "view", IsRequired = true)] - public string View { get; set; } - - /// - /// This allows for custom configuration to be injected into the pre-value editor - /// - [DataMember(Name = "config")] - public IDictionary Config { get; set; } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldSave.cs b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldSave.cs deleted file mode 100644 index 3f3c018ef6..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldSave.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Runtime.Serialization; - -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// Defines a pre value editable field for a data type - /// - [DataContract(Name = "preValue", Namespace = "")] - public class PreValueFieldSave - { - /// - /// The key to store the pre-value against - /// - [DataMember(Name = "key", IsRequired = true)] - public string Key { get; set; } - - /// - /// The value stored for the pre-value field - /// - [DataMember(Name = "value", IsRequired = true)] - public object Value { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyEditorBasic.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyEditorBasic.cs index 50b8641b19..a369eadf34 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyEditorBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyEditorBasic.cs @@ -18,4 +18,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "icon")] public string Icon { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs index 6bfe1dc76e..a516dbd1d5 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; @@ -16,7 +16,7 @@ namespace Umbraco.Web.Models.ContentEditing /// Gets a value indicating whether this tab is the generic properties tab. /// [IgnoreDataMember] - public bool IsGenericProperties { get { return Id == GenericPropertiesGroupId; } } + public bool IsGenericProperties => Id == GenericPropertiesGroupId; /// /// Gets a value indicating whether the property group is inherited through @@ -49,11 +49,7 @@ namespace Umbraco.Web.Models.ContentEditing Properties = new List(); } - - [DataMember(Name = "properties")] public IEnumerable Properties { get; set; } - - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs index c31c93b206..1523daacda 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs @@ -5,7 +5,7 @@ using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { [DataContract(Name = "propertyGroup", Namespace = "")] - public class PropertyGroupDisplay : PropertyGroupBasic + public class PropertyGroupDisplay : PropertyGroupBasic where TPropertyTypeDisplay : PropertyTypeDisplay { public PropertyGroupDisplay() diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs index 107ae287fa..b6f678068e 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs @@ -47,4 +47,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "groupId")] public int GroupId { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeDisplay.cs index 4ee7c3eced..d3878bfeba 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeDisplay.cs @@ -10,7 +10,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "editor")] [ReadOnly(true)] public string Editor { get; set; } - + [DataMember(Name = "view")] [ReadOnly(true)] public string View { get; set; } @@ -18,7 +18,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "config")] [ReadOnly(true)] public IDictionary Config { get; set; } - + /// /// Gets a value indicating whether this property should be locked when editing. /// @@ -29,9 +29,9 @@ namespace Umbraco.Web.Models.ContentEditing public bool Locked { get; set; } /// - /// This is required for the UI editor to know if this particular property belongs to + /// This is required for the UI editor to know if this particular property belongs to /// an inherited item or the current item. - /// + /// [DataMember(Name = "contentTypeId")] [ReadOnly(true)] public int ContentTypeId { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs index 31f1c4de84..929c7ce324 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs @@ -14,4 +14,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "pattern")] public string Pattern { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/RedirectUrlSearchResults.cs b/src/Umbraco.Web/Models/ContentEditing/RedirectUrlSearchResults.cs index 6a64b0dbc4..8833bfdbf6 100644 --- a/src/Umbraco.Web/Models/ContentEditing/RedirectUrlSearchResults.cs +++ b/src/Umbraco.Web/Models/ContentEditing/RedirectUrlSearchResults.cs @@ -9,7 +9,7 @@ namespace Umbraco.Web.Models.ContentEditing { [DataMember(Name = "searchResults")] public IEnumerable SearchResults { get; set; } - + [DataMember(Name = "totalCount")] public long TotalCount { get; set; } @@ -17,6 +17,6 @@ namespace Umbraco.Web.Models.ContentEditing public int PageCount { get; set; } [DataMember(Name = "currentPage")] - public int CurrentPage { get; set; } + public int CurrentPage { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/RelationType.cs b/src/Umbraco.Web/Models/ContentEditing/RelationType.cs index 8c29343319..129376da5c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/RelationType.cs +++ b/src/Umbraco.Web/Models/ContentEditing/RelationType.cs @@ -39,4 +39,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "childObjectType", IsRequired = true)] public Guid ChildObjectType { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/RichTextEditorCommand.cs b/src/Umbraco.Web/Models/ContentEditing/RichTextEditorCommand.cs index fb2a95c2a4..1780d00bba 100644 --- a/src/Umbraco.Web/Models/ContentEditing/RichTextEditorCommand.cs +++ b/src/Umbraco.Web/Models/ContentEditing/RichTextEditorCommand.cs @@ -18,22 +18,22 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "command")] public string Command { get; set; } - + [DataMember(Name = "alias")] public string Alias { get; set; } - + [DataMember(Name = "userInterface")] public string UserInterface { get; set; } - + [DataMember(Name = "frontEndCommand")] public string FrontEndCommand { get; set; } - + [DataMember(Name = "value")] public string Value { get; set; } - + [DataMember(Name = "priority")] public int Priority { get; set; } - + [DataMember(Name = "isStylePicker")] public bool IsStylePicker { get; set; } } diff --git a/src/Umbraco.Web/Models/ContentEditing/SearchResultItem.cs b/src/Umbraco.Web/Models/ContentEditing/SearchResultItem.cs index 495c5d0c30..c82838d9d3 100644 --- a/src/Umbraco.Web/Models/ContentEditing/SearchResultItem.cs +++ b/src/Umbraco.Web/Models/ContentEditing/SearchResultItem.cs @@ -4,12 +4,12 @@ namespace Umbraco.Web.Models.ContentEditing { [DataContract(Name = "searchResult", Namespace = "")] public class SearchResultItem : EntityBasic - { + { /// /// The score of the search result /// [DataMember(Name = "score")] public float Score { get; set; } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/Section.cs b/src/Umbraco.Web/Models/ContentEditing/Section.cs index 06ece10ddf..555b4a7cc1 100644 --- a/src/Umbraco.Web/Models/ContentEditing/Section.cs +++ b/src/Umbraco.Web/Models/ContentEditing/Section.cs @@ -1,20 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { - /// /// Represents a section (application) in the back office /// [DataContract(Name = "section", Namespace = "")] public class Section { - [DataMember(Name = "name")] public string Name { get; set; } @@ -23,13 +16,12 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "alias")] public string Alias { get; set; } - + /// /// In some cases a custom route path can be specified so that when clicking on a section it goes to this /// path instead of the normal dashboard path /// [DataMember(Name = "routePath")] - public string RoutePath { get; set; } - + public string RoutePath { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/SimpleNotificationModel.cs b/src/Umbraco.Web/Models/ContentEditing/SimpleNotificationModel.cs index 83b00662c4..5394636bbf 100644 --- a/src/Umbraco.Web/Models/ContentEditing/SimpleNotificationModel.cs +++ b/src/Umbraco.Web/Models/ContentEditing/SimpleNotificationModel.cs @@ -23,9 +23,9 @@ namespace Umbraco.Web.Models.ContentEditing public List Notifications { get; private set; } /// - /// A default msg + /// A default msg /// [DataMember(Name = "message")] public string Message { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/StyleSheet.cs b/src/Umbraco.Web/Models/ContentEditing/StyleSheet.cs index 6162aebfd7..809aa7185f 100644 --- a/src/Umbraco.Web/Models/ContentEditing/StyleSheet.cs +++ b/src/Umbraco.Web/Models/ContentEditing/StyleSheet.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Models.ContentEditing { [DataMember(Name="name")] public string Name { get; set; } - + [DataMember(Name = "path")] public string Path { get; set; } } diff --git a/src/Umbraco.Web/Models/ContentEditing/Tab.cs b/src/Umbraco.Web/Models/ContentEditing/Tab.cs index dd1c73b418..536fe31182 100644 --- a/src/Umbraco.Web/Models/ContentEditing/Tab.cs +++ b/src/Umbraco.Web/Models/ContentEditing/Tab.cs @@ -24,4 +24,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "properties")] public IEnumerable Properties { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs b/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs index 13c52d2a3a..5447d15e7b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs +++ b/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs @@ -7,21 +7,31 @@ using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing { - public abstract class TabbedContentItem : ContentItemBasic - where T : ContentPropertyBasic + public abstract class TabbedContentItem : ContentItemBasic + where T : ContentPropertyBasic where TPersisted : IContentBase { protected TabbedContentItem() { Tabs = new List>(); - } - + } + /// /// Defines the tabs containing display properties /// [DataMember(Name = "tabs")] public IEnumerable> Tabs { get; set; } + // note + // once a [DataContract] has been defined on a class, with a [DataMember] property, + // one simply cannot ignore that property anymore - [IgnoreDataMember] on an overriden + // property is ignored, and 'newing' the property means that it's the base property + // which is used + // + // OTOH, Json.NET is happy having [JsonIgnore] on overrides, even though the base + // property is [JsonProperty]. so, forcing [JsonIgnore] here, but really, we should + // rething the whole thing. + /// /// Override the properties property to ensure we don't serialize this /// and to simply return the properties based on the properties in the tabs collection @@ -30,10 +40,11 @@ namespace Umbraco.Web.Models.ContentEditing /// This property cannot be set /// [IgnoreDataMember] + [JsonIgnore] // see note above on IgnoreDataMember vs JsonIgnore public override IEnumerable Properties { - get { return Tabs.SelectMany(x => x.Properties); } - set { throw new NotImplementedException(); } + get => Tabs.SelectMany(x => x.Properties); + set => throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/TreeSearchResult.cs b/src/Umbraco.Web/Models/ContentEditing/TreeSearchResult.cs index 1da9d61c98..964ed62c88 100644 --- a/src/Umbraco.Web/Models/ContentEditing/TreeSearchResult.cs +++ b/src/Umbraco.Web/Models/ContentEditing/TreeSearchResult.cs @@ -17,8 +17,8 @@ namespace Umbraco.Web.Models.ContentEditing public string TreeAlias { get; set; } /// - /// This is optional but if specified should be the name of an angular service to format the search result. - /// + /// This is optional but if specified should be the name of an angular service to format the search result. + /// [DataMember(Name = "jsSvc")] public string JsFormatterService { get; set; } @@ -32,4 +32,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "results")] public IEnumerable Results { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UmbracoEntityTypes.cs b/src/Umbraco.Web/Models/ContentEditing/UmbracoEntityTypes.cs index c68ee3c655..5f4414d919 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UmbracoEntityTypes.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UmbracoEntityTypes.cs @@ -7,7 +7,7 @@ namespace Umbraco.Web.Models.ContentEditing /// Represents the type's of Umbraco entities that can be resolved from the EntityController /// public enum UmbracoEntityTypes - { + { /// /// Domain /// @@ -69,10 +69,10 @@ namespace Umbraco.Web.Models.ContentEditing /// Document Type /// DocumentType, - + /// /// Stylesheet - /// + /// Stylesheet, /// @@ -95,4 +95,4 @@ namespace Umbraco.Web.Models.ContentEditing /// PropertyGroup } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs b/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs index 0b4a7e02d7..581df571ab 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs @@ -65,4 +65,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "notifications")] public List Notifications { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs index 5acad7ea49..dc240bfb78 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs @@ -29,25 +29,25 @@ namespace Umbraco.Web.Models.ContentEditing [Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")] [EditorBrowsable(EditorBrowsableState.Never)] [ReadOnly(true)] - [DataMember(Name = "userType")] + [DataMember(Name = "userType")] public string UserType { get; set; } [ReadOnly(true)] - [DataMember(Name = "userGroups")] - public string[] UserGroups { get; set; } + [DataMember(Name = "userGroups")] + public string[] UserGroups { get; set; } /// /// Gets/sets the number of seconds for the user's auth ticket to expire /// [DataMember(Name = "remainingAuthSeconds")] public double SecondsUntilTimeout { get; set; } - + /// /// The user's calculated start nodes based on the start nodes they have assigned directly to them and via the groups they're assigned to /// [DataMember(Name = "startContentIds")] public int[] StartContentIds { get; set; } - + /// /// The user's calculated start nodes based on the start nodes they have assigned directly to them and via the groups they're assigned to /// @@ -58,14 +58,12 @@ namespace Umbraco.Web.Models.ContentEditing /// Returns a list of different size avatars /// [DataMember(Name = "avatars")] - public string[] Avatars { get; set; } + public string[] Avatars { get; set; } /// /// A list of sections the user is allowed to view. /// [DataMember(Name = "allowedSections")] - public IEnumerable AllowedSections { get; set; } - - + public IEnumerable AllowedSections { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs index 1464d9580f..35317d8dd5 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Models.ContentEditing /// [DataMember(Name = "availableCultures")] public IDictionary AvailableCultures { get; set; } - + [DataMember(Name = "startContentIds")] public IEnumerable StartContentIds { get; set; } @@ -80,4 +80,4 @@ namespace Umbraco.Web.Models.ContentEditing public DateTime UpdateDate { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupBasic.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupBasic.cs index b52a653497..0457711c6c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserGroupBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupBasic.cs @@ -34,4 +34,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "userCount")] public int UserCount { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs index ea8eaa4b6c..62f1df305b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; @@ -28,4 +28,4 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "assignedPermissions")] public IEnumerable AssignedPermissions { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupPermissionsSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupPermissionsSave.cs index b38a6d75e3..e61f9debfa 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserGroupPermissionsSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupPermissionsSave.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; @@ -27,7 +27,7 @@ namespace Umbraco.Web.Models.ContentEditing /// A dictionary of permissions to assign, the key is the user group id /// [DataMember(Name = "permissions")] - public IDictionary> AssignedPermissions { get; set; } + public IDictionary> AssignedPermissions { get; set; } public IEnumerable Validate(ValidationContext validationContext) { @@ -37,4 +37,4 @@ namespace Umbraco.Web.Models.ContentEditing } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupSave.cs index 872834988d..4172033188 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserGroupSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupSave.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; @@ -21,11 +21,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "action", IsRequired = true)] [Required] public ContentSaveAction Action { get; set; } - + [DataMember(Name = "alias", IsRequired = true)] [Required] public override string Alias { get; set; } - + [DataMember(Name = "sections")] public IEnumerable Sections { get; set; } @@ -74,7 +74,7 @@ namespace Umbraco.Web.Models.ContentEditing yield return new ValidationResult("A permission value cannot be null or empty", new[] { "AssignedPermissions" }); } } - + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs index 368067814d..cb85f0f3db 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; @@ -22,7 +22,7 @@ namespace Umbraco.Web.Models.ContentEditing [EmailAddress] public string Email { get; set; } - [DataMember(Name = "username")] + [DataMember(Name = "username")] public string Username { get; set; } [DataMember(Name = "message")] @@ -37,4 +37,4 @@ namespace Umbraco.Web.Models.ContentEditing yield return new ValidationResult("A username cannot be empty", new[] { "Username" }); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UserProfile.cs b/src/Umbraco.Web/Models/ContentEditing/UserProfile.cs index eca28e1408..12b6e2a627 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserProfile.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserProfile.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "id", IsRequired = true)] [Required] public int UserId { get; set; } - + [DataMember(Name = "name", IsRequired = true)] [Required] public string Name { get; set; } @@ -25,4 +25,4 @@ namespace Umbraco.Web.Models.ContentEditing return String.Compare(Name, ((UserProfile)obj).Name, StringComparison.Ordinal); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs index 1b6a76ae99..2533ebb105 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; @@ -44,7 +44,7 @@ namespace Umbraco.Web.Models.ContentEditing public int[] StartContentIds { get; set; } [DataMember(Name = "startMediaIds")] - public int[] StartMediaIds { get; set; } + public int[] StartMediaIds { get; set; } public IEnumerable Validate(ValidationContext validationContext) { @@ -52,4 +52,4 @@ namespace Umbraco.Web.Models.ContentEditing yield return new ValidationResult("A user must be assigned to at least one group", new[] { "UserGroups" }); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentExtensions.cs b/src/Umbraco.Web/Models/ContentExtensions.cs index d1f153125e..befada9020 100644 --- a/src/Umbraco.Web/Models/ContentExtensions.cs +++ b/src/Umbraco.Web/Models/ContentExtensions.cs @@ -1,16 +1,40 @@ using System; using System.Globalization; using System.Linq; -using umbraco.cms.businesslogic.web; -using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Routing; +using Domain = Umbraco.Web.Routing.Domain; namespace Umbraco.Web.Models { public static class ContentExtensions { + /// + /// Returns true if the content has any property type that allows language variants + /// + /// + /// + public static bool HasLanguageVariantPropertyType(this IContent content) + { + return content.PropertyTypes.Any(x => x.Variations == ContentVariation.CultureNeutral); + } + + /// + /// Returns true if the content has a variation for the language/segment combination + /// + /// + /// + /// + /// + public static bool HasVariation(this IContent content, int langId, string segment = null) + { + //TODO: Wire up with new APIs + return false; + //return content.Languages.FirstOrDefault(x => x == langId); + } + /// /// Gets the culture that would be selected to render a specified content, /// within the context of a specified current request. @@ -21,9 +45,9 @@ namespace Umbraco.Web.Models public static CultureInfo GetCulture(this IContent content, Uri current = null) { return GetCulture(UmbracoContext.Current, - ApplicationContext.Current.Services.DomainService, - ApplicationContext.Current.Services.LocalizationService, - ApplicationContext.Current.Services.ContentService, + Current.Services.DomainService, + Current.Services.LocalizationService, + Current.Services.ContentService, content.Id, content.Path, current); } @@ -48,8 +72,11 @@ namespace Umbraco.Web.Models ? null // for tests only : umbracoContext.ContentCache.GetRouteById(contentId); // may be cached - var domainHelper = new DomainHelper(domainService); - IDomain domain; + var domainCache = umbracoContext == null + ? new PublishedCache.XmlPublishedCache.DomainCache(domainService) // for tests only + : umbracoContext.PublishedShapshot.Domains; // default + var domainHelper = new DomainHelper(domainCache); + Domain domain; if (route == null) { @@ -63,11 +90,11 @@ namespace Umbraco.Web.Models var hasDomain = domainHelper.NodeHasDomains(content.Id); while (hasDomain == false && content != null) { - content = content.Parent(); + content = content.Parent(contentService); hasDomain = content != null && domainHelper.NodeHasDomains(content.Id); } - domain = hasDomain ? domainHelper.DomainForNode(content.Id, current).UmbracoDomain : null; + domain = hasDomain ? domainHelper.DomainForNode(content.Id, current) : null; } else { @@ -77,14 +104,14 @@ namespace Umbraco.Web.Models var pos = route.IndexOf('/'); domain = pos == 0 ? null - : domainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current).UmbracoDomain; + : domainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current); } - var rootContentId = domain == null ? -1 : domain.RootContentId; - var wcDomain = DomainHelper.FindWildcardDomainInPath(domainService.GetAll(true), contentPath, rootContentId); + var rootContentId = domain == null ? -1 : domain.ContentId; + var wcDomain = DomainHelper.FindWildcardDomainInPath(domainCache.GetAll(true), contentPath, rootContentId); - if (wcDomain != null) return new CultureInfo(wcDomain.LanguageIsoCode); - if (domain != null) return new CultureInfo(domain.LanguageIsoCode); + if (wcDomain != null) return wcDomain.Culture; + if (domain != null) return domain.Culture; return GetDefaultCulture(localizationService); } diff --git a/src/Umbraco.Web/Models/ContentModel.cs b/src/Umbraco.Web/Models/ContentModel.cs new file mode 100644 index 0000000000..9e4a9a3024 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentModel.cs @@ -0,0 +1,26 @@ +using System; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Models +{ + /// + /// Represents the model for the current Umbraco view. + /// + public class ContentModel : IContentModel + { + /// + /// Initializes a new instance of the class with a content. + /// + /// + public ContentModel(IPublishedContent content) + { + if (content == null) throw new ArgumentNullException(nameof(content)); + Content = content; + } + + /// + /// Gets the content. + /// + public IPublishedContent Content { get; } + } +} diff --git a/src/Umbraco.Web/Models/ContentModelOfTContent.cs b/src/Umbraco.Web/Models/ContentModelOfTContent.cs new file mode 100644 index 0000000000..bf4d81dbf3 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentModelOfTContent.cs @@ -0,0 +1,23 @@ +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Models +{ + public class ContentModel : ContentModel + where TContent : IPublishedContent + { + /// + /// Initializes a new instance of the class with a content. + /// + /// + public ContentModel(TContent content) + : base(content) + { + Content = content; + } + + /// + /// Gets the content. + /// + public new TContent Content { get; } + } +} diff --git a/src/Umbraco.Web/Models/DetachedContent.cs b/src/Umbraco.Web/Models/DetachedContent.cs deleted file mode 100644 index df4effa078..0000000000 --- a/src/Umbraco.Web/Models/DetachedContent.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Web.Models -{ - internal class DetachedContent - { - private readonly Dictionary _properties; - - /// - /// Initialized a new instance of the class with properties. - /// - /// The properties - /// Properties must be detached or nested properties ie their property type must be detached or nested. - /// Such a detached content can be part of a published property value. - public DetachedContent(IEnumerable properties) - { - _properties = properties.ToDictionary(x => x.PropertyTypeAlias, x => x, StringComparer.InvariantCultureIgnoreCase); - } - - // don't uncomment until you know what you are doing - /* - public DetachedContent(IPublishedContent content) - { - _properties = content.Properties.ToDictionary(x => x.PropertyTypeAlias, x => x, StringComparer.InvariantCultureIgnoreCase); - } - */ - - // don't uncomment until you know what you are doing - // at the moment, I don't fully - /* - public DetachedContent(IContent content, bool isPreviewing) - { - var publishedContentType = PublishedContentType.Get(PublishedItemType.Content, content.ContentType.Alias); - _properties = PublishedProperty.MapProperties(publishedContentType.PropertyTypes, content.Properties, - (t, v) => PublishedProperty.GetDetached(t.Detached(), v, isPreviewing)) - .ToDictionary(x => x.PropertyTypeAlias, x => x, StringComparer.InvariantCultureIgnoreCase); - } - - public DetachedContent(IMedia media, bool isPreviewing) - { - var publishedContentType = PublishedContentType.Get(PublishedItemType.Media, media.ContentType.Alias); - _properties = PublishedProperty.MapProperties(publishedContentType.PropertyTypes, media.Properties, - (t, v) => PublishedProperty.GetDetached(t.Detached(), v, isPreviewing)) - .ToDictionary(x => x.PropertyTypeAlias, x => x, StringComparer.InvariantCultureIgnoreCase); - } - - public DetachedContent(IMember member, bool isPreviewing) - { - var publishedContentType = PublishedContentType.Get(PublishedItemType.Member, member.ContentType.Alias); - _properties = PublishedProperty.MapProperties(publishedContentType.PropertyTypes, member.Properties, - (t, v) => PublishedProperty.GetDetached(t.Detached(), v, isPreviewing)) - .ToDictionary(x => x.PropertyTypeAlias, x => x, StringComparer.InvariantCultureIgnoreCase); - } - */ - - public ICollection Properties - { - get { return _properties.Values; } - } - - public IPublishedProperty GetProperty(string alias) - { - IPublishedProperty property; - return _properties.TryGetValue(alias, out property) ? property : null; - } - - public bool HasProperty(string alias) - { - var property = GetProperty(alias); - return property != null; - } - - public bool HasValue(string alias) - { - var property = GetProperty(alias); - return property != null && property.HasValue; - } - - public object GetPropertyValue(string alias) - { - var property = GetProperty(alias); - return property == null ? null : property.Value; - } - - public object GetPropertyValue(string alias, string defaultValue) - { - var property = GetProperty(alias); - return property == null || property.HasValue == false ? defaultValue : property.Value; - } - - public object GetPropertyValue(string alias, object defaultValue) - { - var property = GetProperty(alias); - return property == null || property.HasValue == false ? defaultValue : property.Value; - } - - public T GetPropertyValue(string alias) - { - var property = GetProperty(alias); - if (property == null) return default(T); - return property.GetValue(false, default(T)); - } - - public T GetPropertyValue(string alias, T defaultValue) - { - var property = GetProperty(alias); - if (property == null) return defaultValue; - return property.GetValue(true, defaultValue); - } - } -} diff --git a/src/Umbraco.Web/Models/DetachedPublishedContent.cs b/src/Umbraco.Web/Models/DetachedPublishedContent.cs deleted file mode 100644 index b088d9be3c..0000000000 --- a/src/Umbraco.Web/Models/DetachedPublishedContent.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Web.Models -{ - public class DetachedPublishedContent : PublishedContentWithKeyBase - { - private readonly Guid _key; - private readonly string _name; - private readonly PublishedContentType _contentType; - private readonly IEnumerable _properties; - private readonly int _sortOrder; - private readonly bool _isPreviewing; - private readonly IPublishedContent _containerNode; - - public DetachedPublishedContent( - Guid key, - string name, - PublishedContentType contentType, - IEnumerable properties, - IPublishedContent containerNode = null, - int sortOrder = 0, - bool isPreviewing = false) - { - _key = key; - _name = name; - _contentType = contentType; - _properties = properties; - _sortOrder = sortOrder; - _isPreviewing = isPreviewing; - _containerNode = containerNode; - } - - public override Guid Key - { - get { return _key; } - } - - public override int Id - { - get { return 0; } - } - - public override string Name - { - get { return _name; } - } - - public override bool IsDraft - { - get { return _isPreviewing; } - } - - public override PublishedItemType ItemType - { - get { return PublishedItemType.Content; } - } - - public override PublishedContentType ContentType - { - get { return _contentType; } - } - - public override string DocumentTypeAlias - { - get { return _contentType.Alias; } - } - - public override int DocumentTypeId - { - get { return _contentType.Id; } - } - - public override ICollection Properties - { - get { return _properties.ToArray(); } - } - - public override IPublishedProperty GetProperty(string alias) - { - return _properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); - } - - public override IPublishedProperty GetProperty(string alias, bool recurse) - { - if (recurse) - throw new NotSupportedException(); - - return GetProperty(alias); - } - - public override IPublishedContent Parent - { - get { return null; } - } - - public override IEnumerable Children - { - get { return Enumerable.Empty(); } - } - - public override int TemplateId - { - get { return 0; } - } - - public override int SortOrder - { - get { return _sortOrder; } - } - - public override string UrlName - { - get { return null; } - } - - public override string WriterName - { - get { return _containerNode != null ? _containerNode.WriterName : null; } - } - - public override string CreatorName - { - get { return _containerNode != null ? _containerNode.CreatorName : null; } - } - - public override int WriterId - { - get { return _containerNode != null ? _containerNode.WriterId : 0; } - } - - public override int CreatorId - { - get { return _containerNode != null ? _containerNode.CreatorId : 0; } - } - - public override string Path - { - get { return null; } - } - - public override DateTime CreateDate - { - get { return _containerNode != null ? _containerNode.CreateDate : DateTime.MinValue; } - } - - public override DateTime UpdateDate - { - get { return _containerNode != null ? _containerNode.UpdateDate : DateTime.MinValue; } - } - - public override Guid Version - { - get { return _containerNode != null ? _containerNode.Version : Guid.Empty; } - } - - public override int Level - { - get { return 0; } - } - } -} diff --git a/src/Umbraco.Web/Models/DetachedPublishedProperty.cs b/src/Umbraco.Web/Models/DetachedPublishedProperty.cs deleted file mode 100644 index 4a76942dd6..0000000000 --- a/src/Umbraco.Web/Models/DetachedPublishedProperty.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Web.Models -{ - internal class DetachedPublishedProperty : IPublishedProperty - { - private readonly PublishedPropertyType _propertyType; - private readonly object _rawValue; - private readonly Lazy _sourceValue; - private readonly Lazy _objectValue; - private readonly Lazy _xpathValue; - private readonly bool _isPreview; - - public DetachedPublishedProperty(PublishedPropertyType propertyType, object value) - : this(propertyType, value, false) - { - } - - public DetachedPublishedProperty(PublishedPropertyType propertyType, object value, bool isPreview) - { - _propertyType = propertyType; - _isPreview = isPreview; - - _rawValue = value; - - _sourceValue = new Lazy(() => _propertyType.ConvertDataToSource(_rawValue, _isPreview)); - _objectValue = new Lazy(() => _propertyType.ConvertSourceToObject(_sourceValue.Value, _isPreview)); - _xpathValue = new Lazy(() => _propertyType.ConvertSourceToXPath(_sourceValue.Value, _isPreview)); - } - - public string PropertyTypeAlias - { - get - { - return _propertyType.PropertyTypeAlias; - } - } - - public bool HasValue - { - get { return DataValue != null && DataValue.ToString().Trim().Length > 0; } - } - - public object DataValue { get { return _rawValue; } } - - public object Value { get { return _objectValue.Value; } } - - public object XPathValue { get { return _xpathValue.Value; } } - } -} diff --git a/src/Umbraco.Web/Models/DynamicPublishedContent.cs b/src/Umbraco.Web/Models/DynamicPublishedContent.cs deleted file mode 100644 index e776865fb0..0000000000 --- a/src/Umbraco.Web/Models/DynamicPublishedContent.cs +++ /dev/null @@ -1,1318 +0,0 @@ -// ENABLE THE FIX in 7.0.0 -// TODO if all goes well, remove the obsolete code eventually -#define FIX_GET_PROPERTY_VALUE - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Dynamic; -using System.Linq; -using System.Web; -using Umbraco.Core.Cache; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Models; -using Umbraco.Core; -using System.Reflection; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Web.Models -{ - - /// - /// The base dynamic model for views - /// - [Obsolete("The use of dynamics has been deprecated, use strongly typed syntax instead, dynamics will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] - public class DynamicPublishedContent : DynamicObject, IPublishedContent - { - protected internal IPublishedContent PublishedContent { get; private set; } - private DynamicPublishedContentList _contentList; - - // must implement that one if we implement IPublishedContent - public IEnumerable ContentSet - { - // that is a definitively non-efficient way of doing it, though it should work - get { return _contentList ?? (_contentList = new DynamicPublishedContentList(PublishedContent.ContentSet)); } - } - - public PublishedContentType ContentType { get { return PublishedContent.ContentType; } } - - #region Constructors - - public DynamicPublishedContent(IPublishedContent content) - { - if (content == null) throw new ArgumentNullException("content"); - PublishedContent = content; - } - - internal DynamicPublishedContent(IPublishedContent content, DynamicPublishedContentList contentList) - { - PublishedContent = content; - _contentList = contentList; - } - - #endregion - - // these two here have leaked in v6 and so we cannot remove them anymore - // without breaking compatibility but... TODO: remove them in v7 - - [Obsolete("Will be removing in future versions")] - public DynamicPublishedContentList ChildrenAsList { get { return Children; } } - - [Obsolete("Will be removing in future versions")] - public int parentId { get { return PublishedContent.Parent.Id; } } - - #region DynamicObject - - private readonly ConcurrentDictionary _cachedMemberOutput = new ConcurrentDictionary(); - - /// - /// Attempts to call a method on the dynamic object - /// - /// - /// - /// - /// - public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) - { - var runtimeCache = ApplicationContext.Current != null ? ApplicationContext.Current.ApplicationCache.RuntimeCache : new NullCacheProvider(); - - var attempt = DynamicInstanceHelper.TryInvokeMember(runtimeCache, this, binder, args, new[] - { - typeof(DynamicPublishedContent) - }); - - if (attempt.Success) - { - result = attempt.Result.ObjectResult; - - //need to check the return type and possibly cast if result is from an extension method found - if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod) - { - //we don't need to cast if it is already DynamicPublishedContent - if (attempt.Result.ObjectResult != null && (!(attempt.Result.ObjectResult is DynamicPublishedContent))) - { - if (attempt.Result.ObjectResult is IPublishedContent) - { - result = new DynamicPublishedContent((IPublishedContent)attempt.Result.ObjectResult); - } - else if (attempt.Result.ObjectResult is IEnumerable) - { - result = new DynamicPublishedContentList((IEnumerable)attempt.Result.ObjectResult); - } - else if (attempt.Result.ObjectResult is IEnumerable) - { - result = new DynamicPublishedContentList((IEnumerable)attempt.Result.ObjectResult); - } - } - } - return true; - } - - //this is the result of an extension method execution gone wrong so we return dynamic null - if (attempt.Result != null - && attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod - && attempt.Exception is TargetInvocationException) - { - result = DynamicNull.Null; - return true; - } - - result = null; - return false; - } - - /// - /// Attempts to return a custom member (generally based on a string match) - /// - /// - /// - protected virtual Attempt TryGetCustomMember(GetMemberBinder binder) - { - // as of 4.5 the CLR is case-sensitive which means that the default binder - // will handle those methods only when using the proper casing. So what - // this method does is ensure that any casing is supported. - - if (binder.Name.InvariantEquals("ChildrenAsList") || binder.Name.InvariantEquals("Children")) - { - return Attempt.Succeed(Children); - } - - if (binder.Name.InvariantEquals("parentId")) - { - var parent = ((IPublishedContent) this).Parent; - if (parent == null) - { - throw new InvalidOperationException(string.Format("The node {0} does not have a parent", Id)); - } - return Attempt.Succeed(parent.Id); - } - - return Attempt.Fail(); - } - - /// - /// Attempts to return the children by the document type's alias (for example: CurrentPage.NewsItems where NewsItem is the - /// document type alias) - /// - /// - /// - /// - /// This method will work by both the plural and non-plural alias (i.e. NewsItem and NewsItems) - /// - protected virtual Attempt TryGetChildrenByAlias(GetMemberBinder binder) - { - - var filteredTypeChildren = PublishedContent.Children - .Where(x => x.DocumentTypeAlias.InvariantEquals(binder.Name) || x.DocumentTypeAlias.MakePluralName().InvariantEquals(binder.Name)) - .ToArray(); - if (filteredTypeChildren.Any()) - { - return Attempt.Succeed( - new DynamicPublishedContentList(filteredTypeChildren.Select(x => new DynamicPublishedContent(x)))); - } - return Attempt.Fail(); - } - - /// - /// Attempts to return a member based on the reflected document property - /// - /// - /// - protected virtual Attempt TryGetDocumentProperty(GetMemberBinder binder) - { - var reflectedProperty = GetReflectedProperty(binder.Name); - var result = reflectedProperty != null - ? reflectedProperty.Value - : null; - - return Attempt.If(result != null, result); - } - - /// - /// Attempts to return a member based on a user defined umbraco property - /// - /// - /// - protected virtual Attempt TryGetUserProperty(GetMemberBinder binder) - { - var name = binder.Name; - var recurse = false; - if (name.StartsWith("_")) - { - name = name.Substring(1, name.Length - 1); - recurse = true; - } - - var value = PublishedContent.GetPropertyValue(name, recurse); - return Attempt.SucceedIf(value != null, value); - } - - /// - /// Returns the member match methods in the correct order and is used in the TryGetMember method. - /// - /// - protected virtual IEnumerable>> GetMemberMatchMethods() - { - var memberMatchMethods = new List>> - { - TryGetCustomMember, //match custom members - TryGetUserProperty, //then match custom user defined umbraco properties - TryGetChildrenByAlias, //then try to match children based on doc type alias - TryGetDocumentProperty //then try to match on a reflected document property - }; - return memberMatchMethods; - } - - /// - /// Try to return an object based on the dynamic member accessor - /// - /// - /// - /// - /// - /// TODO: SD: This will alwasy return true so that no exceptions are generated, this is only because this is how the - /// old DynamicNode worked, I'm not sure if this is the correct/expected functionality but I've left it like that. - /// IMO I think this is incorrect and it would be better to throw an exception for something that is not supported! - /// - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - if (binder == null) throw new ArgumentNullException("binder"); - - var name = binder.Name; - - //check the cache first! - if (_cachedMemberOutput.TryGetValue(name, out result)) - { - return true; - } - - //loop through each member match method and execute it. - //If it is successful, cache the result and return it. - foreach (var attempt in GetMemberMatchMethods() - .Select(m => m(binder)) - .Where(attempt => attempt.Success)) - { - result = attempt.Result; - //cache the result so we don't have to re-process the whole thing - _cachedMemberOutput.TryAdd(name, result); - return true; - } - - //if property access, type lookup and member invoke all failed - //at this point, we're going to return null - //instead, we return a DynamicNull - see comments in that file - //this will let things like Model.ChildItem work and return nothing instead of crashing - - //.Where explictly checks for this type - //and will make it false - //which means backwards equality (&& property != true) will pass - //forwwards equality (&& property or && property == true) will fail - result = DynamicNull.Null; - - //alwasy return true if we haven't thrown an exception though I'm wondering if we return 'false' if .Net throws an exception for us?? - return true; - } - - /// - /// Returns a property defined on the document object as a member property using reflection - /// - /// - /// - private PropertyResult GetReflectedProperty(string alias) - { - return GetPropertyInternal(alias, PublishedContent, false); - } - - /// - /// Return a user defined property - /// - /// - /// - /// - internal PropertyResult GetUserProperty(string alias, bool recursive = false) - { - if (!recursive) - { - return GetPropertyInternal(alias, PublishedContent); - } - var context = this; - var prop = GetPropertyInternal(alias, PublishedContent); - - while (prop == null || !prop.HasValue) - { - var parent = ((IPublishedContent) context).Parent; - if (parent == null) break; - - // Update the context before attempting to retrieve the property again. - context = parent.AsDynamicOrNull(); - prop = context.GetPropertyInternal(alias, context.PublishedContent); - } - - return prop; - } - - private PropertyResult GetPropertyInternal(string alias, IPublishedContent content, bool checkUserProperty = true) - { - if (alias.IsNullOrWhiteSpace()) throw new ArgumentNullException("alias"); - if (content == null) throw new ArgumentNullException("content"); - - //if we're looking for a user defined property - if (checkUserProperty) - { - var prop = content.GetProperty(alias); - - // get wrap the result in a PropertyResult - just so it's an IHtmlString - ?! - return prop == null - ? null - : new PropertyResult(prop, PropertyResultType.UserProperty); - } - - //reflect - - // as of 4.5 the CLR is case-sensitive which means that the default binder - // can handle properties only when using the proper casing. So what this - // does is ensure that any casing is supported. - - var attempt = content.GetType().GetMemberIgnoreCase(content, alias); - - return attempt.Success == false || attempt.Result == null - ? null - : new PropertyResult(alias, attempt.Result, PropertyResultType.ReflectedProperty); - } - - #endregion - - #region Explicit IPublishedContent implementation - - IPublishedContent IPublishedContent.Parent - { - get { return PublishedContent.Parent; } - } - - int IPublishedContent.Id - { - get { return PublishedContent.Id; } - } - - int IPublishedContent.TemplateId - { - get { return PublishedContent.TemplateId; } - } - - int IPublishedContent.SortOrder - { - get { return PublishedContent.SortOrder; } - } - - string IPublishedContent.Name - { - get { return PublishedContent.Name; } - } - - string IPublishedContent.UrlName - { - get { return PublishedContent.UrlName; } - } - - string IPublishedContent.DocumentTypeAlias - { - get { return PublishedContent.DocumentTypeAlias; } - } - - int IPublishedContent.DocumentTypeId - { - get { return PublishedContent.DocumentTypeId; } - } - - string IPublishedContent.WriterName - { - get { return PublishedContent.WriterName; } - } - - string IPublishedContent.CreatorName - { - get { return PublishedContent.CreatorName; } - } - - int IPublishedContent.WriterId - { - get { return PublishedContent.WriterId; } - } - - int IPublishedContent.CreatorId - { - get { return PublishedContent.CreatorId; } - } - - string IPublishedContent.Path - { - get { return PublishedContent.Path; } - } - - DateTime IPublishedContent.CreateDate - { - get { return PublishedContent.CreateDate; } - } - - DateTime IPublishedContent.UpdateDate - { - get { return PublishedContent.UpdateDate; } - } - - Guid IPublishedContent.Version - { - get { return PublishedContent.Version; } - } - - int IPublishedContent.Level - { - get { return PublishedContent.Level; } - } - - bool IPublishedContent.IsDraft - { - get { return PublishedContent.IsDraft; } - } - - int IPublishedContent.GetIndex() - { - return PublishedContent.GetIndex(); - } - - ICollection IPublishedContent.Properties - { - get { return PublishedContent.Properties; } - } - - IEnumerable IPublishedContent.Children - { - get { return PublishedContent.Children; } - } - - IPublishedProperty IPublishedContent.GetProperty(string alias) - { - return PublishedContent.GetProperty(alias); - } - - #endregion - - #region IPublishedContent implementation - - public int TemplateId - { - get { return PublishedContent.TemplateId; } - } - - public int SortOrder - { - get { return PublishedContent.SortOrder; } - } - - public string Name - { - get { return PublishedContent.Name; } - } - - public string UrlName - { - get { return PublishedContent.UrlName; } - } - - public string DocumentTypeAlias - { - get { return PublishedContent.DocumentTypeAlias; } - } - - public string WriterName - { - get { return PublishedContent.WriterName; } - } - - public string CreatorName - { - get { return PublishedContent.CreatorName; } - } - - public int WriterId - { - get { return PublishedContent.WriterId; } - } - - public int CreatorId - { - get { return PublishedContent.CreatorId; } - } - - public string Path - { - get { return PublishedContent.Path; } - } - - public DateTime CreateDate - { - get { return PublishedContent.CreateDate; } - } - - public int Id - { - get { return PublishedContent.Id; } - } - - public DateTime UpdateDate - { - get { return PublishedContent.UpdateDate; } - } - - public Guid Version - { - get { return PublishedContent.Version; } - } - - public int Level - { - get { return PublishedContent.Level; } - } - - public string Url - { - get { return PublishedContent.Url; } - } - - public PublishedItemType ItemType - { - get { return PublishedContent.ItemType; } - } - - // see note in IPublishedContent - //public bool Published - //{ - // get { return PublishedContent.Published; } - //} - - public IEnumerable Properties - { - get { return PublishedContent.Properties; } - } - - public object this[string propertyAlias] - { - get { return PublishedContent[propertyAlias]; } - } - - #endregion - - #region GetProperty - - // enhanced versions of the extension methods that exist for IPublishedContent, - // here we support the recursive (_) and reflected (@) syntax - - public IPublishedProperty GetProperty(string alias) - { - return alias.StartsWith("_") - ? GetProperty(alias.Substring(1), true) - : GetProperty(alias, false); - } - - public IPublishedProperty GetProperty(string alias, bool recurse) - { - if (alias.StartsWith("@")) return GetReflectedProperty(alias.Substring(1)); - - // get wrap the result in a PropertyResult - just so it's an IHtmlString - ?! - var property = PublishedContent.GetProperty(alias, recurse); - return property == null ? null : new PropertyResult(property, PropertyResultType.UserProperty); - } - - #endregion - - // IPublishedContent extension methods: - // - // all these methods are IPublishedContent extension methods so they should in - // theory apply to DynamicPublishedContent since it is an IPublishedContent and - // we look for extension methods. But that lookup has to be pretty slow. - // Duplicating the methods here makes things much faster. - - #region IPublishedContent extension methods - Template - - public string GetTemplateAlias() - { - return PublishedContentExtensions.GetTemplateAlias(this); - } - - #endregion - - #region IPublishedContent extension methods - HasProperty - - public bool HasProperty(string name) - { - return PublishedContent.HasProperty(name); - } - - #endregion - - #region IPublishedContent extension methods - HasValue - - public bool HasValue(string alias) - { - return PublishedContent.HasValue(alias); - } - - public bool HasValue(string alias, bool recursive) - { - return PublishedContent.HasValue(alias, recursive); - } - - public IHtmlString HasValue(string alias, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.HasValue(alias, valueIfTrue, valueIfFalse); - } - - public IHtmlString HasValue(string alias, bool recursive, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.HasValue(alias, recursive, valueIfTrue, valueIfFalse); - } - - public IHtmlString HasValue(string alias, string valueIfTrue) - { - return PublishedContent.HasValue(alias, valueIfTrue); - } - - public IHtmlString HasValue(string alias, bool recursive, string valueIfTrue) - { - return PublishedContent.HasValue(alias, recursive, valueIfTrue); - } - - #endregion - - #region IPublishedContent extension methods - GetPropertyValue - - // for whatever reason, some methods returning strings were created in DynamicPublishedContent - // and are now considered a "feature" as of v6. So we can't have the proper GetPropertyValue - // methods returning objects, too. And we don't want to change it in v6 as that would be a - // breaking change. - -#if FIX_GET_PROPERTY_VALUE - - public object GetPropertyValue(string alias) - { - return PublishedContent.GetPropertyValue(alias); - } - - public object GetPropertyValue(string alias, string defaultValue) - { - return PublishedContent.GetPropertyValue(alias, defaultValue); - } - - public object GetPropertyValue(string alias, object defaultValue) - { - return PublishedContent.GetPropertyValue(alias, defaultValue); - } - - public object GetPropertyValue(string alias, bool recurse) - { - return PublishedContent.GetPropertyValue(alias, recurse); - } - - public object GetPropertyValue(string alias, bool recurse, object defaultValue) - { - return PublishedContent.GetPropertyValue(alias, recurse, defaultValue); - } - -#else - - public string GetPropertyValue(string alias) - { - return GetPropertyValue(alias, false); - } - - public string GetPropertyValue(string alias, string defaultValue) - { - var value = GetPropertyValue(alias); - return value.IsNullOrWhiteSpace() ? defaultValue : value; - } - - public string GetPropertyValue(string alias, bool recurse, string defaultValue) - { - var value = GetPropertyValue(alias, recurse); - return value.IsNullOrWhiteSpace() ? defaultValue : value; - } - - public string GetPropertyValue(string alias, bool recursive) - { - var property = GetProperty(alias, recursive); - if (property == null || property.Value == null) return null; - return property.Value.ToString(); - } - -#endif - - #endregion - - #region IPublishedContent extension methods - GetPropertyValue - - public T GetPropertyValue(string alias) - { - return PublishedContent.GetPropertyValue(alias); - } - - public T GetPropertyValue(string alias, T defaultValue) - { - return PublishedContent.GetPropertyValue(alias, defaultValue); - } - - public T GetPropertyValue(string alias, bool recurse) - { - return PublishedContent.GetPropertyValue(alias, recurse); - } - - public T GetPropertyValue(string alias, bool recurse, T defaultValue) - { - return PublishedContent.GetPropertyValue(alias, recurse, defaultValue); - } - - #endregion - - #region IPublishedContent extension methods - Search - - public DynamicPublishedContentList Search(string term, bool useWildCards = true, string searchProvider = null) - { - return new DynamicPublishedContentList(PublishedContent.Search(term, useWildCards, searchProvider)); - } - - public DynamicPublishedContentList SearchDescendants(string term, bool useWildCards = true, string searchProvider = null) - { - return new DynamicPublishedContentList(PublishedContent.SearchDescendants(term, useWildCards, searchProvider)); - } - - public DynamicPublishedContentList SearchChildren(string term, bool useWildCards = true, string searchProvider = null) - { - return new DynamicPublishedContentList(PublishedContent.SearchChildren(term, useWildCards, searchProvider)); - } - - public DynamicPublishedContentList Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) - { - return new DynamicPublishedContentList(PublishedContent.Search(criteria, searchProvider)); - } - - #endregion - - #region IPublishedContent extension methods - AsDynamic - - public dynamic AsDynamic() - { - return this; - } - - public dynamic AsDynamicOrNull() - { - return this; - } - - #endregion - - #region IPublishedContente extension methods - ContentSet - - public int Position() - { - return Index(); - } - - public int Index() - { - return PublishedContent.GetIndex(); - } - - #endregion - - #region IPublishedContent extension methods - IsSomething: misc - - public bool Visible - { - get { return PublishedContent.IsVisible(); } - } - - public bool IsVisible() - { - return PublishedContent.IsVisible(); - } - - public bool IsDocumentType(string docTypeAlias) - { - return PublishedContent.IsDocumentType(docTypeAlias); - } - - public bool IsNull(string alias, bool recursive) - { - return PublishedContent.IsNull(alias, recursive); - } - - public bool IsNull(string alias) - { - return PublishedContent.IsNull(alias, false); - } - - #endregion - - #region IPublishedContent extension methods - IsSomething: position in set - - public bool IsFirst() - { - return PublishedContent.IsFirst(); - } - - public HtmlString IsFirst(string valueIfTrue) - { - return PublishedContent.IsFirst(valueIfTrue); - } - - public HtmlString IsFirst(string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsFirst(valueIfTrue, valueIfFalse); - } - - public bool IsNotFirst() - { - return PublishedContent.IsNotFirst(); - } - - public HtmlString IsNotFirst(string valueIfTrue) - { - return PublishedContent.IsNotFirst(valueIfTrue); - } - - public HtmlString IsNotFirst(string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsNotFirst(valueIfTrue, valueIfFalse); - } - - public bool IsPosition(int index) - { - return PublishedContent.IsPosition(index); - } - - public HtmlString IsPosition(int index, string valueIfTrue) - { - return PublishedContent.IsPosition(index, valueIfTrue); - } - - public HtmlString IsPosition(int index, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsPosition(index, valueIfTrue, valueIfFalse); - } - - public bool IsModZero(int modulus) - { - return PublishedContent.IsModZero(modulus); - } - - public HtmlString IsModZero(int modulus, string valueIfTrue) - { - return PublishedContent.IsModZero(modulus, valueIfTrue); - } - - public HtmlString IsModZero(int modulus, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsModZero(modulus, valueIfTrue, valueIfFalse); - } - - public bool IsNotModZero(int modulus) - { - return PublishedContent.IsNotModZero(modulus); - } - - public HtmlString IsNotModZero(int modulus, string valueIfTrue) - { - return PublishedContent.IsNotModZero(modulus, valueIfTrue); - } - - public HtmlString IsNotModZero(int modulus, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsNotModZero(modulus, valueIfTrue, valueIfFalse); - } - - public bool IsNotPosition(int index) - { - return PublishedContent.IsNotPosition(index); - } - - public HtmlString IsNotPosition(int index, string valueIfTrue) - { - return PublishedContent.IsNotPosition(index, valueIfTrue); - } - - public HtmlString IsNotPosition(int index, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsNotPosition(index, valueIfTrue, valueIfFalse); - } - - public bool IsLast() - { - return PublishedContent.IsLast(); - } - - public HtmlString IsLast(string valueIfTrue) - { - return PublishedContent.IsLast(valueIfTrue); - } - - public HtmlString IsLast(string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsLast(valueIfTrue, valueIfFalse); - } - - public bool IsNotLast() - { - return PublishedContent.IsNotLast(); - } - - public HtmlString IsNotLast(string valueIfTrue) - { - return PublishedContent.IsNotLast(valueIfTrue); - } - - public HtmlString IsNotLast(string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsNotLast(valueIfTrue, valueIfFalse); - } - - public bool IsEven() - { - return PublishedContent.IsEven(); - } - - public HtmlString IsEven(string valueIfTrue) - { - return PublishedContent.IsEven(valueIfTrue); - } - - public HtmlString IsEven(string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsEven(valueIfTrue, valueIfFalse); - } - - public bool IsOdd() - { - return PublishedContent.IsOdd(); - } - - public HtmlString IsOdd(string valueIfTrue) - { - return PublishedContent.IsOdd(valueIfTrue); - } - - public HtmlString IsOdd(string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsOdd(valueIfTrue, valueIfFalse); - } - - #endregion - - #region IPublishedContent extension methods - IsSomething: equality - - public bool IsEqual(DynamicPublishedContent other) - { - return PublishedContent.IsEqual(other); - } - - public HtmlString IsEqual(DynamicPublishedContent other, string valueIfTrue) - { - return PublishedContent.IsEqual(other, valueIfTrue); - } - - public HtmlString IsEqual(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsEqual(other, valueIfTrue, valueIfFalse); - } - - public bool IsNotEqual(DynamicPublishedContent other) - { - return PublishedContent.IsNotEqual(other); - } - - public HtmlString IsNotEqual(DynamicPublishedContent other, string valueIfTrue) - { - return PublishedContent.IsNotEqual(other, valueIfTrue); - } - - public HtmlString IsNotEqual(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsNotEqual(other, valueIfTrue, valueIfFalse); - } - - #endregion - - #region IPublishedContent extension methods - IsSomething: ancestors and descendants - - public bool IsDescendant(DynamicPublishedContent other) - { - return PublishedContent.IsDescendant(other); - } - - public HtmlString IsDescendant(DynamicPublishedContent other, string valueIfTrue) - { - return PublishedContent.IsDescendant(other, valueIfTrue); - } - - public HtmlString IsDescendant(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsDescendant(other, valueIfTrue, valueIfFalse); - } - - public bool IsDescendantOrSelf(DynamicPublishedContent other) - { - return PublishedContent.IsDescendantOrSelf(other); - } - - public HtmlString IsDescendantOrSelf(DynamicPublishedContent other, string valueIfTrue) - { - return PublishedContent.IsDescendantOrSelf(other, valueIfTrue); - } - - public HtmlString IsDescendantOrSelf(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsDescendantOrSelf(other, valueIfTrue, valueIfFalse); - } - - public bool IsAncestor(DynamicPublishedContent other) - { - return PublishedContent.IsAncestor(other); - } - - public HtmlString IsAncestor(DynamicPublishedContent other, string valueIfTrue) - { - return PublishedContent.IsAncestor(other, valueIfTrue); - } - - public HtmlString IsAncestor(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsAncestor(other, valueIfTrue, valueIfFalse); - } - - public bool IsAncestorOrSelf(DynamicPublishedContent other) - { - return PublishedContent.IsAncestorOrSelf(other); - } - - public HtmlString IsAncestorOrSelf(DynamicPublishedContent other, string valueIfTrue) - { - return PublishedContent.IsAncestorOrSelf(other, valueIfTrue); - } - - public HtmlString IsAncestorOrSelf(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return PublishedContent.IsAncestorOrSelf(other, valueIfTrue, valueIfFalse); - } - - #endregion - - // all these methods wrap whatever PublishedContent returns in a new - // DynamicPublishedContentList, for dynamic usage. - - #region Ancestors - - public DynamicPublishedContentList Ancestors(int level) - { - return new DynamicPublishedContentList(PublishedContent.Ancestors(level)); - } - - public DynamicPublishedContentList Ancestors(string contentTypeAlias) - { - return new DynamicPublishedContentList(PublishedContent.Ancestors(contentTypeAlias)); - } - - public DynamicPublishedContentList Ancestors() - { - return new DynamicPublishedContentList(PublishedContent.Ancestors()); - } - - public DynamicPublishedContentList Ancestors(Func func) - { - return new DynamicPublishedContentList(PublishedContent.AncestorsOrSelf(false, func)); - } - - public DynamicPublishedContent AncestorOrSelf() - { - return PublishedContent.AncestorOrSelf().AsDynamicOrNull(); - } - - public DynamicPublishedContent AncestorOrSelf(int level) - { - return PublishedContent.AncestorOrSelf(level).AsDynamicOrNull(); - } - - /// - /// Gets the 'site' content for this content. - /// - /// The 'site' content ie AncestorOrSelf(1). - public DynamicPublishedContent Site() - { - return AncestorOrSelf(1); - } - - public DynamicPublishedContent AncestorOrSelf(string contentTypeAlias) - { - return PublishedContent.AncestorOrSelf(contentTypeAlias).AsDynamicOrNull(); - } - - public DynamicPublishedContent AncestorOrSelf(Func func) - { - return PublishedContent.AncestorsOrSelf(true, func).FirstOrDefault().AsDynamicOrNull(); - } - - public DynamicPublishedContentList AncestorsOrSelf(Func func) - { - return new DynamicPublishedContentList(PublishedContent.AncestorsOrSelf(true, func)); - } - - public DynamicPublishedContentList AncestorsOrSelf() - { - return new DynamicPublishedContentList(PublishedContent.AncestorsOrSelf()); - } - - public DynamicPublishedContentList AncestorsOrSelf(string contentTypeAlias) - { - return new DynamicPublishedContentList(PublishedContent.AncestorsOrSelf(contentTypeAlias)); - } - - public DynamicPublishedContentList AncestorsOrSelf(int level) - { - return new DynamicPublishedContentList(PublishedContent.AncestorsOrSelf(level)); - } - - #endregion - - #region Descendants - - public DynamicPublishedContentList Descendants(string contentTypeAlias) - { - return new DynamicPublishedContentList(PublishedContent.Descendants(contentTypeAlias)); - } - public DynamicPublishedContentList Descendants(int level) - { - return new DynamicPublishedContentList(PublishedContent.Descendants(level)); - } - public DynamicPublishedContentList Descendants() - { - return new DynamicPublishedContentList(PublishedContent.Descendants()); - } - public DynamicPublishedContentList DescendantsOrSelf(int level) - { - return new DynamicPublishedContentList(PublishedContent.DescendantsOrSelf(level)); - } - public DynamicPublishedContentList DescendantsOrSelf(string contentTypeAlias) - { - return new DynamicPublishedContentList(PublishedContent.DescendantsOrSelf(contentTypeAlias)); - } - public DynamicPublishedContentList DescendantsOrSelf() - { - return new DynamicPublishedContentList(PublishedContent.DescendantsOrSelf()); - } - - #endregion - - #region Traversal - - public DynamicPublishedContent Up() - { - return PublishedContent.Up().AsDynamicOrNull(); - } - - public DynamicPublishedContent Up(int number) - { - return PublishedContent.Up(number).AsDynamicOrNull(); - } - - public DynamicPublishedContent Up(string contentTypeAlias) - { - return PublishedContent.Up(contentTypeAlias).AsDynamicOrNull(); - } - - public DynamicPublishedContent Down() - { - return PublishedContent.Down().AsDynamicOrNull(); - } - - public DynamicPublishedContent Down(int number) - { - return PublishedContent.Down(number).AsDynamicOrNull(); - } - - public DynamicPublishedContent Down(string contentTypeAlias) - { - return PublishedContent.Down(contentTypeAlias).AsDynamicOrNull(); - } - - public DynamicPublishedContent Next() - { - return PublishedContent.Next().AsDynamicOrNull(); - } - - public DynamicPublishedContent Next(int number) - { - return PublishedContent.Next(number).AsDynamicOrNull(); - } - - public DynamicPublishedContent Next(string contentTypeAlias) - { - return PublishedContent.Next(contentTypeAlias).AsDynamicOrNull(); - } - - public DynamicPublishedContent Previous() - { - return PublishedContent.Previous().AsDynamicOrNull(); - } - - public DynamicPublishedContent Previous(int number) - { - return PublishedContent.Previous(number).AsDynamicOrNull(); - } - - public DynamicPublishedContent Previous(string contentTypeAlias) - { - return PublishedContent.Previous(contentTypeAlias).AsDynamicOrNull(); - } - - public DynamicPublishedContent Sibling(int number) - { - return PublishedContent.Previous(number).AsDynamicOrNull(); - } - - public DynamicPublishedContent Sibling(string contentTypeAlias) - { - return PublishedContent.Previous(contentTypeAlias).AsDynamicOrNull(); - } - - #endregion - - #region Parent - - public DynamicPublishedContent Parent - { - get - { - return PublishedContent.Parent != null ? PublishedContent.Parent.AsDynamicOrNull() : null; - } - } - - #endregion - - #region Children - - // we want to cache the dynamic list of children here - // whether PublishedContent.Children itself is cached, is not our concern - - private DynamicPublishedContentList _children; - - public DynamicPublishedContentList Children - { - get { return _children ?? (_children = new DynamicPublishedContentList(PublishedContent.Children)); } - } - - public DynamicPublishedContent FirstChild() - { - return Children.FirstOrDefault(); - } - - public DynamicPublishedContent FirstChild(string alias) - { - return Children.FirstOrDefault(x => x.DocumentTypeAlias == alias) as DynamicPublishedContent; - } - - #endregion - - // should probably cleanup what's below - - #region Where - - public HtmlString Where(string predicate, string valueIfTrue) - { - return Where(predicate, valueIfTrue, string.Empty); - } - public HtmlString Where(string predicate, string valueIfTrue, string valueIfFalse) - { - if (Where(predicate)) - { - return new HtmlString(valueIfTrue); - } - return new HtmlString(valueIfFalse); - } - public bool Where(string predicate) - { - //Totally gonna cheat here - var dynamicDocumentList = new DynamicPublishedContentList(); - dynamicDocumentList.Add(this); - var filtered = dynamicDocumentList.Where(predicate); - if (Queryable.Count(filtered) == 1) - { - //this node matches the predicate - return true; - } - return false; - } - - #endregion - } -} diff --git a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs deleted file mode 100644 index 382073332c..0000000000 --- a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs +++ /dev/null @@ -1,595 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Dynamic; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Dynamics; -using System.Collections; -using System.ComponentModel; -using System.Reflection; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Dynamics; - -namespace Umbraco.Web.Models -{ - /// - /// Represents a collection of DynamicPublishedContent items. - /// - [Obsolete("The use of dynamics has been deprecated, use strongly typed syntax instead, dynamics will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class DynamicPublishedContentList : DynamicObject, IEnumerable - { - private readonly List _content; - private readonly PublishedContentSet _contentSet; - internal readonly List Items; - - #region Constructor - - public DynamicPublishedContentList() - { - _content = new List(); - _contentSet = new PublishedContentSet(_content); - Items = new List(); - } - - public DynamicPublishedContentList(IEnumerable items) - { - _content = items.ToList(); - _contentSet = new PublishedContentSet(_content); - Items = _contentSet.Select(x => new DynamicPublishedContent(x, this)).ToList(); - } - - public DynamicPublishedContentList(IEnumerable items) - { - _content = items.Select(x => x.PublishedContent).ToList(); - _contentSet = new PublishedContentSet(_content); - Items = _contentSet.Select(x => new DynamicPublishedContent(x, this)).ToList(); - } - - #endregion - - #region ContentSet - - // so we are ~compatible with strongly typed syntax - public DynamicPublishedContentList ToContentSet() - { - return this; - } - - #endregion - - #region IList (well, part of it) - - /// - /// Adds an item to the collection. - /// - /// The item to add. - public void Add(DynamicPublishedContent dynamicContent) - { - var content = dynamicContent.PublishedContent; - _content.Add(content); - _contentSet.SourceChanged(); - - var setContent = _contentSet.MapContent(content); - Items.Add(new DynamicPublishedContent(setContent, this)); - } - - /// - /// Removes an item from the collection. - /// - /// The item to remove. - public void Remove(DynamicPublishedContent dynamicContent) - { - if (Items.Contains(dynamicContent) == false) return; - Items.Remove(dynamicContent); - _content.Remove(dynamicContent.PublishedContent); - _contentSet.SourceChanged(); - } - - #endregion - - #region DynamicObject - - // because we want to return DynamicNull and not null, we need to implement the index property - // via the dynamic getter and not natively - otherwise it's not possible to return DynamicNull - - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) - { - result = DynamicNull.Null; - - if (indexes.Length != 1) - return false; - - var index = indexes[0] as int?; - if (index.HasValue == false) - return false; - - if (index >= 0 && index < Items.Count) - result = Items[index.Value]; - - return true; - } - - public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) - { - //TODO: Nowhere here are we checking if args is the correct length! - - //NOTE: For many of these we could actually leave them out since we are executing custom extension methods and because - // we implement IEnumerable they will execute just fine, however, to do that will be quite a bit slower than checking here. - - var firstArg = args.FirstOrDefault(); - //this is to check for 'DocumentTypeAlias' vs 'NodeTypeAlias' for compatibility - if (firstArg != null && firstArg.ToString().InvariantStartsWith("NodeTypeAlias")) - { - firstArg = "DocumentTypeAlias" + firstArg.ToString().Substring("NodeTypeAlias".Length); - } - - var name = binder.Name; - if (name == "Single") - { - string predicate = firstArg == null ? "" : firstArg.ToString(); - var values = predicate.IsNullOrWhiteSpace() ? new object[] {} : args.Skip(1).ToArray(); - var single = Single(predicate, values); - result = new DynamicPublishedContent(single); - return true; - } - if (name == "SingleOrDefault") - { - string predicate = firstArg == null ? "" : firstArg.ToString(); - var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray(); - var single = SingleOrDefault(predicate, values); - if (single == null) - result = DynamicNull.Null; - else - result = new DynamicPublishedContent(single); - return true; - } - if (name == "First") - { - string predicate = firstArg == null ? "" : firstArg.ToString(); - var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray(); - var first = First(predicate, values); - result = new DynamicPublishedContent(first); - return true; - } - if (name == "FirstOrDefault") - { - string predicate = firstArg == null ? "" : firstArg.ToString(); - var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray(); - var first = FirstOrDefault(predicate, values); - if (first == null) - result = DynamicNull.Null; - else - result = new DynamicPublishedContent(first); - return true; - } - if (name == "Last") - { - string predicate = firstArg == null ? "" : firstArg.ToString(); - var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray(); - var last = Last(predicate, values); - result = new DynamicPublishedContent(last); - return true; - } - if (name == "LastOrDefault") - { - string predicate = firstArg == null ? "" : firstArg.ToString(); - var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray(); - var last = LastOrDefault(predicate, values); - if (last == null) - result = DynamicNull.Null; - else - result = new DynamicPublishedContent(last); - return true; - } - if (name == "Where") - { - string predicate = firstArg.ToString(); - var values = args.Skip(1).ToArray(); - //TODO: We are pre-resolving the where into a ToList() here which will have performance impacts if there where clauses - // are nested! We should somehow support an QueryableDocumentList! - result = new DynamicPublishedContentList(Where(predicate, values).ToList()); - return true; - } - if (name == "OrderBy") - { - //TODO: We are pre-resolving the where into a ToList() here which will have performance impacts if there where clauses - // are nested! We should somehow support an QueryableDocumentList! - result = new DynamicPublishedContentList(OrderBy(firstArg.ToString()).ToList()); - return true; - } - if (name == "Take") - { - result = new DynamicPublishedContentList(this.Take((int)firstArg)); - return true; - } - if (name == "Skip") - { - result = new DynamicPublishedContentList(this.Skip((int)firstArg)); - return true; - } - if (name == "InGroupsOf") - { - int groupSize; - if (int.TryParse(firstArg.ToString(), out groupSize)) - { - result = InGroupsOf(groupSize); - return true; - } - result = DynamicNull.Null; - return true; - } - if (name == "GroupedInto") - { - int groupCount; - if (int.TryParse(firstArg.ToString(), out groupCount)) - { - result = GroupedInto(groupCount); - return true; - } - result = DynamicNull.Null; - return true; - } - if (name == "GroupBy") - { - result = GroupBy(firstArg.ToString()); - return true; - } - if (name == "Average" || name == "Min" || name == "Max" || name == "Sum") - { - result = Aggregate(args, name); - return true; - } - if (name == "Union") - { - // check DynamicPublishedContentList before IEnumerable<...> because DynamicPublishedContentList - // is IEnumerable<...> so ... the check on DynamicPublishedContentList would never be reached. - - var firstArgAsDynamicPublishedContentList = firstArg as DynamicPublishedContentList; - if (firstArgAsDynamicPublishedContentList != null) - { - result = new DynamicPublishedContentList(Items.Union((firstArgAsDynamicPublishedContentList).Items)); - return true; - } - - var firstArgAsIEnumerable = firstArg as IEnumerable; - if (firstArgAsIEnumerable != null) - { - result = new DynamicPublishedContentList(Items.Union(firstArgAsIEnumerable)); - return true; - } - } - if (name == "Except") - { - if ((firstArg as IEnumerable) != null) - { - result = new DynamicPublishedContentList(Items.Except(firstArg as IEnumerable, new DynamicPublishedContentIdEqualityComparer())); - return true; - } - } - if (name == "Intersect") - { - if ((firstArg as IEnumerable) != null) - { - result = new DynamicPublishedContentList(Items.Intersect(firstArg as IEnumerable, new DynamicPublishedContentIdEqualityComparer())); - return true; - } - } - if (name == "Distinct") - { - result = new DynamicPublishedContentList(Items.Distinct(new DynamicPublishedContentIdEqualityComparer())); - return true; - } - if (name == "Pluck" || name == "Select") - { - result = Pluck(args); - return true; - } - - var runtimeCache = ApplicationContext.Current != null ? ApplicationContext.Current.ApplicationCache.RuntimeCache : new NullCacheProvider(); - - //ok, now lets try to match by member, property, extensino method - var attempt = DynamicInstanceHelper.TryInvokeMember(runtimeCache, this, binder, args, new[] - { - typeof (IEnumerable), - typeof (DynamicPublishedContentList) - }); - - if (attempt.Success) - { - result = attempt.Result.ObjectResult; - - //need to check the return type and possibly cast if result is from an extension method found - if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod) - { - //we don't need to cast if the result is already DynamicPublishedContentList - if (attempt.Result.ObjectResult != null && (!(attempt.Result.ObjectResult is DynamicPublishedContentList))) - { - if (attempt.Result.ObjectResult is IPublishedContent) - { - result = new DynamicPublishedContent((IPublishedContent)attempt.Result.ObjectResult); - } - else if (attempt.Result.ObjectResult is IEnumerable) - { - result = new DynamicPublishedContentList((IEnumerable)attempt.Result.ObjectResult); - } - else if (attempt.Result.ObjectResult is IEnumerable) - { - result = new DynamicPublishedContentList((IEnumerable)attempt.Result.ObjectResult); - } - } - } - return true; - } - - //this is the result of an extension method execution gone wrong so we return dynamic null - if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod - && attempt.Exception != null && attempt.Exception is TargetInvocationException) - { - result = DynamicNull.Null; - return true; - } - - result = null; - return false; - - } - - #endregion - - #region Linq and stuff - - private T Aggregate(IEnumerable data, string name) where T : struct - { - switch (name) - { - case "Min": - return data.Min(); - case "Max": - return data.Max(); - case "Average": - if (typeof(T) == typeof(int)) - { - return (T)Convert.ChangeType((data as List).Average(), typeof(T)); - } - if (typeof(T) == typeof(decimal)) - { - return (T)Convert.ChangeType((data as List).Average(), typeof(T)); - } - break; - case "Sum": - if (typeof(T) == typeof(int)) - { - return (T)Convert.ChangeType((data as List).Sum(), typeof(T)); - } - if (typeof(T) == typeof(decimal)) - { - return (T)Convert.ChangeType((data as List).Sum(), typeof(T)); - } - break; - } - return default(T); - } - private object Aggregate(object[] args, string name) - { - object result; - string predicate = args.First().ToString(); - var values = args.Skip(1).ToArray(); - var query = (IQueryable)this.Select(predicate, values); - object firstItem = query.FirstOrDefault(); - if (firstItem == null) - { - result = DynamicNull.Null; - } - else - { - var types = from i in query - group i by i.GetType() into g - where g.Key != typeof(DynamicNull) - orderby g.Count() descending - select new { g, Instances = g.Count() }; - var dominantType = types.First().g.Key; - //remove items that are not the dominant type - //e.g. string,string,string,string,false[DynamicNull],string - var itemsOfDominantTypeOnly = query.ToList(); - itemsOfDominantTypeOnly.RemoveAll(item => !item.GetType().IsAssignableFrom(dominantType)); - if (dominantType == typeof(string)) - { - throw new ArgumentException("Can only use aggregate methods on properties which are numeric"); - } - else if (dominantType == typeof(int)) - { - List data = (List)itemsOfDominantTypeOnly.Cast().ToList(); - return Aggregate(data, name); - } - else if (dominantType == typeof(decimal)) - { - List data = (List)itemsOfDominantTypeOnly.Cast().ToList(); - return Aggregate(data, name); - } - else if (dominantType == typeof(bool)) - { - throw new ArgumentException("Can only use aggregate methods on properties which are numeric or datetime"); - } - else if (dominantType == typeof(DateTime)) - { - if (name != "Min" || name != "Max") - { - throw new ArgumentException("Can only use aggregate min or max methods on properties which are datetime"); - } - List data = (List)itemsOfDominantTypeOnly.Cast().ToList(); - return Aggregate(data, name); - } - else - { - result = query.ToList(); - } - } - return result; - } - private object Pluck(object[] args) - { - object result; - string predicate = args.First().ToString(); - var values = args.Skip(1).ToArray(); - var query = (IQueryable)this.Select(predicate, values); - object firstItem = query.FirstOrDefault(); - if (firstItem == null) - { - result = new List(); - } - else - { - var types = from i in query - group i by i.GetType() into g - where g.Key != typeof(DynamicNull) - orderby g.Count() descending - select new { g, Instances = g.Count() }; - var dominantType = types.First().g.Key; - //remove items that are not the dominant type - //e.g. string,string,string,string,false[DynamicNull],string - var itemsOfDominantTypeOnly = query.ToList(); - itemsOfDominantTypeOnly.RemoveAll(item => !item.GetType().IsAssignableFrom(dominantType)); - if (dominantType == typeof(string)) - { - result = (List)itemsOfDominantTypeOnly.Cast().ToList(); - } - else if (dominantType == typeof(int)) - { - result = (List)itemsOfDominantTypeOnly.Cast().ToList(); - } - else if (dominantType == typeof(decimal)) - { - result = (List)itemsOfDominantTypeOnly.Cast().ToList(); - } - else if (dominantType == typeof(bool)) - { - result = (List)itemsOfDominantTypeOnly.Cast().ToList(); - } - else if (dominantType == typeof(DateTime)) - { - result = (List)itemsOfDominantTypeOnly.Cast().ToList(); - } - else - { - result = query.ToList(); - } - } - return result; - } - - public T Single(string predicate, params object[] values) - { - return predicate.IsNullOrWhiteSpace() - ? ((IQueryable) Items.AsQueryable()).Single() - : Where(predicate, values).Single(); - } - - public T SingleOrDefault(string predicate, params object[] values) - { - return predicate.IsNullOrWhiteSpace() - ? ((IQueryable)Items.AsQueryable()).SingleOrDefault() - : Where(predicate, values).SingleOrDefault(); - } - public T First(string predicate, params object[] values) - { - return predicate.IsNullOrWhiteSpace() - ? ((IQueryable)Items.AsQueryable()).First() - : Where(predicate, values).First(); - } - public T FirstOrDefault(string predicate, params object[] values) - { - return predicate.IsNullOrWhiteSpace() - ? ((IQueryable)Items.AsQueryable()).FirstOrDefault() - : Where(predicate, values).FirstOrDefault(); - } - public T Last(string predicate, params object[] values) - { - return predicate.IsNullOrWhiteSpace() - ? ((IQueryable)Items.AsQueryable()).Last() - : Where(predicate, values).Last(); - } - public T LastOrDefault(string predicate, params object[] values) - { - return predicate.IsNullOrWhiteSpace() - ? ((IQueryable)Items.AsQueryable()).LastOrDefault() - : Where(predicate, values).LastOrDefault(); - } - public IQueryable Where(string predicate, params object[] values) - { - return ((IQueryable)Items.AsQueryable()).Where(predicate, values); - } - public IQueryable OrderBy(string key) - { - return ((IQueryable)Items.AsQueryable()).OrderBy(key, () => typeof(DynamicPublishedContentListOrdering)); - } - public DynamicGrouping GroupBy(string key) - { - var group = new DynamicGrouping(this, key); - return group; - } - public DynamicGrouping GroupedInto(int groupCount) - { - var groupSize = (int)Math.Ceiling(((decimal)Items.Count() / groupCount)); - return new DynamicGrouping( - Items - .Select((node, index) => new KeyValuePair(index, node)) - .GroupBy(kv => (object)(kv.Key / groupSize)) - .Select(item => new Grouping() - { - Key = item.Key, - Elements = item.Select(inner => inner.Value) - })); - } - public DynamicGrouping InGroupsOf(int groupSize) - { - return new DynamicGrouping( - Items - .Select((node, index) => new KeyValuePair(index, node)) - .GroupBy(kv => (object)(kv.Key / groupSize)) - .Select(item => new Grouping() - { - Key = item.Key, - Elements = item.Select(inner => inner.Value) - })); - - } - - public IQueryable Select(string predicate, params object[] values) - { - return DynamicQueryable.Select(Items.AsQueryable(), predicate, values); - } - - #endregion - - #region Dynamic - - public bool IsNull() - { - return false; - } - - public bool HasValue() - { - return true; - } - - #endregion - - #region Enumerate inner IPublishedContent items - - public IEnumerator GetEnumerator() - { - return Items.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - } -} diff --git a/src/Umbraco.Web/Models/IContentModel.cs b/src/Umbraco.Web/Models/IContentModel.cs new file mode 100644 index 0000000000..d0d4f175d7 --- /dev/null +++ b/src/Umbraco.Web/Models/IContentModel.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Models +{ + public interface IContentModel + { + IPublishedContent Content { get; } + } +} diff --git a/src/Umbraco.Web/Models/IRenderModel.cs b/src/Umbraco.Web/Models/IRenderModel.cs deleted file mode 100644 index c2955328f5..0000000000 --- a/src/Umbraco.Web/Models/IRenderModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Umbraco.Core.Models; - -namespace Umbraco.Web.Models -{ - public interface IRenderModel - { - IPublishedContent Content { get; } - } -} diff --git a/src/Umbraco.Web/Models/ImageCropAnchor.cs b/src/Umbraco.Web/Models/ImageCropAnchor.cs index 94e666e5ea..a24b4df6fa 100644 --- a/src/Umbraco.Web/Models/ImageCropAnchor.cs +++ b/src/Umbraco.Web/Models/ImageCropAnchor.cs @@ -12,4 +12,4 @@ BottomLeft, BottomRight } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ImageCropCoordinates.cs b/src/Umbraco.Web/Models/ImageCropCoordinates.cs deleted file mode 100644 index 4a480b4beb..0000000000 --- a/src/Umbraco.Web/Models/ImageCropCoordinates.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Core.Dynamics; - -namespace Umbraco.Web.Models -{ - [DataContract(Name = "imageCropCoordinates")] - public class ImageCropCoordinates : CaseInsensitiveDynamicObject, IEquatable - { - [DataMember(Name = "x1")] - public decimal X1 { get; set; } - - [DataMember(Name = "y1")] - public decimal Y1 { get; set; } - - [DataMember(Name = "x2")] - public decimal X2 { get; set; } - - [DataMember(Name = "y2")] - public decimal Y2 { get; set; } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. - public bool Equals(ImageCropCoordinates other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return X1 == other.X1 && Y1 == other.Y1 && X2 == other.X2 && Y2 == other.Y2; - } - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// - /// true if the specified object is equal to the current object; otherwise, false. - /// - /// The object to compare with the current object. - 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((ImageCropCoordinates) obj); - } - - /// - /// Serves as the default hash function. - /// - /// - /// A hash code for the current object. - /// - public override int GetHashCode() - { - unchecked - { - var hashCode = X1.GetHashCode(); - hashCode = (hashCode*397) ^ Y1.GetHashCode(); - hashCode = (hashCode*397) ^ X2.GetHashCode(); - hashCode = (hashCode*397) ^ Y2.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(ImageCropCoordinates left, ImageCropCoordinates right) - { - return Equals(left, right); - } - - public static bool operator !=(ImageCropCoordinates left, ImageCropCoordinates right) - { - return !Equals(left, right); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ImageCropData.cs b/src/Umbraco.Web/Models/ImageCropData.cs deleted file mode 100644 index fb1b3c7424..0000000000 --- a/src/Umbraco.Web/Models/ImageCropData.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Runtime.Serialization; -using System.Threading.Tasks; -using Umbraco.Core.Dynamics; - -namespace Umbraco.Web.Models -{ - [DataContract(Name = "imageCropData")] - public class ImageCropData : CaseInsensitiveDynamicObject, IEquatable - { - [DataMember(Name = "alias")] - public string Alias { get; set; } - - [DataMember(Name = "width")] - public int Width { get; set; } - - [DataMember(Name = "height")] - public int Height { get; set; } - - [DataMember(Name = "coordinates")] - public ImageCropCoordinates Coordinates { get; set; } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. - public bool Equals(ImageCropData other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return string.Equals(Alias, other.Alias) && Width == other.Width && Height == other.Height && Equals(Coordinates, other.Coordinates); - } - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// - /// true if the specified object is equal to the current object; otherwise, false. - /// - /// The object to compare with the current object. - 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((ImageCropData) obj); - } - - /// - /// Serves as the default hash function. - /// - /// - /// A hash code for the current object. - /// - public override int GetHashCode() - { - unchecked - { - var hashCode = (Alias != null ? Alias.GetHashCode() : 0); - hashCode = (hashCode*397) ^ Width; - hashCode = (hashCode*397) ^ Height; - hashCode = (hashCode*397) ^ (Coordinates != null ? Coordinates.GetHashCode() : 0); - return hashCode; - } - } - - public static bool operator ==(ImageCropData left, ImageCropData right) - { - return Equals(left, right); - } - - public static bool operator !=(ImageCropData left, ImageCropData right) - { - return !Equals(left, right); - } - } - -} diff --git a/src/Umbraco.Web/Models/ImageCropDataSet.cs b/src/Umbraco.Web/Models/ImageCropDataSet.cs deleted file mode 100644 index 00f46dd2d7..0000000000 --- a/src/Umbraco.Web/Models/ImageCropDataSet.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Dynamic; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.Serialization; -using System.Text; -using System.Web; -using Newtonsoft.Json; -using Umbraco.Core; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Serialization; -using Umbraco.Web.PropertyEditors.ValueConverters; - -namespace Umbraco.Web.Models -{ - [JsonConverter(typeof(NoTypeConverterJsonConverter))] - [TypeConverter(typeof(ImageCropDataSetConverter))] - [DataContract(Name="imageCropDataSet")] - public class ImageCropDataSet : CaseInsensitiveDynamicObject, IHtmlString, IEquatable - { - - [DataMember(Name="src")] - public string Src { get; set;} - - [DataMember(Name = "focalPoint")] - public ImageCropFocalPoint FocalPoint { get; set; } - - [DataMember(Name = "crops")] - public IEnumerable Crops { get; set; } - - public string GetCropUrl(string alias, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null) - { - - var crop = Crops.GetCrop(alias); - - if (crop == null && string.IsNullOrEmpty(alias) == false) - { - return null; - } - - var sb = new StringBuilder(); - - var cropBaseUrl = this.GetCropBaseUrl(alias, useFocalPoint); - if (cropBaseUrl != null) - { - sb.Append(cropBaseUrl); - } - - if (crop != null && useCropDimensions) - { - sb.Append("&width=").Append(crop.Width); - sb.Append("&height=").Append(crop.Height); - } - - if (cacheBusterValue != null) - { - sb.Append("&rnd=").Append(cacheBusterValue); - } - - return sb.ToString(); - - } - - public bool HasFocalPoint() - { - return FocalPoint != null && FocalPoint.Left != 0.5m && FocalPoint.Top != 0.5m; - } - - public bool HasCrop(string alias) - { - return Crops.Any(x => x.Alias == alias); - } - - public bool HasImage() - { - return ! string.IsNullOrEmpty(Src); - } - - public string ToHtmlString() - { - return this.Src; - } - - /// - /// Returns a string that represents the current object. - /// - /// - /// If there are crops defined, it will return the JSON value, otherwise it will just return the Src value - /// - public override string ToString() - { - return Crops.Any() ? JsonConvert.SerializeObject(this) : Src; - } - - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. - public bool Equals(ImageCropDataSet other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return string.Equals(Src, other.Src) && Equals(FocalPoint, other.FocalPoint) - && Crops.SequenceEqual(other.Crops); - } - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// - /// true if the specified object is equal to the current object; otherwise, false. - /// - /// The object to compare with the current object. - 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((ImageCropDataSet) obj); - } - - /// - /// Serves as the default hash function. - /// - /// - /// A hash code for the current object. - /// - public override int GetHashCode() - { - unchecked - { - var hashCode = (Src != null ? Src.GetHashCode() : 0); - hashCode = (hashCode*397) ^ (FocalPoint != null ? FocalPoint.GetHashCode() : 0); - hashCode = (hashCode*397) ^ (Crops != null ? Crops.GetHashCode() : 0); - return hashCode; - } - } - - public static bool operator ==(ImageCropDataSet left, ImageCropDataSet right) - { - return Equals(left, right); - } - - public static bool operator !=(ImageCropDataSet left, ImageCropDataSet right) - { - return !Equals(left, right); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ImageCropFocalPoint.cs b/src/Umbraco.Web/Models/ImageCropFocalPoint.cs deleted file mode 100644 index e47bab5b7a..0000000000 --- a/src/Umbraco.Web/Models/ImageCropFocalPoint.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Core.Dynamics; - -namespace Umbraco.Web.Models -{ - [DataContract(Name = "imageCropFocalPoint")] - public class ImageCropFocalPoint : CaseInsensitiveDynamicObject, IEquatable - { - [DataMember(Name = "left")] - public decimal Left { get; set; } - - [DataMember(Name = "top")] - public decimal Top { get; set; } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. - public bool Equals(ImageCropFocalPoint other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Left == other.Left && Top == other.Top; - } - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// - /// true if the specified object is equal to the current object; otherwise, false. - /// - /// The object to compare with the current object. - 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((ImageCropFocalPoint) obj); - } - - /// - /// Serves as the default hash function. - /// - /// - /// A hash code for the current object. - /// - public override int GetHashCode() - { - unchecked - { - return (Left.GetHashCode()*397) ^ Top.GetHashCode(); - } - } - - public static bool operator ==(ImageCropFocalPoint left, ImageCropFocalPoint right) - { - return Equals(left, right); - } - - public static bool operator !=(ImageCropFocalPoint left, ImageCropFocalPoint right) - { - return !Equals(left, right); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/LegacyConvertedNode.cs b/src/Umbraco.Web/Models/LegacyConvertedNode.cs deleted file mode 100644 index b2f5712364..0000000000 --- a/src/Umbraco.Web/Models/LegacyConvertedNode.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using umbraco.interfaces; - -namespace Umbraco.Web.Models -{ - /// - /// A legacy INode that wraps IPublishedContent - /// - internal class LegacyConvertedNode : PublishedContentWrapped, INode - { - private readonly IPublishedContent _publishedContent; - private readonly int _id; - private readonly int _template; - private readonly int _sortOrder; - private readonly string _name; - private readonly string _urlName; - private readonly string _nodeTypeAlias; - private readonly string _writerName; - private readonly string _creatorName; - private readonly int _writerId; - private readonly int _creatorId; - private readonly string _path; - private readonly DateTime _createDate; - private readonly DateTime _updateDate; - private readonly Guid _version; - private readonly int _level; - - public LegacyConvertedNode(IPublishedContent publishedContent) : base(publishedContent) - { - _publishedContent = publishedContent; - - if (publishedContent == null) - { - _id = 0; - return; - } - - _template = publishedContent.TemplateId; - _id = publishedContent.Id; - _path = publishedContent.Path; - _creatorName = publishedContent.CreatorName; - _sortOrder = publishedContent.SortOrder; - _updateDate = publishedContent.UpdateDate; - _name = publishedContent.Name; - _nodeTypeAlias = publishedContent.DocumentTypeAlias; - _createDate = publishedContent.CreateDate; - _creatorId = publishedContent.CreatorId; - _level = publishedContent.Level; - _urlName = publishedContent.UrlName; - _version = publishedContent.Version; - _writerId = publishedContent.WriterId; - _writerName = publishedContent.WriterName; - } - - INode INode.Parent - { - get { return _publishedContent.Parent == null ? null : LegacyNodeHelper.ConvertToNode(_publishedContent.Parent); } - } - - int INode.Id - { - get { return _id; } - } - - int INode.template - { - get { return _template; } - } - - int INode.SortOrder - { - get { return _sortOrder; } - } - - string INode.Name - { - get { return _name; } - } - - string INode.UrlName - { - get { return _urlName; } - } - - string INode.NodeTypeAlias - { - get { return _nodeTypeAlias; } - } - - string INode.WriterName - { - get { return _writerName; } - } - - string INode.CreatorName - { - get { return _creatorName; } - } - - int INode.WriterID - { - get { return _writerId; } - } - - int INode.CreatorID - { - get { return _creatorId; } - } - - string INode.Path - { - get { return _path; } - } - - DateTime INode.CreateDate - { - get { return _createDate; } - } - - DateTime INode.UpdateDate - { - get { return _updateDate; } - } - - Guid INode.Version - { - get { return _version; } - } - - string INode.NiceUrl - { - get { return _publishedContent.Url; } - } - - string INode.Url - { - get { return _publishedContent.Url; } - } - - int INode.Level - { - get { return _level; } - } - - List INode.PropertiesAsList - { - get { return _publishedContent.Properties.Select(LegacyNodeHelper.ConvertToNodeProperty).ToList(); } - } - - List INode.ChildrenAsList - { - get { return _publishedContent.Children.Select(LegacyNodeHelper.ConvertToNode).ToList(); } - } - - IProperty INode.GetProperty(string alias) - { - return ((INode)this).PropertiesAsList.Cast().FirstOrDefault(p => p.Alias == alias); - } - - IProperty INode.GetProperty(string alias, out bool propertyExists) - { - var prop = _publishedContent.GetProperty(alias); - propertyExists = prop != null; - return prop == null ? null : LegacyNodeHelper.ConvertToNodeProperty(prop); - } - - DataTable INode.ChildrenAsTable() - { - return _publishedContent.ChildrenAsTable(); - } - - DataTable INode.ChildrenAsTable(string nodeTypeAliasFilter) - { - return _publishedContent.ChildrenAsTable(nodeTypeAliasFilter); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/LegacyConvertedNodeProperty.cs b/src/Umbraco.Web/Models/LegacyConvertedNodeProperty.cs deleted file mode 100644 index 17ca6a39cc..0000000000 --- a/src/Umbraco.Web/Models/LegacyConvertedNodeProperty.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Web; -using Umbraco.Core.Models; -using umbraco.interfaces; - -namespace Umbraco.Web.Models -{ - /// - /// A legacy IProperty that wraps IPublishedProperty - /// - internal class LegacyConvertedNodeProperty : IProperty, IHtmlString - { - public IPublishedProperty PublishedProperty { get; private set; } - - public LegacyConvertedNodeProperty(IPublishedProperty prop) - { - PublishedProperty = prop; - } - - public string Alias - { - get { return PublishedProperty.PropertyTypeAlias; } - } - - public string Value - { - get { return PublishedProperty.DataValue == null ? null : PublishedProperty.DataValue.ToString(); } - } - - public Guid Version - { - get { return Guid.Empty; } - } - - public bool IsNull() - { - return Value == null; - } - - public bool HasValue() - { - return PublishedProperty.HasValue; - } - - public int ContextId { get; set; } - public string ContextAlias { get; set; } - - // implements IHtmlString.ToHtmlString - public string ToHtmlString() - { - return Value; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/LegacyNodeHelper.cs b/src/Umbraco.Web/Models/LegacyNodeHelper.cs deleted file mode 100644 index 6a102e54c8..0000000000 --- a/src/Umbraco.Web/Models/LegacyNodeHelper.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Umbraco.Core.Models; -using umbraco.interfaces; - -namespace Umbraco.Web.Models -{ - /// - /// Used to convert to/from the legacy INode from IPublishedContent - /// - internal static class LegacyNodeHelper - { - // NOTE - moved from umbraco.MacroEngines to avoid circ. references - - public static INode ConvertToNode(IPublishedContent doc) - { - var node = new LegacyConvertedNode(doc); - return node; - } - - public static IProperty ConvertToNodeProperty(IPublishedProperty prop) - { - return new LegacyConvertedNodeProperty(prop); - } - - } -} diff --git a/src/Umbraco.Web/Models/LocalPackageInstallModel.cs b/src/Umbraco.Web/Models/LocalPackageInstallModel.cs index 819674d3f2..06216597cb 100644 --- a/src/Umbraco.Web/Models/LocalPackageInstallModel.cs +++ b/src/Umbraco.Web/Models/LocalPackageInstallModel.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Runtime.Serialization; +using Umbraco.Core.Models.Editors; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models @@ -12,14 +13,14 @@ namespace Umbraco.Web.Models { public LocalPackageInstallModel() { - UploadedFiles = new List(); + UploadedFiles = new List(); Notifications = new List(); } - - public List UploadedFiles { get; private set; } - + + public List UploadedFiles { get; } + [DataMember(Name = "notifications")] - public List Notifications { get; private set; } + public List Notifications { get; } /// /// A flag to determine if this package is compatible to be installed @@ -54,9 +55,6 @@ namespace Umbraco.Web.Models [DataMember(Name = "containsMacroConflict")] public bool ContainsMacroConflict { get; set; } - [DataMember(Name = "containsLegacyPropertyEditors")] - public bool ContainsLegacyPropertyEditors { get; set; } - [DataMember(Name = "containsBinaryFileErrors")] public bool ContainsBinaryFileErrors { get; set; } @@ -87,4 +85,4 @@ namespace Umbraco.Web.Models [DataMember(Name = "iconUrl")] public string IconUrl { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/LoginModel.cs b/src/Umbraco.Web/Models/LoginModel.cs index 5e6b2e5c18..a48ed4a0bb 100644 --- a/src/Umbraco.Web/Models/LoginModel.cs +++ b/src/Umbraco.Web/Models/LoginModel.cs @@ -10,7 +10,7 @@ namespace Umbraco.Web.Models public string Username { get; set; } [Required] - [DataMember(Name = "password", IsRequired = true)] + [DataMember(Name = "password", IsRequired = true)] [StringLength(maximumLength:256)] public string Password { get; set; } diff --git a/src/Umbraco.Web/Models/LoginStatusModel.cs b/src/Umbraco.Web/Models/LoginStatusModel.cs index 23fb2039e9..78425969dd 100644 --- a/src/Umbraco.Web/Models/LoginStatusModel.cs +++ b/src/Umbraco.Web/Models/LoginStatusModel.cs @@ -2,30 +2,30 @@ using System.ComponentModel.DataAnnotations; using System.Web; using Umbraco.Core; +using Umbraco.Web.Composing; using Umbraco.Web.Security; namespace Umbraco.Web.Models { /// - /// The model representing the status of a logged in member + /// The model representing the status of a logged in member. /// public class LoginStatusModel { /// - /// Creates a new empty LoginStatusModel + /// Creates a new empty LoginStatusModel. /// /// public static LoginStatusModel CreateModel() { - var model = new LoginStatusModel(false); - return model; + return new LoginStatusModel(false); } private LoginStatusModel(bool doLookup) { - if (doLookup && UmbracoContext.Current != null) + if (doLookup && Current.UmbracoContext != null) { - var helper = new MembershipHelper(UmbracoContext.Current); + var helper = new MembershipHelper(Current.UmbracoContext); var model = helper.GetCurrentLoginStatus(); if (model != null) { @@ -43,9 +43,7 @@ namespace Umbraco.Web.Models [Obsolete("Do not use this ctor as it will perform business logic lookups. Use the MembershipHelper.GetCurrentLoginStatus or the static LoginStatusModel.CreateModel() to create an empty model.")] public LoginStatusModel() : this(true) - { - - } + { } /// /// The name of the member @@ -69,4 +67,4 @@ namespace Umbraco.Web.Models /// public bool IsLoggedIn { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ActionButtonsResolver.cs b/src/Umbraco.Web/Models/Mapping/ActionButtonsResolver.cs new file mode 100644 index 0000000000..59b594757f --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ActionButtonsResolver.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates the list of action buttons allowed for this user - Publish, Send to publish, save, unpublish returned as the button's 'letter' + /// + internal class ActionButtonsResolver + { + public ActionButtonsResolver(IUserService userService, IContentService contentService) + { + UserService = userService; + ContentService = contentService; + } + + private IUserService UserService { get; } + private IContentService ContentService { get; } + + public IEnumerable Resolve(IContent source) + { + //cannot check permissions without a context + if (UmbracoContext.Current == null) + return Enumerable.Empty(); + + string path; + if (source.HasIdentity) + path = source.Path; + else + { + var parent = ContentService.GetById(source.ParentId); + path = parent == null ? "-1" : parent.Path; + } + + //TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is + // with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null + // refrence exception :( + return UserService.GetPermissionsForPath(UmbracoContext.Current.Security.CurrentUser, path).GetAllPermissions(); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/AuditMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/AuditMapperProfile.cs new file mode 100644 index 0000000000..34749c51c6 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/AuditMapperProfile.cs @@ -0,0 +1,20 @@ +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class AuditMapperProfile : Profile + { + public AuditMapperProfile() + { + CreateMap() + .ForMember(log => log.UserAvatars, expression => expression.Ignore()) + .ForMember(log => log.UserName, expression => expression.Ignore()) + .ForMember(log => log.NodeId, expression => expression.MapFrom(item => item.Id)) + .ForMember(log => log.Timestamp, expression => expression.MapFrom(item => item.CreateDate)) + .ForMember(log => log.LogType, expression => expression.MapFrom(item => item.AuditType)); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs b/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs index 2b9047c284..7b8bdb91c5 100644 --- a/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs @@ -1,15 +1,14 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using AutoMapper; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { - internal class AvailablePropertyEditorsResolver : ValueResolver> + internal class AvailablePropertyEditorsResolver { private readonly IContentSection _contentSection; @@ -18,17 +17,12 @@ namespace Umbraco.Web.Models.Mapping _contentSection = contentSection; } - protected override IEnumerable ResolveCore(IDataTypeDefinition source) + public IEnumerable Resolve(IDataType source) { - return PropertyEditorResolver.Current.PropertyEditors - .Where(x => - { - if (_contentSection.ShowDeprecatedPropertyEditors) - return true; - return source.PropertyEditorAlias == x.Alias || x.IsDeprecated == false; - }) + return Current.PropertyEditors + .Where(x => !x.IsDeprecated || _contentSection.ShowDeprecatedPropertyEditors || source.EditorAlias == x.Alias) .OrderBy(x => x.Name) .Select(Mapper.Map); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/CodeFileDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/CodeFileDisplayMapper.cs deleted file mode 100644 index b997653844..0000000000 --- a/src/Umbraco.Web/Models/Mapping/CodeFileDisplayMapper.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.Models; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - public class CodeFileDisplayMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - config.CreateMap() - .ForMember(x => x.FileType, exp => exp.Ignore()) - .ForMember(x => x.Notifications, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.Snippet, exp => exp.Ignore()); - - config.CreateMap() - .ForMember(x => x.FileType, exp => exp.Ignore()) - .ForMember(x => x.Notifications, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.Snippet, exp => exp.Ignore()); - - config.CreateMap() - .IgnoreDeletableEntityCommonProperties() - .ForMember(x => x.Id, exp => exp.Ignore()) - .ForMember(x => x.Key, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.Alias, exp => exp.Ignore()) - .ForMember(x => x.Name, exp => exp.Ignore()) - .ForMember(x => x.OriginalPath, exp => exp.Ignore()) - .ForMember(x => x.HasIdentity, exp => exp.Ignore()); - - config.CreateMap() - .IgnoreDeletableEntityCommonProperties() - .ForMember(x => x.Id, exp => exp.Ignore()) - .ForMember(x => x.Key, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.Alias, exp => exp.Ignore()) - .ForMember(x => x.Name, exp => exp.Ignore()) - .ForMember(x => x.OriginalPath, exp => exp.Ignore()) - .ForMember(x => x.HasIdentity, exp => exp.Ignore()); - } - } -} diff --git a/src/Umbraco.Web/Models/Mapping/CodeFileMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/CodeFileMapperProfile.cs new file mode 100644 index 0000000000..082abfdace --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/CodeFileMapperProfile.cs @@ -0,0 +1,46 @@ +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + public class CodeFileMapperProfile : Profile + { + public CodeFileMapperProfile() + { + CreateMap() + .ForMember(dest => dest.FileType, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Snippet, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.FileType, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Snippet, opt => opt.Ignore()); + + CreateMap() + .IgnoreEntityCommonProperties() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.Key, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Name, opt => opt.Ignore()) + .ForMember(dest => dest.OriginalPath, opt => opt.Ignore()) + .ForMember(dest => dest.HasIdentity, opt => opt.Ignore()); + + CreateMap() + .IgnoreEntityCommonProperties() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.Key, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Name, opt => opt.Ignore()) + .ForMember(dest => dest.OriginalPath, opt => opt.Ignore()) + .ForMember(dest => dest.HasIdentity, opt => opt.Ignore()); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentChildOfListViewResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentChildOfListViewResolver.cs new file mode 100644 index 0000000000..a2f250d6f3 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentChildOfListViewResolver.cs @@ -0,0 +1,26 @@ +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class ContentChildOfListViewResolver : IValueResolver + { + private readonly IContentService _contentService; + private readonly IContentTypeService _contentTypeService; + + public ContentChildOfListViewResolver(IContentService contentService, IContentTypeService contentTypeService) + { + _contentService = contentService; + _contentTypeService = contentTypeService; + } + + public bool Resolve(IContent source, ContentItemDisplay destination, bool destMember, ResolutionContext context) + { + // map the IsChildOfListView (this is actually if it is a descendant of a list view!) + var parent = _contentService.GetParent(source); + return parent != null && (parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(parent.Path)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs new file mode 100644 index 0000000000..6791afb921 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -0,0 +1,89 @@ +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Declares how model mappings for content + /// + internal class ContentMapperProfile : Profile + { + public ContentMapperProfile(IUserService userService, ILocalizedTextService textService, IContentService contentService, IContentTypeService contentTypeService, IDataTypeService dataTypeService, ILocalizationService localizationService) + { + // create, capture, cache + var contentOwnerResolver = new OwnerResolver(userService); + var creatorResolver = new CreatorResolver(userService); + var actionButtonsResolver = new ActionButtonsResolver(userService, contentService); + var tabsAndPropertiesResolver = new TabsAndPropertiesResolver(textService); + var childOfListViewResolver = new ContentChildOfListViewResolver(contentService, contentTypeService); + var contentTypeBasicResolver = new ContentTypeBasicResolver(); + var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver(); + var defaultTemplateResolver = new DefaultTemplateResolver(); + var contentUrlResolver = new ContentUrlResolver(); + var variantResolver = new VariationResolver(localizationService); + + //FROM IContent TO ContentItemDisplay + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key))) + .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src))) + .ForMember(dest => dest.Updater, opt => opt.ResolveUsing(src => creatorResolver.Resolve(src))) + .ForMember(dest => dest.Variants, opt => opt.ResolveUsing(variantResolver)) + .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon)) + .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) + .ForMember(dest => dest.ContentTypeName, opt => opt.MapFrom(src => src.ContentType.Name)) + .ForMember(dest => dest.IsContainer, opt => opt.MapFrom(src => src.ContentType.IsContainer)) + .ForMember(dest => dest.IsBlueprint, opt => opt.MapFrom(src => src.Blueprint)) + .ForMember(dest => dest.IsChildOfListView, opt => opt.ResolveUsing(childOfListViewResolver)) + .ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed)) + .ForMember(dest => dest.PublishDate, opt => opt.MapFrom(src => src.PublishDate)) + .ForMember(dest => dest.TemplateAlias, opt => opt.ResolveUsing(defaultTemplateResolver)) + .ForMember(dest => dest.Urls, opt => opt.ResolveUsing(contentUrlResolver)) + .ForMember(dest => dest.Properties, opt => opt.Ignore()) + .ForMember(dest => dest.AllowPreview, opt => opt.Ignore()) + .ForMember(dest => dest.TreeNodeUrl, opt => opt.ResolveUsing(contentTreeNodeUrlResolver)) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Errors, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.DocumentType, opt => opt.ResolveUsing(contentTypeBasicResolver)) + .ForMember(dest => dest.AllowedTemplates, opt => + opt.MapFrom(content => content.ContentType.AllowedTemplates + .Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false) + .ToDictionary(t => t.Alias, t => t.Name))) + .ForMember(dest => dest.Tabs, opt => opt.ResolveUsing(tabsAndPropertiesResolver)) + .ForMember(dest => dest.AllowedActions, opt => opt.ResolveUsing(src => actionButtonsResolver.Resolve(src))) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .AfterMap((content, display) => + { + if (content.ContentType.IsContainer) + TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, textService); + }); + + //FROM IContent TO ContentItemBasic + CreateMap>() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => + Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key))) + .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src))) + .ForMember(dest => dest.Updater, opt => opt.ResolveUsing(src => creatorResolver.Resolve(src))) + .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon)) + .ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed)) + .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + + //FROM IContent TO ContentItemDto + CreateMap>() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => + Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key))) + .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src))) + .ForMember(dest => dest.Updater, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs deleted file mode 100644 index 2fa919716c..0000000000 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Trees; -using Umbraco.Web.Routing; -using umbraco.BusinessLogic.Actions; -using Umbraco.Core.PropertyEditors; -using Content = Umbraco.Core.Models.Content; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Declares how model mappings for content - /// - internal class ContentModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - - //FROM IContent TO ContentItemDisplay - config.CreateMap() - .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(content.IsBlueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, content.Key))) - .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember(display => display.Updater, expression => expression.ResolveUsing(new CreatorResolver())) - .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name)) - .ForMember(display => display.IsContainer, expression => expression.MapFrom(content => content.ContentType.IsContainer)) - .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) - .ForMember(display => display.Trashed, expression => expression.MapFrom(content => content.Trashed)) - .ForMember(display => display.PublishDate, expression => expression.MapFrom(content => GetPublishedDate(content))) - .ForMember(display => display.TemplateAlias, expression => expression.MapFrom(content => content.Template.Alias)) - .ForMember(display => display.HasPublishedVersion, expression => expression.MapFrom(content => content.HasPublishedVersion)) - .ForMember(display => display.Urls, - expression => expression.MapFrom(content => - UmbracoContext.Current == null - ? new[] { "Cannot generate urls without a current Umbraco Context" } - : content.GetContentUrls(UmbracoContext.Current))) - .ForMember(display => display.Properties, expression => expression.Ignore()) - .ForMember(display => display.AllowPreview, expression => expression.Ignore()) - .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) - .ForMember(display => display.Notifications, expression => expression.Ignore()) - .ForMember(display => display.Errors, expression => expression.Ignore()) - .ForMember(display => display.Alias, expression => expression.Ignore()) - .ForMember(display => display.DocumentType, expression => expression.ResolveUsing()) - .ForMember(display => display.AllowedTemplates, expression => - expression.MapFrom(content => content.ContentType.AllowedTemplates - .Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false) - .ToDictionary(t => t.Alias, t => t.Name))) - .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) - .ForMember(display => display.AllowedActions, expression => expression.ResolveUsing( - new ActionButtonsResolver(new Lazy(() => applicationContext.Services.UserService), new Lazy(() => applicationContext.Services.ContentService)))) - .AfterMap((content, display) => AfterMap(content, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, - applicationContext.Services.ContentTypeService)); - - //FROM IContent TO ContentItemBasic - config.CreateMap>() - .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(content.IsBlueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, content.Key))) - .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember(dto => dto.Updater, expression => expression.ResolveUsing(new CreatorResolver())) - .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember(dto => dto.Trashed, expression => expression.MapFrom(content => content.Trashed)) - .ForMember(dto => dto.HasPublishedVersion, expression => expression.MapFrom(content => content.HasPublishedVersion)) - .ForMember(dto => dto.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember(dto => dto.Alias, expression => expression.Ignore()); - - //FROM IContent TO ContentItemDto - config.CreateMap>() - .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(content.IsBlueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, content.Key))) - .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember(dto => dto.HasPublishedVersion, expression => expression.MapFrom(content => content.HasPublishedVersion)) - .ForMember(dto => dto.Updater, expression => expression.Ignore()) - .ForMember(dto => dto.Icon, expression => expression.Ignore()) - .ForMember(dto => dto.Alias, expression => expression.Ignore()); - } - - private static DateTime? GetPublishedDate(IContent content) - { - var date = ((Content)content).PublishedDate; - return date == default(DateTime) ? (DateTime?)null : date; - } - - /// - /// Maps the generic tab with custom properties for content - /// - /// - /// - /// - /// - /// - private static void AfterMap(IContent content, ContentItemDisplay display, IDataTypeService dataTypeService, - ILocalizedTextService localizedText, IContentTypeService contentTypeService) - { - // map the IsChildOfListView (this is actually if it is a descendant of a list view!) - var parent = content.Parent(); - display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path)); - - //map the tree node url - if (HttpContext.Current != null) - { - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Id.ToString(), null)); - display.TreeNodeUrl = url; - } - - //set default template if template isn't set - if (string.IsNullOrEmpty(display.TemplateAlias)) - display.TemplateAlias = content.ContentType.DefaultTemplate == null - ? string.Empty - : content.ContentType.DefaultTemplate.Alias; - - if (content.ContentType.IsContainer) - { - TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText); - } - - TabsAndPropertiesResolver.MapGenericProperties(content, display, localizedText); - } - - /// - /// Resolves a from the item and checks if the current user - /// has access to see this data - /// - private class ContentTypeBasicResolver : ValueResolver - { - protected override ContentTypeBasic ResolveCore(IContent source) - { - //TODO: This would be much nicer with the IUmbracoContextAccessor so we don't use singletons - //If this is a web request and there's a user signed in and the - // user has access to the settings section, we will - if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null - && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) - { - var contentTypeBasic = Mapper.Map(source.ContentType); - return contentTypeBasic; - } - //no access - return null; - } - } - - /// - /// Creates the list of action buttons allowed for this user - Publish, Send to publish, save, unpublish returned as the button's 'letter' - /// - private class ActionButtonsResolver : ValueResolver> - { - private readonly Lazy _userService; - private readonly Lazy _contentService; - - public ActionButtonsResolver(Lazy userService, Lazy contentService) - { - if (userService == null) throw new ArgumentNullException("userService"); - if (contentService == null) throw new ArgumentNullException("contentService"); - _userService = userService; - _contentService = contentService; - } - - protected override IEnumerable ResolveCore(IContent source) - { - if (UmbracoContext.Current == null) - { - //cannot check permissions without a context - return Enumerable.Empty(); - } - var svc = _userService.Value; - - string path; - if (source.HasIdentity) - path = source.Path; - else - { - var parent = _contentService.Value.GetById(source.ParentId); - path = parent == null ? "-1" : parent.Path; - } - - var permissions = svc.GetPermissionsForPath( - //TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is - // with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null - // refrence exception :( - UmbracoContext.Current.Security.CurrentUser, - path) - .GetAllPermissions(); - - return permissions; - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index f61d9adf77..83af05c464 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -5,45 +5,58 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; +using ContentVariation = Umbraco.Core.Models.ContentVariation; namespace Umbraco.Web.Models.Mapping { /// /// Creates a base generic ContentPropertyBasic from a Property /// - /// - internal class ContentPropertyBasicConverter : TypeConverter - where T : ContentPropertyBasic, new() + internal class ContentPropertyBasicConverter : ITypeConverter + where TDestination : ContentPropertyBasic, new() { - protected Lazy DataTypeService { get; private set; } + private readonly ILogger _logger; + private readonly PropertyEditorCollection _propertyEditors; + protected IDataTypeService DataTypeService { get; } - public ContentPropertyBasicConverter(Lazy dataTypeService) + public ContentPropertyBasicConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors) { + _logger = logger; + _propertyEditors = propertyEditors; DataTypeService = dataTypeService; } /// /// Assigns the PropertyEditor, Id, Alias and Value to the property /// - /// /// - protected override T ConvertCore(Property property) + public virtual TDestination Convert(Property property, TDestination dest, ResolutionContext context) { - var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); + var editor = _propertyEditors[property.PropertyType.PropertyEditorAlias]; if (editor == null) { - LogHelper.Error>( + _logger.Error>( "No property editor found, converting to a Label", new NullReferenceException("The property editor with alias " + property.PropertyType.PropertyEditorAlias + " does not exist")); - editor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias); + editor = _propertyEditors[Constants.PropertyEditors.Aliases.NoEdit]; } - var result = new T + + var languageId = context.GetLanguageId(); + + if (!languageId.HasValue && property.PropertyType.Variations == ContentVariation.CultureNeutral) + { + //a language Id needs to be set for a property type that can be varried by language + throw new InvalidOperationException($"No languageId found in mapping operation when one is required for the culture neutral property type {property.PropertyType.Alias}"); + } + + var result = new TDestination { Id = property.Id, - Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService.Value), - Alias = property.Alias, + Value = editor.GetValueEditor().ToEditor(property, DataTypeService, languageId), + Alias = property.Alias, PropertyEditor = editor, Editor = editor.Alias }; @@ -51,4 +64,4 @@ namespace Umbraco.Web.Models.Mapping return result; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs index 20ff209371..51a2dec819 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -1,8 +1,11 @@ using System; using System.Linq; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -13,33 +16,38 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter { - public ContentPropertyDisplayConverter(Lazy dataTypeService) - : base(dataTypeService) - { + private readonly ILocalizedTextService _textService; + public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) + : base(dataTypeService, logger, propertyEditors) + { + _textService = textService; } - - protected override ContentPropertyDisplay ConvertCore(Property originalProp) + public override ContentPropertyDisplay Convert(Property originalProp, ContentPropertyDisplay dest, ResolutionContext context) { - var display = base.ConvertCore(originalProp); + var display = base.Convert(originalProp, dest, context); - var dataTypeService = DataTypeService.Value; - var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProp.PropertyType.DataTypeDefinitionId); + var config = DataTypeService.GetDataType(originalProp.PropertyType.DataTypeId).Configuration; - //configure the editor for display with the pre-values - var valEditor = display.PropertyEditor.ValueEditor; - valEditor.ConfigureForDisplay(preVals); + // fixme - IDataValueEditor configuration - general issue + // GetValueEditor() returns a non-configured IDataValueEditor + // - for richtext and nested, configuration determines HideLabel, so we need to configure the value editor + // - could configuration also determines ValueType, everywhere? + // - does it make any sense to use a IDataValueEditor without configuring it? + + // configure the editor for display with configuration + var valEditor = display.PropertyEditor.GetValueEditor(config); //set the display properties after mapping display.Alias = originalProp.Alias; display.Description = originalProp.PropertyType.Description; display.Label = originalProp.PropertyType.Name; display.HideLabel = valEditor.HideLabel; - + //add the validation information display.Validation.Mandatory = originalProp.PropertyType.Mandatory; display.Validation.Pattern = originalProp.PropertyType.ValidationRegExp; - + if (display.PropertyEditor == null) { //display.Config = PreValueCollection.AsDictionary(preVals); @@ -50,11 +58,15 @@ namespace Umbraco.Web.Models.Mapping else { //let the property editor format the pre-values - display.Config = display.PropertyEditor.PreValueEditor.ConvertDbToEditor(display.PropertyEditor.DefaultPreValues, preVals); + display.Config = display.PropertyEditor.GetConfigurationEditor().ToValueEditor(config); display.View = valEditor.View; } + //Translate + display.Label = _textService.UmbracoDictionaryTranslate(display.Label); + display.Description = _textService.UmbracoDictionaryTranslate(display.Description); + return display; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs index 3a6e199f96..11ecc518c1 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs @@ -1,5 +1,6 @@ using System; -using Umbraco.Core; +using AutoMapper; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; @@ -12,27 +13,21 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter { - public ContentPropertyDtoConverter(Lazy dataTypeService) - : base(dataTypeService) - { - } + public ContentPropertyDtoConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors) + : base(dataTypeService, logger, propertyEditors) + { } - protected override ContentPropertyDto ConvertCore(Property originalProperty) + public override ContentPropertyDto Convert(Property originalProperty, ContentPropertyDto dest, ResolutionContext context) { - var propertyDto = base.ConvertCore(originalProperty); - - var dataTypeService = DataTypeService.Value; + var propertyDto = base.Convert(originalProperty, dest, context); propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; propertyDto.Description = originalProperty.PropertyType.Description; propertyDto.Label = originalProperty.PropertyType.Name; - - //TODO: We should be able to look both of these up at the same time! - propertyDto.DataType = dataTypeService.GetDataTypeDefinitionById(originalProperty.PropertyType.DataTypeDefinitionId); - propertyDto.PreValues = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProperty.PropertyType.DataTypeDefinitionId); + propertyDto.DataType = DataTypeService.GetDataType(originalProperty.PropertyType.DataTypeId); return propertyDto; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs new file mode 100644 index 0000000000..b249760053 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs @@ -0,0 +1,39 @@ +using AutoMapper; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) which is + /// why they are in their own mapper + /// + internal class ContentPropertyMapperProfile : Profile + { + public ContentPropertyMapperProfile(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) + { + var contentPropertyBasicConverter = new ContentPropertyBasicConverter(dataTypeService, logger, propertyEditors); + var contentPropertyDtoConverter = new ContentPropertyDtoConverter(dataTypeService, logger, propertyEditors); + var contentPropertyDisplayConverter = new ContentPropertyDisplayConverter(dataTypeService, textService, logger, propertyEditors); + + //FROM Property TO ContentPropertyBasic + CreateMap>() + .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) + .ForMember(tab => tab.IsActive, expression => expression.UseValue(true)) + .ForMember(tab => tab.Properties, expression => expression.Ignore()) + .ForMember(tab => tab.Alias, expression => expression.Ignore()); + + //FROM Property TO ContentPropertyBasic + CreateMap().ConvertUsing(contentPropertyBasicConverter); + + //FROM Property TO ContentPropertyDto + CreateMap().ConvertUsing(contentPropertyDtoConverter); + + //FROM Property TO ContentPropertyDisplay + CreateMap().ConvertUsing(contentPropertyDisplayConverter); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs deleted file mode 100644 index ba51eb1790..0000000000 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) which is - /// why they are in their own mapper - /// - internal class ContentPropertyModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - var lazyDataTypeService = new Lazy(() => applicationContext.Services.DataTypeService); - - //FROM Property TO ContentPropertyBasic - config.CreateMap>() - .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) - .ForMember(tab => tab.IsActive, expression => expression.UseValue(true)) - .ForMember(tab => tab.Properties, expression => expression.Ignore()) - .ForMember(tab => tab.Alias, expression => expression.Ignore()); - - //FROM Property TO ContentPropertyBasic - config.CreateMap() - .ConvertUsing(new ContentPropertyBasicConverter(lazyDataTypeService)); - - //FROM Property TO ContentPropertyDto - config.CreateMap() - .ConvertUsing(new ContentPropertyDtoConverter(lazyDataTypeService)); - - //FROM Property TO ContentPropertyDisplay - config.CreateMap() - .ConvertUsing(new ContentPropertyDisplayConverter(lazyDataTypeService)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs new file mode 100644 index 0000000000..48400f51f6 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs @@ -0,0 +1,24 @@ +using System.Web.Mvc; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Gets the tree node url for the content or media + /// + internal class ContentTreeNodeUrlResolver : IValueResolver + where TSource : IContentBase + where TController : ContentTreeControllerBase + { + public string Resolve(TSource source, object destination, string destMember, ResolutionContext context) + { + var umbracoContext = context.GetUmbracoContext(throwIfMissing: false); + if (umbracoContext == null) return null; + + var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext); + return urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeBasicResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeBasicResolver.cs new file mode 100644 index 0000000000..01ff458bd5 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeBasicResolver.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using System.Web; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Resolves a from the item and checks if the current user + /// has access to see this data + /// + internal class ContentTypeBasicResolver : IValueResolver + where TSource : IContentBase + { + public ContentTypeBasic Resolve(TSource source, TDestination destination, ContentTypeBasic destMember, ResolutionContext context) + { + //TODO: We can resolve the UmbracoContext from the IValueResolver options! + // OMG + if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null + && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + ContentTypeBasic contentTypeBasic; + if (source is IContent content) + contentTypeBasic = Mapper.Map(content.ContentType); + else if (source is IMedia media) + contentTypeBasic = Mapper.Map(media.ContentType); + else + throw new NotSupportedException($"Expected TSource to be IContent or IMedia, got {typeof(TSource).Name}."); + + return contentTypeBasic; + } + //no access + return null; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs new file mode 100644 index 0000000000..c9e9554b93 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs @@ -0,0 +1,260 @@ +using System; +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Defines mappings for content/media/members type mappings + /// + internal class ContentTypeMapperProfile : Profile + { + public ContentTypeMapperProfile(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService, IContentTypeService contentTypeService, IMediaTypeService mediaTypeService) + { + CreateMap() + //do the base mapping + .MapBaseContentTypeSaveToEntity() + .ConstructUsing((source) => new ContentType(source.ParentId)) + .ForMember(source => source.AllowedTemplates, opt => opt.Ignore()) + .ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore()) + .AfterMap((source, dest) => + { + dest.AllowedTemplates = source.AllowedTemplates + .Where(x => x != null) + .Select(s => fileService.GetTemplate(s)) + .ToArray(); + + if (source.DefaultTemplate != null) + dest.SetDefaultTemplate(fileService.GetTemplate(source.DefaultTemplate)); + else + dest.SetDefaultTemplate(null); + + ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, contentTypeService); + }); + + CreateMap() + //do the base mapping + .MapBaseContentTypeSaveToEntity() + .ConstructUsing((source) => new MediaType(source.ParentId)) + .AfterMap((source, dest) => + { + ContentTypeProfileExtensions.AfterMapMediaTypeSaveToEntity(source, dest, mediaTypeService); + }); + + CreateMap() + //do the base mapping + .MapBaseContentTypeSaveToEntity() + .ConstructUsing(source => new MemberType(source.ParentId)) + .AfterMap((source, dest) => + { + ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, contentTypeService); + + //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData + foreach (var propertyType in source.Groups.SelectMany(x => x.Properties)) + { + var localCopy = propertyType; + var destProp = dest.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias)); + if (destProp != null) + { + dest.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty); + dest.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty); + dest.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData); + } + } + }); + + CreateMap().ConvertUsing(dest => dest.Alias); + + CreateMap() + //map base logic + .MapBaseContentTypeEntityToDisplay(propertyEditors, dataTypeService, contentTypeService) + .AfterMap((memberType, display) => + { + //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData + foreach (var propertyType in memberType.PropertyTypes) + { + var localCopy = propertyType; + var displayProp = display.Groups.SelectMany(dest => dest.Properties).SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias)); + if (displayProp != null) + { + displayProp.MemberCanEditProperty = memberType.MemberCanEditProperty(localCopy.Alias); + displayProp.MemberCanViewProperty = memberType.MemberCanViewProperty(localCopy.Alias); + displayProp.IsSensitiveData = memberType.IsSensitiveProperty(localCopy.Alias); + } + } + }); + + CreateMap() + //map base logic + .MapBaseContentTypeEntityToDisplay(propertyEditors, dataTypeService, contentTypeService) + .AfterMap((source, dest) => + { + //default listview + dest.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media"; + + if (string.IsNullOrEmpty(source.Name) == false) + { + var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name; + if (dataTypeService.GetDataType(name) != null) + dest.ListViewEditorName = name; + } + }); + + CreateMap() + //map base logic + .MapBaseContentTypeEntityToDisplay(propertyEditors, dataTypeService, contentTypeService) + .ForMember(dto => dto.AllowedTemplates, opt => opt.Ignore()) + .ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore()) + .ForMember(display => display.Notifications, opt => opt.Ignore()) + .AfterMap((source, dest) => + { + //sync templates + dest.AllowedTemplates = source.AllowedTemplates.Select(Mapper.Map).ToArray(); + + if (source.DefaultTemplate != null) + dest.DefaultTemplate = Mapper.Map(source.DefaultTemplate); + + //default listview + dest.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content"; + + if (string.IsNullOrEmpty(source.Alias) == false) + { + var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias; + if (dataTypeService.GetDataType(name) != null) + dest.ListViewEditorName = name; + } + + }); + + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.MemberType, source.Key))) + .ForMember(dest => dest.Blueprints, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.MediaType, source.Key))) + .ForMember(dest => dest.Blueprints, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.DocumentType, source.Key))) + .ForMember(dest => dest.Blueprints, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + + CreateMap() + + .ConstructUsing(propertyTypeBasic => + { + var dataType = dataTypeService.GetDataType(propertyTypeBasic.DataTypeId); + if (dataType == null) throw new NullReferenceException("No data type found with id " + propertyTypeBasic.DataTypeId); + return new PropertyType(dataType, propertyTypeBasic.Alias); + }) + + .IgnoreEntityCommonProperties() + + .ForMember(dest => dest.IsPublishing, opt => opt.Ignore()) + + // see note above - have to do this here? + .ForMember(dest => dest.PropertyEditorAlias, opt => opt.Ignore()) + .ForMember(dest => dest.DeleteDate, opt => opt.Ignore()) + + .ForMember(dto => dto.Variations, opt => opt.Ignore()) // fixme - change when UI supports it! + + //only map if it is actually set + .ForMember(dest => dest.Id, opt => opt.Condition(source => source.Id > 0)) + //only map if it is actually set, if it's not set, it needs to be handled differently and will be taken care of in the + // IContentType.AddPropertyType + .ForMember(dest => dest.PropertyGroupId, opt => opt.Condition(source => source.GroupId > 0)) + .ForMember(dest => dest.PropertyGroupId, opt => opt.MapFrom(display => new Lazy(() => display.GroupId, false))) + .ForMember(dest => dest.Key, opt => opt.Ignore()) + .ForMember(dest => dest.HasIdentity, opt => opt.Ignore()) + //ignore because this is set in the ctor NOT ON UPDATE, STUPID! + //.ForMember(type => type.Alias, opt => opt.Ignore()) + //ignore because this is obsolete and shouldn't be used + .ForMember(dest => dest.DataTypeId, opt => opt.Ignore()) + .ForMember(dest => dest.Mandatory, opt => opt.MapFrom(source => source.Validation.Mandatory)) + .ForMember(dest => dest.ValidationRegExp, opt => opt.MapFrom(source => source.Validation.Pattern)) + .ForMember(dest => dest.DataTypeId, opt => opt.MapFrom(source => source.DataTypeId)) + .ForMember(dest => dest.Name, opt => opt.MapFrom(source => source.Label)); + + #region *** Used for mapping on top of an existing display object from a save object *** + + CreateMap() + .MapBaseContentTypeSaveToDisplay(); + + CreateMap() + .MapBaseContentTypeSaveToDisplay(); + + CreateMap() + .MapBaseContentTypeSaveToDisplay() + .ForMember(dto => dto.AllowedTemplates, opt => opt.Ignore()) + .ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore()) + .AfterMap((source, dest) => + { + //sync templates + var destAllowedTemplateAliases = dest.AllowedTemplates.Select(x => x.Alias); + //if the dest is set and it's the same as the source, then don't change + if (destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false) + { + var templates = fileService.GetTemplates(source.AllowedTemplates.ToArray()); + dest.AllowedTemplates = source.AllowedTemplates + .Select(x => Mapper.Map(templates.SingleOrDefault(t => t.Alias == x))) + .WhereNotNull() + .ToArray(); + } + + if (source.DefaultTemplate.IsNullOrWhiteSpace() == false) + { + //if the dest is set and it's the same as the source, then don't change + if (dest.DefaultTemplate == null || source.DefaultTemplate != dest.DefaultTemplate.Alias) + { + var template = fileService.GetTemplate(source.DefaultTemplate); + dest.DefaultTemplate = template == null ? null : Mapper.Map(template); + } + } + else + { + dest.DefaultTemplate = null; + } + }); + + //for doc types, media types + CreateMap, PropertyGroup>() + .MapPropertyGroupBasicToPropertyGroupPersistence, PropertyTypeBasic>(); + + //for members + CreateMap, PropertyGroup>() + .MapPropertyGroupBasicToPropertyGroupPersistence, MemberPropertyTypeBasic>(); + + //for doc types, media types + CreateMap, PropertyGroupDisplay>() + .MapPropertyGroupBasicToPropertyGroupDisplay, PropertyTypeBasic, PropertyTypeDisplay>(); + + //for members + CreateMap, PropertyGroupDisplay>() + .MapPropertyGroupBasicToPropertyGroupDisplay, MemberPropertyTypeBasic, MemberPropertyTypeDisplay>(); + + CreateMap() + .ForMember(g => g.Editor, opt => opt.Ignore()) + .ForMember(g => g.View, opt => opt.Ignore()) + .ForMember(g => g.Config, opt => opt.Ignore()) + .ForMember(g => g.ContentTypeId, opt => opt.Ignore()) + .ForMember(g => g.ContentTypeName, opt => opt.Ignore()) + .ForMember(g => g.Locked, exp => exp.Ignore()); + + CreateMap() + .ForMember(g => g.Editor, opt => opt.Ignore()) + .ForMember(g => g.View, opt => opt.Ignore()) + .ForMember(g => g.Config, opt => opt.Ignore()) + .ForMember(g => g.ContentTypeId, opt => opt.Ignore()) + .ForMember(g => g.ContentTypeName, opt => opt.Ignore()) + .ForMember(g => g.Locked, exp => exp.Ignore()); + + #endregion + + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs deleted file mode 100644 index 3daf22f172..0000000000 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs +++ /dev/null @@ -1,287 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Models.ContentEditing; -using System.Collections.Generic; -using AutoMapper.Internal; -using Umbraco.Core.Services; -using Property = umbraco.NodeFactory.Property; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Defines mappings for content/media/members type mappings - /// - internal class ContentTypeModelMapper : MapperConfiguration - { - private readonly Lazy _propertyEditorResolver; - - //default ctor - public ContentTypeModelMapper() - { - _propertyEditorResolver = new Lazy(() => PropertyEditorResolver.Current); - } - - //ctor can be used for testing - public ContentTypeModelMapper(Lazy propertyEditorResolver) - { - _propertyEditorResolver = propertyEditorResolver; - } - - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - - config.CreateMap() - .ConstructUsing(basic => new PropertyType(applicationContext.Services.DataTypeService.GetDataTypeDefinitionById(basic.DataTypeId))) - .IgnoreEntityCommonProperties() - .ForMember(type => type.ValidationRegExp, expression => expression.ResolveUsing(basic => basic.Validation.Pattern)) - .ForMember(type => type.Mandatory, expression => expression.ResolveUsing(basic => basic.Validation.Mandatory)) - .ForMember(type => type.Name, expression => expression.ResolveUsing(basic => basic.Label)) - .ForMember(type => type.DataTypeDefinitionId, expression => expression.ResolveUsing(basic => basic.DataTypeId)) - .ForMember(type => type.DataTypeId, expression => expression.Ignore()) - .ForMember(type => type.PropertyEditorAlias, expression => expression.Ignore()) - .ForMember(type => type.HelpText, expression => expression.Ignore()) - .ForMember(type => type.Key, expression => expression.Ignore()) - .ForMember(type => type.DeletedDate, expression => expression.Ignore()) - .ForMember(type => type.HasIdentity, expression => expression.Ignore()); - - config.CreateMap() - //do the base mapping - .MapBaseContentTypeSaveToEntity(applicationContext) - .ConstructUsing((source) => new ContentType(source.ParentId)) - .ForMember(source => source.AllowedTemplates, expression => expression.Ignore()) - .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) - .AfterMap((source, dest) => - { - dest.AllowedTemplates = source.AllowedTemplates - .Where(x => x != null) - .Select(s => applicationContext.Services.FileService.GetTemplate(s)) - .ToArray(); - - if (source.DefaultTemplate != null) - dest.SetDefaultTemplate(applicationContext.Services.FileService.GetTemplate(source.DefaultTemplate)); - else - dest.SetDefaultTemplate(null); - - ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, applicationContext); - }); - - config.CreateMap() - //do the base mapping - .MapBaseContentTypeSaveToEntity(applicationContext) - .ConstructUsing((source) => new MediaType(source.ParentId)) - .AfterMap((source, dest) => - { - ContentTypeModelMapperExtensions.AfterMapMediaTypeSaveToEntity(source, dest, applicationContext); - }); - - config.CreateMap() - //do the base mapping - .MapBaseContentTypeSaveToEntity(applicationContext) - .ConstructUsing((source) => new MemberType(source.ParentId)) - .AfterMap((source, dest) => - { - ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, applicationContext); - - //map the MemberCanEditProperty,MemberCanViewProperty - foreach (var propertyType in source.Groups.SelectMany(x => x.Properties)) - { - var localCopy = propertyType; - var destProp = dest.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias)); - if (destProp != null) - { - dest.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty); - dest.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty); - } - } - }); - - config.CreateMap().ConvertUsing(x => x.Alias); - - config.CreateMap() - //map base logic - .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver) - .AfterMap((memberType, display) => - { - //map the MemberCanEditProperty,MemberCanViewProperty - foreach (var propertyType in memberType.PropertyTypes) - { - var localCopy = propertyType; - var displayProp = display.Groups.SelectMany(x => x.Properties).SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias)); - if (displayProp != null) - { - displayProp.MemberCanEditProperty = memberType.MemberCanEditProperty(localCopy.Alias); - displayProp.MemberCanViewProperty = memberType.MemberCanViewProperty(localCopy.Alias); - } - } - }); - - config.CreateMap() - //map base logic - .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver) - .AfterMap((source, dest) => - { - //default listview - dest.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media"; - - if (string.IsNullOrEmpty(source.Name) == false) - { - var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name; - if (applicationContext.Services.DataTypeService.GetDataTypeDefinitionByName(name) != null) - dest.ListViewEditorName = name; - } - }); - - config.CreateMap() - //map base logic - .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver) - .ForMember(dto => dto.AllowedTemplates, expression => expression.Ignore()) - .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) - .ForMember(display => display.Notifications, expression => expression.Ignore()) - .AfterMap((source, dest) => - { - //sync templates - dest.AllowedTemplates = source.AllowedTemplates.Select(Mapper.Map).ToArray(); - - if (source.DefaultTemplate != null) - dest.DefaultTemplate = Mapper.Map(source.DefaultTemplate); - - //default listview - dest.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content"; - - if (string.IsNullOrEmpty(source.Alias) == false) - { - var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias; - if (applicationContext.Services.DataTypeService.GetDataTypeDefinitionByName(name) != null) - dest.ListViewEditorName = name; - } - - }); - - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MemberType, content.Key))) - .ForMember(x => x.Blueprints, expression => expression.Ignore()); - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.MediaType, content.Key))) - .ForMember(x => x.Blueprints, expression => expression.Ignore()); - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DocumentType, content.Key))) - .ForMember(x => x.Blueprints, expression => expression.Ignore()); - - config.CreateMap() - - .ConstructUsing(propertyTypeBasic => - { - var dataType = applicationContext.Services.DataTypeService.GetDataTypeDefinitionById(propertyTypeBasic.DataTypeId); - if (dataType == null) throw new NullReferenceException("No data type found with id " + propertyTypeBasic.DataTypeId); - return new PropertyType(dataType, propertyTypeBasic.Alias); - }) - - //only map if it is actually set - .ForMember(dest => dest.Id, expression => expression.Condition(source => source.Id > 0)) - .ForMember(dto => dto.CreateDate, expression => expression.Ignore()) - .ForMember(dto => dto.UpdateDate, expression => expression.Ignore()) - //only map if it is actually set, if it's not set, it needs to be handled differently and will be taken care of in the - // IContentType.AddPropertyType - .ForMember(dest => dest.PropertyGroupId, expression => expression.Condition(source => source.GroupId > 0)) - .ForMember(type => type.PropertyGroupId, expression => expression.MapFrom(display => new Lazy(() => display.GroupId, false))) - .ForMember(type => type.Key, expression => expression.Ignore()) - .ForMember(type => type.HelpText, expression => expression.Ignore()) - .ForMember(type => type.HasIdentity, expression => expression.Ignore()) - //ignore because this is set in the ctor NOT ON UPDATE, STUPID! - //.ForMember(type => type.Alias, expression => expression.Ignore()) - //ignore because this is obsolete and shouldn't be used - .ForMember(type => type.DataTypeId, expression => expression.Ignore()) - .ForMember(type => type.Mandatory, expression => expression.MapFrom(display => display.Validation.Mandatory)) - .ForMember(type => type.ValidationRegExp, expression => expression.MapFrom(display => display.Validation.Pattern)) - .ForMember(type => type.DataTypeDefinitionId, expression => expression.MapFrom(display => display.DataTypeId)) - .ForMember(type => type.Name, expression => expression.MapFrom(display => display.Label)); - - #region *** Used for mapping on top of an existing display object from a save object *** - - config.CreateMap() - .MapBaseContentTypeSaveToDisplay(); - - config.CreateMap() - .MapBaseContentTypeSaveToDisplay(); - - config.CreateMap() - .MapBaseContentTypeSaveToDisplay() - .ForMember(dto => dto.AllowedTemplates, expression => expression.Ignore()) - .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) - .AfterMap((source, dest) => - { - //sync templates - var destAllowedTemplateAliases = dest.AllowedTemplates.Select(x => x.Alias); - //if the dest is set and it's the same as the source, then don't change - if (destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false) - { - var templates = applicationContext.Services.FileService.GetTemplates(source.AllowedTemplates.ToArray()); - dest.AllowedTemplates = source.AllowedTemplates - .Select(x => Mapper.Map(templates.SingleOrDefault(t => t.Alias == x))) - .WhereNotNull() - .ToArray(); - } - - if (source.DefaultTemplate.IsNullOrWhiteSpace() == false) - { - //if the dest is set and it's the same as the source, then don't change - if (dest.DefaultTemplate == null || source.DefaultTemplate != dest.DefaultTemplate.Alias) - { - var template = applicationContext.Services.FileService.GetTemplate(source.DefaultTemplate); - dest.DefaultTemplate = template == null ? null : Mapper.Map(template); - } - } - else - { - dest.DefaultTemplate = null; - } - }); - - //for doc types, media types - config.CreateMap, PropertyGroup>() - .MapPropertyGroupBasicToPropertyGroupPersistence, PropertyTypeBasic>(); - - //for members - config.CreateMap, PropertyGroup>() - .MapPropertyGroupBasicToPropertyGroupPersistence, MemberPropertyTypeBasic>(); - - //for doc types, media types - config.CreateMap, PropertyGroupDisplay>() - .MapPropertyGroupBasicToPropertyGroupDisplay, PropertyTypeBasic, PropertyTypeDisplay>(); - - //for members - config.CreateMap, PropertyGroupDisplay>() - .MapPropertyGroupBasicToPropertyGroupDisplay, MemberPropertyTypeBasic, MemberPropertyTypeDisplay>(); - - config.CreateMap() - .ForMember(g => g.Editor, expression => expression.Ignore()) - .ForMember(g => g.View, expression => expression.Ignore()) - .ForMember(g => g.Config, expression => expression.Ignore()) - .ForMember(g => g.ContentTypeId, expression => expression.Ignore()) - .ForMember(g => g.ContentTypeName, expression => expression.Ignore()) - .ForMember(g => g.Locked, exp => exp.Ignore()); - - config.CreateMap() - .ForMember(g => g.Editor, expression => expression.Ignore()) - .ForMember(g => g.View, expression => expression.Ignore()) - .ForMember(g => g.Config, expression => expression.Ignore()) - .ForMember(g => g.ContentTypeId, expression => expression.Ignore()) - .ForMember(g => g.ContentTypeName, expression => expression.Ignore()) - .ForMember(g => g.Locked, exp => exp.Ignore()); - - #endregion - - - - - } - - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs deleted file mode 100644 index fea46ddedb..0000000000 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs +++ /dev/null @@ -1,338 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Used as a shared way to do the underlying mapping for content types base classes - /// - /// - /// We used to use 'Include' Automapper inheritance functionality and although this works, the unit test - /// to assert mappings fails which is an Automapper bug. So instead we will use an extension method for the mappings - /// to re-use mappings. - /// - internal static class ContentTypeModelMapperExtensions - { - - public static IMappingExpression MapPropertyGroupBasicToPropertyGroupPersistence( - this IMappingExpression mapping) - where TSource : PropertyGroupBasic - where TPropertyTypeBasic : PropertyTypeBasic - { - return mapping - .IgnoreEntityCommonProperties() - .ForMember(dest => dest.Id, map => map.Condition(source => source.Id > 0)) - .ForMember(dest => dest.Key, map => map.Ignore()) - .ForMember(dest => dest.HasIdentity, map => map.Ignore()) - .ForMember(dest => dest.DeletedDate, map => map.Ignore()) - .ForMember(dest => dest.PropertyTypes, map => map.Ignore()); - } - - public static IMappingExpression> MapPropertyGroupBasicToPropertyGroupDisplay( - this IMappingExpression> mapping) - where TSource : PropertyGroupBasic - where TPropertyTypeBasic : PropertyTypeBasic - where TPropertyTypeDisplay : PropertyTypeDisplay - { - return mapping - .ForMember(dest => dest.Id, expression => expression.Condition(source => source.Id > 0)) - .ForMember(g => g.ContentTypeId, expression => expression.Ignore()) - .ForMember(g => g.ParentTabContentTypes, expression => expression.Ignore()) - .ForMember(g => g.ParentTabContentTypeNames, expression => expression.Ignore()) - .ForMember(g => g.Properties, expression => expression.MapFrom(display => display.Properties.Select(Mapper.Map))); - } - - public static void AfterMapContentTypeSaveToEntity( - TSource source, TDestination dest, - ApplicationContext applicationContext) - where TSource : ContentTypeSave - where TDestination : IContentTypeComposition - { - //sync compositions - var current = dest.CompositionAliases().ToArray(); - var proposed = source.CompositeContentTypes; - - var remove = current.Where(x => proposed.Contains(x) == false); - var add = proposed.Where(x => current.Contains(x) == false); - - foreach (var rem in remove) - { - dest.RemoveContentType(rem); - } - - foreach (var a in add) - { - //TODO: Remove N+1 lookup - var addCt = applicationContext.Services.ContentTypeService.GetContentType(a); - if (addCt != null) - dest.AddContentType(addCt); - } - } - - public static void AfterMapMediaTypeSaveToEntity( - TSource source, TDestination dest, - ApplicationContext applicationContext) - where TSource : MediaTypeSave - where TDestination : IContentTypeComposition - { - //sync compositions - var current = dest.CompositionAliases().ToArray(); - var proposed = source.CompositeContentTypes; - - var remove = current.Where(x => proposed.Contains(x) == false); - var add = proposed.Where(x => current.Contains(x) == false); - - foreach (var rem in remove) - { - dest.RemoveContentType(rem); - } - - foreach (var a in add) - { - //TODO: Remove N+1 lookup - var addCt = applicationContext.Services.ContentTypeService.GetMediaType(a); - if (addCt != null) - dest.AddContentType(addCt); - } - } - - public static IMappingExpression MapBaseContentTypeSaveToDisplay( - this IMappingExpression mapping) - where TSource : ContentTypeSave - where TDestination : ContentTypeCompositionDisplay - where TPropertyTypeDestination : PropertyTypeDisplay - where TPropertyTypeSource : PropertyTypeBasic - { - return mapping - .ForMember(dto => dto.CreateDate, expression => expression.Ignore()) - .ForMember(dto => dto.UpdateDate, expression => expression.Ignore()) - .ForMember(dto => dto.ListViewEditorName, expression => expression.Ignore()) - .ForMember(dto => dto.Notifications, expression => expression.Ignore()) - .ForMember(dto => dto.Errors, expression => expression.Ignore()) - .ForMember(dto => dto.LockedCompositeContentTypes, exp => exp.Ignore()) - .ForMember(dto => dto.Groups, expression => expression.ResolveUsing(new PropertyGroupDisplayResolver())); - } - - public static IMappingExpression MapBaseContentTypeEntityToDisplay( - this IMappingExpression mapping, ApplicationContext applicationContext, Lazy propertyEditorResolver) - where TSource : IContentTypeComposition - where TDestination : ContentTypeCompositionDisplay - where TPropertyTypeDisplay : PropertyTypeDisplay, new() - { - return mapping - .ForMember(x => x.Udi, expression => expression.ResolveUsing(new ContentTypeUdiResolver())) - .ForMember(display => display.Notifications, expression => expression.Ignore()) - .ForMember(display => display.Blueprints, expression => expression.Ignore()) - .ForMember(display => display.Errors, expression => expression.Ignore()) - .ForMember(display => display.AllowAsRoot, expression => expression.MapFrom(type => type.AllowedAsRoot)) - .ForMember(display => display.ListViewEditorName, expression => expression.Ignore()) - //Ignore because this is not actually used for content types - .ForMember(display => display.Trashed, expression => expression.Ignore()) - - .ForMember( - dto => dto.AllowedContentTypes, - expression => expression.MapFrom(dto => dto.AllowedContentTypes.Select(x => x.Id.Value))) - - .ForMember( - dto => dto.CompositeContentTypes, - expression => expression.MapFrom(dto => dto.ContentTypeComposition)) - - .ForMember( - dto => dto.LockedCompositeContentTypes, - expression => expression.ResolveUsing(new LockedCompositionsResolver(applicationContext))) - - .ForMember( - dto => dto.Groups, - expression => expression.ResolveUsing(new PropertyTypeGroupResolver(applicationContext, propertyEditorResolver))); - } - - /// - /// Display -> Entity class base mapping logic - /// - /// - /// - /// - /// - /// - /// - public static IMappingExpression MapBaseContentTypeSaveToEntity( - this IMappingExpression mapping, ApplicationContext applicationContext) - //where TSource : ContentTypeCompositionDisplay - where TSource : ContentTypeSave - where TDestination : IContentTypeComposition - where TSourcePropertyType : PropertyTypeBasic - { - return mapping - //only map id if set to something higher then zero - .ForMember(dto => dto.Id, expression => expression.Condition(display => (Convert.ToInt32(display.Id) > 0))) - .ForMember(dto => dto.Id, expression => expression.MapFrom(display => Convert.ToInt32(display.Id))) - - //These get persisted as part of the saving procedure, nothing to do with the display model - .IgnoreDeletableEntityCommonProperties() - - .ForMember(dto => dto.AllowedAsRoot, expression => expression.MapFrom(display => display.AllowAsRoot)) - .ForMember(dto => dto.CreatorId, expression => expression.Ignore()) - .ForMember(dto => dto.Level, expression => expression.Ignore()) - .ForMember(dto => dto.SortOrder, expression => expression.Ignore()) - //ignore, we'll do this in after map - .ForMember(dto => dto.PropertyGroups, expression => expression.Ignore()) - .ForMember(dto => dto.NoGroupPropertyTypes, expression => expression.Ignore()) - // ignore, composition is managed in AfterMapContentTypeSaveToEntity - .ForMember(dest => dest.ContentTypeComposition, opt => opt.Ignore()) - - .ForMember( - dto => dto.AllowedContentTypes, - expression => expression.MapFrom(dto => dto.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)))) - - .AfterMap((source, dest) => - { - // handle property groups and property types - // note that ContentTypeSave has - // - all groups, inherited and local; only *one* occurence per group *name* - // - potentially including the generic properties group - // - all properties, inherited and local - // - // also, see PropertyTypeGroupResolver.ResolveCore: - // - if a group is local *and* inherited, then Inherited is true - // and the identifier is the identifier of the *local* group - // - // IContentTypeComposition AddPropertyGroup, AddPropertyType methods do some - // unique-alias-checking, etc that is *not* compatible with re-mapping everything - // the way we do it here, so we should exclusively do it by - // - managing a property group's PropertyTypes collection - // - managing the content type's PropertyTypes collection (for generic properties) - - // handle actual groups (non-generic-properties) - var destOrigGroups = dest.PropertyGroups.ToArray(); // local groups - var destOrigProperties = dest.PropertyTypes.ToArray(); // all properties, in groups or not - var destGroups = new List(); - var sourceGroups = source.Groups.Where(x => x.IsGenericProperties == false).ToArray(); - foreach (var sourceGroup in sourceGroups) - { - // get the dest group - var destGroup = MapSaveGroup(sourceGroup, destOrigGroups); - - // handle local properties - var destProperties = sourceGroup.Properties - .Where(x => x.Inherited == false) - .Select(x => MapSaveProperty(x, destOrigProperties)) - .ToArray(); - - // if the group has no local properties, skip it, ie sort-of garbage-collect - // local groups which would not have local properties anymore - if (destProperties.Length == 0) - continue; - - // ensure no duplicate alias, then assign the group properties collection - EnsureUniqueAliases(destProperties); - destGroup.PropertyTypes = new PropertyTypeCollection(destProperties); - destGroups.Add(destGroup); - } - - // ensure no duplicate name, then assign the groups collection - EnsureUniqueNames(destGroups); - dest.PropertyGroups = new PropertyGroupCollection(destGroups); - - // because the property groups collection was rebuilt, there is no need to remove - // the old groups - they are just gone and will be cleared by the repository - - // handle non-grouped (ie generic) properties - var genericPropertiesGroup = source.Groups.FirstOrDefault(x => x.IsGenericProperties); - if (genericPropertiesGroup != null) - { - // handle local properties - var destProperties = genericPropertiesGroup.Properties - .Where(x => x.Inherited == false) - .Select(x => MapSaveProperty(x, destOrigProperties)) - .ToArray(); - - // ensure no duplicate alias, then assign the generic properties collection - EnsureUniqueAliases(destProperties); - dest.NoGroupPropertyTypes = new PropertyTypeCollection(destProperties); - } - - // because all property collections were rebuilt, there is no need to remove - // some old properties, they are just gone and will be cleared by the repository - }); - } - - private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, IEnumerable destOrigGroups) - where TPropertyType : PropertyTypeBasic - { - PropertyGroup destGroup; - if (sourceGroup.Id > 0) - { - // update an existing group - // ensure it is still there, then map/update - destGroup = destOrigGroups.FirstOrDefault(x => x.Id == sourceGroup.Id); - if (destGroup != null) - { - Mapper.Map(sourceGroup, destGroup); - return destGroup; - } - - // force-clear the ID as it does not match anything - sourceGroup.Id = 0; - } - - // insert a new group, or update an existing group that has - // been deleted in the meantime and we need to re-create - // map/create - destGroup = Mapper.Map(sourceGroup); - return destGroup; - } - - private static PropertyType MapSaveProperty(PropertyTypeBasic sourceProperty, IEnumerable destOrigProperties) - { - PropertyType destProperty; - if (sourceProperty.Id > 0) - { - // updateg an existing property - // ensure it is still there, then map/update - destProperty = destOrigProperties.FirstOrDefault(x => x.Id == sourceProperty.Id); - if (destProperty != null) - { - Mapper.Map(sourceProperty, destProperty); - return destProperty; - } - - // force-clear the ID as it does not match anything - sourceProperty.Id = 0; - } - - // insert a new property, or update an existing property that has - // been deletedin the meantime and we need to re-create - // map/create - destProperty = Mapper.Map(sourceProperty); - return destProperty; - } - - private static void EnsureUniqueAliases(IEnumerable properties) - { - var propertiesA = properties.ToArray(); - var distinctProperties = propertiesA - .Select(x => x.Alias.ToUpperInvariant()) - .Distinct() - .Count(); - if (distinctProperties != propertiesA.Length) - throw new InvalidOperationException("Cannot map properties due to alias conflict."); - } - - private static void EnsureUniqueNames(IEnumerable groups) - { - var groupsA = groups.ToArray(); - var distinctProperties = groupsA - .Select(x => x.Name.ToUpperInvariant()) - .Distinct() - .Count(); - if (distinctProperties != groupsA.Length) - throw new InvalidOperationException("Cannot map groups due to name conflict."); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs new file mode 100644 index 0000000000..d67eaf8ea2 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Used as a shared way to do the underlying mapping for content types base classes + /// + /// + /// We used to use 'Include' Automapper inheritance functionality and although this works, the unit test + /// to assert mappings fails which is an Automapper bug. So instead we will use an extension method for the mappings + /// to re-use mappings. + /// + internal static class ContentTypeProfileExtensions + { + public static IMappingExpression MapPropertyGroupBasicToPropertyGroupPersistence( + this IMappingExpression mapping) + where TSource : PropertyGroupBasic + where TPropertyTypeBasic : PropertyTypeBasic + { + return mapping + .ConstructUsing(x => new PropertyGroup(false)) // fixme - we have NO idea of isPublishing here = wtf? + .IgnoreEntityCommonProperties() + .ForMember(dest => dest.Id, map => map.Condition(src => src.Id > 0)) + .ForMember(dest => dest.Key, map => map.Ignore()) + .ForMember(dest => dest.HasIdentity, map => map.Ignore()) + .ForMember(dest => dest.DeleteDate, map => map.Ignore()) + .ForMember(dest => dest.PropertyTypes, map => map.Ignore()); + } + + public static IMappingExpression> MapPropertyGroupBasicToPropertyGroupDisplay( + this IMappingExpression> mapping) + where TSource : PropertyGroupBasic + where TPropertyTypeBasic : PropertyTypeBasic + where TPropertyTypeDisplay : PropertyTypeDisplay + { + return mapping + .ForMember(dest => dest.Id, opt => opt.Condition(src => src.Id > 0)) + .ForMember(dest => dest.ContentTypeId, opt => opt.Ignore()) + .ForMember(dest => dest.ParentTabContentTypes, opt => opt.Ignore()) + .ForMember(dest => dest.ParentTabContentTypeNames, opt => opt.Ignore()) + .ForMember(dest => dest.Properties, opt => opt.MapFrom(src => src.Properties.Select(Mapper.Map))); + } + + public static void AfterMapContentTypeSaveToEntity(TSource source, TDestination dest, IContentTypeService contentTypeService) + where TSource : ContentTypeSave + where TDestination : IContentTypeComposition + { + //sync compositions + var current = dest.CompositionAliases().ToArray(); + var proposed = source.CompositeContentTypes; + + var remove = current.Where(x => proposed.Contains(x) == false); + var add = proposed.Where(x => current.Contains(x) == false); + + foreach (var rem in remove) + { + dest.RemoveContentType(rem); + } + + foreach (var a in add) + { + //TODO: Remove N+1 lookup + var addCt = contentTypeService.Get(a); + if (addCt != null) + dest.AddContentType(addCt); + } + } + + public static void AfterMapMediaTypeSaveToEntity(TSource source, TDestination dest, IMediaTypeService mediaTypeService) + where TSource : MediaTypeSave + where TDestination : IContentTypeComposition + { + //sync compositions + var current = dest.CompositionAliases().ToArray(); + var proposed = source.CompositeContentTypes; + + var remove = current.Where(x => proposed.Contains(x) == false); + var add = proposed.Where(x => current.Contains(x) == false); + + foreach (var rem in remove) + { + dest.RemoveContentType(rem); + } + + foreach (var a in add) + { + //TODO: Remove N+1 lookup + var addCt = mediaTypeService.Get(a); + if (addCt != null) + dest.AddContentType(addCt); + } + } + + public static IMappingExpression MapBaseContentTypeSaveToDisplay( + this IMappingExpression mapping) + where TSource : ContentTypeSave + where TDestination : ContentTypeCompositionDisplay + where TPropertyTypeDestination : PropertyTypeDisplay + where TPropertyTypeSource : PropertyTypeBasic + { + var propertyGroupDisplayResolver = new PropertyGroupDisplayResolver(); + + return mapping + .ForMember(dest => dest.CreateDate, opt => opt.Ignore()) + .ForMember(dest => dest.UpdateDate, opt => opt.Ignore()) + .ForMember(dest => dest.ListViewEditorName, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Errors, opt => opt.Ignore()) + .ForMember(dest => dest.LockedCompositeContentTypes, opt => opt.Ignore()) + .ForMember(dest => dest.Groups, opt => opt.ResolveUsing(src => propertyGroupDisplayResolver.Resolve(src))); + } + + public static IMappingExpression MapBaseContentTypeEntityToDisplay( + this IMappingExpression mapping, PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, IContentTypeService contentTypeService) + where TSource : IContentTypeComposition + where TDestination : ContentTypeCompositionDisplay + where TPropertyTypeDisplay : PropertyTypeDisplay, new() + { + var contentTypeUdiResolver = new ContentTypeUdiResolver(); + var lockedCompositionsResolver = new LockedCompositionsResolver(contentTypeService); + var propertyTypeGroupResolver = new PropertyTypeGroupResolver(propertyEditors, dataTypeService); + + return mapping + .ForMember(dest => dest.Udi, opt => opt.ResolveUsing(src => contentTypeUdiResolver.Resolve(src))) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Blueprints, opt => opt.Ignore()) + .ForMember(dest => dest.Errors, opt => opt.Ignore()) + .ForMember(dest => dest.AllowAsRoot, opt => opt.MapFrom(src => src.AllowedAsRoot)) + .ForMember(dest => dest.ListViewEditorName, opt => opt.Ignore()) + //Ignore because this is not actually used for content types + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + + .ForMember(dest => dest.AllowedContentTypes, opt => opt.MapFrom(src => src.AllowedContentTypes.Select(x => x.Id.Value))) + .ForMember(dest => dest.CompositeContentTypes, opt => opt.MapFrom(src => src.ContentTypeComposition)) + .ForMember(dest => dest.LockedCompositeContentTypes, opt => opt.ResolveUsing(src => lockedCompositionsResolver.Resolve(src))) + .ForMember(dest => dest.Groups, opt => opt.ResolveUsing(src => propertyTypeGroupResolver.Resolve(src))) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + } + + /// + /// Display -> Entity class base mapping logic + /// + /// + /// + /// + /// + /// + public static IMappingExpression MapBaseContentTypeSaveToEntity( + this IMappingExpression mapping) + //where TSource : ContentTypeCompositionDisplay + where TSource : ContentTypeSave + where TDestination : IContentTypeComposition + where TSourcePropertyType : PropertyTypeBasic + { + // fixme not so clean really + var isPublishing = typeof(IContentType).IsAssignableFrom(typeof(TDestination)); + + return mapping + //only map id if set to something higher then zero + .ForMember(dest => dest.Id, opt => opt.Condition(src => (Convert.ToInt32(src.Id) > 0))) + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => Convert.ToInt32(src.Id))) + + //These get persisted as part of the saving procedure, nothing to do with the display model + .IgnoreEntityCommonProperties() + + .ForMember(dest => dest.AllowedAsRoot, opt => opt.MapFrom(src => src.AllowAsRoot)) + .ForMember(dest => dest.CreatorId, opt => opt.Ignore()) + .ForMember(dest => dest.Level, opt => opt.Ignore()) + .ForMember(dest => dest.SortOrder, opt => opt.Ignore()) + //ignore, we'll do this in after map + .ForMember(dest => dest.PropertyGroups, opt => opt.Ignore()) + .ForMember(dest => dest.NoGroupPropertyTypes, opt => opt.Ignore()) + // ignore, composition is managed in AfterMapContentTypeSaveToEntity + .ForMember(dest => dest.ContentTypeComposition, opt => opt.Ignore()) + + .ForMember(dto => dto.Variations, opt => opt.Ignore()) // fixme - change when UI supports it! + + .ForMember( + dest => dest.AllowedContentTypes, + opt => opt.MapFrom(src => src.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)))) + + .AfterMap((src, dest) => + { + // handle property groups and property types + // note that ContentTypeSave has + // - all groups, inherited and local; only *one* occurence per group *name* + // - potentially including the generic properties group + // - all properties, inherited and local + // + // also, see PropertyTypeGroupResolver.ResolveCore: + // - if a group is local *and* inherited, then Inherited is true + // and the identifier is the identifier of the *local* group + // + // IContentTypeComposition AddPropertyGroup, AddPropertyType methods do some + // unique-alias-checking, etc that is *not* compatible with re-mapping everything + // the way we do it here, so we should exclusively do it by + // - managing a property group's PropertyTypes collection + // - managing the content type's PropertyTypes collection (for generic properties) + + // handle actual groups (non-generic-properties) + var destOrigGroups = dest.PropertyGroups.ToArray(); // local groups + var destOrigProperties = dest.PropertyTypes.ToArray(); // all properties, in groups or not + var destGroups = new List(); + var sourceGroups = src.Groups.Where(x => x.IsGenericProperties == false).ToArray(); + foreach (var sourceGroup in sourceGroups) + { + // get the dest group + var destGroup = MapSaveGroup(sourceGroup, destOrigGroups); + + // handle local properties + var destProperties = sourceGroup.Properties + .Where(x => x.Inherited == false) + .Select(x => MapSaveProperty(x, destOrigProperties)) + .ToArray(); + + // if the group has no local properties, skip it, ie sort-of garbage-collect + // local groups which would not have local properties anymore + if (destProperties.Length == 0) + continue; + + // ensure no duplicate alias, then assign the group properties collection + EnsureUniqueAliases(destProperties); + destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); + destGroups.Add(destGroup); + } + + // ensure no duplicate name, then assign the groups collection + EnsureUniqueNames(destGroups); + dest.PropertyGroups = new PropertyGroupCollection(destGroups); + + // because the property groups collection was rebuilt, there is no need to remove + // the old groups - they are just gone and will be cleared by the repository + + // handle non-grouped (ie generic) properties + var genericPropertiesGroup = src.Groups.FirstOrDefault(x => x.IsGenericProperties); + if (genericPropertiesGroup != null) + { + // handle local properties + var destProperties = genericPropertiesGroup.Properties + .Where(x => x.Inherited == false) + .Select(x => MapSaveProperty(x, destOrigProperties)) + .ToArray(); + + // ensure no duplicate alias, then assign the generic properties collection + EnsureUniqueAliases(destProperties); + dest.NoGroupPropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); + } + + // because all property collections were rebuilt, there is no need to remove + // some old properties, they are just gone and will be cleared by the repository + }); + } + + private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, IEnumerable destOrigGroups) + where TPropertyType : PropertyTypeBasic + { + PropertyGroup destGroup; + if (sourceGroup.Id > 0) + { + // update an existing group + // ensure it is still there, then map/update + destGroup = destOrigGroups.FirstOrDefault(x => x.Id == sourceGroup.Id); + if (destGroup != null) + { + Mapper.Map(sourceGroup, destGroup); + return destGroup; + } + + // force-clear the ID as it does not match anything + sourceGroup.Id = 0; + } + + // insert a new group, or update an existing group that has + // been deleted in the meantime and we need to re-create + // map/create + destGroup = Mapper.Map(sourceGroup); + return destGroup; + } + + private static PropertyType MapSaveProperty(PropertyTypeBasic sourceProperty, IEnumerable destOrigProperties) + { + PropertyType destProperty; + if (sourceProperty.Id > 0) + { + // updateg an existing property + // ensure it is still there, then map/update + destProperty = destOrigProperties.FirstOrDefault(x => x.Id == sourceProperty.Id); + if (destProperty != null) + { + Mapper.Map(sourceProperty, destProperty); + return destProperty; + } + + // force-clear the ID as it does not match anything + sourceProperty.Id = 0; + } + + // insert a new property, or update an existing property that has + // been deletedin the meantime and we need to re-create + // map/create + destProperty = Mapper.Map(sourceProperty); + return destProperty; + } + + private static void EnsureUniqueAliases(IEnumerable properties) + { + var propertiesA = properties.ToArray(); + var distinctProperties = propertiesA + .Select(x => x.Alias.ToUpperInvariant()) + .Distinct() + .Count(); + if (distinctProperties != propertiesA.Length) + throw new InvalidOperationException("Cannot map properties due to alias conflict."); + } + + private static void EnsureUniqueNames(IEnumerable groups) + { + var groupsA = groups.ToArray(); + var distinctProperties = groupsA + .Select(x => x.Name.ToUpperInvariant()) + .Distinct() + .Count(); + if (distinctProperties != groupsA.Length) + throw new InvalidOperationException("Cannot map groups due to name conflict."); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs index 2d33b17155..b819a38064 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs @@ -1,5 +1,4 @@ -using AutoMapper; -using Umbraco.Core; +using Umbraco.Core; using Umbraco.Core.Models; namespace Umbraco.Web.Models.Mapping @@ -7,9 +6,9 @@ namespace Umbraco.Web.Models.Mapping /// /// Resolves a UDI for a content type based on it's type /// - internal class ContentTypeUdiResolver : ValueResolver + internal class ContentTypeUdiResolver { - protected override Udi ResolveCore(IContentTypeComposition source) + public Udi Resolve(IContentTypeComposition source) { if (source == null) return null; @@ -20,4 +19,4 @@ namespace Umbraco.Web.Models.Mapping ? Constants.UdiEntityType.MediaType : Constants.UdiEntityType.DocumentType, source.Key); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentUrlResolver.cs new file mode 100644 index 0000000000..24b82b4dd3 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentUrlResolver.cs @@ -0,0 +1,22 @@ +using System.Linq; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class ContentUrlResolver : IValueResolver + { + public string[] Resolve(IContent source, ContentItemDisplay destination, string[] destMember, ResolutionContext context) + { + var umbracoContext = context.GetUmbracoContext(); + + var urls = umbracoContext == null + ? new[] {"Cannot generate urls without a current Umbraco Context"} + : source.GetContentUrls(umbracoContext).ToArray(); + + return urls; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContextMapper.cs b/src/Umbraco.Web/Models/Mapping/ContextMapper.cs new file mode 100644 index 0000000000..b46cd219d8 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContextMapper.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using AutoMapper; +using ClientDependency.Core; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Extends AutoMapper's class to handle Umbraco's context and other contextual data + /// + internal static class ContextMapper + { + public const string UmbracoContextKey = "ContextMapper.UmbracoContext"; + public const string LanguageKey = "ContextMapper.LanguageId"; + + public static TDestination Map(TSource obj, UmbracoContext umbracoContext) + => Mapper.Map(obj, opt => opt.Items[UmbracoContextKey] = umbracoContext); + + public static TDestination Map(TSource obj, UmbracoContext umbracoContext, IDictionary contextVals) + => Mapper.Map(obj, opt => + { + //set the umb ctx + opt.Items[UmbracoContextKey] = umbracoContext; + //set other supplied context vals + if (contextVals != null) + { + foreach (var contextVal in contextVals) + { + opt.Items[contextVal.Key] = contextVal.Value; + } + } + }); + + public static TDestination Map(TSource obj, UmbracoContext umbracoContext, object contextVals) + => Mapper.Map(obj, opt => + { + //set the umb ctx + opt.Items[UmbracoContextKey] = umbracoContext; + //set other supplied context vals + if (contextVals != null) + { + foreach (var contextVal in contextVals.ToDictionary()) + { + opt.Items[contextVal.Key] = contextVal.Value; + } + } + }); + + public static TDestination Map(TSource obj, IDictionary contextVals) + => Mapper.Map(obj, opt => + { + //set other supplied context vals + if (contextVals != null) + { + foreach (var contextVal in contextVals) + { + opt.Items[contextVal.Key] = contextVal.Value; + } + } + }); + + public static TDestination Map(TSource obj, object contextVals) + => Mapper.Map(obj, opt => + { + //set other supplied context vals + if (contextVals != null) + { + foreach (var contextVal in contextVals.ToDictionary()) + { + opt.Items[contextVal.Key] = contextVal.Value; + } + } + }); + + /// + /// Returns the language Id in the mapping context if one is found + /// + /// + /// + public static int? GetLanguageId(this ResolutionContext resolutionContext) + { + if (!resolutionContext.Options.Items.TryGetValue(LanguageKey, out var obj)) return null; + + if (obj is int i) + return i; + + return null; + } + + /// + /// Returns the in the mapping context if one is found + /// + /// + /// + /// + public static UmbracoContext GetUmbracoContext(this ResolutionContext resolutionContext, bool throwIfMissing = true) + { + if (resolutionContext.Options.Items.TryGetValue(UmbracoContextKey, out var obj) && obj is UmbracoContext umbracoContext) + return umbracoContext; + + // better fail fast + if (throwIfMissing) + throw new InvalidOperationException("AutoMapper ResolutionContext does not contain an UmbracoContext."); + + // fixme - not a good idea at all + // because this falls back to magic singletons + // so really we should remove this line, but then some tests+app breaks ;( + return Umbraco.Web.Composing.Current.UmbracoContext; + } + } +} + diff --git a/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs b/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs index 28e9799861..0874c2cd4f 100644 --- a/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/CreatorResolver.cs @@ -1,7 +1,7 @@ using AutoMapper; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; +using Umbraco.Core.Services; using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; namespace Umbraco.Web.Models.Mapping @@ -9,11 +9,18 @@ namespace Umbraco.Web.Models.Mapping /// /// Maps the Creator for content /// - internal class CreatorResolver : ValueResolver + internal class CreatorResolver { - protected override UserProfile ResolveCore(IContent source) + private readonly IUserService _userService; + + public CreatorResolver(IUserService userService) { - return Mapper.Map(source.GetWriterProfile()); + _userService = userService; + } + + public UserProfile Resolve(IContent source) + { + return Mapper.Map(source.GetWriterProfile(_userService)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs new file mode 100644 index 0000000000..9cdbd6101e --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Web.Composing; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class DataTypeConfigurationFieldDisplayResolver + { + /// + /// Maps pre-values in the dictionary to the values for the fields + /// + internal static void MapConfigurationFields(DataTypeConfigurationFieldDisplay[] fields, IDictionary configuration) + { + if (fields == null) throw new ArgumentNullException(nameof(fields)); + if (configuration == null) throw new ArgumentNullException(nameof(configuration)); + + // now we need to wire up the pre-values values with the actual fields defined + foreach (var field in fields) + { + if (configuration.TryGetValue(field.Key, out var value)) + field.Value = value; + else // weird - just leave the field without a value - but warn + Current.Logger.Warn($"Could not find a value for configuration field \"{field.Key}\"."); + } + } + + /// + /// Creates a set of configuration fields for a data type. + /// + public IEnumerable Resolve(IDataType dataType) + { + // in v7 it was apparently fine to have an empty .EditorAlias here, in which case we would map onto + // an empty fields list, which made no sense since there would be nothing to map to - and besides, + // a datatype without an editor alias is a serious issue - v8 wants an editor here + + if (string.IsNullOrWhiteSpace(dataType.EditorAlias) || !Current.PropertyEditors.TryGet(dataType.EditorAlias, out var editor)) + throw new InvalidOperationException($"Could not find a property editor with alias \"{dataType.EditorAlias}\"."); + + var configurationEditor = editor.GetConfigurationEditor(); + var fields = configurationEditor.Fields.Select(Mapper.Map).ToArray(); + var configurationDictionary = configurationEditor.ToConfigurationEditor(dataType.Configuration); + + MapConfigurationFields(fields, configurationDictionary); + + return fields; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs new file mode 100644 index 0000000000..92a0be1346 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using AutoMapper; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Composing; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Configures model mappings for datatypes. + /// + internal class DataTypeMapperProfile : Profile + { + public DataTypeMapperProfile(PropertyEditorCollection propertyEditors) + { + // create, capture, cache + var availablePropertyEditorsResolver = new AvailablePropertyEditorsResolver(UmbracoConfig.For.UmbracoSettings().Content); + var configurationDisplayResolver = new DataTypeConfigurationFieldDisplayResolver(); + var databaseTypeResolver = new DatabaseTypeResolver(); + + CreateMap(); + + // map the standard properties, not the values + CreateMap() + .ForMember(dest => dest.Value, opt => opt.Ignore()); + + var systemIds = new[] + { + Constants.DataTypes.DefaultContentListView, + Constants.DataTypes.DefaultMediaListView, + Constants.DataTypes.DefaultMembersListView + }; + + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.HasPrevalues, opt => opt.Ignore()) + .ForMember(dest => dest.IsSystemDataType, opt => opt.Ignore()) + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.Key, opt => opt.Ignore()) + .ForMember(dest => dest.ParentId, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.DataType, src.Key))) + .ForMember(dest => dest.HasPrevalues, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Group, opt => opt.Ignore()) + .ForMember(dest => dest.IsSystemDataType, opt => opt.MapFrom(src => systemIds.Contains(src.Id))) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .AfterMap((src, dest) => + { + if (Current.PropertyEditors.TryGet(src.EditorAlias, out var editor)) + { + dest.Alias = editor.Alias; + dest.Group = editor.Group; + dest.Icon = editor.Icon; + } + }); + + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.DataType, src.Key))) + .ForMember(dest => dest.AvailableEditors, opt => opt.ResolveUsing(src => availablePropertyEditorsResolver.Resolve(src))) + .ForMember(dest => dest.PreValues, opt => opt.ResolveUsing(src => configurationDisplayResolver.Resolve(src))) + .ForMember(dest => dest.SelectedEditor, opt => opt.MapFrom(src => src.EditorAlias.IsNullOrWhiteSpace() ? null : src.EditorAlias)) + .ForMember(dest => dest.HasPrevalues, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Group, opt => opt.Ignore()) + .ForMember(dest => dest.IsSystemDataType, opt => opt.MapFrom(src => systemIds.Contains(src.Id))) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .AfterMap((src, dest) => + { + if (Current.PropertyEditors.TryGet(src.EditorAlias, out var editor)) + { + dest.Group = editor.Group; + dest.Icon = editor.Icon; + } + }); + + //gets a list of PreValueFieldDisplay objects from the data type definition + CreateMap>() + .ConvertUsing(src => configurationDisplayResolver.Resolve(src)); + + CreateMap() + .ConstructUsing(src => new DataType(propertyEditors[src.EditorAlias]) {CreateDate = DateTime.Now}) + .IgnoreEntityCommonProperties() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => Convert.ToInt32(src.Id))) + .ForMember(dest => dest.Key, opt => opt.Ignore()) // ignore key, else resets UniqueId - U4-3911 + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.EditorAlias, opt => opt.MapFrom(src => src.EditorAlias)) + .ForMember(dest => dest.DatabaseType, opt => opt.ResolveUsing(src => databaseTypeResolver.Resolve(src))) + .ForMember(dest => dest.CreatorId, opt => opt.Ignore()) + .ForMember(dest => dest.Level, opt => opt.Ignore()) + .ForMember(dest => dest.SortOrder, opt => opt.Ignore()) + .ForMember(dest => dest.Configuration, opt => opt.Ignore()) + .ForMember(dest => dest.Editor, opt => opt.MapFrom(src => propertyEditors[src.EditorAlias])); + + //Converts a property editor to a new list of pre-value fields - used when creating a new data type or changing a data type with new pre-vals + CreateMap>() + .ConvertUsing(src => + { + // this is a new data type, initialize default configuration + // get the configuration editor, + // get the configuration fields and map to UI, + // get the configuration default values and map to UI + + var configurationEditor = src.GetConfigurationEditor(); + + var fields = configurationEditor.Fields.Select(Mapper.Map).ToArray(); + + var defaultConfiguration = configurationEditor.DefaultConfiguration; + if (defaultConfiguration != null) + DataTypeConfigurationFieldDisplayResolver.MapConfigurationFields(fields, defaultConfiguration); + + return fields; + }); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs deleted file mode 100644 index 32d25028e4..0000000000 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.Collections.Generic; -using AutoMapper; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Configure's model mappings for Data types - /// - internal class DataTypeModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - var lazyDataTypeService = new Lazy(() => applicationContext.Services.DataTypeService); - - config.CreateMap(); - - //just maps the standard properties, does not map the value! - config.CreateMap() - .ForMember(x => x.Value, expression => expression.Ignore()); - - var systemIds = new[] - { - Constants.System.DefaultContentListViewDataTypeId, - Constants.System.DefaultMediaListViewDataTypeId, - Constants.System.DefaultMembersListViewDataTypeId - }; - - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.Ignore()) - .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) - .ForMember(x => x.IsSystemDataType, expression => expression.Ignore()) - .ForMember(x => x.Id, expression => expression.Ignore()) - .ForMember(x => x.Trashed, expression => expression.Ignore()) - .ForMember(x => x.Key, expression => expression.Ignore()) - .ForMember(x => x.ParentId, expression => expression.Ignore()) - .ForMember(x => x.Path, expression => expression.Ignore()) - .ForMember(x => x.AdditionalData, expression => expression.Ignore()); - - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DataType, content.Key))) - .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) - .ForMember(x => x.Icon, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(x => x.Group, expression => expression.Ignore()) - .ForMember(x => x.IsSystemDataType, expression => expression.MapFrom(definition => systemIds.Contains(definition.Id))) - .AfterMap((def, basic) => - { - var editor = PropertyEditorResolver.Current.GetByAlias(def.PropertyEditorAlias); - if (editor != null) - { - basic.Alias = editor.Alias; - basic.Group = editor.Group; - basic.Icon = editor.Icon; - } - }); - - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DataType, content.Key))) - .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing(new AvailablePropertyEditorsResolver(UmbracoConfig.For.UmbracoSettings().Content))) - .ForMember(display => display.PreValues, expression => expression.ResolveUsing( - new PreValueDisplayResolver(lazyDataTypeService))) - .ForMember(display => display.SelectedEditor, expression => expression.MapFrom( - definition => definition.PropertyEditorAlias.IsNullOrWhiteSpace() ? null : definition.PropertyEditorAlias)) - .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) - .ForMember(x => x.Notifications, expression => expression.Ignore()) - .ForMember(x => x.Icon, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(x => x.Group, expression => expression.Ignore()) - .ForMember(x => x.IsSystemDataType, expression => expression.MapFrom(definition => systemIds.Contains(definition.Id))) - .AfterMap((def, basic) => - { - var editor = PropertyEditorResolver.Current.GetByAlias(def.PropertyEditorAlias); - if (editor != null) - { - basic.Group = editor.Group; - basic.Icon = editor.Icon; - } - }); - - //gets a list of PreValueFieldDisplay objects from the data type definition - config.CreateMap>() - .ConvertUsing(definition => - { - var resolver = new PreValueDisplayResolver(lazyDataTypeService); - return resolver.Convert(definition); - }); - - config.CreateMap() - .ConstructUsing(save => new DataTypeDefinition(save.SelectedEditor) {CreateDate = DateTime.Now}) - .IgnoreDeletableEntityCommonProperties() - .ForMember(definition => definition.Id, expression => expression.MapFrom(save => Convert.ToInt32(save.Id))) - //we have to ignore the Key otherwise this will reset the UniqueId field which should never change! - // http://issues.umbraco.org/issue/U4-3911 - .ForMember(definition => definition.Key, expression => expression.Ignore()) - .ForMember(definition => definition.Path, expression => expression.Ignore()) - .ForMember(definition => definition.PropertyEditorAlias, expression => expression.MapFrom(save => save.SelectedEditor)) - .ForMember(definition => definition.DatabaseType, expression => expression.ResolveUsing(new DatabaseTypeResolver())) - .ForMember(x => x.ControlId, expression => expression.Ignore()) - .ForMember(x => x.CreatorId, expression => expression.Ignore()) - .ForMember(x => x.Level, expression => expression.Ignore()) - .ForMember(x => x.SortOrder, expression => expression.Ignore()); - - //Converts a property editor to a new list of pre-value fields - used when creating a new data type or changing a data type with new pre-vals - config.CreateMap>() - .ConvertUsing(editor => - { - //this is a new data type, so just return the field editors, there are no values yet - var defaultVals = editor.DefaultPreValues; - var fields = editor.PreValueEditor.Fields.Select(Mapper.Map).ToArray(); - if (defaultVals != null) - { - PreValueDisplayResolver.MapPreValueValuesToPreValueFields(fields, defaultVals); - } - return fields; - }); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs b/src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs index cdfbb97960..8cf32339f9 100644 --- a/src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs @@ -1,7 +1,7 @@ using System; -using AutoMapper; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping @@ -9,16 +9,16 @@ namespace Umbraco.Web.Models.Mapping /// /// Gets the DataTypeDatabaseType from the selected property editor for the data type /// - internal class DatabaseTypeResolver : ValueResolver + internal class DatabaseTypeResolver { - protected override DataTypeDatabaseType ResolveCore(DataTypeSave source) + public ValueStorageType Resolve(DataTypeSave source) { - var propertyEditor = PropertyEditorResolver.Current.GetByAlias(source.SelectedEditor); - if (propertyEditor == null) - { - throw new InvalidOperationException("Could not find property editor with id " + source.SelectedEditor); - } - return propertyEditor.ValueEditor.GetDatabaseType(); + if (!Current.PropertyEditors.TryGet(source.EditorAlias, out var editor)) + throw new InvalidOperationException($"Could not find property editor \"{source.EditorAlias}\"."); + + // fixme - what about source.PropertyEditor? can we get the configuration here? 'cos it may change the storage type?! + var valueType = editor.GetValueEditor().ValueType; + return ValueTypes.ToStorageType(valueType); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/DefaultTemplateResolver.cs b/src/Umbraco.Web/Models/Mapping/DefaultTemplateResolver.cs new file mode 100644 index 0000000000..ae32e7a691 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/DefaultTemplateResolver.cs @@ -0,0 +1,24 @@ +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class DefaultTemplateResolver : IValueResolver + { + public string Resolve(IContent source, ContentItemDisplay destination, string destMember, ResolutionContext context) + { + if (source == null || source.Template == null) return null; + + var alias = source.Template.Alias; + + //set default template if template isn't set + if (string.IsNullOrEmpty(alias)) + alias = source.ContentType.DefaultTemplate == null + ? string.Empty + : source.ContentType.DefaultTemplate.Alias; + + return alias; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs new file mode 100644 index 0000000000..917f61a429 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Examine; +using Examine.LuceneEngine.Providers; +using Examine.LuceneEngine; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Examine; + +namespace Umbraco.Web.Models.Mapping +{ + internal class EntityMapperProfile : Profile + { + private static string GetContentTypeIcon(EntitySlim entity) + => entity is ContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; + + public EntityMapperProfile() + { + // create, capture, cache + var contentTypeUdiResolver = new ContentTypeUdiResolver(); + + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(ObjectTypes.GetUdiType(src.NodeObjectType), src.Key))) + .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => GetContentTypeIcon(src))) + .ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed)) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .AfterMap((src, dest) => + { + if (src.NodeObjectType == Constants.ObjectTypes.Member && dest.Icon.IsNullOrWhiteSpace()) + { + dest.Icon = "icon-user"; + } + }); + + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.UseValue("icon-box")) + .ForMember(dest => dest.Path, opt => opt.UseValue("")) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.UseValue("icon-tab")) + .ForMember(dest => dest.Path, opt => opt.UseValue("")) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + //in v6 the 'alias' is it's lower cased name so we'll stick to that. + .ForMember(dest => dest.Alias, opt => opt.MapFrom(src => src.Name.ToLowerInvariant())) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.UseValue("icon-user")) + .ForMember(dest => dest.Path, opt => opt.UseValue("")) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Alias, opt => opt.MapFrom(src => src.Username)) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.Template, src.Key))) + .ForMember(dest => dest.Icon, opt => opt.UseValue("icon-layout")) + .ForMember(dest => dest.Path, opt => opt.MapFrom(src => src.Path)) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => new Lazy(() => Convert.ToInt32(src.Id)))) + .ForMember(dest => dest.SortOrder, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.ResolveUsing(src => contentTypeUdiResolver.Resolve(src))) + .ForMember(dest => dest.Path, opt => opt.MapFrom(src => src.Path)) + .ForMember(dest => dest.ParentId, opt => opt.MapFrom(src => src.ParentId)) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(ObjectTypes.GetUdiType(src.NodeObjectType), src.Key))) + .ForMember(dest => dest.Icon, opt => opt.MapFrom(src=> GetContentTypeIcon(src))) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Score, opt => opt.Ignore()) + .AfterMap((entity, basic) => + { + if (basic.Icon.IsNullOrWhiteSpace()) + { + if (entity.NodeObjectType == Constants.ObjectTypes.Member) + basic.Icon = "icon-user"; + else if (entity.NodeObjectType == Constants.ObjectTypes.DataType) + basic.Icon = "icon-autofill"; + else if (entity.NodeObjectType == Constants.ObjectTypes.DocumentType) + basic.Icon = "icon-item-arrangement"; + else if (entity.NodeObjectType == Constants.ObjectTypes.MediaType) + basic.Icon = "icon-thumbnails"; + else if (entity.NodeObjectType == Constants.ObjectTypes.TemplateType) + basic.Icon = "icon-newspaper-alt"; + } + }); + + CreateMap() + //default to document icon + .ForMember(dest => dest.Score, opt => opt.MapFrom(result => result.Score)) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.Name, opt => opt.Ignore()) + .ForMember(dest => dest.Key, opt => opt.Ignore()) + .ForMember(dest => dest.ParentId, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .AfterMap((src, dest) => + { + //get the icon if there is one + dest.Icon = src.Fields.ContainsKey(UmbracoExamineIndexer.IconFieldName) + ? src.Fields[UmbracoExamineIndexer.IconFieldName] + : "icon-document"; + + dest.Name = src.Fields.ContainsKey("nodeName") ? src.Fields["nodeName"] : "[no name]"; + if (src.Fields.ContainsKey(UmbracoExamineIndexer.NodeKeyFieldName)) + { + Guid key; + if (Guid.TryParse(src.Fields[UmbracoExamineIndexer.NodeKeyFieldName], out key)) + { + dest.Key = key; + + //need to set the UDI + if (src.Fields.ContainsKey(LuceneIndexer.CategoryFieldName)) + { + switch (src.Fields[LuceneIndexer.CategoryFieldName]) + { + case IndexTypes.Member: + dest.Udi = new GuidUdi(Constants.UdiEntityType.Member, dest.Key); + break; + case IndexTypes.Content: + dest.Udi = new GuidUdi(Constants.UdiEntityType.Document, dest.Key); + break; + case IndexTypes.Media: + dest.Udi = new GuidUdi(Constants.UdiEntityType.Media, dest.Key); + break; + } + } + } + } + + if (src.Fields.ContainsKey("parentID")) + { + int parentId; + if (int.TryParse(src.Fields["parentID"], out parentId)) + { + dest.ParentId = parentId; + } + else + { + dest.ParentId = -1; + } + } + dest.Path = src.Fields.ContainsKey(UmbracoExamineIndexer.IndexPathFieldName) ? src.Fields[UmbracoExamineIndexer.IndexPathFieldName] : ""; + + if (src.Fields.ContainsKey(LuceneIndexer.ItemTypeFieldName)) + { + dest.AdditionalData.Add("contentType", src.Fields[LuceneIndexer.ItemTypeFieldName]); + } + }); + + CreateMap>() + .ConvertUsing(results => results.Select(Mapper.Map).ToList()); + + CreateMap, IEnumerable>() + .ConvertUsing(results => results.Select(Mapper.Map).ToList()); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs b/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs deleted file mode 100644 index ec72c9839d..0000000000 --- a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Examine; -using Examine.LuceneEngine.Providers; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; -using UmbracoExamine; - -namespace Umbraco.Web.Models.Mapping -{ - internal class EntityModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(x => Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(x.NodeObjectTypeId), x.Key))) - .ForMember(basic => basic.Icon, expression => expression.MapFrom(entity => entity.ContentTypeIcon)) - .ForMember(dto => dto.Trashed, expression => expression.MapFrom(x => x.Trashed)) - .ForMember(x => x.Alias, expression => expression.Ignore()) - .AfterMap((entity, basic) => - { - if (entity.NodeObjectTypeId == Constants.ObjectTypes.MemberGuid && basic.Icon.IsNullOrWhiteSpace()) - { - basic.Icon = "icon-user"; - } - }); - - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.Ignore()) - .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-box")) - .ForMember(basic => basic.Path, expression => expression.UseValue("")) - .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(x => x.AdditionalData, expression => expression.Ignore()); - - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.Ignore()) - .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-tab")) - .ForMember(basic => basic.Path, expression => expression.UseValue("")) - .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) - //in v6 the 'alias' is it's lower cased name so we'll stick to that. - .ForMember(basic => basic.Alias, expression => expression.MapFrom(group => group.Name.ToLowerInvariant())) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(x => x.AdditionalData, expression => expression.Ignore()); - - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.Ignore()) - .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-user")) - .ForMember(basic => basic.Path, expression => expression.UseValue("")) - .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) - .ForMember(basic => basic.Alias, expression => expression.MapFrom(user => user.Username)) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(x => x.AdditionalData, expression => expression.Ignore()); - - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(x => Udi.Create(Constants.UdiEntityType.Template, x.Key))) - .ForMember(basic => basic.Icon, expression => expression.UseValue("icon-layout")) - .ForMember(basic => basic.Path, expression => expression.MapFrom(template => template.Path)) - .ForMember(basic => basic.ParentId, expression => expression.UseValue(-1)) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(x => x.AdditionalData, expression => expression.Ignore()); - - config.CreateMap() - .ForMember(x => x.Id, expression => expression.MapFrom(entity => new Lazy(() => Convert.ToInt32(entity.Id)))) - .ForMember(x => x.SortOrder, expression => expression.Ignore()); - - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.ResolveUsing(new ContentTypeUdiResolver())) - .ForMember(basic => basic.Path, expression => expression.MapFrom(x => x.Path)) - .ForMember(basic => basic.ParentId, expression => expression.MapFrom(x => x.ParentId)) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(x => x.AdditionalData, expression => expression.Ignore()); - - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(x => Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(x.NodeObjectTypeId), x.Key))) - .ForMember(basic => basic.Icon, expression => expression.MapFrom(entity => entity.ContentTypeIcon)) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(x => x.Score, expression => expression.Ignore()) - .AfterMap((entity, basic) => - { - if (basic.Icon.IsNullOrWhiteSpace()) - { - if (entity.NodeObjectTypeId == Constants.ObjectTypes.MemberGuid) - basic.Icon = "icon-user"; - else if (entity.NodeObjectTypeId == Constants.ObjectTypes.DataTypeGuid) - basic.Icon = "icon-autofill"; - else if (entity.NodeObjectTypeId == Constants.ObjectTypes.DocumentTypeGuid) - basic.Icon = "icon-item-arrangement"; - else if (entity.NodeObjectTypeId == Constants.ObjectTypes.MediaTypeGuid) - basic.Icon = "icon-thumbnails"; - else if (entity.NodeObjectTypeId == Constants.ObjectTypes.TemplateTypeGuid) - basic.Icon = "icon-newspaper-alt"; - } - }); - - config.CreateMap() - //default to document icon - .ForMember(x => x.Score, expression => expression.MapFrom(result => result.Score)) - .ForMember(x => x.Udi, expression => expression.Ignore()) - .ForMember(x => x.Icon, expression => expression.Ignore()) - .ForMember(x => x.Id, expression => expression.MapFrom(result => result.Id)) - .ForMember(x => x.Name, expression => expression.Ignore()) - .ForMember(x => x.Key, expression => expression.Ignore()) - .ForMember(x => x.ParentId, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(x => x.Path, expression => expression.Ignore()) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(x => x.AdditionalData, expression => expression.Ignore()) - .AfterMap((result, basic) => - { - - //get the icon if there is one - basic.Icon = result.Fields.ContainsKey(UmbracoContentIndexer.IconFieldName) - ? result.Fields[UmbracoContentIndexer.IconFieldName] - : "icon-document"; - - basic.Name = result.Fields.ContainsKey("nodeName") ? result.Fields["nodeName"] : "[no name]"; - if (result.Fields.ContainsKey(UmbracoContentIndexer.NodeKeyFieldName)) - { - Guid key; - if (Guid.TryParse(result.Fields[UmbracoContentIndexer.NodeKeyFieldName], out key)) - { - basic.Key = key; - - //need to set the UDI - if (result.Fields.ContainsKey(LuceneIndexer.IndexTypeFieldName)) - { - switch (result.Fields[LuceneIndexer.IndexTypeFieldName]) - { - case IndexTypes.Member: - basic.Udi = new GuidUdi(Constants.UdiEntityType.Member, basic.Key); - break; - case IndexTypes.Content: - basic.Udi = new GuidUdi(Constants.UdiEntityType.Document, basic.Key); - break; - case IndexTypes.Media: - basic.Udi = new GuidUdi(Constants.UdiEntityType.Media, basic.Key); - break; - } - } - } - } - - if (result.Fields.ContainsKey("parentID")) - { - int parentId; - if (int.TryParse(result.Fields["parentID"], out parentId)) - { - basic.ParentId = parentId; - } - else - { - basic.ParentId = -1; - } - } - basic.Path = result.Fields.ContainsKey(UmbracoContentIndexer.IndexPathFieldName) ? result.Fields[UmbracoContentIndexer.IndexPathFieldName] : ""; - - if (result.Fields.ContainsKey(UmbracoContentIndexer.NodeTypeAliasFieldName)) - { - basic.AdditionalData.Add("contentType", result.Fields[UmbracoContentIndexer.NodeTypeAliasFieldName]); - } - }); - - config.CreateMap>() - .ConvertUsing(results => results.Select(Mapper.Map).ToList()); - - config.CreateMap, IEnumerable>() - .ConvertUsing(results => results.Select(Mapper.Map).ToList()); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/EntityModelMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/EntityModelMapperExtensions.cs deleted file mode 100644 index 39f92af5d2..0000000000 --- a/src/Umbraco.Web/Models/Mapping/EntityModelMapperExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -using AutoMapper; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Mapping extension methods for re-use with other mappers (saves code duplication) - /// - internal static class EntityModelMapperExtensions - { - /// - /// Ignores readonly properties and the date values - /// - /// - /// - public static IMappingExpression IgnoreDeletableEntityCommonProperties( - this IMappingExpression mapping) - where TDest: IDeletableEntity - { - return mapping - .IgnoreEntityCommonProperties() - .ForMember(dest => dest.DeletedDate, map => map.Ignore()); - } - - /// - /// Ignores readonly properties and the date values - /// - /// - /// - public static IMappingExpression IgnoreEntityCommonProperties( - this IMappingExpression mapping) - where TDest : IEntity - { - return mapping - .IgnoreAllPropertiesWithAnInaccessibleSetter() - .ForMember(dest => dest.CreateDate, map => map.Ignore()) - .ForMember(dest => dest.UpdateDate, map => map.Ignore()); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/EntityProfileExtensions.cs b/src/Umbraco.Web/Models/Mapping/EntityProfileExtensions.cs new file mode 100644 index 0000000000..46d48c2b1a --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/EntityProfileExtensions.cs @@ -0,0 +1,26 @@ +using AutoMapper; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Mapping extension methods for re-use with other mappers (saves code duplication) + /// + internal static class EntityProfileExtensions + { + /// + /// Ignores readonly properties and the date values + /// + /// + /// + public static IMappingExpression IgnoreEntityCommonProperties(this IMappingExpression mapping) + where TDest : IEntity + { + return mapping + .IgnoreAllPropertiesWithAnInaccessibleSetter() + .ForMember(dest => dest.CreateDate, opt => opt.Ignore()) + .ForMember(dest => dest.UpdateDate, opt => opt.Ignore()) + .ForMember(dest => dest.DeleteDate, opt => opt.Ignore()); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs new file mode 100644 index 0000000000..b00a0949ec --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; +using Language = Umbraco.Web.Models.ContentEditing.Language; + +namespace Umbraco.Web.Models.Mapping +{ + internal class LanguageMapperProfile : Profile + { + public LanguageMapperProfile() + { + CreateMap() + .ForMember(l => l.Name, expression => expression.MapFrom(x => x.CultureInfo.DisplayName)); + + CreateMap, IEnumerable>() + .ConvertUsing(); + + } + + /// + /// Converts a list of to a list of and ensures the correct order and defaults are set + /// + // ReSharper disable once ClassNeverInstantiated.Local + private class LanguageCollectionTypeConverter : ITypeConverter, IEnumerable> + { + public IEnumerable Convert(IEnumerable source, IEnumerable destination, ResolutionContext context) + { + var allLanguages = source.OrderBy(x => x.Id).ToList(); + var langs = new List(allLanguages.Select(x => context.Mapper.Map(x, null, context))); + + //if there's only one language, by default it is the default + if (langs.Count == 1) + { + langs[0].IsDefaultVariantLanguage = true; + langs[0].Mandatory = true; + } + else if (allLanguages.All(x => !x.IsDefaultVariantLanguage)) + { + //if no language has the default flag, then the defaul language is the one with the lowest id + langs[0].IsDefaultVariantLanguage = true; + langs[0].Mandatory = true; + } + + return langs.OrderBy(x => x.Name); + } + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/LockedCompositionsResolver.cs b/src/Umbraco.Web/Models/Mapping/LockedCompositionsResolver.cs index d3d692d10d..f1dcd2d40f 100644 --- a/src/Umbraco.Web/Models/Mapping/LockedCompositionsResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/LockedCompositionsResolver.cs @@ -3,30 +3,31 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Services; namespace Umbraco.Web.Models.Mapping { - internal class LockedCompositionsResolver : ValueResolver> + internal class LockedCompositionsResolver { - private readonly ApplicationContext _applicationContext; + private readonly IContentTypeService _contentTypeService; - public LockedCompositionsResolver(ApplicationContext applicationContext) + public LockedCompositionsResolver(IContentTypeService contentTypeService) { - _applicationContext = applicationContext; + _contentTypeService = contentTypeService; } - protected override IEnumerable ResolveCore(IContentTypeComposition source) + public IEnumerable Resolve(IContentTypeComposition source) { var aliases = new List(); // get ancestor ids from path of parent if not root if (source.ParentId != Constants.System.Root) { - var parent = _applicationContext.Services.ContentTypeService.GetContentType(source.ParentId); + var parent = _contentTypeService.Get(source.ParentId); if (parent != null) { var ancestorIds = parent.Path.Split(',').Select(int.Parse); // loop through all content types and return ordered aliases of ancestors - var allContentTypes = _applicationContext.Services.ContentTypeService.GetAllContentTypes().ToArray(); + var allContentTypes = _contentTypeService.GetAll().ToArray(); foreach (var ancestorId in ancestorIds) { var ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId); diff --git a/src/Umbraco.Web/Models/Mapping/MacroMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MacroMapperProfile.cs new file mode 100644 index 0000000000..6eedccf50b --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MacroMapperProfile.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Web.Composing; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Declares model mappings for macros. + /// + internal class MacroMapperProfile : Profile + { + public MacroMapperProfile() + { + //FROM IMacro TO EntityBasic + CreateMap() + .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Macro, content.Key))) + .ForMember(entityBasic => entityBasic.Icon, expression => expression.UseValue("icon-settings-alt")) + .ForMember(dto => dto.ParentId, expression => expression.UseValue(-1)) + .ForMember(dto => dto.Path, expression => expression.ResolveUsing(macro => "-1," + macro.Id)) + .ForMember(dto => dto.Trashed, expression => expression.Ignore()) + .ForMember(dto => dto.AdditionalData, expression => expression.Ignore()); + + CreateMap>() + .ConvertUsing(macro => macro.Properties.Select(Mapper.Map).ToList()); + + CreateMap() + .ForMember(x => x.View, expression => expression.Ignore()) + .ForMember(x => x.Configuration, expression => expression.Ignore()) + .ForMember(x => x.Value, expression => expression.Ignore()) + .AfterMap((property, parameter) => + { + //map the view and the config + // we need to show the depracated ones for backwards compatibility + var paramEditor = Current.ParameterEditors[property.EditorAlias]; // fixme - include/filter deprecated?! + if (paramEditor == null) + { + //we'll just map this to a text box + paramEditor = Current.ParameterEditors[Constants.PropertyEditors.Aliases.TextBox]; + Current.Logger.Warn("Could not resolve a parameter editor with alias " + property.EditorAlias + ", a textbox will be rendered in it's place"); + } + + parameter.View = paramEditor.GetValueEditor().View; + //set the config + parameter.Configuration = paramEditor.DefaultConfiguration; + }); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/MacroModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MacroModelMapper.cs deleted file mode 100644 index 8ef4432cab..0000000000 --- a/src/Umbraco.Web/Models/Mapping/MacroModelMapper.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Declares model mappings for macros. - /// - internal class MacroModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - //FROM IMacro TO EntityBasic - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Macro, content.Key))) - .ForMember(entityBasic => entityBasic.Icon, expression => expression.UseValue("icon-settings-alt")) - .ForMember(dto => dto.ParentId, expression => expression.UseValue(-1)) - .ForMember(dto => dto.Path, expression => expression.ResolveUsing(macro => "-1," + macro.Id)) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(dto => dto.AdditionalData, expression => expression.Ignore()); - - config.CreateMap>() - .ConvertUsing(macro => macro.Properties.Select(Mapper.Map).ToList()); - - config.CreateMap() - .ForMember(x => x.View, expression => expression.Ignore()) - .ForMember(x => x.Configuration, expression => expression.Ignore()) - .ForMember(x => x.Value, expression => expression.Ignore()) - .AfterMap((property, parameter) => - { - //map the view and the config - // we need to show the depracated ones for backwards compatibility - var paramEditor = ParameterEditorResolver.Current.GetByAlias(property.EditorAlias, true); - if (paramEditor == null) - { - //we'll just map this to a text box - paramEditor = ParameterEditorResolver.Current.GetByAlias(Constants.PropertyEditors.TextboxAlias); - LogHelper.Warn("Could not resolve a parameter editor with alias " + property.EditorAlias + ", a textbox will be rendered in it's place"); - } - - parameter.View = paramEditor.ValueEditor.View; - //set the config - parameter.Configuration = paramEditor.Configuration; - - }); - - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/MediaChildOfListViewResolver.cs b/src/Umbraco.Web/Models/Mapping/MediaChildOfListViewResolver.cs new file mode 100644 index 0000000000..997c900f84 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MediaChildOfListViewResolver.cs @@ -0,0 +1,26 @@ +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class MediaChildOfListViewResolver : IValueResolver + { + private readonly IMediaService _mediaService; + private readonly IMediaTypeService _mediaTypeService; + + public MediaChildOfListViewResolver(IMediaService mediaService, IMediaTypeService mediaTypeService) + { + _mediaService = mediaService; + _mediaTypeService = mediaTypeService; + } + + public bool Resolve(IMedia source, MediaItemDisplay destination, bool destMember, ResolutionContext context) + { + // map the IsChildOfListView (this is actually if it is a descendant of a list view!) + var parent = _mediaService.GetParent(source); + return parent != null && (parent.ContentType.IsContainer || _mediaTypeService.HasContainerInPath(parent.Path)); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs new file mode 100644 index 0000000000..540f1d509d --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs @@ -0,0 +1,76 @@ +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Declares model mappings for media. + /// + internal class MediaMapperProfile : Profile + { + public MediaMapperProfile(IUserService userService, ILocalizedTextService textService, IDataTypeService dataTypeService, IMediaService mediaService, IMediaTypeService mediaTypeService, ILogger logger) + { + // create, capture, cache + var mediaOwnerResolver = new OwnerResolver(userService); + var tabsAndPropertiesResolver = new TabsAndPropertiesResolver(textService); + var childOfListViewResolver = new MediaChildOfListViewResolver(mediaService, mediaTypeService); + var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver(); + var mediaTypeBasicResolver = new ContentTypeBasicResolver(); + + //FROM IMedia TO MediaItemDisplay + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(content => Udi.Create(Constants.UdiEntityType.Media, content.Key))) + .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => mediaOwnerResolver.Resolve(src))) + .ForMember(dest => dest.Icon, opt => opt.MapFrom(content => content.ContentType.Icon)) + .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(content => content.ContentType.Alias)) + .ForMember(dest => dest.IsChildOfListView, opt => opt.ResolveUsing(childOfListViewResolver)) + .ForMember(dest => dest.Trashed, opt => opt.MapFrom(content => content.Trashed)) + .ForMember(dest => dest.ContentTypeName, opt => opt.MapFrom(content => content.ContentType.Name)) + .ForMember(dest => dest.Properties, opt => opt.Ignore()) + .ForMember(dest => dest.TreeNodeUrl, opt => opt.ResolveUsing(contentTreeNodeUrlResolver)) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Errors, opt => opt.Ignore()) + .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.Updater, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.IsContainer, opt => opt.Ignore()) + .ForMember(dest => dest.Tabs, opt => opt.ResolveUsing(tabsAndPropertiesResolver)) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .ForMember(dest => dest.ContentType, opt => opt.ResolveUsing(mediaTypeBasicResolver)) + .ForMember(dest => dest.MediaLink, opt => opt.ResolveUsing(content => string.Join(",", content.GetUrls(UmbracoConfig.For.UmbracoSettings().Content, logger)))) + .AfterMap((media, display) => + { + if (media.ContentType.IsContainer) + TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, textService); + }); + + //FROM IMedia TO ContentItemBasic + CreateMap>() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.Media, src.Key))) + .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => mediaOwnerResolver.Resolve(src))) + .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon)) + .ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed)) + .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) + .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.Updater, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + + //FROM IMedia TO ContentItemDto + CreateMap>() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.Media, src.Key))) + .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => mediaOwnerResolver.Resolve(src))) + .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.Updater, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs deleted file mode 100644 index 157205eb51..0000000000 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Trees; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Declares model mappings for media. - /// - internal class MediaModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - //FROM IMedia TO MediaItemDisplay - config.CreateMap() - .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Media, content.Key))) - .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) - .ForMember(display => display.Trashed, expression => expression.MapFrom(content => content.Trashed)) - .ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name)) - .ForMember(display => display.Properties, expression => expression.Ignore()) - .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) - .ForMember(display => display.Notifications, expression => expression.Ignore()) - .ForMember(display => display.Errors, expression => expression.Ignore()) - .ForMember(display => display.Published, expression => expression.Ignore()) - .ForMember(display => display.Updater, expression => expression.Ignore()) - .ForMember(display => display.Alias, expression => expression.Ignore()) - .ForMember(display => display.IsContainer, expression => expression.Ignore()) - .ForMember(display => display.HasPublishedVersion, expression => expression.Ignore()) - .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) - .ForMember(display => display.ContentType, expression => expression.ResolveUsing()) - .ForMember(display => display.MediaLink, expression => expression.ResolveUsing( - content => string.Join(",", content.GetUrls(UmbracoConfig.For.UmbracoSettings().Content, applicationContext.ProfilingLogger.Logger)))) - .AfterMap((media, display) => AfterMap(media, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, applicationContext.Services.ContentTypeService, applicationContext.ProfilingLogger.Logger)); - - //FROM IMedia TO ContentItemBasic - config.CreateMap>() - .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Media, content.Key))) - .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember(dto => dto.Trashed, expression => expression.MapFrom(content => content.Trashed)) - .ForMember(dto => dto.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember(dto => dto.Published, expression => expression.Ignore()) - .ForMember(dto => dto.Updater, expression => expression.Ignore()) - .ForMember(dto => dto.Alias, expression => expression.Ignore()) - .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); - - //FROM IMedia TO ContentItemDto - config.CreateMap>() - .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Media, content.Key))) - .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember(dto => dto.Published, expression => expression.Ignore()) - .ForMember(dto => dto.Updater, expression => expression.Ignore()) - .ForMember(dto => dto.Icon, expression => expression.Ignore()) - .ForMember(dto => dto.Alias, expression => expression.Ignore()) - .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); - } - - private static void AfterMap(IMedia media, MediaItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, IContentTypeService contentTypeService, ILogger logger) - { - // Adapted from ContentModelMapper - //map the IsChildOfListView (this is actually if it is a descendant of a list view!) - var parent = media.Parent(); - display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path)); - - //map the tree node url - if (HttpContext.Current != null) - { - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Id.ToString(), null)); - display.TreeNodeUrl = url; - } - - if (media.ContentType.IsContainer) - { - TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, localizedText); - } - - TabsAndPropertiesResolver.MapGenericProperties(media, display, localizedText); - } - - /// - /// Resolves a from the item and checks if the current user - /// has access to see this data - /// - private class MediaTypeBasicResolver : ValueResolver - { - protected override ContentTypeBasic ResolveCore(IMedia source) - { - //TODO: This would be much nicer with the IUmbracoContextAccessor so we don't use singletons - //If this is a web request and there's a user signed in and the - // user has access to the settings section, we will - if (HttpContext.Current != null && UmbracoContext.Current != null && - UmbracoContext.Current.Security.CurrentUser != null - && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants - .Applications.Settings))) - { - var contentTypeBasic = Mapper.Map(source.ContentType); - return contentTypeBasic; - } - //no access - return null; - } - - } - } -} diff --git a/src/Umbraco.Web/Models/Mapping/MemberBasicPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberBasicPropertiesResolver.cs new file mode 100644 index 0000000000..09d0657530 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MemberBasicPropertiesResolver.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// A resolver to map properties to a collection of + /// + internal class MemberBasicPropertiesResolver : IValueResolver> + { + public IEnumerable Resolve(IMember source, MemberBasic destination, IEnumerable destMember, ResolutionContext context) + { + var umbracoContext = context.GetUmbracoContext(); + + var result = Mapper.Map, IEnumerable>( + // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. + source.Properties.OrderBy(prop => prop.PropertyType.SortOrder)) + .ToList(); + + var memberType = source.ContentType; + + //now update the IsSensitive value + foreach (var prop in result) + { + //check if this property is flagged as sensitive + var isSensitiveProperty = memberType.IsSensitiveProperty(prop.Alias); + //check permissions for viewing sensitive data + if (isSensitiveProperty && umbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false) + { + //mark this property as sensitive + prop.IsSensitive = true; + //clear the value + prop.Value = null; + } + } + return result; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/MemberDtoPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberDtoPropertiesResolver.cs new file mode 100644 index 0000000000..0d6478d737 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MemberDtoPropertiesResolver.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// This ensures that the custom membership provider properties are not mapped - these property values are controller by the membership provider + /// + /// + /// Because these properties don't exist on the form, if we don't remove them for this map we'll get validation errors when posting data + /// + internal class MemberDtoPropertiesResolver + { + public IEnumerable Resolve(IMember source) + { + var defaultProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); + + //remove all membership properties, these values are set with the membership provider. + var exclude = defaultProps.Select(x => x.Value.Alias).ToArray(); + + return source.Properties + .Where(x => exclude.Contains(x.Alias) == false) + .Select(Mapper.Map); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs new file mode 100644 index 0000000000..eac93657f4 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs @@ -0,0 +1,141 @@ +using System; +using System.Web.Security; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Declares model mappings for members. + /// + internal class MemberMapperProfile : Profile + { + public MemberMapperProfile(IUserService userService, ILocalizedTextService textService, IMemberTypeService memberTypeService, IMemberService memberService) + { + // create, capture, cache + var memberOwnerResolver = new OwnerResolver(userService); + var tabsAndPropertiesResolver = new MemberTabsAndPropertiesResolver(textService, memberService, userService); + var memberProfiderFieldMappingResolver = new MemberProviderFieldResolver(); + var membershipScenarioMappingResolver = new MembershipScenarioResolver(memberTypeService); + var memberDtoPropertiesResolver = new MemberDtoPropertiesResolver(); + var memberTreeNodeUrlResolver = new MemberTreeNodeUrlResolver(); + var memberBasicPropertiesResolver = new MemberBasicPropertiesResolver(); + + //FROM MembershipUser TO MediaItemDisplay - used when using a non-umbraco membership provider + CreateMap().ConvertUsing(); + + //FROM MembershipUser TO IMember - used when using a non-umbraco membership provider + CreateMap() + .ConstructUsing(src => MemberService.CreateGenericMembershipProviderMember(src.UserName, src.Email, src.UserName, "")) + //we're giving this entity an ID of 0 - we cannot really map it but it needs an id so the system knows it's not a new entity + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => int.MaxValue)) + .ForMember(dest => dest.Comments, opt => opt.MapFrom(src => src.Comment)) + .ForMember(dest => dest.CreateDate, opt => opt.MapFrom(src => src.CreationDate)) + .ForMember(dest => dest.UpdateDate, opt => opt.MapFrom(src => src.LastActivityDate)) + .ForMember(dest => dest.LastPasswordChangeDate, opt => opt.MapFrom(src => src.LastPasswordChangedDate)) + .ForMember(dest => dest.Key, opt => opt.MapFrom(src => src.ProviderUserKey.TryConvertTo().Result.ToString("N"))) + //This is a special case for password - we don't actually care what the password is but it either needs to be something or nothing + // so we'll set it to something if the member is actually created, otherwise nothing if it is a new member. + .ForMember(dest => dest.RawPasswordValue, opt => opt.MapFrom(src => src.CreationDate > DateTime.MinValue ? Guid.NewGuid().ToString("N") : "")) + .ForMember(dest => dest.Properties, opt => opt.Ignore()) + .ForMember(dest => dest.CreatorId, opt => opt.Ignore()) + .ForMember(dest => dest.Level, opt => opt.Ignore()) + .ForMember(dest => dest.Name, opt => opt.Ignore()) + .ForMember(dest => dest.ParentId, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.SortOrder, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .ForMember(dest => dest.FailedPasswordAttempts, opt => opt.Ignore()) + .ForMember(dest => dest.DeleteDate, opt => opt.Ignore()) + .ForMember(dest => dest.WriterId, opt => opt.Ignore()) + //TODO: Support these eventually + .ForMember(dest => dest.PasswordQuestion, opt => opt.Ignore()) + .ForMember(dest => dest.RawPasswordAnswerValue, opt => opt.Ignore()); + + //FROM IMember TO MediaItemDisplay + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) + .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => memberOwnerResolver.Resolve(src))) + .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon)) + .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) + .ForMember(dest => dest.ContentTypeName, opt => opt.MapFrom(src => src.ContentType.Name)) + .ForMember(dest => dest.Properties, opt => opt.Ignore()) + .ForMember(dest => dest.Tabs, opt => opt.ResolveUsing(tabsAndPropertiesResolver)) + .ForMember(dest => dest.MemberProviderFieldMapping, opt => opt.ResolveUsing(src => memberProfiderFieldMappingResolver.Resolve(src))) + .ForMember(dest => dest.MembershipScenario, opt => opt.ResolveUsing(src => membershipScenarioMappingResolver.Resolve(src))) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Errors, opt => opt.Ignore()) + .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.Updater, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.IsChildOfListView, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.IsContainer, opt => opt.Ignore()) + .ForMember(dest => dest.TreeNodeUrl, opt => opt.ResolveUsing(memberTreeNodeUrlResolver)); + + //FROM IMember TO MemberBasic + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) + .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => memberOwnerResolver.Resolve(src))) + .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon)) + .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) + .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email)) + .ForMember(dest => dest.Username, opt => opt.MapFrom(src => src.Username)) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.Updater, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dto => dto.Properties, expression => expression.ResolveUsing(memberBasicPropertiesResolver)); + + //FROM MembershipUser TO MemberBasic + CreateMap() + //we're giving this entity an ID of 0 - we cannot really map it but it needs an id so the system knows it's not a new entity + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => int.MaxValue)) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.CreateDate, opt => opt.MapFrom(src => src.CreationDate)) + .ForMember(dest => dest.UpdateDate, opt => opt.MapFrom(src => src.LastActivityDate)) + .ForMember(dest => dest.Key, opt => opt.MapFrom(src => src.ProviderUserKey.TryConvertTo().Result.ToString("N"))) + .ForMember(dest => dest.Owner, opt => opt.UseValue(new UserProfile {Name = "Admin", UserId = 0})) + .ForMember(dest => dest.Icon, opt => opt.UseValue("icon-user")) + .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.UserName)) + .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email)) + .ForMember(dest => dest.Username, opt => opt.MapFrom(src => src.UserName)) + .ForMember(dest => dest.Properties, opt => opt.Ignore()) + .ForMember(dest => dest.ParentId, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.SortOrder, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.Updater, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.ContentTypeAlias, opt => opt.Ignore()); + + //FROM IMember TO ContentItemDto + CreateMap>() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) + .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => memberOwnerResolver.Resolve(src))) + .ForMember(dest => dest.Published, opt => opt.Ignore()) + .ForMember(dest => dest.Updater, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + //do no map the custom member properties (currently anyways, they were never there in 6.x) + .ForMember(dest => dest.Properties, opt => opt.ResolveUsing(src => memberDtoPropertiesResolver.Resolve(src))); + + //FROM IMemberGroup TO MemberGroupDisplay + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.MemberGroup, src.Key))) + .ForMember(dest => dest.Path, opt => opt.MapFrom(group => "-1," + group.Id)) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.ParentId, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs deleted file mode 100644 index e3c82cf782..0000000000 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ /dev/null @@ -1,447 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; -using System.Web.Security; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; -using System.Linq; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Security; -using Umbraco.Web.Trees; -using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Declares model mappings for members. - /// - internal class MemberModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - //FROM MembershipUser TO MediaItemDisplay - used when using a non-umbraco membership provider - config.CreateMap() - .ConvertUsing(user => - { - var member = Mapper.Map(user); - return Mapper.Map(member); - }); - - //FROM MembershipUser TO IMember - used when using a non-umbraco membership provider - config.CreateMap() - .ConstructUsing(user => MemberService.CreateGenericMembershipProviderMember(user.UserName, user.Email, user.UserName, "")) - //we're giving this entity an ID of 0 - we cannot really map it but it needs an id so the system knows it's not a new entity - .ForMember(member => member.Id, expression => expression.MapFrom(user => int.MaxValue)) - .ForMember(member => member.Comments, expression => expression.MapFrom(user => user.Comment)) - .ForMember(member => member.CreateDate, expression => expression.MapFrom(user => user.CreationDate)) - .ForMember(member => member.UpdateDate, expression => expression.MapFrom(user => user.LastActivityDate)) - .ForMember(member => member.LastPasswordChangeDate, expression => expression.MapFrom(user => user.LastPasswordChangedDate)) - .ForMember(member => member.Key, expression => expression.MapFrom(user => user.ProviderUserKey.TryConvertTo().Result.ToString("N"))) - //This is a special case for password - we don't actually care what the password is but it either needs to be something or nothing - // so we'll set it to something if the member is actually created, otherwise nothing if it is a new member. - .ForMember(member => member.RawPasswordValue, expression => expression.MapFrom(user => user.CreationDate > DateTime.MinValue ? Guid.NewGuid().ToString("N") : "")) - .ForMember(member => member.Properties, expression => expression.Ignore()) - .ForMember(member => member.CreatorId, expression => expression.Ignore()) - .ForMember(member => member.Level, expression => expression.Ignore()) - .ForMember(member => member.Name, expression => expression.Ignore()) - .ForMember(member => member.ParentId, expression => expression.Ignore()) - .ForMember(member => member.Path, expression => expression.Ignore()) - .ForMember(member => member.SortOrder, expression => expression.Ignore()) - .ForMember(member => member.AdditionalData, expression => expression.Ignore()) - .ForMember(member => member.FailedPasswordAttempts, expression => expression.Ignore()) - .ForMember(member => member.DeletedDate, expression => expression.Ignore()) - //TODO: Support these eventually - .ForMember(member => member.PasswordQuestion, expression => expression.Ignore()) - .ForMember(member => member.RawPasswordAnswerValue, expression => expression.Ignore()); - - //FROM IMember TO MediaItemDisplay - config.CreateMap() - .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) - .ForMember(display => display.Owner, expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember(display => display.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name)) - .ForMember(display => display.Properties, expression => expression.Ignore()) - .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new MemberTabsAndPropertiesResolver(applicationContext.Services.TextService))) - .ForMember(display => display.MemberProviderFieldMapping, expression => expression.ResolveUsing(new MemberProviderFieldMappingResolver())) - .ForMember(display => display.MembershipScenario, - expression => expression.ResolveUsing(new MembershipScenarioMappingResolver(new Lazy(() => applicationContext.Services.MemberTypeService)))) - .ForMember(display => display.Notifications, expression => expression.Ignore()) - .ForMember(display => display.Errors, expression => expression.Ignore()) - .ForMember(display => display.Published, expression => expression.Ignore()) - .ForMember(display => display.Updater, expression => expression.Ignore()) - .ForMember(display => display.Alias, expression => expression.Ignore()) - .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) - .ForMember(display => display.Trashed, expression => expression.Ignore()) - .ForMember(display => display.IsContainer, expression => expression.Ignore()) - .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) - .ForMember(display => display.HasPublishedVersion, expression => expression.Ignore()) - .AfterMap((member, display) => MapGenericCustomProperties(applicationContext.Services.MemberService, applicationContext.Services.UserService, member, display, applicationContext.Services.TextService)); - - //FROM IMember TO MemberBasic - config.CreateMap() - .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) - .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember(dto => dto.Icon, expression => expression.MapFrom(content => content.ContentType.Icon)) - .ForMember(dto => dto.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) - .ForMember(dto => dto.Email, expression => expression.MapFrom(content => content.Email)) - .ForMember(dto => dto.Username, expression => expression.MapFrom(content => content.Username)) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(dto => dto.Published, expression => expression.Ignore()) - .ForMember(dto => dto.Updater, expression => expression.Ignore()) - .ForMember(dto => dto.Alias, expression => expression.Ignore()) - .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); - - //FROM MembershipUser TO MemberBasic - config.CreateMap() - //we're giving this entity an ID of 0 - we cannot really map it but it needs an id so the system knows it's not a new entity - .ForMember(member => member.Id, expression => expression.MapFrom(user => int.MaxValue)) - .ForMember(display => display.Udi, expression => expression.Ignore()) - .ForMember(member => member.CreateDate, expression => expression.MapFrom(user => user.CreationDate)) - .ForMember(member => member.UpdateDate, expression => expression.MapFrom(user => user.LastActivityDate)) - .ForMember(member => member.Key, expression => expression.MapFrom(user => user.ProviderUserKey.TryConvertTo().Result.ToString("N"))) - .ForMember(member => member.Owner, expression => expression.UseValue(new UserProfile {Name = "Admin", UserId = 0})) - .ForMember(member => member.Icon, expression => expression.UseValue("icon-user")) - .ForMember(member => member.Name, expression => expression.MapFrom(user => user.UserName)) - .ForMember(member => member.Email, expression => expression.MapFrom(content => content.Email)) - .ForMember(member => member.Username, expression => expression.MapFrom(content => content.UserName)) - .ForMember(member => member.Properties, expression => expression.Ignore()) - .ForMember(member => member.ParentId, expression => expression.Ignore()) - .ForMember(member => member.Path, expression => expression.Ignore()) - .ForMember(member => member.SortOrder, expression => expression.Ignore()) - .ForMember(member => member.AdditionalData, expression => expression.Ignore()) - .ForMember(member => member.Published, expression => expression.Ignore()) - .ForMember(member => member.Updater, expression => expression.Ignore()) - .ForMember(member => member.Trashed, expression => expression.Ignore()) - .ForMember(member => member.Alias, expression => expression.Ignore()) - .ForMember(member => member.ContentTypeAlias, expression => expression.Ignore()) - .ForMember(member => member.HasPublishedVersion, expression => expression.Ignore()); - - //FROM IMember TO ContentItemDto - config.CreateMap>() - .ForMember(display => display.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) - .ForMember(dto => dto.Owner, expression => expression.ResolveUsing(new OwnerResolver())) - .ForMember(dto => dto.Published, expression => expression.Ignore()) - .ForMember(dto => dto.Updater, expression => expression.Ignore()) - .ForMember(dto => dto.Icon, expression => expression.Ignore()) - .ForMember(dto => dto.Alias, expression => expression.Ignore()) - .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()) - //do no map the custom member properties (currently anyways, they were never there in 6.x) - .ForMember(dto => dto.Properties, expression => expression.ResolveUsing(new MemberDtoPropertiesValueResolver())); - } - - /// - /// Maps the generic tab with custom properties for content - /// - /// - /// - /// - /// - /// - /// - /// If this is a new entity and there is an approved field then we'll set it to true by default. - /// - private static void MapGenericCustomProperties(IMemberService memberService, IUserService userService, IMember member, MemberDisplay display, ILocalizedTextService localizedText) - { - var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - //map the tree node url - if (HttpContext.Current != null) - { - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Key.ToString("N"), null)); - display.TreeNodeUrl = url; - } - - var genericProperties = new List - { - new ContentPropertyDisplay - { - Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("content/membertype"), - Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName), - View = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View - }, - GetLoginProperty(memberService, member, display, localizedText), - new ContentPropertyDisplay - { - Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("general/email"), - Value = display.Email, - View = "email", - Validation = {Mandatory = true} - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("password"), - //NOTE: The value here is a json value - but the only property we care about is the generatedPassword one if it exists, the newPassword exists - // only when creating a new member and we want to have a generated password pre-filled. - Value = new Dictionary - { - {"generatedPassword", member.GetAdditionalDataValueIgnoreCase("GeneratedPassword", null)}, - {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)}, - }, - //TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor - View = "changepassword", - //initialize the dictionary with the configuration from the default membership provider - Config = new Dictionary(membersProvider.GetConfiguration(userService)) - { - //the password change toggle will only be displayed if there is already a password assigned. - {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} - } - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}membergroup", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("content/membergroup"), - Value = GetMemberGroupValue(display.Username), - View = "membergroups", - Config = new Dictionary {{"IsRequired", true}} - } - }; - - TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties, properties => - { - if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null - && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) - { - var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); - - //Replace the doctype property - var docTypeProperty = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - docTypeProperty.Value = new List - { - new - { - linkText = member.ContentType.Name, - url = memberTypeLink, - target = "_self", - icon = "icon-item-arrangement" - } - }; - docTypeProperty.View = "urllist"; - } - }); - - //check if there's an approval field - var provider = membersProvider as global::umbraco.providers.members.UmbracoMembershipProvider; - if (member.HasIdentity == false && provider != null) - { - var approvedField = provider.ApprovedPropertyTypeAlias; - var prop = display.Properties.FirstOrDefault(x => x.Alias == approvedField); - if (prop != null) - { - prop.Value = 1; - } - } - } - - /// - /// Returns the login property display field - /// - /// - /// - /// - /// - /// - /// - /// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, however if - /// the membership provider is a custom one, we cannot allow chaning the username because MembershipProvider's do not actually natively - /// allow that. - /// - internal static ContentPropertyDisplay GetLoginProperty(IMemberService memberService, IMember member, MemberDisplay display, ILocalizedTextService localizedText) - { - var prop = new ContentPropertyDisplay - { - Alias = string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("login"), - Value = display.Username - }; - - var scenario = memberService.GetMembershipScenario(); - - //only allow editing if this is a new member, or if the membership provider is the umbraco one - if (member.HasIdentity == false || scenario == MembershipScenario.NativeUmbraco) - { - prop.View = "textbox"; - prop.Validation.Mandatory = true; - } - else - { - prop.View = "readonlyvalue"; - } - return prop; - } - - internal static IDictionary GetMemberGroupValue(string username) - { - var userRoles = username.IsNullOrWhiteSpace() ? null : Roles.GetRolesForUser(username); - - // create a dictionary of all roles (except internal roles) + "false" - var result = Roles.GetAllRoles().Distinct() - // if a role starts with __umbracoRole we won't show it as it's an internal role used for public access - .Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) - .ToDictionary(x => x, x => false); - - // if user has no roles, just return the dictionary - if (userRoles == null) return result; - - // else update the dictionary to "true" for the user roles (except internal roles) - foreach (var userRole in userRoles.Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)) - result[userRole] = true; - - return result; - } - - /// - /// This ensures that the custom membership provider properties are not mapped - these property values are controller by the membership provider - /// - /// - /// Because these properties don't exist on the form, if we don't remove them for this map we'll get validation errors when posting data - /// - internal class MemberDtoPropertiesValueResolver : ValueResolver> - { - protected override IEnumerable ResolveCore(IMember source) - { - var defaultProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs(); - - //remove all membership properties, these values are set with the membership provider. - var exclude = defaultProps.Select(x => x.Value.Alias).ToArray(); - - return source.Properties - .Where(x => exclude.Contains(x.Alias) == false) - .Select(Mapper.Map); - } - } - - /// - /// A custom tab/property resolver for members which will ensure that the built-in membership properties are or arent' displayed - /// depending on if the member type has these properties - /// - /// - /// This also ensures that the IsLocked out property is readonly when the member is not locked out - this is because - /// an admin cannot actually set isLockedOut = true, they can only unlock. - /// - internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver - { - private readonly ILocalizedTextService _localizedTextService; - - public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService) - : base(localizedTextService) - { - _localizedTextService = localizedTextService; - } - - public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, - IEnumerable ignoreProperties) : base(localizedTextService, ignoreProperties) - { - _localizedTextService = localizedTextService; - } - - protected override IEnumerable> ResolveCore(IContentBase content) - { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - IgnoreProperties = content.PropertyTypes - .Where(x => x.HasIdentity == false) - .Select(x => x.Alias) - .ToArray(); - - var result = base.ResolveCore(content).ToArray(); - - if (provider.IsUmbracoMembershipProvider() == false) - { - //it's a generic provider so update the locked out property based on our known constant alias - var isLockedOutProperty = result.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == Constants.Conventions.Member.IsLockedOut); - if (isLockedOutProperty != null && isLockedOutProperty.Value.ToString() != "1") - { - isLockedOutProperty.View = "readonlyvalue"; - isLockedOutProperty.Value = _localizedTextService.Localize("general/no"); - } - - return result; - } - else - { - var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider; - - //This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier - // if we just had all of the membeship provider fields on the member table :( - // TODO: But is there a way to map the IMember.IsLockedOut to the property ? i dunno. - var isLockedOutProperty = result.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == umbracoProvider.LockPropertyTypeAlias); - if (isLockedOutProperty != null && isLockedOutProperty.Value.ToString() != "1") - { - isLockedOutProperty.View = "readonlyvalue"; - isLockedOutProperty.Value = _localizedTextService.Localize("general/no"); - } - - return result; - } - } - } - - internal class MembershipScenarioMappingResolver : ValueResolver - { - private readonly Lazy _memberTypeService; - - public MembershipScenarioMappingResolver(Lazy memberTypeService) - { - _memberTypeService = memberTypeService; - } - - protected override MembershipScenario ResolveCore(IMember source) - { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - if (provider.IsUmbracoMembershipProvider()) - { - return MembershipScenario.NativeUmbraco; - } - var memberType = _memberTypeService.Value.Get(Constants.Conventions.MemberTypes.DefaultAlias); - return memberType != null - ? MembershipScenario.CustomProviderWithUmbracoLink - : MembershipScenario.StandaloneCustomProvider; - } - } - - /// - /// A resolver to map the provider field aliases - /// - internal class MemberProviderFieldMappingResolver : ValueResolver> - { - protected override IDictionary ResolveCore(IMember source) - { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - if (provider.IsUmbracoMembershipProvider() == false) - { - return new Dictionary - { - {Constants.Conventions.Member.IsLockedOut, Constants.Conventions.Member.IsLockedOut}, - {Constants.Conventions.Member.IsApproved, Constants.Conventions.Member.IsApproved}, - {Constants.Conventions.Member.Comments, Constants.Conventions.Member.Comments} - }; - } - else - { - var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider; - - return new Dictionary - { - {Constants.Conventions.Member.IsLockedOut, umbracoProvider.LockPropertyTypeAlias}, - {Constants.Conventions.Member.IsApproved, umbracoProvider.ApprovedPropertyTypeAlias}, - {Constants.Conventions.Member.Comments, umbracoProvider.CommentPropertyTypeAlias} - }; - } - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/MemberProviderFieldResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberProviderFieldResolver.cs new file mode 100644 index 0000000000..4325ebd566 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MemberProviderFieldResolver.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Security; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// A resolver to map the provider field aliases + /// + internal class MemberProviderFieldResolver + { + public IDictionary Resolve(IMember source) + { + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + + if (provider.IsUmbracoMembershipProvider() == false) + { + return new Dictionary + { + {Constants.Conventions.Member.IsLockedOut, Constants.Conventions.Member.IsLockedOut}, + {Constants.Conventions.Member.IsApproved, Constants.Conventions.Member.IsApproved}, + {Constants.Conventions.Member.Comments, Constants.Conventions.Member.Comments} + }; + } + else + { + var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider; + + return new Dictionary + { + {Constants.Conventions.Member.IsLockedOut, umbracoProvider.LockPropertyTypeAlias}, + {Constants.Conventions.Member.IsApproved, umbracoProvider.ApprovedPropertyTypeAlias}, + {Constants.Conventions.Member.Comments, umbracoProvider.CommentPropertyTypeAlias} + }; + } + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs new file mode 100644 index 0000000000..66fb3619cf --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs @@ -0,0 +1,258 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Security; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// A custom tab/property resolver for members which will ensure that the built-in membership properties are or arent' displayed + /// depending on if the member type has these properties + /// + /// + /// This also ensures that the IsLocked out property is readonly when the member is not locked out - this is because + /// an admin cannot actually set isLockedOut = true, they can only unlock. + /// + internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver + { + private readonly ILocalizedTextService _localizedTextService; + private readonly IMemberService _memberService; + private readonly IUserService _userService; + + public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IMemberService memberService, IUserService userService) + : base(localizedTextService) + { + _localizedTextService = localizedTextService; + _memberService = memberService; + _userService = userService; + } + + public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable ignoreProperties, IMemberService memberService, IUserService userService) + : base(localizedTextService, ignoreProperties) + { + _localizedTextService = localizedTextService; + _memberService = memberService; + _userService = userService; + } + + /// + /// Overriden to deal with custom member properties and permissions. + public override IEnumerable> Resolve(IMember source, MemberDisplay destination, IEnumerable> destMember, ResolutionContext context) + { + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + + IgnoreProperties = source.PropertyTypes + .Where(x => x.HasIdentity == false) + .Select(x => x.Alias) + .ToArray(); + + var resolved = base.Resolve(source, destination, destMember, context); + + if (provider.IsUmbracoMembershipProvider() == false) + { + //it's a generic provider so update the locked out property based on our known constant alias + var isLockedOutProperty = resolved.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == Constants.Conventions.Member.IsLockedOut); + if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1") + { + isLockedOutProperty.View = "readonlyvalue"; + isLockedOutProperty.Value = _localizedTextService.Localize("general/no"); + } + } + else + { + var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider; + + //This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier + // if we just had all of the membeship provider fields on the member table :( + // TODO: But is there a way to map the IMember.IsLockedOut to the property ? i dunno. + var isLockedOutProperty = resolved.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == umbracoProvider.LockPropertyTypeAlias); + if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1") + { + isLockedOutProperty.View = "readonlyvalue"; + isLockedOutProperty.Value = _localizedTextService.Localize("general/no"); + } + } + + var umbracoContext = context.GetUmbracoContext(); + if (umbracoContext != null + && umbracoContext.Security.CurrentUser != null + && umbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", source.ContentTypeId); + + //Replace the doctype property + var docTypeProperty = resolved.SelectMany(x => x.Properties) + .First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProperty.Value = new List + { + new + { + linkText = source.ContentType.Name, + url = memberTypeLink, + target = "_self", + icon = "icon-item-arrangement" + } + }; + docTypeProperty.View = "urllist"; + } + + return resolved; + } + + protected override IEnumerable GetCustomGenericProperties(IContentBase content) + { + var member = (IMember) content; + var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + + var genericProperties = new List + { + new ContentPropertyDisplay + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}doctype", + Label = _localizedTextService.Localize("content/membertype"), + Value = _localizedTextService.UmbracoDictionaryTranslate(member.ContentType.Name), + View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View + }, + GetLoginProperty(_memberService, member, _localizedTextService), + new ContentPropertyDisplay + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email", + Label = _localizedTextService.Localize("general/email"), + Value = member.Email, + View = "email", + Validation = {Mandatory = true} + }, + new ContentPropertyDisplay + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password", + Label = _localizedTextService.Localize("password"), + //NOTE: The value here is a json value - but the only property we care about is the generatedPassword one if it exists, the newPassword exists + // only when creating a new member and we want to have a generated password pre-filled. + Value = new Dictionary + { + // fixme why ignoreCase, what are we doing here?! + {"generatedPassword", member.GetAdditionalDataValueIgnoreCase("GeneratedPassword", null)}, + {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)}, + }, + //TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor + View = "changepassword", + //initialize the dictionary with the configuration from the default membership provider + Config = new Dictionary(membersProvider.GetConfiguration(_userService)) + { + //the password change toggle will only be displayed if there is already a password assigned. + {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} + } + }, + new ContentPropertyDisplay + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup", + Label = _localizedTextService.Localize("content/membergroup"), + Value = GetMemberGroupValue(member.Username), + View = "membergroups", + Config = new Dictionary {{"IsRequired", true}} + } + }; + + return genericProperties; + } + + /// + /// Overridden to assign the IsSensitive property values + /// + /// + /// + /// + /// + /// + protected override List MapProperties(UmbracoContext umbracoContext, IContentBase content, List properties, ResolutionContext context) + { + var result = base.MapProperties(umbracoContext, content, properties, context); + var member = (IMember)content; + var memberType = member.ContentType; + + //now update the IsSensitive value + foreach (var prop in result) + { + //check if this property is flagged as sensitive + var isSensitiveProperty = memberType.IsSensitiveProperty(prop.Alias); + //check permissions for viewing sensitive data + if (isSensitiveProperty && umbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false) + { + //mark this property as sensitive + prop.IsSensitive = true; + //mark this property as readonly so that it does not post any data + prop.Readonly = true; + //replace this editor with a sensitivevalue + prop.View = "sensitivevalue"; + //clear the value + prop.Value = null; + } + } + return result; + } + + /// + /// Returns the login property display field + /// + /// + /// + /// + /// + /// + /// + /// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, however if + /// the membership provider is a custom one, we cannot allow chaning the username because MembershipProvider's do not actually natively + /// allow that. + /// + internal static ContentPropertyDisplay GetLoginProperty(IMemberService memberService, IMember member, ILocalizedTextService localizedText) + { + var prop = new ContentPropertyDisplay + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login", + Label = localizedText.Localize("login"), + Value = member.Username + }; + + var scenario = memberService.GetMembershipScenario(); + + //only allow editing if this is a new member, or if the membership provider is the umbraco one + if (member.HasIdentity == false || scenario == MembershipScenario.NativeUmbraco) + { + prop.View = "textbox"; + prop.Validation.Mandatory = true; + } + else + { + prop.View = "readonlyvalue"; + } + return prop; + } + + internal static IDictionary GetMemberGroupValue(string username) + { + var userRoles = username.IsNullOrWhiteSpace() ? null : Roles.GetRolesForUser(username); + + // create a dictionary of all roles (except internal roles) + "false" + var result = Roles.GetAllRoles().Distinct() + // if a role starts with __umbracoRole we won't show it as it's an internal role used for public access + .Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) + .ToDictionary(x => x, x => false); + + // if user has no roles, just return the dictionary + if (userRoles == null) return result; + + // else update the dictionary to "true" for the user roles (except internal roles) + foreach (var userRole in userRoles.Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)) + result[userRole] = true; + + return result; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs new file mode 100644 index 0000000000..864fd18ab2 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs @@ -0,0 +1,23 @@ +using System.Web.Mvc; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Gets the tree node url for the IMember + /// + internal class MemberTreeNodeUrlResolver : IValueResolver + { + public string Resolve(IMember source, MemberDisplay destination, string destMember, ResolutionContext context) + { + var umbracoContext = context.GetUmbracoContext(throwIfMissing: false); + if (umbracoContext == null) return null; + + var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext); + return urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/MembershipScenarioResolver.cs b/src/Umbraco.Web/Models/Mapping/MembershipScenarioResolver.cs new file mode 100644 index 0000000000..3c3e0af5aa --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MembershipScenarioResolver.cs @@ -0,0 +1,33 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Models.Mapping +{ + internal class MembershipScenarioResolver + { + private readonly IMemberTypeService _memberTypeService; + + public MembershipScenarioResolver(IMemberTypeService memberTypeService) + { + _memberTypeService = memberTypeService; + } + + public MembershipScenario Resolve(IMember source) + { + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + + if (provider.IsUmbracoMembershipProvider()) + { + return MembershipScenario.NativeUmbraco; + } + var memberType = _memberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); + return memberType != null + ? MembershipScenario.CustomProviderWithUmbracoLink + : MembershipScenario.StandaloneCustomProvider; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/MembershipUserTypeConverter.cs b/src/Umbraco.Web/Models/Mapping/MembershipUserTypeConverter.cs new file mode 100644 index 0000000000..dbbf6f69df --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MembershipUserTypeConverter.cs @@ -0,0 +1,21 @@ +using System.Web.Security; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// A converter to go from a to a + /// + internal class MembershipUserTypeConverter : ITypeConverter + { + public MemberDisplay Convert(MembershipUser source, MemberDisplay destination, ResolutionContext context) + { + //first convert to IMember + var member = Mapper.Map(source); + //then convert to MemberDisplay + return ContextMapper.Map(member, context.GetUmbracoContext()); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/MiscModelsMapper.cs b/src/Umbraco.Web/Models/Mapping/MiscModelsMapper.cs deleted file mode 100644 index 16794bc21c..0000000000 --- a/src/Umbraco.Web/Models/Mapping/MiscModelsMapper.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Linq; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models.Mapping; -using Umbraco.Web.Models.ContentEditing; -using umbraco.BusinessLogic; -using Umbraco.Core.Models; -using Umbraco.Web.Routing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// A model mapper used to map models for the various dashboards - /// - internal class MiscModelsMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - config.CreateMap() - .ForMember(x => x.OriginalUrl, expression => expression.MapFrom(item => UmbracoContext.Current.UrlProvider.GetUrlFromRoute(item.ContentId, item.Url))) - .ForMember(x => x.DestinationUrl, expression => expression.Ignore()) - .ForMember(x => x.RedirectId, expression => expression.MapFrom(item => item.Key)); - - //for the logging controller (and assuming dashboard that is used in uaas? otherwise not sure what that controller is used for) - config.CreateMap() - .ForMember(log => log.UserAvatars, expression => expression.Ignore()) - .ForMember(log => log.UserName, expression => expression.Ignore()) - .ForMember(log => log.LogType, expression => expression.MapFrom(item => Enum.Parse(item.LogType.ToString()))); - - config.CreateMap() - .ForMember(log => log.UserAvatars, expression => expression.Ignore()) - .ForMember(log => log.UserName, expression => expression.Ignore()) - .ForMember(log => log.NodeId, expression => expression.MapFrom(item => item.Id)) - .ForMember(log => log.Timestamp, expression => expression.MapFrom(item => item.CreateDate)) - .ForMember(log => log.LogType, expression => expression.MapFrom(item => item.AuditType)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs index 8992451e9e..e9915b66d7 100644 --- a/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/OwnerResolver.cs @@ -1,8 +1,7 @@ using AutoMapper; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; +using Umbraco.Core.Services; using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; namespace Umbraco.Web.Models.Mapping @@ -11,12 +10,19 @@ namespace Umbraco.Web.Models.Mapping /// Maps the Owner for IContentBase /// /// - internal class OwnerResolver : ValueResolver + internal class OwnerResolver where TPersisted : IContentBase { - protected override UserProfile ResolveCore(TPersisted source) + private readonly IUserService _userService; + + public OwnerResolver(IUserService userService) { - return Mapper.Map(source.GetCreatorProfile()); + _userService = userService; + } + + public UserProfile Resolve(TPersisted source) + { + return Mapper.Map(source.GetCreatorProfile(_userService)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs deleted file mode 100644 index 295a5df63d..0000000000 --- a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - internal class PreValueDisplayResolver : ValueResolver> - { - private readonly Lazy _dataTypeService; - - public PreValueDisplayResolver(Lazy dataTypeService) - { - _dataTypeService = dataTypeService; - } - - /// - /// Maps pre-values in the dictionary to the values for the fields - /// - /// - /// - internal static void MapPreValueValuesToPreValueFields(PreValueFieldDisplay[] fields, IDictionary preValues) - { - if (fields == null) throw new ArgumentNullException("fields"); - if (preValues == null) throw new ArgumentNullException("preValues"); - //now we need to wire up the pre-values values with the actual fields defined - foreach (var field in fields) - { - var found = preValues.Any(x => x.Key.InvariantEquals(field.Key)); - if (found == false) - { - LogHelper.Warn("Could not find persisted pre-value for field " + field.Key); - continue; - } - field.Value = preValues.Single(x => x.Key.InvariantEquals(field.Key)).Value; - } - } - - internal IEnumerable Convert(IDataTypeDefinition source) - { - PropertyEditor propEd = null; - if (source.PropertyEditorAlias.IsNullOrWhiteSpace() == false) - { - propEd = PropertyEditorResolver.Current.GetByAlias(source.PropertyEditorAlias); - if (propEd == null) - { - throw new InvalidOperationException("Could not find property editor with alias " + source.PropertyEditorAlias); - } - } - - //set up the defaults - var dataTypeService = _dataTypeService.Value; - var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id); - IDictionary dictionaryVals = preVals.FormatAsDictionary().ToDictionary(x => x.Key, x => (object)x.Value); - var result = Enumerable.Empty().ToArray(); - - //if we have a prop editor, then format the pre-values based on it and create it's fields. - if (propEd != null) - { - result = propEd.PreValueEditor.Fields.Select(Mapper.Map).ToArray(); - dictionaryVals = propEd.PreValueEditor.ConvertDbToEditor(propEd.DefaultPreValues, preVals); - } - - MapPreValueValuesToPreValueFields(result, dictionaryVals); - - return result; - } - - protected override IEnumerable ResolveCore(IDataTypeDefinition source) - { - return Convert(source); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/PropertyGroupDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PropertyGroupDisplayResolver.cs index 2f11eb04c4..ba165f44b4 100644 --- a/src/Umbraco.Web/Models/Mapping/PropertyGroupDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PropertyGroupDisplayResolver.cs @@ -5,14 +5,14 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { - internal class PropertyGroupDisplayResolver : ValueResolver>> + internal class PropertyGroupDisplayResolver where TSource : ContentTypeSave - where TPropertyTypeDestination : PropertyTypeDisplay + where TPropertyTypeDestination : PropertyTypeDisplay where TPropertyTypeSource : PropertyTypeBasic { - protected override IEnumerable> ResolveCore(TSource source) + public IEnumerable> Resolve(TSource source) { return source.Groups.Select(Mapper.Map>); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs index a2273b07b2..9a1aeda845 100644 --- a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs @@ -5,20 +5,21 @@ using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { - internal class PropertyTypeGroupResolver : ValueResolver>> + internal class PropertyTypeGroupResolver where TPropertyType : PropertyTypeDisplay, new() { - private readonly ApplicationContext _applicationContext; - private readonly Lazy _propertyEditorResolver; + private readonly PropertyEditorCollection _propertyEditors; + private readonly IDataTypeService _dataTypeService; - public PropertyTypeGroupResolver(ApplicationContext applicationContext, Lazy propertyEditorResolver) + public PropertyTypeGroupResolver(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) { - _applicationContext = applicationContext; - _propertyEditorResolver = propertyEditorResolver; + _propertyEditors = propertyEditors; + _dataTypeService = dataTypeService; } /// @@ -61,7 +62,7 @@ namespace Umbraco.Web.Models.Mapping .FirstOrDefault(x => x != null); } - protected override IEnumerable> ResolveCore(IContentTypeComposition source) + public IEnumerable> Resolve(IContentTypeComposition source) { // deal with groups var groups = new List>(); @@ -80,7 +81,7 @@ namespace Umbraco.Web.Models.Mapping group.Properties = MapProperties(tab.PropertyTypes, source, tab.Id, false); groups.Add(group); - } + } // add groups inherited through composition var localGroupIds = groups.Select(x => x.Id).ToArray(); @@ -91,7 +92,7 @@ namespace Umbraco.Web.Models.Mapping // get the content type that defines this group var definingContentType = GetContentTypeForPropertyGroup(source, tab.Id); - if (definingContentType == null) + if (definingContentType == null) throw new Exception("PropertyGroup with id=" + tab.Id + " was not found on any of the content type's compositions."); var group = new PropertyGroupDisplay @@ -134,11 +135,11 @@ namespace Umbraco.Web.Models.Mapping { var genericTab = new PropertyGroupDisplay { - Id = PropertyGroupBasic.GenericPropertiesGroupId, + Id = PropertyGroupBasic.GenericPropertiesGroupId, Name = "Generic properties", - ContentTypeId = source.Id, + ContentTypeId = source.Id, SortOrder = 999, - Inherited = false, + Inherited = false, Properties = genericProperties }; groups.Add(genericTab); @@ -195,12 +196,12 @@ namespace Umbraco.Web.Models.Mapping { var mappedProperties = new List(); - foreach (var p in properties.Where(x => x.DataTypeDefinitionId != 0).OrderBy(x => x.SortOrder)) + foreach (var p in properties.Where(x => x.DataTypeId != 0).OrderBy(x => x.SortOrder)) { - var propertyEditor = _propertyEditorResolver.Value.GetByAlias(p.PropertyEditorAlias); - var preValues = _applicationContext.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(p.DataTypeDefinitionId); + var propertyEditor = _propertyEditors[p.PropertyEditorAlias]; + var configuration = _dataTypeService.GetDataType(p.DataTypeId).Configuration; - if (propertyEditor == null) + if (propertyEditor == null) throw new InvalidOperationException("No property editor could be resolved with the alias: " + p.PropertyEditorAlias + ", ensure all packages are installed correctly."); mappedProperties.Add(new TPropertyType @@ -211,12 +212,12 @@ namespace Umbraco.Web.Models.Mapping Editor = p.PropertyEditorAlias, Validation = new PropertyTypeValidation {Mandatory = p.Mandatory, Pattern = p.ValidationRegExp}, Label = p.Name, - View = propertyEditor.ValueEditor.View, - Config = propertyEditor.PreValueEditor.ConvertDbToEditor(propertyEditor.DefaultPreValues, preValues), + View = propertyEditor.GetValueEditor().View, + Config = propertyEditor.GetConfigurationEditor().ToConfigurationEditor(configuration), //Value = "", GroupId = groupId, Inherited = inherited, - DataTypeId = p.DataTypeDefinitionId, + DataTypeId = p.DataTypeId, SortOrder = p.SortOrder, ContentTypeId = contentType.Id, ContentTypeName = contentType.Name diff --git a/src/Umbraco.Web/Models/Mapping/RedirectUrlMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/RedirectUrlMapperProfile.cs new file mode 100644 index 0000000000..e92e72db77 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/RedirectUrlMapperProfile.cs @@ -0,0 +1,18 @@ +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Composing; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class RedirectUrlMapperProfile : Profile + { + public RedirectUrlMapperProfile() + { + CreateMap() + .ForMember(x => x.OriginalUrl, expression => expression.MapFrom(item => Current.UmbracoContext.UrlProvider.GetUrlFromRoute(item.ContentId, item.Url))) + .ForMember(x => x.DestinationUrl, expression => expression.Ignore()) + .ForMember(x => x.RedirectId, expression => expression.MapFrom(item => item.Key)); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/RelationMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/RelationMapperProfile.cs new file mode 100644 index 0000000000..31acf4e5e1 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/RelationMapperProfile.cs @@ -0,0 +1,19 @@ +using AutoMapper; +using Umbraco.Core.Models; +using Relation = Umbraco.Web.Models.ContentEditing.Relation; +using RelationType = Umbraco.Web.Models.ContentEditing.RelationType; + +namespace Umbraco.Web.Models.Mapping +{ + internal class RelationMapperProfile : Profile + { + public RelationMapperProfile() + { + //FROM IRelationType TO RelationType + CreateMap(); + + //FROM IRelation TO Relation + CreateMap(); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/RelationModelMapper.cs b/src/Umbraco.Web/Models/Mapping/RelationModelMapper.cs deleted file mode 100644 index bc133abf96..0000000000 --- a/src/Umbraco.Web/Models/Mapping/RelationModelMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Relation = Umbraco.Web.Models.ContentEditing.Relation; -using RelationType = Umbraco.Web.Models.ContentEditing.RelationType; - -namespace Umbraco.Web.Models.Mapping -{ - internal class RelationModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - //FROM IRelationType TO RelationType - config.CreateMap(); - - //FROM IRelation TO Relation - config.CreateMap(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/SectionMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/SectionMapperProfile.cs new file mode 100644 index 0000000000..11b4deff48 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/SectionMapperProfile.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using AutoMapper; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class SectionMapperProfile : Profile + { + public SectionMapperProfile(ILocalizedTextService textService) + { + CreateMap() + .ForMember(dest => dest.RoutePath, opt => opt.Ignore()) + .ForMember(dest => dest.Name, opt => opt.MapFrom(src => textService.Localize("sections/" + src.Alias, (IDictionary)null))) + .ReverseMap(); //backwards too! + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/SectionModelMapper.cs b/src/Umbraco.Web/Models/Mapping/SectionModelMapper.cs deleted file mode 100644 index 787eea5bc4..0000000000 --- a/src/Umbraco.Web/Models/Mapping/SectionModelMapper.cs +++ /dev/null @@ -1,21 +0,0 @@ -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models.Mapping; -using Umbraco.Web.Models.ContentEditing; -using umbraco; - -namespace Umbraco.Web.Models.Mapping -{ - internal class SectionModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - config.CreateMap() - .ForMember(section => section.RoutePath, x => x.Ignore()) - .ForMember( - dto => dto.Name, - expression => expression.MapFrom(section => ui.Text("sections", section.Alias))) - .ReverseMap(); //backwards too! - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/TabModelMapper.cs b/src/Umbraco.Web/Models/Mapping/TabModelMapper.cs deleted file mode 100644 index 0bdfb311a5..0000000000 --- a/src/Umbraco.Web/Models/Mapping/TabModelMapper.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; - -namespace Umbraco.Web.Models.Mapping -{ - internal class TabModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - config.CreateMap(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 4ac35b48f8..9bef3747de 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -1,92 +1,30 @@ -using System; +using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.Linq; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; -using umbraco; +using Umbraco.Web.Composing; namespace Umbraco.Web.Models.Mapping { - /// - /// Creates the tabs collection with properties assigned for display models - /// - internal class TabsAndPropertiesResolver : ValueResolver>> + internal abstract class TabsAndPropertiesResolver { - private readonly ILocalizedTextService _localizedTextService; + protected ILocalizedTextService LocalizedTextService { get; } protected IEnumerable IgnoreProperties { get; set; } - public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService) + protected TabsAndPropertiesResolver(ILocalizedTextService localizedTextService) { - if (localizedTextService == null) throw new ArgumentNullException("localizedTextService"); - _localizedTextService = localizedTextService; + LocalizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); IgnoreProperties = new List(); } - public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) + protected TabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) : this(localizedTextService) { - if (ignoreProperties == null) throw new ArgumentNullException("ignoreProperties"); - IgnoreProperties = ignoreProperties; - } - - /// - /// Maps properties on to the generic properties tab - /// - /// - /// - /// - /// - /// Any additional custom properties to assign to the generic properties tab. - /// - /// - /// - /// The generic properties tab is mapped during AfterMap and is responsible for - /// setting up the properties such as Created date, updated date, template selected, etc... - /// - public static void MapGenericProperties( - TPersisted content, - ContentItemDisplayBase display, - ILocalizedTextService localizedTextService, - IEnumerable customProperties = null, - Action> onGenericPropertiesMapped = null) - where TPersisted : IContentBase - { - var genericProps = display.Tabs.Single(x => x.Id == 0); - - //store the current props to append to the newly inserted ones - var currProps = genericProps.Properties.ToArray(); - - var contentProps = new List(); - - if (customProperties != null) - { - //add the custom ones - contentProps.AddRange(customProperties); - } - - //now add the user props - contentProps.AddRange(currProps); - - //callback - if (onGenericPropertiesMapped != null) - { - onGenericPropertiesMapped(contentProps); - } - - //re-assign - genericProps.Properties = contentProps; - - //Show or hide properties tab based on wether it has or not any properties - if (genericProps.Properties.Any() == false) - { - display.Tabs = display.Tabs.Where(x => x.Id != 0); - } + IgnoreProperties = ignoreProperties ?? throw new ArgumentNullException(nameof(ignoreProperties)); } /// @@ -105,43 +43,43 @@ namespace Umbraco.Web.Models.Mapping switch (entityType) { case "content": - dtdId = Constants.System.DefaultContentListViewDataTypeId; + dtdId = Constants.DataTypes.DefaultContentListView; break; case "media": - dtdId = Constants.System.DefaultMediaListViewDataTypeId; + dtdId = Constants.DataTypes.DefaultMediaListView; break; case "member": - dtdId = Constants.System.DefaultMembersListViewDataTypeId; + dtdId = Constants.DataTypes.DefaultMembersListView; break; default: - throw new ArgumentOutOfRangeException("entityType does not match a required value"); + throw new ArgumentOutOfRangeException(nameof(entityType), "entityType does not match a required value"); } //first try to get the custom one if there is one - var dt = dataTypeService.GetDataTypeDefinitionByName(customDtdName) - ?? dataTypeService.GetDataTypeDefinitionById(dtdId); + var dt = dataTypeService.GetDataType(customDtdName) + ?? dataTypeService.GetDataType(dtdId); if (dt == null) { throw new InvalidOperationException("No list view data type was found for this document type, ensure that the default list view data types exists and/or that your custom list view data type exists"); } - var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(dt.Id); - - var editor = PropertyEditorResolver.Current.GetByAlias(dt.PropertyEditorAlias); + var editor = Current.PropertyEditors[dt.EditorAlias]; if (editor == null) { - throw new NullReferenceException("The property editor with alias " + dt.PropertyEditorAlias + " does not exist"); + throw new NullReferenceException("The property editor with alias " + dt.EditorAlias + " does not exist"); } - var listViewTab = new Tab(); - listViewTab.Alias = Constants.Conventions.PropertyGroups.ListViewGroupName; - listViewTab.Label = localizedTextService.Localize("content/childItems"); - listViewTab.Id = display.Tabs.Count() + 1; - listViewTab.IsActive = true; + var listViewTab = new Tab + { + Alias = Constants.Conventions.PropertyGroups.ListViewGroupName, + Label = localizedTextService.Localize("content/childItems"), + Id = display.Tabs.Count() + 1, + IsActive = true + }; - var listViewConfig = editor.PreValueEditor.ConvertDbToEditor(editor.DefaultPreValues, preVals); + var listViewConfig = editor.GetConfigurationEditor().ToConfigurationEditor(dt.Configuration); //add the entity type to the config listViewConfig["entityType"] = entityType; @@ -156,10 +94,10 @@ namespace Umbraco.Web.Models.Mapping var listViewProperties = new List(); listViewProperties.Add(new ContentPropertyDisplay { - Alias = string.Format("{0}containerView", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView", Label = "", Value = null, - View = editor.ValueEditor.View, + View = editor.GetValueEditor().View, HideLabel = true, Config = listViewConfig }); @@ -168,14 +106,28 @@ namespace Umbraco.Web.Models.Mapping SetChildItemsTabPosition(display, listViewConfig, listViewTab); } + private static int GetTabNumberFromConfig(IDictionary listViewConfig) + { + if (!listViewConfig.TryGetValue("displayAtTabNumber", out var displayTabNum)) + return -1; + switch (displayTabNum) + { + case int i: + return i; + case string s when int.TryParse(s, out var parsed): + return parsed; + } + return -1; + } + private static void SetChildItemsTabPosition(TabbedContentItem display, IDictionary listViewConfig, Tab listViewTab) where TPersisted : IContentBase { // Find position of tab from config - var tabIndexForChildItems = 0; - if (listViewConfig["displayAtTabNumber"] != null && int.TryParse((string)listViewConfig["displayAtTabNumber"], out tabIndexForChildItems)) + var tabIndexForChildItems = GetTabNumberFromConfig(listViewConfig); + if (tabIndexForChildItems != -1) { // Tab position is recorded 1-based but we insert into collection 0-based tabIndexForChildItems--; @@ -191,6 +143,7 @@ namespace Umbraco.Web.Models.Mapping tabIndexForChildItems = display.Tabs.Count(); } } + else tabIndexForChildItems = 0; // Recreate tab list with child items tab at configured position var tabs = new List>(); @@ -199,15 +152,122 @@ namespace Umbraco.Web.Models.Mapping display.Tabs = tabs; } - protected override IEnumerable> ResolveCore(IContentBase content) + /// + /// Returns a collection of custom generic properties that exist on the generic properties tab + /// + /// + protected virtual IEnumerable GetCustomGenericProperties(IContentBase content) { + return Enumerable.Empty(); + } + + /// + /// Maps properties on to the generic properties tab + /// + /// + /// + /// + /// + /// + /// The generic properties tab is responsible for + /// setting up the properties such as Created date, updated date, template selected, etc... + /// + protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List> tabs, ResolutionContext context) + { + // add the generic properties tab, for properties that don't belong to a tab + // get the properties, map and translate them, then add the tab + var noGroupProperties = content.GetNonGroupedProperties() + .Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored + .ToList(); + var genericproperties = MapProperties(umbracoContext, content, noGroupProperties, context); + + tabs.Add(new Tab + { + Id = 0, + Label = LocalizedTextService.Localize("general/properties"), + Alias = "Generic properties", + Properties = genericproperties + }); + + var genericProps = tabs.Single(x => x.Id == 0); + + //store the current props to append to the newly inserted ones + var currProps = genericProps.Properties.ToArray(); + + var contentProps = new List(); + + var customProperties = GetCustomGenericProperties(content); + if (customProperties != null) + { + //add the custom ones + contentProps.AddRange(customProperties); + } + + //now add the user props + contentProps.AddRange(currProps); + + //re-assign + genericProps.Properties = contentProps; + + //Show or hide properties tab based on wether it has or not any properties + if (genericProps.Properties.Any() == false) + { + //loop throug the tabs, remove the one with the id of zero and exit the loop + for (var i = 0; i < tabs.Count; i++) + { + if (tabs[i].Id != 0) continue; + tabs.RemoveAt(i); + break; + } + } + } + + /// + /// Maps a list of to a list of + /// + /// + /// + /// + /// + /// + protected virtual List MapProperties(UmbracoContext umbracoContext, IContentBase content, List properties, ResolutionContext context) + { + //we need to map this way to pass the context through, I don't like it but we'll see what AutoMapper says: https://github.com/AutoMapper/AutoMapper/issues/2588 + var result = context.Mapper.Map, IEnumerable>( + // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. + properties.OrderBy(prop => prop.PropertyType.SortOrder), + null, + context) + .ToList(); + + return result; + } + } + + /// + /// Creates the tabs collection with properties assigned for display models + /// + internal class TabsAndPropertiesResolver : TabsAndPropertiesResolver, IValueResolver>> + where TSource : IContentBase + { + public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService) + : base(localizedTextService) + { } + + public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) + : base(localizedTextService, ignoreProperties) + { } + + public virtual IEnumerable> Resolve(TSource source, TDestination destination, IEnumerable> destMember, ResolutionContext context) + { + var umbracoContext = context.GetUmbracoContext(); var tabs = new List>(); // add the tabs, for properties that belong to a tab // need to aggregate the tabs, as content.PropertyGroups contains all the composition tabs, - // and there might be duplicates (content does not work like contentType and there is no + // and there might be duplicates (content does not work like contentType and there is no // content.CompositionPropertyGroups). - var groupsGroupsByName = content.PropertyGroups.OrderBy(x => x.SortOrder).GroupBy(x => x.Name); + var groupsGroupsByName = source.PropertyGroups.OrderBy(x => x.SortOrder).GroupBy(x => x.Name); foreach (var groupsByName in groupsGroupsByName) { var properties = new List(); @@ -215,7 +275,7 @@ namespace Umbraco.Web.Models.Mapping // merge properties for groups with the same name foreach (var group in groupsByName) { - var groupProperties = content.GetPropertiesForGroup(group) + var groupProperties = source.GetPropertiesForGroup(group) .Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored properties.AddRange(groupProperties); @@ -224,14 +284,12 @@ namespace Umbraco.Web.Models.Mapping if (properties.Count == 0) continue; - // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. - var mappedProperties = Mapper.Map, IEnumerable>(properties.OrderBy(prop => prop.PropertyType.SortOrder)); - - TranslateProperties(mappedProperties); + //map the properties + var mappedProperties = MapProperties(umbracoContext, source, properties, context); // add the tab // we need to pick an identifier... there is no "right" way... - var g = groupsByName.FirstOrDefault(x => x.Id == content.ContentTypeId) // try local + var g = groupsByName.FirstOrDefault(x => x.Id == source.ContentTypeId) // try local ?? groupsByName.First(); // else pick one randomly var groupId = g.Id; var groupName = groupsByName.Key; @@ -239,41 +297,19 @@ namespace Umbraco.Web.Models.Mapping { Id = groupId, Alias = groupName, - Label = _localizedTextService.UmbracoDictionaryTranslate(groupName), + Label = LocalizedTextService.UmbracoDictionaryTranslate(groupName), Properties = mappedProperties, IsActive = false }); } - // add the generic properties tab, for properties that don't belong to a tab - // get the properties, map and translate them, then add the tab - var noGroupProperties = content.GetNonGroupedProperties() - .Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored - var genericproperties = Mapper.Map, IEnumerable>(noGroupProperties).ToList(); - TranslateProperties(genericproperties); + MapGenericProperties(umbracoContext, source, tabs, context); - tabs.Add(new Tab - { - Id = 0, - Label = _localizedTextService.Localize("general/properties"), - Alias = "Generic properties", - Properties = genericproperties - }); - - // activate the first tab - tabs.First().IsActive = true; + // activate the first tab, if any + if (tabs.Count > 0) + tabs[0].IsActive = true; return tabs; } - - private void TranslateProperties(IEnumerable properties) - { - // Not sure whether it's a good idea to add this to the ContentPropertyDisplay mapper - foreach (var prop in properties) - { - prop.Label = _localizedTextService.UmbracoDictionaryTranslate(prop.Label); - prop.Description = _localizedTextService.UmbracoDictionaryTranslate(prop.Description); - } - } } } diff --git a/src/Umbraco.Web/Models/Mapping/TagMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/TagMapperProfile.cs new file mode 100644 index 0000000000..bcb6c2bb23 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/TagMapperProfile.cs @@ -0,0 +1,13 @@ +using AutoMapper; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models.Mapping +{ + internal class TagMapperProfile : Profile + { + public TagMapperProfile() + { + CreateMap(); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/TemplateMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/TemplateMapperProfile.cs new file mode 100644 index 0000000000..3c283cb3a5 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/TemplateMapperProfile.cs @@ -0,0 +1,24 @@ +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class TemplateMapperProfile : Profile + { + public TemplateMapperProfile() + { + CreateMap() + .ForMember(dest => dest.Notifications, opt => opt.Ignore()); + + CreateMap() + .IgnoreEntityCommonProperties() + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.VirtualPath, opt => opt.Ignore()) + .ForMember(dest => dest.Path, opt => opt.Ignore()) + .ForMember(dest => dest.MasterTemplateId, opt => opt.Ignore()) // ok, assigned when creating the template + .ForMember(dest => dest.IsMasterTemplate, opt => opt.Ignore()) + .ForMember(dest => dest.HasIdentity, opt => opt.Ignore()); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/TemplateModelMapper.cs b/src/Umbraco.Web/Models/Mapping/TemplateModelMapper.cs deleted file mode 100644 index 43c75f416b..0000000000 --- a/src/Umbraco.Web/Models/Mapping/TemplateModelMapper.cs +++ /dev/null @@ -1,26 +0,0 @@ -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - internal class TemplateModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - config.CreateMap() - .ForMember(x => x.Notifications, exp => exp.Ignore()); - - config.CreateMap() - .IgnoreDeletableEntityCommonProperties() - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.VirtualPath, exp => exp.Ignore()) - .ForMember(x => x.Path, exp => exp.Ignore()) - .ForMember(x => x.MasterTemplateId, exp => exp.Ignore()) // ok, assigned when creating the template - .ForMember(x => x.IsMasterTemplate, exp => exp.Ignore()) - .ForMember(x => x.HasIdentity, exp => exp.Ignore()); - } - } -} diff --git a/src/Umbraco.Web/Models/Mapping/UserGroupDefaultPermissionsResolver.cs b/src/Umbraco.Web/Models/Mapping/UserGroupDefaultPermissionsResolver.cs index 20f43fc74c..89144084e2 100644 --- a/src/Umbraco.Web/Models/Mapping/UserGroupDefaultPermissionsResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/UserGroupDefaultPermissionsResolver.cs @@ -1,38 +1,37 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using AutoMapper; -using umbraco.interfaces; using Umbraco.Core; using Umbraco.Core.CodeAnnotations; -using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.Models.Mapping { /// /// Converts an IUserGroup instance into a dictionary of permissions by category /// - internal class UserGroupDefaultPermissionsResolver : ValueResolver>> + internal class UserGroupDefaultPermissionsResolver { private readonly ILocalizedTextService _textService; + private readonly ActionCollection _actions; - public UserGroupDefaultPermissionsResolver(ILocalizedTextService textService) + public UserGroupDefaultPermissionsResolver(ILocalizedTextService textService, ActionCollection actions) { - if (textService == null) throw new ArgumentNullException("textService"); - _textService = textService; + _actions = actions; + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); } - protected override IDictionary> ResolveCore(IUserGroup source) + public IDictionary> Resolve(IUserGroup source) { - return ActionsResolver.Current.Actions + return _actions .Where(x => x.CanBePermissionAssigned) .Select(x => GetPermission(x, source)) .GroupBy(x => x.Category) - .ToDictionary(x => x.Key, x => (IEnumerable)x.ToArray()); + .ToDictionary(x => x.Key, x => (IEnumerable) x.ToArray()); } private Permission GetPermission(IAction action, IUserGroup source) @@ -40,16 +39,16 @@ namespace Umbraco.Web.Models.Mapping var result = new Permission(); var attribute = action.GetType().GetCustomAttribute(false); result.Category = attribute == null - ? _textService.Localize(string.Format("actionCategories/{0}", Constants.Conventions.PermissionCategories.OtherCategory)) - : _textService.Localize(string.Format("actionCategories/{0}", attribute.Category)); + ? _textService.Localize($"actionCategories/{Constants.Conventions.PermissionCategories.OtherCategory}") + : _textService.Localize($"actionCategories/{attribute.Category}"); result.Name = attribute == null || attribute.Name.IsNullOrWhiteSpace() - ? _textService.Localize(string.Format("actions/{0}", action.Alias)) + ? _textService.Localize($"actions/{action.Alias}") : attribute.Name; - result.Description = _textService.Localize(String.Format("actionDescriptions/{0}", action.Alias)); + result.Description = _textService.Localize($"actionDescriptions/{action.Alias}"); result.Icon = action.Icon; result.Checked = source.Permissions != null && source.Permissions.Contains(action.Letter.ToString(CultureInfo.InvariantCulture)); result.PermissionCode = action.Letter.ToString(CultureInfo.InvariantCulture); return result; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs new file mode 100644 index 0000000000..ad4eaa5b52 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs @@ -0,0 +1,449 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Web._Legacy.Actions; + +namespace Umbraco.Web.Models.Mapping +{ + internal class UserMapperProfile : Profile + { + private static string GetContentTypeIcon(EntitySlim entity) + => entity is ContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; + + public UserMapperProfile(ILocalizedTextService textService, IUserService userService, IEntityService entityService, ISectionService sectionService, + IRuntimeCacheProvider runtimeCache, ActionCollection actions, IGlobalSettings globalSettings) + { + var userGroupDefaultPermissionsResolver = new UserGroupDefaultPermissionsResolver(textService, actions); + + CreateMap() + .ConstructUsing(save => new UserGroup { CreateDate = DateTime.UtcNow }) + .IgnoreEntityCommonProperties() + .ForMember(dest => dest.Id, opt => opt.Condition(source => GetIntId(source.Id) > 0)) + .ForMember(dest => dest.Id, opt => opt.MapFrom(source => GetIntId(source.Id))) + .ForMember(dest => dest.Permissions, opt => opt.MapFrom(source => source.DefaultPermissions)) + .AfterMap((save, userGroup) => + { + userGroup.ClearAllowedSections(); + foreach (var section in save.Sections) + { + userGroup.AddAllowedSection(section); + } + }); + + //Used for merging existing UserSave to an existing IUser instance - this will not create an IUser instance! + CreateMap() + .IgnoreEntityCommonProperties() + .ForMember(dest => dest.Id, opt => opt.Condition(src => GetIntId(src.Id) > 0)) + .ForMember(detail => detail.TourData, opt => opt.Ignore()) + .ForMember(dest => dest.SessionTimeout, opt => opt.Ignore()) + .ForMember(dest => dest.EmailConfirmedDate, opt => opt.Ignore()) + .ForMember(dest => dest.UserType, opt => opt.Ignore()) + .ForMember(dest => dest.InvitedDate, opt => opt.Ignore()) + .ForMember(dest => dest.SecurityStamp, opt => opt.Ignore()) + .ForMember(dest => dest.Avatar, opt => opt.Ignore()) + .ForMember(dest => dest.ProviderUserKey, opt => opt.Ignore()) + .ForMember(dest => dest.RawPasswordValue, opt => opt.Ignore()) + .ForMember(dest => dest.RawPasswordAnswerValue, opt => opt.Ignore()) + .ForMember(dest => dest.PasswordQuestion, opt => opt.Ignore()) + .ForMember(dest => dest.Comments, opt => opt.Ignore()) + .ForMember(dest => dest.IsApproved, opt => opt.Ignore()) + .ForMember(dest => dest.IsLockedOut, opt => opt.Ignore()) + .ForMember(dest => dest.LastLoginDate, opt => opt.Ignore()) + .ForMember(dest => dest.LastPasswordChangeDate, opt => opt.Ignore()) + .ForMember(dest => dest.LastLockoutDate, opt => opt.Ignore()) + .ForMember(dest => dest.FailedPasswordAttempts, opt => opt.Ignore()) + .ForMember(user => user.Language, opt => opt.MapFrom(save => save.Culture)) + .AfterMap((save, user) => + { + user.ClearGroups(); + var foundGroups = userService.GetUserGroupsByAlias(save.UserGroups.ToArray()); + foreach (var group in foundGroups) + { + user.AddGroup(group.ToReadOnlyGroup()); + } + }); + + CreateMap() + .IgnoreEntityCommonProperties() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(detail => detail.TourData, opt => opt.Ignore()) + .ForMember(dest => dest.StartContentIds, opt => opt.Ignore()) + .ForMember(dest => dest.StartMediaIds, opt => opt.Ignore()) + .ForMember(dest => dest.UserType, opt => opt.Ignore()) + .ForMember(dest => dest.Language, opt => opt.Ignore()) + .ForMember(dest => dest.Username, opt => opt.Ignore()) + .ForMember(dest => dest.PasswordQuestion, opt => opt.Ignore()) + .ForMember(dest => dest.SessionTimeout, opt => opt.Ignore()) + .ForMember(dest => dest.EmailConfirmedDate, opt => opt.Ignore()) + .ForMember(dest => dest.InvitedDate, opt => opt.Ignore()) + .ForMember(dest => dest.SecurityStamp, opt => opt.Ignore()) + .ForMember(dest => dest.Avatar, opt => opt.Ignore()) + .ForMember(dest => dest.ProviderUserKey, opt => opt.Ignore()) + .ForMember(dest => dest.RawPasswordValue, opt => opt.Ignore()) + .ForMember(dest => dest.RawPasswordAnswerValue, opt => opt.Ignore()) + .ForMember(dest => dest.Comments, opt => opt.Ignore()) + .ForMember(dest => dest.IsApproved, opt => opt.Ignore()) + .ForMember(dest => dest.IsLockedOut, opt => opt.Ignore()) + .ForMember(dest => dest.LastLoginDate, opt => opt.Ignore()) + .ForMember(dest => dest.LastPasswordChangeDate, opt => opt.Ignore()) + .ForMember(dest => dest.LastLockoutDate, opt => opt.Ignore()) + .ForMember(dest => dest.FailedPasswordAttempts, opt => opt.Ignore()) + //all invited users will not be approved, completing the invite will approve the user + .ForMember(user => user.IsApproved, opt => opt.UseValue(false)) + .AfterMap((invite, user) => + { + user.ClearGroups(); + var foundGroups = userService.GetUserGroupsByAlias(invite.UserGroups.ToArray()); + foreach (var group in foundGroups) + { + user.AddGroup(group.ToReadOnlyGroup()); + } + }); + + CreateMap() + .ForMember(dest => dest.ContentStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.UserCount, opt => opt.Ignore()) + .ForMember(dest => dest.MediaStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.Key, opt => opt.Ignore()) + .ForMember(dest => dest.Sections, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .AfterMap((group, display) => + { + MapUserGroupBasic(sectionService, entityService, textService, group, display); + }); + + CreateMap() + .ForMember(dest => dest.ContentStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.MediaStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.Sections, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .AfterMap((group, display) => + { + MapUserGroupBasic(sectionService, entityService, textService, group, display); + }); + + //create a map to assign a user group's default permissions to the AssignedUserGroupPermissions instance + CreateMap() + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .ForMember(dest => dest.Id, opt => opt.MapFrom(group => group.Id)) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) + .ForMember(dest => dest.DefaultPermissions, opt => opt.ResolveUsing(src => userGroupDefaultPermissionsResolver.Resolve(src))) + //these will be manually mapped and by default they are null + .ForMember(dest => dest.AssignedPermissions, opt => opt.Ignore()) + .AfterMap((group, display) => + { + if (display.Icon.IsNullOrWhiteSpace()) + { + display.Icon = "icon-users"; + } + }); + + CreateMap() + .ForMember(x => x.Udi, opt => opt.MapFrom(x => Udi.Create(ObjectTypes.GetUdiType(x.NodeObjectType), x.Key))) + .ForMember(basic => basic.Icon, opt => opt.MapFrom(entity => GetContentTypeIcon(entity))) + .ForMember(dto => dto.Trashed, opt => opt.Ignore()) + .ForMember(x => x.Alias, opt => opt.Ignore()) + .ForMember(x => x.AssignedPermissions, opt => opt.Ignore()) + .AfterMap((entity, basic) => + { + if (entity.NodeObjectType == Constants.ObjectTypes.Member && basic.Icon.IsNullOrWhiteSpace()) + { + basic.Icon = "icon-user"; + } + }); + + CreateMap() + .ForMember(dest => dest.ContentStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.MediaStartNode, opt => opt.Ignore()) + .ForMember(dest => dest.Sections, opt => opt.Ignore()) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) + .ForMember(dest => dest.Users, opt => opt.Ignore()) + .ForMember(dest => dest.DefaultPermissions, opt => opt.ResolveUsing(src => userGroupDefaultPermissionsResolver.Resolve(src))) + .ForMember(dest => dest.AssignedPermissions, opt => opt.Ignore()) + .AfterMap((group, display) => + { + MapUserGroupBasic(sectionService, entityService, textService, group, display); + + //Important! Currently we are never mapping to multiple UserGroupDisplay objects but if we start doing that + // this will cause an N+1 and we'll need to change how this works. + var users = userService.GetAllInGroup(group.Id); + display.Users = Mapper.Map>(users); + + //Deal with assigned permissions: + + var allContentPermissions = userService.GetPermissions(@group, true) + .ToDictionary(x => x.EntityId, x => x); + + IEntitySlim[] contentEntities; + if (allContentPermissions.Keys.Count == 0) + { + contentEntities = Array.Empty(); + } + else + { + // a group can end up with way more than 2000 assigned permissions, + // so we need to break them into groups in order to avoid breaking + // the entity service due to too many Sql parameters. + + var list = new List(); + foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(2000)) + list.AddRange(entityService.GetAll(UmbracoObjectTypes.Document, idGroup.ToArray())); + contentEntities = list.ToArray(); + } + + var allAssignedPermissions = new List(); + foreach (var entity in contentEntities) + { + var contentPermissions = allContentPermissions[entity.Id]; + + var assignedContentPermissions = Mapper.Map(entity); + assignedContentPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(display.DefaultPermissions); + + //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions + //and we'll re-check it if it's one of the explicitly assigned ones + foreach (var permission in assignedContentPermissions.AssignedPermissions.SelectMany(x => x.Value)) + { + permission.Checked = false; + permission.Checked = contentPermissions.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); + } + + allAssignedPermissions.Add(assignedContentPermissions); + } + + display.AssignedPermissions = allAssignedPermissions; + }); + + //Important! Currently we are never mapping to multiple UserDisplay objects but if we start doing that + // this will cause an N+1 and we'll need to change how this works. + CreateMap() + .ForMember(dest => dest.Avatars, opt => opt.MapFrom(user => user.GetUserAvatarUrls(runtimeCache))) + .ForMember(dest => dest.Username, opt => opt.MapFrom(user => user.Username)) + .ForMember(dest => dest.LastLoginDate, opt => opt.MapFrom(user => user.LastLoginDate == default(DateTime) ? null : (DateTime?)user.LastLoginDate)) + .ForMember(dest => dest.UserGroups, opt => opt.MapFrom(user => user.Groups)) + .ForMember(detail => detail.Navigation, opt => opt.MapFrom(user => CreateUserEditorNavigation(textService))) + .ForMember( + dest => dest.CalculatedStartContentIds, + opt => opt.MapFrom(src => GetStartNodeValues( + src.CalculateContentStartNodeIds(entityService), + textService, entityService, UmbracoObjectTypes.Document, "content/contentRoot"))) + .ForMember( + dest => dest.CalculatedStartMediaIds, + opt => opt.MapFrom(src => GetStartNodeValues( + src.CalculateMediaStartNodeIds(entityService), + textService, entityService, UmbracoObjectTypes.Media, "media/mediaRoot"))) + .ForMember( + dest => dest.StartContentIds, + opt => opt.MapFrom(src => GetStartNodeValues( + src.StartContentIds.ToArray(), + textService, entityService, UmbracoObjectTypes.Document, "content/contentRoot"))) + .ForMember( + dest => dest.StartMediaIds, + opt => opt.MapFrom(src => GetStartNodeValues( + src.StartMediaIds.ToArray(), + textService, entityService, UmbracoObjectTypes.Media, "media/mediaRoot"))) + .ForMember(dest => dest.Culture, opt => opt.MapFrom(user => user.GetUserCulture(textService, globalSettings))) + .ForMember( + dest => dest.AvailableCultures, + opt => opt.MapFrom(user => textService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName))) + .ForMember( + dest => dest.EmailHash, + opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().GenerateHash())) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(user => "-1," + user.Id)) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) + .ForMember(dest => dest.IsCurrentUser, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.ResetPasswordValue, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + + CreateMap() + //Loading in the user avatar's requires an external request if they don't have a local file avatar, this means that initial load of paging may incur a cost + //Alternatively, if this is annoying the back office UI would need to be updated to request the avatars for the list of users separately so it doesn't look + //like the load time is waiting. + .ForMember(detail => + detail.Avatars, + opt => opt.MapFrom(user => user.GetUserAvatarUrls(runtimeCache))) + .ForMember(dest => dest.Username, opt => opt.MapFrom(user => user.Username)) + .ForMember(dest => dest.UserGroups, opt => opt.MapFrom(user => user.Groups)) + .ForMember(dest => dest.LastLoginDate, opt => opt.MapFrom(user => user.LastLoginDate == default(DateTime) ? null : (DateTime?)user.LastLoginDate)) + .ForMember(dest => dest.Culture, opt => opt.MapFrom(user => user.GetUserCulture(textService, globalSettings))) + .ForMember( + dest => dest.EmailHash, + opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())) + .ForMember(dest => dest.ParentId, opt => opt.UseValue(-1)) + .ForMember(dest => dest.Path, opt => opt.MapFrom(user => "-1," + user.Id)) + .ForMember(dest => dest.Notifications, opt => opt.Ignore()) + .ForMember(dest => dest.IsCurrentUser, opt => opt.Ignore()) + .ForMember(dest => dest.Udi, opt => opt.Ignore()) + .ForMember(dest => dest.Icon, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.Alias, opt => opt.Ignore()) + .ForMember(dest => dest.Trashed, opt => opt.Ignore()) + .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Avatars, opt => opt.MapFrom(user => user.GetUserAvatarUrls(runtimeCache))) + .ForMember(dest => dest.UserId, opt => opt.MapFrom(user => GetIntId(user.Id))) + .ForMember(dest => dest.StartContentIds, opt => opt.MapFrom(user => user.CalculateContentStartNodeIds(entityService))) + .ForMember(dest => dest.StartMediaIds, opt => opt.MapFrom(user => user.CalculateMediaStartNodeIds(entityService))) + .ForMember(dest => dest.Culture, opt => opt.MapFrom(user => user.GetUserCulture(textService, globalSettings))) + .ForMember( + dest => dest.EmailHash, + opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().GenerateHash())) + .ForMember(dest => dest.SecondsUntilTimeout, opt => opt.Ignore()) + .ForMember(dest => dest.UserGroups, opt => opt.Ignore()) + .AfterMap((user, detail) => + { + //we need to map the legacy UserType + //the best we can do here is to return the user's first user group as a IUserType object + //but we should attempt to return any group that is the built in ones first + var groups = user.Groups.ToArray(); + detail.UserGroups = user.Groups.Select(x => x.Alias).ToArray(); + + if (groups.Length == 0) + { + //In backwards compatibility land, a user type cannot be null! so we need to return a fake one. + detail.UserType = "temp"; + } + else + { + var builtIns = new[] { Constants.Security.AdminGroupAlias, "writer", "editor", Constants.Security.TranslatorGroupAlias }; + var foundBuiltIn = groups.FirstOrDefault(x => builtIns.Contains(x.Alias)); + if (foundBuiltIn != null) + { + detail.UserType = foundBuiltIn.Alias; + } + else + { + //otherwise return the first + detail.UserType = groups[0].Alias; + } + } + + }); + + CreateMap() + .ForMember(dest => dest.UserId, opt => opt.MapFrom(profile => GetIntId(profile.Id))); + } + + private IEnumerable CreateUserEditorNavigation(ILocalizedTextService textService) + { + return new[] + { + new EditorNavigation + { + Active = true, + Alias = "details", + Icon = "icon-umb-users", + Name = textService.Localize("general/user"), + View = "views/users/views/user/details.html" + } + }; + } + + private IEnumerable GetStartNodeValues(int[] startNodeIds, + ILocalizedTextService textService, IEntityService entityService, UmbracoObjectTypes objectType, + string localizedKey) + { + if (startNodeIds.Length <= 0) + return Enumerable.Empty(); + + var startNodes = new List(); + if (startNodeIds.Contains(-1)) + startNodes.Add(RootNode(textService.Localize(localizedKey))); + + var mediaItems = entityService.GetAll(objectType, startNodeIds); + startNodes.AddRange(Mapper.Map, IEnumerable>(mediaItems)); + return startNodes; + } + + private void MapUserGroupBasic(ISectionService sectionService, IEntityService entityService, ILocalizedTextService textService, dynamic group, UserGroupBasic display) + { + var allSections = sectionService.GetSections(); + display.Sections = allSections.Where(x => Enumerable.Contains(group.AllowedSections, x.Alias)).Select(Mapper.Map); + + if (group.StartMediaId > 0) + { + display.MediaStartNode = Mapper.Map( + entityService.Get(group.StartMediaId, UmbracoObjectTypes.Media)); + } + else if (group.StartMediaId == -1) + { + //create the root node + display.MediaStartNode = RootNode(textService.Localize("media/mediaRoot")); + } + + if (group.StartContentId > 0) + { + display.ContentStartNode = Mapper.Map( + entityService.Get(group.StartContentId, UmbracoObjectTypes.Document)); + } + else if (group.StartContentId == -1) + { + //create the root node + display.ContentStartNode = RootNode(textService.Localize("content/contentRoot")); + } + + if (display.Icon.IsNullOrWhiteSpace()) + { + display.Icon = "icon-users"; + } + } + + private EntityBasic RootNode(string name) + { + return new EntityBasic + { + Name = name, + Path = "-1", + Icon = "icon-folder", + Id = -1, + Trashed = false, + ParentId = -1 + }; + } + + private static int GetIntId(object id) + { + var result = id.TryConvertTo(); + if (result.Success == false) + { + throw new InvalidOperationException( + "Cannot convert the profile to a " + typeof(UserDetail).Name + " object since the id is not an integer"); + } + return result.Result; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs deleted file mode 100644 index 446a63ee99..0000000000 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ /dev/null @@ -1,469 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; -using umbraco; -using umbraco.BusinessLogic.Actions; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Security; -using Umbraco.Core.Services; -using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; - -namespace Umbraco.Web.Models.Mapping -{ - internal class UserModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - - config.CreateMap() - .ConstructUsing((UserGroupSave save) => new UserGroup() { CreateDate = DateTime.Now }) - .IgnoreDeletableEntityCommonProperties() - .ForMember(dest => dest.Id, map => map.Condition(source => GetIntId(source.Id) > 0)) - .ForMember(dest => dest.Id, map => map.MapFrom(source => GetIntId(source.Id))) - .ForMember(dest => dest.Permissions, map => map.MapFrom(source => source.DefaultPermissions)) - .AfterMap((save, userGroup) => - { - userGroup.ClearAllowedSections(); - foreach (var section in save.Sections) - { - userGroup.AddAllowedSection(section); - } - }); - - //Used for merging existing UserSave to an existing IUser instance - this will not create an IUser instance! - config.CreateMap() - .IgnoreDeletableEntityCommonProperties() - .ForMember(dest => dest.Id, map => map.Condition(source => GetIntId(source.Id) > 0)) - .ForMember(detail => detail.TourData, opt => opt.Ignore()) - .ForMember(detail => detail.SessionTimeout, opt => opt.Ignore()) - .ForMember(detail => detail.EmailConfirmedDate, opt => opt.Ignore()) - .ForMember(detail => detail.UserType, opt => opt.Ignore()) - .ForMember(detail => detail.StartContentId, opt => opt.Ignore()) - .ForMember(detail => detail.StartMediaId, opt => opt.Ignore()) - .ForMember(detail => detail.InvitedDate, opt => opt.Ignore()) - .ForMember(detail => detail.SecurityStamp, opt => opt.Ignore()) - .ForMember(detail => detail.Avatar, opt => opt.Ignore()) - .ForMember(detail => detail.ProviderUserKey, opt => opt.Ignore()) - .ForMember(detail => detail.RawPasswordValue, opt => opt.Ignore()) - .ForMember(detail => detail.RawPasswordAnswerValue, opt => opt.Ignore()) - .ForMember(detail => detail.PasswordQuestion, opt => opt.Ignore()) - .ForMember(detail => detail.Comments, opt => opt.Ignore()) - .ForMember(detail => detail.IsApproved, opt => opt.Ignore()) - .ForMember(detail => detail.IsLockedOut, opt => opt.Ignore()) - .ForMember(detail => detail.LastLoginDate, opt => opt.Ignore()) - .ForMember(detail => detail.LastPasswordChangeDate, opt => opt.Ignore()) - .ForMember(detail => detail.LastLockoutDate, opt => opt.Ignore()) - .ForMember(detail => detail.FailedPasswordAttempts, opt => opt.Ignore()) - .ForMember(user => user.Language, expression => expression.MapFrom(save => save.Culture)) - .AfterMap((save, user) => - { - user.ClearGroups(); - var foundGroups = applicationContext.Services.UserService.GetUserGroupsByAlias(save.UserGroups.ToArray()); - foreach (var group in foundGroups) - { - user.AddGroup(group.ToReadOnlyGroup()); - } - }); - - config.CreateMap() - .IgnoreDeletableEntityCommonProperties() - .ForMember(detail => detail.Id, opt => opt.Ignore()) - .ForMember(detail => detail.TourData, opt => opt.Ignore()) - .ForMember(detail => detail.StartContentIds, opt => opt.Ignore()) - .ForMember(detail => detail.StartMediaIds, opt => opt.Ignore()) - .ForMember(detail => detail.UserType, opt => opt.Ignore()) - .ForMember(detail => detail.StartContentId, opt => opt.Ignore()) - .ForMember(detail => detail.StartMediaId, opt => opt.Ignore()) - .ForMember(detail => detail.Language, opt => opt.Ignore()) - .ForMember(detail => detail.Username, opt => opt.Ignore()) - .ForMember(detail => detail.PasswordQuestion, opt => opt.Ignore()) - .ForMember(detail => detail.SessionTimeout, opt => opt.Ignore()) - .ForMember(detail => detail.EmailConfirmedDate, opt => opt.Ignore()) - .ForMember(detail => detail.InvitedDate, opt => opt.Ignore()) - .ForMember(detail => detail.SecurityStamp, opt => opt.Ignore()) - .ForMember(detail => detail.Avatar, opt => opt.Ignore()) - .ForMember(detail => detail.ProviderUserKey, opt => opt.Ignore()) - .ForMember(detail => detail.RawPasswordValue, opt => opt.Ignore()) - .ForMember(detail => detail.RawPasswordAnswerValue, opt => opt.Ignore()) - .ForMember(detail => detail.Comments, opt => opt.Ignore()) - .ForMember(detail => detail.IsApproved, opt => opt.Ignore()) - .ForMember(detail => detail.IsLockedOut, opt => opt.Ignore()) - .ForMember(detail => detail.LastLoginDate, opt => opt.Ignore()) - .ForMember(detail => detail.LastPasswordChangeDate, opt => opt.Ignore()) - .ForMember(detail => detail.LastLockoutDate, opt => opt.Ignore()) - .ForMember(detail => detail.FailedPasswordAttempts, opt => opt.Ignore()) - //all invited users will not be approved, completing the invite will approve the user - .ForMember(user => user.IsApproved, expression => expression.UseValue(false)) - .AfterMap((invite, user) => - { - user.ClearGroups(); - var foundGroups = applicationContext.Services.UserService.GetUserGroupsByAlias(invite.UserGroups.ToArray()); - foreach (var group in foundGroups) - { - user.AddGroup(group.ToReadOnlyGroup()); - } - }); - - config.CreateMap() - .ForMember(detail => detail.ContentStartNode, opt => opt.Ignore()) - .ForMember(detail => detail.UserCount, opt => opt.Ignore()) - .ForMember(detail => detail.MediaStartNode, opt => opt.Ignore()) - .ForMember(detail => detail.Key, opt => opt.Ignore()) - .ForMember(detail => detail.Sections, opt => opt.Ignore()) - .ForMember(detail => detail.Notifications, opt => opt.Ignore()) - .ForMember(detail => detail.Udi, opt => opt.Ignore()) - .ForMember(detail => detail.Trashed, opt => opt.Ignore()) - .ForMember(detail => detail.ParentId, opt => opt.UseValue(-1)) - .ForMember(detail => detail.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) - .ForMember(detail => detail.AdditionalData, opt => opt.Ignore()) - .AfterMap((group, display) => - { - MapUserGroupBasic(applicationContext.Services, group, display); - }); - - config.CreateMap() - .ForMember(detail => detail.ContentStartNode, opt => opt.Ignore()) - .ForMember(detail => detail.MediaStartNode, opt => opt.Ignore()) - .ForMember(detail => detail.Sections, opt => opt.Ignore()) - .ForMember(detail => detail.Notifications, opt => opt.Ignore()) - .ForMember(detail => detail.Udi, opt => opt.Ignore()) - .ForMember(detail => detail.Trashed, opt => opt.Ignore()) - .ForMember(detail => detail.ParentId, opt => opt.UseValue(-1)) - .ForMember(detail => detail.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) - .ForMember(detail => detail.AdditionalData, opt => opt.Ignore()) - .AfterMap((group, display) => - { - MapUserGroupBasic(applicationContext.Services, group, display); - }); - - //create a map to assign a user group's default permissions to the AssignedUserGroupPermissions instance - config.CreateMap() - .ForMember(detail => detail.Udi, opt => opt.Ignore()) - .ForMember(detail => detail.Trashed, opt => opt.Ignore()) - .ForMember(detail => detail.AdditionalData, opt => opt.Ignore()) - .ForMember(detail => detail.Id, opt => opt.MapFrom(group => group.Id)) - .ForMember(detail => detail.ParentId, opt => opt.UseValue(-1)) - .ForMember(detail => detail.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) - .ForMember(detail => detail.DefaultPermissions, expression => expression.ResolveUsing(new UserGroupDefaultPermissionsResolver(applicationContext.Services.TextService))) - //these will be manually mapped and by default they are null - .ForMember(detail => detail.AssignedPermissions, opt => opt.Ignore()) - .AfterMap((group, display) => - { - if (display.Icon.IsNullOrWhiteSpace()) - { - display.Icon = "icon-users"; - } - }); - - config.CreateMap() - .ForMember(x => x.Udi, expression => expression.MapFrom(x => Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(x.NodeObjectTypeId), x.Key))) - .ForMember(basic => basic.Icon, expression => expression.MapFrom(entity => entity.ContentTypeIcon)) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) - .ForMember(x => x.Alias, expression => expression.Ignore()) - .ForMember(x => x.AssignedPermissions, expression => expression.Ignore()) - .AfterMap((entity, basic) => - { - if (entity.NodeObjectTypeId == Constants.ObjectTypes.MemberGuid && basic.Icon.IsNullOrWhiteSpace()) - { - basic.Icon = "icon-user"; - } - }); - - config.CreateMap() - .ForMember(detail => detail.ContentStartNode, opt => opt.Ignore()) - .ForMember(detail => detail.MediaStartNode, opt => opt.Ignore()) - .ForMember(detail => detail.Sections, opt => opt.Ignore()) - .ForMember(detail => detail.Notifications, opt => opt.Ignore()) - .ForMember(detail => detail.Udi, opt => opt.Ignore()) - .ForMember(detail => detail.Trashed, opt => opt.Ignore()) - .ForMember(detail => detail.ParentId, opt => opt.UseValue(-1)) - .ForMember(detail => detail.Path, opt => opt.MapFrom(userGroup => "-1," + userGroup.Id)) - .ForMember(detail => detail.AdditionalData, opt => opt.Ignore()) - .ForMember(detail => detail.Users, opt => opt.Ignore()) - .ForMember(detail => detail.DefaultPermissions, expression => expression.ResolveUsing(new UserGroupDefaultPermissionsResolver(applicationContext.Services.TextService))) - .ForMember(detail => detail.AssignedPermissions, opt => opt.Ignore()) - .AfterMap((group, display) => - { - MapUserGroupBasic(applicationContext.Services, group, display); - - //Important! Currently we are never mapping to multiple UserGroupDisplay objects but if we start doing that - // this will cause an N+1 and we'll need to change how this works. - var users = applicationContext.Services.UserService.GetAllInGroup(group.Id); - display.Users = Mapper.Map>(users); - - //Deal with assigned permissions: - - var allContentPermissions = applicationContext.Services.UserService.GetPermissions(@group, true) - .ToDictionary(x => x.EntityId, x => x); - - IEnumerable contentEntities; - if (allContentPermissions.Keys.Count == 0) - { - contentEntities = new IUmbracoEntity[0]; - } - else - { - // a group can end up with way more than 2000 assigned permissions, - // so we need to break them into groups in order to avoid breaking - // the entity service due to too many Sql parameters. - - var list = new List(); - contentEntities = list; - var entityService = applicationContext.Services.EntityService; - foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(2000)) - list.AddRange(entityService.GetAll(UmbracoObjectTypes.Document, idGroup.ToArray())); - } - - var allAssignedPermissions = new List(); - foreach (var entity in contentEntities) - { - var contentPermissions = allContentPermissions[entity.Id]; - - var assignedContentPermissions = Mapper.Map(entity); - assignedContentPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(display.DefaultPermissions); - - //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions - //and we'll re-check it if it's one of the explicitly assigned ones - foreach (var permission in assignedContentPermissions.AssignedPermissions.SelectMany(x => x.Value)) - { - permission.Checked = false; - permission.Checked = contentPermissions.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); - } - - allAssignedPermissions.Add(assignedContentPermissions); - } - - display.AssignedPermissions = allAssignedPermissions; - }); - - //Important! Currently we are never mapping to multiple UserDisplay objects but if we start doing that - // this will cause an N+1 and we'll need to change how this works. - - config.CreateMap() - .ForMember(detail => detail.Avatars, opt => opt.MapFrom(user => user.GetUserAvatarUrls(applicationContext.ApplicationCache.RuntimeCache))) - .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username)) - .ForMember(detail => detail.LastLoginDate, opt => opt.MapFrom(user => user.LastLoginDate == default(DateTime) ? null : (DateTime?) user.LastLoginDate)) - .ForMember(detail => detail.UserGroups, opt => opt.MapFrom(user => user.Groups)) - .ForMember(detail => detail.Navigation, opt => opt.MapFrom(user => CreateUserEditorNavigation(applicationContext.Services.TextService))) - .ForMember( - detail => detail.CalculatedStartContentIds, - opt => opt.MapFrom(user => GetStartNodeValues( - user.CalculateContentStartNodeIds(applicationContext.Services.EntityService), - applicationContext.Services.TextService, - applicationContext.Services.EntityService, - UmbracoObjectTypes.Document, - "content/contentRoot"))) - .ForMember( - detail => detail.CalculatedStartMediaIds, - opt => opt.MapFrom(user => GetStartNodeValues( - user.CalculateMediaStartNodeIds(applicationContext.Services.EntityService), - applicationContext.Services.TextService, - applicationContext.Services.EntityService, - UmbracoObjectTypes.Media, - "media/mediaRoot"))) - .ForMember( - detail => detail.StartContentIds, - opt => opt.MapFrom(user => GetStartNodeValues( - user.StartContentIds.ToArray(), - applicationContext.Services.TextService, - applicationContext.Services.EntityService, - UmbracoObjectTypes.Document, - "content/contentRoot"))) - .ForMember( - detail => detail.StartMediaIds, - opt => opt.MapFrom(user => GetStartNodeValues( - user.StartMediaIds.ToArray(), - applicationContext.Services.TextService, - applicationContext.Services.EntityService, - UmbracoObjectTypes.Media, - "media/mediaRoot"))) - .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) - .ForMember( - detail => detail.AvailableCultures, - opt => opt.MapFrom(user => applicationContext.Services.TextService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName))) - .ForMember( - detail => detail.EmailHash, - opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().GenerateHash())) - .ForMember(detail => detail.ParentId, opt => opt.UseValue(-1)) - .ForMember(detail => detail.Path, opt => opt.MapFrom(user => "-1," + user.Id)) - .ForMember(detail => detail.Notifications, opt => opt.Ignore()) - .ForMember(detail => detail.Udi, opt => opt.Ignore()) - .ForMember(detail => detail.Icon, opt => opt.Ignore()) - .ForMember(detail => detail.IsCurrentUser, opt => opt.Ignore()) - .ForMember(detail => detail.Trashed, opt => opt.Ignore()) - .ForMember(detail => detail.ResetPasswordValue, opt => opt.Ignore()) - .ForMember(detail => detail.Alias, opt => opt.Ignore()) - .ForMember(detail => detail.Trashed, opt => opt.Ignore()) - .ForMember(detail => detail.AdditionalData, opt => opt.Ignore()); - - config.CreateMap() - //Loading in the user avatar's requires an external request if they don't have a local file avatar, this means that initial load of paging may incur a cost - //Alternatively, if this is annoying the back office UI would need to be updated to request the avatars for the list of users separately so it doesn't look - //like the load time is waiting. - .ForMember(detail => - detail.Avatars, - opt => opt.MapFrom(user => user.GetUserAvatarUrls(applicationContext.ApplicationCache.RuntimeCache))) - .ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username)) - .ForMember(detail => detail.UserGroups, opt => opt.MapFrom(user => user.Groups)) - .ForMember(detail => detail.LastLoginDate, opt => opt.MapFrom(user => user.LastLoginDate == default(DateTime) ? null : (DateTime?) user.LastLoginDate)) - .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) - .ForMember( - detail => detail.EmailHash, - opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())) - .ForMember(detail => detail.ParentId, opt => opt.UseValue(-1)) - .ForMember(detail => detail.Path, opt => opt.MapFrom(user => "-1," + user.Id)) - .ForMember(detail => detail.Notifications, opt => opt.Ignore()) - .ForMember(detail => detail.IsCurrentUser, opt => opt.Ignore()) - .ForMember(detail => detail.Udi, opt => opt.Ignore()) - .ForMember(detail => detail.Icon, opt => opt.Ignore()) - .ForMember(detail => detail.Trashed, opt => opt.Ignore()) - .ForMember(detail => detail.Alias, opt => opt.Ignore()) - .ForMember(detail => detail.Trashed, opt => opt.Ignore()) - .ForMember(detail => detail.AdditionalData, opt => opt.Ignore()); - - config.CreateMap() - .ForMember(detail => detail.Avatars, opt => opt.MapFrom(user => user.GetUserAvatarUrls(applicationContext.ApplicationCache.RuntimeCache))) - .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => GetIntId(user.Id))) - .ForMember(detail => detail.StartContentIds, opt => opt.MapFrom(user => user.CalculateContentStartNodeIds(applicationContext.Services.EntityService))) - .ForMember(detail => detail.StartMediaIds, opt => opt.MapFrom(user => user.CalculateMediaStartNodeIds(applicationContext.Services.EntityService))) - .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) - .ForMember( - detail => detail.EmailHash, - opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().GenerateHash())) - .ForMember(detail => detail.SecondsUntilTimeout, opt => opt.Ignore()) - .ForMember(detail => detail.UserGroups, opt => opt.Ignore()) - .AfterMap((user, detail) => - { - //we need to map the legacy UserType - //the best we can do here is to return the user's first user group as a IUserType object - //but we should attempt to return any group that is the built in ones first - var groups = user.Groups.ToArray(); - detail.UserGroups = user.Groups.Select(x => x.Alias).ToArray(); - - if (groups.Length == 0) - { - //In backwards compatibility land, a user type cannot be null! so we need to return a fake one. - detail.UserType = "temp"; - } - else - { - var builtIns = new[] { Constants.Security.AdminGroupAlias, "writer", "editor", Constants.Security.TranslatorGroupAlias }; - var foundBuiltIn = groups.FirstOrDefault(x => builtIns.Contains(x.Alias)); - if (foundBuiltIn != null) - { - detail.UserType = foundBuiltIn.Alias; - } - else - { - //otherwise return the first - detail.UserType = groups[0].Alias; - } - } - - }); - - config.CreateMap() - .ForMember(detail => detail.UserId, opt => opt.MapFrom(profile => GetIntId(profile.Id))); - } - - private IEnumerable CreateUserEditorNavigation(ILocalizedTextService textService) - { - return new[] - { - new EditorNavigation - { - Active = true, - Alias = "details", - Icon = "icon-umb-users", - Name = textService.Localize("general/user"), - View = "views/users/views/user/details.html" - } - }; - } - - private IEnumerable GetStartNodeValues(int[] startNodeIds, - ILocalizedTextService textService, IEntityService entityService, UmbracoObjectTypes objectType, - string localizedKey) - { - if (startNodeIds.Length > 0) - { - var startNodes = new List(); - if (startNodeIds.Contains(-1)) - { - startNodes.Add(RootNode(textService.Localize(localizedKey))); - } - var mediaItems = entityService.GetAll(objectType, startNodeIds); - startNodes.AddRange(Mapper.Map, IEnumerable>(mediaItems)); - return startNodes; - } - return Enumerable.Empty(); - } - - private void MapUserGroupBasic(ServiceContext services, dynamic group, UserGroupBasic display) - { - var allSections = services.SectionService.GetSections(); - display.Sections = allSections.Where(x => Enumerable.Contains(group.AllowedSections, x.Alias)).Select(Mapper.Map); - - if (group.StartMediaId > 0) - { - display.MediaStartNode = Mapper.Map( - services.EntityService.Get(group.StartMediaId, UmbracoObjectTypes.Media)); - } - else if (group.StartMediaId == -1) - { - //create the root node - display.MediaStartNode = RootNode(services.TextService.Localize("media/mediaRoot")); - } - - if (group.StartContentId > 0) - { - display.ContentStartNode = Mapper.Map( - services.EntityService.Get(group.StartContentId, UmbracoObjectTypes.Document)); - } - else if (group.StartContentId == -1) - { - //create the root node - display.ContentStartNode = RootNode(services.TextService.Localize("content/contentRoot")); - } - - if (display.Icon.IsNullOrWhiteSpace()) - { - display.Icon = "icon-users"; - } - } - - private EntityBasic RootNode(string name) - { - return new EntityBasic - { - Name = name, - Path = "-1", - Icon = "icon-folder", - Id = -1, - Trashed = false, - ParentId = -1 - }; - } - - private static int GetIntId(object id) - { - var result = id.TryConvertTo(); - if (result.Success == false) - { - throw new InvalidOperationException( - "Cannot convert the profile to a " + typeof(UserDetail).Name + " object since the id is not an integer"); - } - return result.Result; - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/VariationResolver.cs b/src/Umbraco.Web/Models/Mapping/VariationResolver.cs new file mode 100644 index 0000000000..84bd79133a --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/VariationResolver.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using ContentVariation = Umbraco.Web.Models.ContentEditing.ContentVariation; +using Language = Umbraco.Web.Models.ContentEditing.Language; + +namespace Umbraco.Web.Models.Mapping +{ + internal class VariationResolver : IValueResolver> + { + private readonly ILocalizationService _localizationService; + + public VariationResolver(ILocalizationService localizationService) + { + _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + } + + public IEnumerable Resolve(IContent source, ContentItemDisplay destination, IEnumerable destMember, ResolutionContext context) + { + var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); + if (allLanguages.Count == 0) return Enumerable.Empty(); + + var langs = context.Mapper.Map, IEnumerable>(allLanguages, null, context); + var variants = langs.Select(x => new ContentVariation + { + Language = x, + Mandatory = x.Mandatory, + //fixme these all need to the variant values but we need to wait for the db/service changes + Name = source.Name , + Exists = source.HasVariation(x.Id), //TODO: This needs to be wired up with new APIs when they are ready + PublishedState = source.PublishedState.ToString(), + //Segment = ?? We'll need to populate this one day when we support segments + }).ToList(); + + var langId = context.GetLanguageId(); + + //set the current variant being edited to the one found in the context or the default if nothing matches + var foundCurrent = false; + foreach (var variant in variants) + { + if (langId.HasValue && langId.Value == variant.Language.Id) + { + variant.IsCurrent = true; + foundCurrent = true; + break; + } + } + if (!foundCurrent) + variants.First(x => x.Language.IsDefaultVariantLanguage).IsCurrent = true; + + return variants; + } + + } +} diff --git a/src/Umbraco.Web/Models/PackageInstallResult.cs b/src/Umbraco.Web/Models/PackageInstallResult.cs index bb76bc663b..91b19cda14 100644 --- a/src/Umbraco.Web/Models/PackageInstallResult.cs +++ b/src/Umbraco.Web/Models/PackageInstallResult.cs @@ -11,4 +11,4 @@ namespace Umbraco.Web.Models [DataMember(Name = "postInstallationPath")] public string PostInstallationPath { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/PartialViewMacroModel.cs b/src/Umbraco.Web/Models/PartialViewMacroModel.cs index 0a3129d876..14a1b8ee9c 100644 --- a/src/Umbraco.Web/Models/PartialViewMacroModel.cs +++ b/src/Umbraco.Web/Models/PartialViewMacroModel.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Models { @@ -32,12 +33,12 @@ namespace Umbraco.Web.Models MacroParameters = macroParams; } - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use the Content property instead")] - public IPublishedContent CurrentPage - { - get { return Content; } - } + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the Content property instead")] + public IPublishedContent CurrentPage + { + get { return Content; } + } public IPublishedContent Content { get; private set; } public string MacroName { get; private set; } @@ -46,4 +47,4 @@ namespace Umbraco.Web.Models public IDictionary MacroParameters { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/PartialViewMacroModelExtensions.cs b/src/Umbraco.Web/Models/PartialViewMacroModelExtensions.cs index 71c3462bf8..9a74a1db31 100644 --- a/src/Umbraco.Web/Models/PartialViewMacroModelExtensions.cs +++ b/src/Umbraco.Web/Models/PartialViewMacroModelExtensions.cs @@ -13,10 +13,10 @@ namespace Umbraco.Web.Models /// /// /// - /// Parameter value if available, the default value that was passed otherwise. + /// Parameter value if available, the default value that was passed otherwise. public static T GetParameterValue(this PartialViewMacroModel partialViewMacroModel, string parameterAlias, T defaultValue) { - if (partialViewMacroModel.MacroParameters.ContainsKey(parameterAlias) == false || string.IsNullOrEmpty(partialViewMacroModel.MacroParameters[parameterAlias].ToString())) + if (partialViewMacroModel.MacroParameters.ContainsKey(parameterAlias) == false || string.IsNullOrEmpty(partialViewMacroModel.MacroParameters[parameterAlias].ToString())) return defaultValue; var attempt = partialViewMacroModel.MacroParameters[parameterAlias].TryConvertTo(typeof(T)); @@ -24,12 +24,12 @@ namespace Umbraco.Web.Models return attempt.Success ? (T) attempt.Result : defaultValue; } - /// + /// /// Attempt to get a Macro parameter from a PartialViewMacroModel /// /// /// - /// Parameter value if available, the default value for the type otherwise. + /// Parameter value if available, the default value for the type otherwise. public static T GetParameterValue(this PartialViewMacroModel partialViewMacroModel, string parameterAlias) { return partialViewMacroModel.GetParameterValue(parameterAlias, default(T)); diff --git a/src/Umbraco.Web/Models/PasswordChangedModel.cs b/src/Umbraco.Web/Models/PasswordChangedModel.cs index c81cdffb46..64fd1e7351 100644 --- a/src/Umbraco.Web/Models/PasswordChangedModel.cs +++ b/src/Umbraco.Web/Models/PasswordChangedModel.cs @@ -17,4 +17,4 @@ namespace Umbraco.Web.Models /// public string ResetPassword { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/PostRedirectModel.cs b/src/Umbraco.Web/Models/PostRedirectModel.cs index ed8176cc5f..e04584704d 100644 --- a/src/Umbraco.Web/Models/PostRedirectModel.cs +++ b/src/Umbraco.Web/Models/PostRedirectModel.cs @@ -1,7 +1,7 @@ namespace Umbraco.Web.Models { /// - /// A base model containing a value to indicate to Umbraco where to redirect to after Posting if + /// A base model containing a value to indicate to Umbraco where to redirect to after Posting if /// a developer doesn't want the controller to redirect to the current Umbraco page - which is the default. /// public class PostRedirectModel @@ -12,4 +12,4 @@ /// public string RedirectUrl { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ProfileModel.cs b/src/Umbraco.Web/Models/ProfileModel.cs index 39043a4b11..c999657b6a 100644 --- a/src/Umbraco.Web/Models/ProfileModel.cs +++ b/src/Umbraco.Web/Models/ProfileModel.cs @@ -2,16 +2,9 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Web; using System.Web.Mvc; -using System.Xml; -using System.Xml.Linq; -using umbraco.cms.businesslogic.member; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.PublishedCache; +using Umbraco.Web.Composing; using Umbraco.Web.Security; namespace Umbraco.Web.Models @@ -32,12 +25,12 @@ namespace Umbraco.Web.Models private ProfileModel(bool doLookup) { MemberProperties = new List(); - if (doLookup && UmbracoContext.Current != null) + if (doLookup && Current.UmbracoContext != null) { - var helper = new MembershipHelper(UmbracoContext.Current); + var helper = new MembershipHelper(Current.UmbracoContext); var model = helper.GetCurrentMemberProfileModel(); MemberProperties = model.MemberProperties; - } + } } [Obsolete("Do not use this ctor as it will perform business logic lookups. Use the MembershipHelper.CreateProfileModel or the static ProfileModel.CreateModel() to create an empty model.")] @@ -58,7 +51,7 @@ namespace Umbraco.Web.Models /// /// The member's member type alias - /// + /// [ReadOnly(true)] public string MemberTypeAlias { get; set; } diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 98d7085a78..89a735688c 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -7,13 +7,14 @@ using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors.ValueConverters; namespace Umbraco.Web.Models { /// /// Provide an abstract base class for IPublishedContent implementations. - /// - /// This base class does which (a) consitently resolves and caches the Url, (b) provides an implementation + /// + /// This base class does which (a) consitently resolves and caches the Url, (b) provides an implementation /// for this[alias], and (c) provides basic content set management. [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] public abstract class PublishedContentBase : IPublishedContent @@ -22,105 +23,90 @@ namespace Umbraco.Web.Models private string _url; - /// - /// Gets the url of the content. - /// - /// - /// If this content is Content, the url that is returned is the one computed by the NiceUrlProvider, otherwise if - /// this content is Media, the url returned is the value found in the 'umbracoFile' property. - /// - public virtual string Url - { - get - { - // should be thread-safe although it won't prevent url from being resolved more than once - if (_url != null) - return _url; + /// + /// Gets the url of the content. + /// + /// + /// If this content is Content, the url that is returned is the one computed by the NiceUrlProvider, otherwise if + /// this content is Media, the url returned is the value found in the 'umbracoFile' property. + /// + public virtual string Url + { + get + { + // should be thread-safe although it won't prevent url from being resolved more than once + if (_url != null) + return _url; - switch (ItemType) - { - case PublishedItemType.Content: - if (UmbracoContext.Current == null) - throw new InvalidOperationException( - "Cannot resolve a Url for a content item when UmbracoContext.Current is null."); - if (UmbracoContext.Current.UrlProvider == null) - throw new InvalidOperationException( - "Cannot resolve a Url for a content item when UmbracoContext.Current.UrlProvider is null."); - _url = UmbracoContext.Current.UrlProvider.GetUrl(Id); - break; - case PublishedItemType.Media: - var prop = GetProperty(Constants.Conventions.Media.File); - if (prop == null || prop.Value == null) - { - _url = string.Empty; - return _url; - } + switch (ItemType) + { + case PublishedItemType.Content: + if (UmbracoContext.Current == null) + throw new InvalidOperationException( + "Cannot resolve a Url for a content item when UmbracoContext.Current is null."); + if (UmbracoContext.Current.UrlProvider == null) + throw new InvalidOperationException( + "Cannot resolve a Url for a content item when UmbracoContext.Current.UrlProvider is null."); + _url = UmbracoContext.Current.UrlProvider.GetUrl(Id); + break; + case PublishedItemType.Media: + var prop = GetProperty(Constants.Conventions.Media.File); + if (prop == null || prop.GetValue() == null) + { + _url = string.Empty; + return _url; + } - var propType = ContentType.GetPropertyType(Constants.Conventions.Media.File); + var propType = ContentType.GetPropertyType(Constants.Conventions.Media.File); - //This is a hack - since we now have 2 properties that support a URL: upload and cropper, we need to detect this since we always - // want to return the normal URL and the cropper stores data as json - switch (propType.PropertyEditorAlias) - { - case Constants.PropertyEditors.UploadFieldAlias: - _url = prop.Value.ToString(); - break; - case Constants.PropertyEditors.ImageCropperAlias: - //get the url from the json format + // fixme this is horrible we need url providers for media too + //This is a hack - since we now have 2 properties that support a URL: upload and cropper, we need to detect this since we always + // want to return the normal URL and the cropper stores data as json + switch (propType.EditorAlias) + { + case Constants.PropertyEditors.Aliases.UploadField: + _url = prop.GetValue().ToString(); + break; + case Constants.PropertyEditors.Aliases.ImageCropper: + //get the url from the json format - var stronglyTyped = prop.Value as ImageCropDataSet; - if (stronglyTyped != null) - { + var stronglyTyped = prop.GetValue() as ImageCropperValue; + if (stronglyTyped != null) + { _url = stronglyTyped.Src; break; } + _url = prop.GetValue()?.ToString(); + break; + } + break; + default: + throw new NotSupportedException(); + } - var json = prop.Value as JObject; - if (json != null) - { - _url = json.ToObject(new JsonSerializer { Culture = CultureInfo.InvariantCulture, FloatParseHandling = FloatParseHandling.Decimal }).Src; - break; - } - - _url = prop.Value.ToString(); - break; - } - break; - default: - throw new NotSupportedException(); - } + return _url; + } + } - return _url; - } - } - - public abstract PublishedItemType ItemType { get; } - public abstract int Id { get; } - public abstract int TemplateId { get; } - public abstract int SortOrder { get; } - public abstract string Name { get; } - public abstract string UrlName { get; } - public abstract string DocumentTypeAlias { get; } - public abstract int DocumentTypeId { get; } - public abstract string WriterName { get; } - public abstract string CreatorName { get; } - public abstract int WriterId { get; } - public abstract int CreatorId { get; } - public abstract string Path { get; } - public abstract DateTime CreateDate { get; } - public abstract DateTime UpdateDate { get; } - public abstract Guid Version { get; } - public abstract int Level { get; } + public abstract PublishedItemType ItemType { get; } + public abstract int Id { get; } + public abstract Guid Key { get; } + public abstract int TemplateId { get; } + public abstract int SortOrder { get; } + public abstract string Name { get; } + public abstract string UrlName { get; } + public abstract string DocumentTypeAlias { get; } + public abstract int DocumentTypeId { get; } + public abstract string WriterName { get; } + public abstract string CreatorName { get; } + public abstract int WriterId { get; } + public abstract int CreatorId { get; } + public abstract string Path { get; } + public abstract DateTime CreateDate { get; } + public abstract DateTime UpdateDate { get; } + public abstract int Level { get; } public abstract bool IsDraft { get; } - - public int GetIndex() - { - var index = this.Siblings().FindIndex(x => x.Id == Id); - if (index < 0) - throw new IndexOutOfRangeException("Could not find content in the content set."); - return index; - } #endregion @@ -139,16 +125,6 @@ namespace Umbraco.Web.Models #endregion - #region ContentSet - - public virtual IEnumerable ContentSet - { - // the default content set of a content is its siblings - get { return this.Siblings(); } - } - - #endregion - #region ContentType public abstract PublishedContentType ContentType { get; } @@ -160,30 +136,7 @@ namespace Umbraco.Web.Models /// /// Gets the properties of the content. /// - public abstract ICollection Properties { get; } - - /// - /// Gets the value of a property identified by its alias. - /// - /// The property alias. - /// The value of the property identified by the alias. - /// - /// If GetProperty(alias) is null then returns null else return GetProperty(alias).Value. - /// So if the property has no value, returns the default value for that property type. - /// This one is defined here really because we cannot define index extension methods, but all it should do is: - /// var p = GetProperty(alias); return p == null ? null : p.Value; and nothing else. - /// The recursive syntax (eg "_title") is _not_ supported here. - /// The alias is case-insensitive. - /// - public virtual object this[string alias] - { - get - { - // no cache here: GetProperty should be fast, and .Value cache should be managed by the property. - var property = GetProperty(alias); - return property == null ? null : property.Value; - } - } + public abstract IEnumerable Properties { get; } /// /// Gets a property identified by its alias. @@ -216,10 +169,10 @@ namespace Umbraco.Web.Models IPublishedContent content = this; var firstNonNullProperty = property; - while (content != null && (property == null || property.HasValue == false)) + while (content != null && (property == null || property.HasValue() == false)) { content = content.Parent; - property = content == null ? null : content.GetProperty(alias); + property = content?.GetProperty(alias); if (firstNonNullProperty == null && property != null) firstNonNullProperty = property; } @@ -228,7 +181,7 @@ namespace Umbraco.Web.Models // if we find a content with the property without a value, return that property // have to save that first property while we look further up, hence firstNonNullProperty - return property != null && property.HasValue ? property : firstNonNullProperty; + return property != null && property.HasValue() ? property : firstNonNullProperty; } #endregion diff --git a/src/Umbraco.Web/Models/PublishedContentWithKeyBase.cs b/src/Umbraco.Web/Models/PublishedContentWithKeyBase.cs deleted file mode 100644 index 52bd8b2f59..0000000000 --- a/src/Umbraco.Web/Models/PublishedContentWithKeyBase.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Diagnostics; -using Umbraco.Core.Models; - -namespace Umbraco.Web.Models -{ - /// - /// Provide an abstract base class for IPublishedContent implementations. - /// - [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] - public abstract class PublishedContentWithKeyBase : PublishedContentBase, IPublishedContentWithKey - { - public abstract Guid Key { get; } - } -} diff --git a/src/Umbraco.Web/Models/PublishedProperty.cs b/src/Umbraco.Web/Models/PublishedProperty.cs index 59618c047e..b3bad63609 100644 --- a/src/Umbraco.Web/Models/PublishedProperty.cs +++ b/src/Umbraco.Web/Models/PublishedProperty.cs @@ -1,33 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; +using Umbraco.Web.Composing; namespace Umbraco.Web.Models { public static class PublishedProperty { - /// - /// Creates a detached published property. - /// - /// A published property type. - /// The property data raw value. - /// A value indicating whether to evaluate the property value in previewing context. - /// A detached published property holding the value. - internal static IPublishedProperty GetDetached(PublishedPropertyType propertyType, object value, bool isPreviewing = false) - { - if (propertyType.IsDetachedOrNested == false) - throw new ArgumentException("Property type is neither detached nor nested.", "propertyType"); - var property = UmbracoContext.Current.ContentCache.InnerCache.CreateDetachedProperty(propertyType, value, isPreviewing); - return property; - } - /// /// Maps a collection of Property to a collection of IPublishedProperty for a specified collection of PublishedPropertyType. /// @@ -41,16 +22,19 @@ namespace Umbraco.Web.Models IEnumerable propertyTypes, IEnumerable properties, Func map) { - var propertyEditorResolver = PropertyEditorResolver.Current; - var dataTypeService = ApplicationContext.Current.Services.DataTypeService; + var propertyEditors = Current.PropertyEditors; + var dataTypeService = Current.Services.DataTypeService; + + // fixme not dealing with variants + // but the entire thing should die anyways return propertyTypes.Select(x => { - var p = properties.SingleOrDefault(xx => xx.Alias == x.PropertyTypeAlias); - var v = p == null || p.Value == null ? null : p.Value; + var p = properties.SingleOrDefault(xx => xx.Alias == x.Alias); + var v = p == null || p.GetValue() == null ? null : p.GetValue(); if (v != null) { - var e = propertyEditorResolver.GetByAlias(x.PropertyEditorAlias); + var e = propertyEditors[x.EditorAlias]; // We are converting to string, even for database values which are integer or // DateTime, which is not optimum. Doing differently would require that we have a way to tell @@ -64,7 +48,7 @@ namespace Umbraco.Web.Models // nothing ensures that the two methods are consistent. if (e != null) - v = e.ValueEditor.ConvertDbToString(p, p.PropertyType, dataTypeService); + v = e.GetValueEditor().ConvertDbToString(p.PropertyType, v, dataTypeService); } return map(x, v); diff --git a/src/Umbraco.Web/Models/RegisterModel.cs b/src/Umbraco.Web/Models/RegisterModel.cs index b51f09b631..971cb8b916 100644 --- a/src/Umbraco.Web/Models/RegisterModel.cs +++ b/src/Umbraco.Web/Models/RegisterModel.cs @@ -2,11 +2,10 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Web; using System.Web.Mvc; -using umbraco.cms.businesslogic.member; using Umbraco.Core; +using Umbraco.Web.Composing; using Umbraco.Web.Security; namespace Umbraco.Web.Models @@ -15,13 +14,12 @@ namespace Umbraco.Web.Models public class RegisterModel : PostRedirectModel { /// - /// Creates a new empty RegisterModel + /// Creates a new empty RegisterModel. /// /// public static RegisterModel CreateModel() { - var model = new RegisterModel(false); - return model; + return new RegisterModel(false); } private RegisterModel(bool doLookup) @@ -32,9 +30,9 @@ namespace Umbraco.Web.Models MemberProperties = new List(); LoginOnSuccess = true; CreatePersistentLoginCookie = true; - if (doLookup && UmbracoContext.Current != null) + if (doLookup && Current.UmbracoContext != null) { - var helper = new MembershipHelper(UmbracoContext.Current); + var helper = new MembershipHelper(Current.UmbracoContext); var model = helper.CreateRegistrationModel(MemberTypeAlias); MemberProperties = model.MemberProperties; } @@ -43,8 +41,7 @@ namespace Umbraco.Web.Models [Obsolete("Do not use this ctor as it will perform business logic lookups. Use the MembershipHelper.CreateRegistrationModel or the static RegisterModel.CreateModel() to create an empty model.")] public RegisterModel() : this(true) - { - } + { } [Required] [RegularExpression(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", @@ -55,7 +52,7 @@ namespace Umbraco.Web.Models /// Returns the member properties /// public List MemberProperties { get; set; } - + /// /// The member type alias to use to register the member /// @@ -72,11 +69,11 @@ namespace Umbraco.Web.Models /// [Required] public string Password { get; set; } - + [ReadOnly(true)] [Obsolete("This is no longer used and will be removed from the codebase in future versions")] public bool RedirectOnSucces { get; set; } - + /// /// The username of the model, if UsernameIsEmail is true then this is ignored. /// @@ -86,7 +83,7 @@ namespace Umbraco.Web.Models /// Flag to determine if the username should be the email address, if true then the Username property is ignored /// public bool UsernameIsEmail { get; set; } - + /// /// Specifies if the member should be logged in if they are succesfully created /// @@ -107,6 +104,5 @@ namespace Umbraco.Web.Models return RegisterModel.CreateModel(); } } - } } diff --git a/src/Umbraco.Web/Models/RelatedLink.cs b/src/Umbraco.Web/Models/RelatedLink.cs index 2dcb63dd5c..1e1d7636ad 100644 --- a/src/Umbraco.Web/Models/RelatedLink.cs +++ b/src/Umbraco.Web/Models/RelatedLink.cs @@ -1,4 +1,4 @@ -using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Models { diff --git a/src/Umbraco.Web/Models/RelatedLinks.cs b/src/Umbraco.Web/Models/RelatedLinks.cs index afccdfb31f..22cdcd11b6 100644 --- a/src/Umbraco.Web/Models/RelatedLinks.cs +++ b/src/Umbraco.Web/Models/RelatedLinks.cs @@ -27,7 +27,7 @@ namespace Umbraco.Web.Models { return this._propertyData; } - } + } public IEnumerator GetEnumerator() { diff --git a/src/Umbraco.Web/Models/RenderModel.cs b/src/Umbraco.Web/Models/RenderModel.cs deleted file mode 100644 index ff2ea8a674..0000000000 --- a/src/Umbraco.Web/Models/RenderModel.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Web.Mvc; - -namespace Umbraco.Web.Models -{ - /// - /// Represents the model for the current rendering page in Umbraco - /// - public class RenderModel : IRenderModel - { - /// - /// Constructor specifying both the IPublishedContent and the CultureInfo - /// - /// - /// - public RenderModel(IPublishedContent content, CultureInfo culture) - { - if (content == null) throw new ArgumentNullException("content"); - if (culture == null) throw new ArgumentNullException("culture"); - Content = content; - CurrentCulture = culture; - } - - /// - /// Constructor to set the IPublishedContent and the CurrentCulture is set by the UmbracoContext - /// - /// - public RenderModel(IPublishedContent content) - { - if (content == null) throw new ArgumentNullException("content"); - if (UmbracoContext.Current == null) - { - throw new InvalidOperationException("Cannot construct a RenderModel without specifying a CultureInfo when no UmbracoContext has been initialized"); - } - Content = content; - CurrentCulture = UmbracoContext.Current.PublishedContentRequest.Culture; - } - - /// - /// Returns the current IPublishedContent object - /// - public IPublishedContent Content { get; private set; } - - /// - /// Returns the current Culture assigned to the page being rendered - /// - public CultureInfo CurrentCulture { get; private set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/RenderModelOfTContent.cs b/src/Umbraco.Web/Models/RenderModelOfTContent.cs deleted file mode 100644 index b3778e6230..0000000000 --- a/src/Umbraco.Web/Models/RenderModelOfTContent.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Web.Mvc; - -namespace Umbraco.Web.Models -{ - public class RenderModel : RenderModel - where TContent : IPublishedContent - { - /// - /// Constructor specifying both the IPublishedContent and the CultureInfo - /// - /// - /// - public RenderModel(TContent content, CultureInfo culture) - : base(content, culture) - { - Content = content; - } - - /// - /// Constructor to set the IPublishedContent and the CurrentCulture is set by the UmbracoContext - /// - /// - public RenderModel(TContent content) - : base(content) - { - Content = content; - } - - /// - /// Returns the current IPublishedContent object - /// - public new TContent Content { get; private set; } - } -} diff --git a/src/Umbraco.Web/Models/SendCodeViewModel.cs b/src/Umbraco.Web/Models/SendCodeViewModel.cs index 31c6644089..750c55a207 100644 --- a/src/Umbraco.Web/Models/SendCodeViewModel.cs +++ b/src/Umbraco.Web/Models/SendCodeViewModel.cs @@ -19,4 +19,4 @@ namespace Umbraco.Web.Models [DataMember(Name = "provider", IsRequired = true)] public string Provider { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/SetPasswordModel.cs b/src/Umbraco.Web/Models/SetPasswordModel.cs index 02d0e4f901..d3f8cfef6f 100644 --- a/src/Umbraco.Web/Models/SetPasswordModel.cs +++ b/src/Umbraco.Web/Models/SetPasswordModel.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Models [Required] [DataMember(Name = "password", IsRequired = true)] public string Password { get; set; } - + [Required] [DataMember(Name = "resetCode", IsRequired = true)] public string ResetCode { get; set; } diff --git a/src/Umbraco.Web/Models/TemplateQuery/Operathor.cs b/src/Umbraco.Web/Models/TemplateQuery/Operathor.cs index 0bfb990b84..561caec362 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/Operathor.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/Operathor.cs @@ -11,4 +11,4 @@ GreaterThan = 7, GreaterThanEqualTo = 8 } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs b/src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs index 8f9b4ec4b3..c14e1854aa 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs @@ -22,4 +22,4 @@ namespace Umbraco.Web.Models.TemplateQuery public Operathor Operathor { get; set; } public IEnumerable AppliesTo { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/TemplateQuery/PropertyModel.cs b/src/Umbraco.Web/Models/TemplateQuery/PropertyModel.cs index 6c3bdcf037..d1bc860ea0 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/PropertyModel.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/PropertyModel.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Web.Models.TemplateQuery +namespace Umbraco.Web.Models.TemplateQuery { public class PropertyModel { diff --git a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs index 36593736d3..39caf15775 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs @@ -2,7 +2,7 @@ { public class QueryCondition { - + public PropertyModel Property { get; set; } public OperathorTerm Term { get; set; } public string ConstraintValue { get; set; } @@ -11,7 +11,7 @@ internal static class QueryConditionExtensions { - + public static string BuildTokenizedCondition(this QueryCondition condition, int token) { return condition.BuildConditionString(string.Empty, token); @@ -24,7 +24,7 @@ private static string BuildConditionString(this QueryCondition condition, string prefix, int token = -1) { - + var operand = string.Empty; @@ -88,11 +88,11 @@ if (string.IsNullOrEmpty(value) == false) return value; - + return string.Format("{0}{1}{2}{3}", prefix, condition.Property.Alias, operand, constraintValue); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/TemplateQuery/QueryModel.cs b/src/Umbraco.Web/Models/TemplateQuery/QueryModel.cs index 0b7e27f991..90ee4c2452 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/QueryModel.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/QueryModel.cs @@ -10,4 +10,4 @@ namespace Umbraco.Web.Models.TemplateQuery public SortExpression Sort { get; set; } public int Take { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/TemplateQuery/QueryResultModel.cs b/src/Umbraco.Web/Models/TemplateQuery/QueryResultModel.cs index 13421e1199..a1bda3c4ae 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/QueryResultModel.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/QueryResultModel.cs @@ -2,14 +2,14 @@ namespace Umbraco.Web.Models.TemplateQuery { - + public class QueryResultModel { - + public string QueryExpression { get; set; } public IEnumerable SampleResults { get; set; } public int ResultCount { get; set; } public double ExecutionTime { get; set; } public int Take { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/TemplateQuery/SortExpression.cs b/src/Umbraco.Web/Models/TemplateQuery/SortExpression.cs index 76f42b8a69..46f3afa308 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/SortExpression.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/SortExpression.cs @@ -6,4 +6,4 @@ public string Direction { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/TemplateQuery/SourceModel.cs b/src/Umbraco.Web/Models/TemplateQuery/SourceModel.cs index 828de22759..b26912f113 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/SourceModel.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/SourceModel.cs @@ -5,4 +5,4 @@ public int Id { get; set; } public string Name { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/TemplateQuery/TemplateQueryResult.cs b/src/Umbraco.Web/Models/TemplateQuery/TemplateQueryResult.cs index 376caca7df..a59b0dc2d8 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/TemplateQueryResult.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/TemplateQueryResult.cs @@ -6,4 +6,4 @@ public string Name { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs b/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs index ba6172ba81..77ad9f004b 100644 --- a/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs +++ b/src/Umbraco.Web/Models/Trees/ActionMenuItem.cs @@ -9,10 +9,10 @@ namespace Umbraco.Web.Models.Trees /// /// These types of menu items are rare but they do exist. Things like refresh node simply execute /// JS and don't launch a dialog. - /// + /// /// Each action menu item describes what angular service that it's method exists in and what the method name is. - /// - /// An action menu item must describe the angular service name for which it's method exists. It may also define what the + /// + /// An action menu item must describe the angular service name for which it's method exists. It may also define what the /// method name is that will be called in this service but if one is not specified then we will assume the method name is the /// same as the Type name of the current action menu class. /// @@ -35,8 +35,8 @@ namespace Umbraco.Web.Models.Trees } else { - AdditionalData.Add("jsAction", string.Format("{0}.{1}", attribute.ServiceName, attribute.MethodName)); + AdditionalData.Add("jsAction", string.Format("{0}.{1}", attribute.ServiceName, attribute.MethodName)); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Trees/ActionMenuItemAttribute.cs b/src/Umbraco.Web/Models/Trees/ActionMenuItemAttribute.cs index f27cc65eda..04c4e02452 100644 --- a/src/Umbraco.Web/Models/Trees/ActionMenuItemAttribute.cs +++ b/src/Umbraco.Web/Models/Trees/ActionMenuItemAttribute.cs @@ -1,5 +1,5 @@ using System; -using Umbraco.Core; +using Umbraco.Core.Exceptions; namespace Umbraco.Web.Models.Trees { @@ -16,8 +16,8 @@ namespace Umbraco.Web.Models.Trees /// public ActionMenuItemAttribute(string serviceName, string methodName) { - Mandate.ParameterNotNullOrEmpty(serviceName, "serviceName"); - Mandate.ParameterNotNullOrEmpty(methodName, "methodName"); + if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName)); + if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentNullOrEmptyException(nameof(methodName)); MethodName = methodName; ServiceName = serviceName; } @@ -28,12 +28,12 @@ namespace Umbraco.Web.Models.Trees /// public ActionMenuItemAttribute(string serviceName) { - Mandate.ParameterNotNullOrEmpty(serviceName, "serviceName"); + if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName)); MethodName = ""; ServiceName = serviceName; } - public string MethodName { get; private set; } - public string ServiceName { get; private set; } + public string MethodName { get; } + public string ServiceName { get; } } -} \ No newline at end of file +} diff --git a/src/umbraco.businesslogic/ApplicationAttribute.cs b/src/Umbraco.Web/Models/Trees/ApplicationAttribute.cs similarity index 87% rename from src/umbraco.businesslogic/ApplicationAttribute.cs rename to src/Umbraco.Web/Models/Trees/ApplicationAttribute.cs index ffa0f668c4..221346cd23 100644 --- a/src/umbraco.businesslogic/ApplicationAttribute.cs +++ b/src/Umbraco.Web/Models/Trees/ApplicationAttribute.cs @@ -1,37 +1,34 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace umbraco.businesslogic -{ - /// - /// Identifies an application tree - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class ApplicationAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The alias. - /// The name. - /// The icon. - /// The sort order. - public ApplicationAttribute(string alias, - string name, - string icon, - int sortOrder = 0) - { - Alias = alias; - Name = name; - Icon = icon; - SortOrder = sortOrder; - } - - public string Alias { get; private set; } - public string Name { get; private set; } - public string Icon { get; private set; } - public int SortOrder { get; private set; } - } -} +using System; + +namespace Umbraco.Web.Models.Trees +{ + /// + /// Identifies an application tree + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ApplicationAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The alias. + /// The name. + /// The icon. + /// The sort order. + public ApplicationAttribute(string alias, + string name, + string icon, + int sortOrder = 0) + { + Alias = alias; + Name = name; + Icon = icon; + SortOrder = sortOrder; + } + + public string Alias { get; private set; } + public string Name { get; private set; } + public string Icon { get; private set; } + public int SortOrder { get; private set; } + } +} diff --git a/src/umbraco.businesslogic/ApplicationDefinitions.cs b/src/Umbraco.Web/Models/Trees/ApplicationDefinitions.cs similarity index 86% rename from src/umbraco.businesslogic/ApplicationDefinitions.cs rename to src/Umbraco.Web/Models/Trees/ApplicationDefinitions.cs index c2e8e9178b..002b96bc64 100644 --- a/src/umbraco.businesslogic/ApplicationDefinitions.cs +++ b/src/Umbraco.Web/Models/Trees/ApplicationDefinitions.cs @@ -1,41 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using umbraco.interfaces; -using Umbraco.Core; - -namespace umbraco.businesslogic -{ - // The application definitions are intended as a means to auto populate - // the application.config. On app startup, Umbraco will look for any - // unregistered classes with an ApplicationAttribute and add them to the cache - - [Application(Constants.Applications.Content, "Content", ".traycontent")] - public class ContentApplicationDefinition : IApplication - { } - - [Application(Constants.Applications.Media, "Media", ".traymedia", sortOrder: 1)] - public class MediaApplicationDefinition : IApplication - { } - - [Application(Constants.Applications.Settings, "Settings", ".traysettings", sortOrder: 2)] - public class SettingsApplicationDefinition : IApplication - { } - - [Application(Constants.Applications.Developer, "Developer", ".traydeveloper", sortOrder: 3)] - public class DeveloperApplicationDefinition : IApplication - { } - - [Application(Constants.Applications.Users, "Users", ".trayusers", sortOrder: 4)] - public class UsersApplicationDefinition : IApplication - { } - - [Application(Constants.Applications.Members, "Members", ".traymember", sortOrder: 5)] - public class MembersApplicationDefinition : IApplication - { } - - [Application(Constants.Applications.Translation, "Translation", ".traytranslation", sortOrder: 6)] - public class TranslationApplicationDefinition : IApplication - { } -} +using Umbraco.Core; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models.Trees +{ + // The application definitions are intended as a means to auto populate + // the application.config. On app startup, Umbraco will look for any + // unregistered classes with an ApplicationAttribute and add them to the cache + + [Application(Constants.Applications.Content, "Content", ".traycontent")] + public class ContentApplicationDefinition : IApplication + { } + + [Application(Constants.Applications.Media, "Media", ".traymedia", sortOrder: 1)] + public class MediaApplicationDefinition : IApplication + { } + + [Application(Constants.Applications.Settings, "Settings", ".traysettings", sortOrder: 2)] + public class SettingsApplicationDefinition : IApplication + { } + + [Application(Constants.Applications.Developer, "Developer", ".traydeveloper", sortOrder: 3)] + public class DeveloperApplicationDefinition : IApplication + { } + + [Application(Constants.Applications.Users, "Users", ".trayusers", sortOrder: 4)] + public class UsersApplicationDefinition : IApplication + { } + + [Application(Constants.Applications.Members, "Members", ".traymember", sortOrder: 5)] + public class MembersApplicationDefinition : IApplication + { } + + [Application(Constants.Applications.Translation, "Translation", ".traytranslation", sortOrder: 6)] + public class TranslationApplicationDefinition : IApplication + { } +} diff --git a/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs b/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs index bbcfe588ef..6d3856d6c2 100644 --- a/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs +++ b/src/Umbraco.Web/Models/Trees/CreateChildEntity.cs @@ -7,4 +7,4 @@ public sealed class CreateChildEntity : ActionMenuItem { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Trees/DisableUser.cs b/src/Umbraco.Web/Models/Trees/DisableUser.cs new file mode 100644 index 0000000000..3602897ac2 --- /dev/null +++ b/src/Umbraco.Web/Models/Trees/DisableUser.cs @@ -0,0 +1,15 @@ +namespace Umbraco.Web.Models.Trees +{ + /// + /// Represents the disable user menu item + /// + [ActionMenuItem("umbracoMenuActions")] + public sealed class DisableUser : ActionMenuItem + { + public DisableUser() + { + Alias = "disable"; + Icon = "remove"; + } + } +} diff --git a/src/Umbraco.Web/Models/Trees/ExportMember.cs b/src/Umbraco.Web/Models/Trees/ExportMember.cs new file mode 100644 index 0000000000..66a10da007 --- /dev/null +++ b/src/Umbraco.Web/Models/Trees/ExportMember.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Web.Models.Trees +{ + /// + /// Represents the export member menu item + /// + [ActionMenuItem("umbracoMenuActions")] + public sealed class ExportMember : ActionMenuItem + { } +} diff --git a/src/Umbraco.Web/Models/Trees/IApplication.cs b/src/Umbraco.Web/Models/Trees/IApplication.cs new file mode 100644 index 0000000000..4e9cae43fd --- /dev/null +++ b/src/Umbraco.Web/Models/Trees/IApplication.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Models.Trees +{ + /// + /// Marker interface for created applications in the umbraco backoffice + /// + public interface IApplication : IDiscoverable + { } +} diff --git a/src/Umbraco.Web/Models/Trees/MenuItem.cs b/src/Umbraco.Web/Models/Trees/MenuItem.cs index d06fb92ecd..bba400d1dd 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItem.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItem.cs @@ -1,11 +1,12 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -using Umbraco.Core.Models.EntityBase; using Umbraco.Web.Trees; -using umbraco; -using umbraco.interfaces; using System.Collections.Generic; using Umbraco.Core; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Services; +using Umbraco.Web.Composing; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.Models.Trees { @@ -37,14 +38,14 @@ namespace Umbraco.Web.Models.Trees SeperatorBefore = false; Icon = legacyMenu.Icon; Action = legacyMenu; - } + } #endregion #region Properties internal IAction Action { get; set; } /// - /// A dictionary to support any additional meta data that should be rendered for the node which is + /// A dictionary to support any additional meta data that should be rendered for the node which is /// useful for custom action commands such as 'create', 'copy', etc... /// /// @@ -69,7 +70,7 @@ namespace Umbraco.Web.Models.Trees public bool SeperatorBefore { get; set; } [DataMember(Name = "cssclass")] - public string Icon { get; set; } + public string Icon { get; set; } #endregion #region Constants @@ -85,8 +86,8 @@ namespace Umbraco.Web.Models.Trees internal const string ActionUrlKey = "actionUrl"; //TODO: some action's want to launch a new window like live editing, we support this in the menu item's metadata with - // a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog - // if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window, + // a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog + // if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window, // though would be v-easy, just not sure we want to ever support that? internal const string ActionUrlMethodKey = "actionUrlMethod"; @@ -96,14 +97,14 @@ namespace Umbraco.Web.Models.Trees internal const string ActionViewKey = "actionView"; /// - /// Used to specify the js method to execute for the menu item + /// Used to specify the js method to execute for the menu item /// internal const string JsActionKey = "jsAction"; /// /// Used to specify an angular route to go to for the menu item /// - internal const string ActionRouteKey = "actionRoute"; + internal const string ActionRouteKey = "actionRoute"; #endregion @@ -177,22 +178,56 @@ namespace Umbraco.Web.Models.Trees internal void ConvertLegacyMenuItem(IUmbracoEntity item, string nodeType, string currentSection) { - //First try to get a URL/title from the legacy action, - // if that doesn't work, try to get the legacy confirm view + // try to get a URL/title from the legacy action, + // in some edge cases, item can be null so we'll just convert those to "-1" and "" for id and name since these edge cases don't need that. + var attempt = LegacyTreeDataConverter.GetUrlAndTitleFromLegacyAction(Action, + item == null ? "-1" : item.Id.ToInvariantString(), + nodeType, + item == null ? "" : item.Name, currentSection); + if (attempt) + { + var action = attempt.Result; + LaunchDialogUrl(action.Url, action.DialogTitle); + } + else + { + // if that doesn't work, try to get the legacy confirm view + var attempt2 = LegacyTreeDataConverter.GetLegacyConfirmView(Action); + if (attempt2) + { + var view = attempt2.Result; + var textService = Current.Services.TextService; + LaunchDialogView(view, textService.Localize("defaultdialogs/confirmdelete") + " '" + (item == null ? "" : item.Name) + "' ?"); + } + } + } - //in some edge cases, item can be null so we'll just convert those to "-1" and "" for id and name since these edge cases don't need that. - Attempt - .Try(LegacyTreeDataConverter.GetUrlAndTitleFromLegacyAction(Action, - item == null ? "-1" : item.Id.ToInvariantString(), - nodeType, - item == null ? "" : item.Name, currentSection), - action => LaunchDialogUrl(action.Url, action.DialogTitle)) - .OnFailure(() => LegacyTreeDataConverter.GetLegacyConfirmView(Action), - view => LaunchDialogView( - view, - ui.GetText("defaultdialogs", "confirmdelete") + " '" + (item == null ? "" : item.Name) + "' ?")); - } + internal void ConvertLegacyFileSystemMenuItem(string path, string nodeType, string currentSection) + { + // try to get a URL/title from the legacy action, + // in some edge cases, item can be null so we'll just convert those to "-1" and "" for id and name since these edge cases don't need that. + var attempt = LegacyTreeDataConverter.GetUrlAndTitleFromLegacyAction(Action, + path, + nodeType, + path, currentSection); + if (attempt) + { + var action = attempt.Result; + LaunchDialogUrl(action.Url, action.DialogTitle); + } + else + { + // if that doesn't work, try to get the legacy confirm view + var attempt2 = LegacyTreeDataConverter.GetLegacyConfirmView(Action); + if (attempt2) + { + var view = attempt2.Result; + var textService = Current.Services.TextService; + LaunchDialogView(view, textService.Localize("defaultdialogs/confirmdelete") + " '" + path + "' ?"); + } + } + } #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs b/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs index 9130ebc67c..b9d7ede7cb 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs @@ -7,19 +7,19 @@ namespace Umbraco.Web.Models.Trees /// A menu item collection for a given tree node /// [DataContract(Name = "menuItems", Namespace = "")] - public class MenuItemCollection + public class MenuItemCollection { private readonly MenuItemList _menuItems = new MenuItemList(); public MenuItemCollection() - { + { } public MenuItemCollection(IEnumerable items) { _menuItems = new MenuItemList(items); } - + /// /// Sets the default menu item alias to be shown when the menu is launched - this is optional and if not set then the menu will just be shown normally. /// @@ -38,7 +38,7 @@ namespace Umbraco.Web.Models.Trees get { return _menuItems; } } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Trees/MenuItemList.cs b/src/Umbraco.Web/Models/Trees/MenuItemList.cs index a450208905..2e38939f1e 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItemList.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItemList.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using Umbraco.Core; -using umbraco.BusinessLogic.Actions; -using umbraco.interfaces; +using Umbraco.Web.Composing; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.Models.Trees { @@ -15,12 +15,12 @@ namespace Umbraco.Web.Models.Trees public class MenuItemList : List { public MenuItemList() - { + { } public MenuItemList(IEnumerable items) : base(items) - { + { } /// @@ -113,7 +113,7 @@ namespace Umbraco.Web.Models.Trees } /// - /// + /// /// /// /// @@ -123,7 +123,7 @@ namespace Umbraco.Web.Models.Trees internal MenuItem CreateMenuItem(string name, bool hasSeparator = false, IDictionary additionalData = null) where T : IAction { - var item = ActionsResolver.Current.GetAction(); + var item = Current.Actions.GetAction(); if (item != null) { var menuItem = new MenuItem(item, name) @@ -153,7 +153,7 @@ namespace Umbraco.Web.Models.Trees } /// - /// Checks if the IAction type passed in is attributed with LegacyActionMenuItemAttribute and if so + /// Checks if the IAction type passed in is attributed with LegacyActionMenuItemAttribute and if so /// ensures that the correct action metadata is added. /// /// @@ -178,4 +178,4 @@ namespace Umbraco.Web.Models.Trees } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Trees/RefreshNode.cs b/src/Umbraco.Web/Models/Trees/RefreshNode.cs index eadd0b2a4d..0414a5997a 100644 --- a/src/Umbraco.Web/Models/Trees/RefreshNode.cs +++ b/src/Umbraco.Web/Models/Trees/RefreshNode.cs @@ -5,6 +5,6 @@ /// [ActionMenuItem("umbracoMenuActions")] public sealed class RefreshNode : ActionMenuItem - { + { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs index b89a632368..931cb729fc 100644 --- a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs +++ b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs @@ -10,7 +10,7 @@ namespace Umbraco.Web.Models.Trees /// /// This is required to return the tree data for a given section. Some sections may only contain one tree which means it's section /// root should also display a menu, whereas other sections have multiple trees and the section root shouldn't display a menu. - /// + /// /// The section root also contains an explicit collection of children. /// [DataContract(Name = "node", Namespace = "")] @@ -45,11 +45,11 @@ namespace Umbraco.Web.Models.Trees //default to false IsContainer = false; } - + [DataMember(Name = "isContainer")] public bool IsContainer { get; private set; } [DataMember(Name = "children")] public TreeNodeCollection Children { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Trees/TreeNode.cs b/src/Umbraco.Web/Models/Trees/TreeNode.cs index 5c8d36dd6b..6337957fcb 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNode.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNode.cs @@ -3,6 +3,7 @@ using Umbraco.Core.IO; using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Exceptions; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Trees @@ -25,7 +26,7 @@ namespace Umbraco.Web.Models.Trees /// internal TreeNode(string nodeId, string parentId, string getChildNodesUrl, string menuUrl) { - Mandate.ParameterNotNullOrEmpty(nodeId, "nodeId"); + if (string.IsNullOrWhiteSpace(nodeId)) throw new ArgumentNullOrEmptyException(nameof(nodeId)); Id = nodeId; ParentId = parentId; @@ -35,8 +36,8 @@ namespace Umbraco.Web.Models.Trees //default Icon = "icon-folder-close"; } - - [DataMember(Name = "parentId", IsRequired = true)] + + [DataMember(Name = "parentId", IsRequired = true)] public new object ParentId { get; set; } /// @@ -44,13 +45,13 @@ namespace Umbraco.Web.Models.Trees /// [DataMember(Name = "hasChildren")] public bool HasChildren { get; set; } - + /// /// The tree nodetype which refers to the type of node rendered in the tree /// [DataMember(Name = "nodeType")] public string NodeType { get; set; } - + /// /// Optional: The Route path for the editor for this node /// @@ -110,7 +111,7 @@ namespace Umbraco.Web.Models.Trees return IOHelper.ResolveUrl("~" + Icon.TrimStart('~')); //legacy icon path - return string.Format("{0}images/umbraco/{1}", GlobalSettings.Path.EnsureEndsWith("/"), Icon); + return string.Format("{0}images/umbraco/{1}", UmbracoConfig.For.GlobalSettings().Path.EnsureEndsWith("/"), Icon); } } diff --git a/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs b/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs index f4a4b048d6..1d43f9a743 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs @@ -7,4 +7,4 @@ namespace Umbraco.Web.Models.Trees public sealed class TreeNodeCollection : List { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs b/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs index ec95098f7b..c29c947b7f 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs @@ -75,4 +75,4 @@ } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/UmbracoProperty.cs b/src/Umbraco.Web/Models/UmbracoProperty.cs index db3c17fcb7..fc3806486c 100644 --- a/src/Umbraco.Web/Models/UmbracoProperty.cs +++ b/src/Umbraco.Web/Models/UmbracoProperty.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Xml; using Umbraco.Core; using Umbraco.Core.Models; +using DataType = System.ComponentModel.DataAnnotations.DataType; namespace Umbraco.Web.Models { @@ -18,7 +19,7 @@ namespace Umbraco.Web.Models // If we want to have this as an 'object' with a true type on it, we have to create a custom model binder // for an UmbracoProperty and then bind with the correct type based on the property type for this alias. This // would be a bit long winded and perhaps unnecessary. The reason is because it is always posted as a string anyways - // and when we set this value on the property object that gets sent to the database we do a TryConvertTo to the + // and when we set this value on the property object that gets sent to the database we do a TryConvertTo to the // real type anyways. [DataType(DataType.Text)] @@ -30,7 +31,7 @@ namespace Umbraco.Web.Models //TODO: Perhaps one day we'll ship with our own EditorTempates but for now developers can just render their own inside the view ///// - ///// This can dynamically be set to a custom template name to change + ///// This can dynamically be set to a custom template name to change ///// the editor type for this property ///// //[ReadOnly(true)] diff --git a/src/Umbraco.Web/Models/UnLinkLoginModel.cs b/src/Umbraco.Web/Models/UnLinkLoginModel.cs index 776baf03fc..824fadaf90 100644 --- a/src/Umbraco.Web/Models/UnLinkLoginModel.cs +++ b/src/Umbraco.Web/Models/UnLinkLoginModel.cs @@ -13,4 +13,4 @@ namespace Umbraco.Web.Models [DataMember(Name = "providerKey", IsRequired = true)] public string ProviderKey { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs b/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs index 7e30e0881b..152d7460fd 100644 --- a/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs +++ b/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Models [Required] [DataMember(Name = "userId", IsRequired = true)] public int UserId { get; set; } - + [Required] [DataMember(Name = "resetCode", IsRequired = true)] public string ResetCode { get; set; } diff --git a/src/Umbraco.Web/Mvc/ActionExecutedEventArgs.cs b/src/Umbraco.Web/Mvc/ActionExecutedEventArgs.cs index aabe7133fe..1c596ff80c 100644 --- a/src/Umbraco.Web/Mvc/ActionExecutedEventArgs.cs +++ b/src/Umbraco.Web/Mvc/ActionExecutedEventArgs.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web.Mvc; namespace Umbraco.Web.Mvc @@ -11,7 +11,7 @@ namespace Umbraco.Web.Mvc public ActionExecutedEventArgs(Controller controller, object model) { Controller = controller; - Model = model; + Model = model; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs index dc8aa15f6a..47a4e7b060 100644 --- a/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Mvc/AdminTokenAuthorizeAttribute.cs @@ -1,13 +1,13 @@ using System; -using System.Linq; using System.Net.Http.Headers; using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Web.Mvc; -using umbraco; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Services; +using Umbraco.Web.Composing; namespace Umbraco.Web.Mvc { @@ -16,56 +16,62 @@ namespace Umbraco.Web.Mvc /// public sealed class AdminTokenAuthorizeAttribute : AuthorizeAttribute { - private readonly ApplicationContext _applicationContext; + // see note in HttpInstallAuthorizeAttribute + private readonly IUserService _userService; + private readonly IRuntimeState _runtimeState; + private readonly ILogger _logger; + + private IUserService UserService => _userService ?? Current.Services.UserService; + + private IRuntimeState RuntimeState => _runtimeState ?? Current.RuntimeState; + + private ILogger Logger => _logger ?? Current.Logger; /// /// THIS SHOULD BE ONLY USED FOR UNIT TESTS /// - /// - public AdminTokenAuthorizeAttribute(ApplicationContext appContext) + /// + /// + public AdminTokenAuthorizeAttribute(IUserService userService, IRuntimeState runtimeState) { - if (appContext == null) throw new ArgumentNullException("appContext"); - _applicationContext = appContext; + if (userService == null) throw new ArgumentNullException(nameof(userService)); + if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); + _userService = userService; + _runtimeState = runtimeState; } public AdminTokenAuthorizeAttribute() - { - } - - private ApplicationContext GetApplicationContext() - { - return _applicationContext ?? ApplicationContext.Current; - } + { } public const string AuthorizationType = "AToken"; /// /// Used to return the full value that needs to go in the Authorization header /// - /// + /// /// - public static string GetAuthHeaderTokenVal(ApplicationContext appContext) + public static string GetAuthHeaderTokenVal(IUserService userService) { - return string.Format("{0} {1}", AuthorizationType, GetAuthHeaderVal(appContext)); + return $"{AuthorizationType} {GetAuthHeaderVal(userService)}"; } - public static AuthenticationHeaderValue GetAuthenticationHeaderValue(ApplicationContext appContext) + public static AuthenticationHeaderValue GetAuthenticationHeaderValue(IUserService userService) { - return new AuthenticationHeaderValue(AuthorizationType, GetAuthHeaderVal(appContext)); + return new AuthenticationHeaderValue(AuthorizationType, GetAuthHeaderVal(userService)); } - private static string GetAuthHeaderVal(ApplicationContext appContext) + private static string GetAuthHeaderVal(IUserService userService) { - var admin = appContext.Services.UserService.GetUserById(0); + var admin = userService.GetUserById(Core.Constants.Security.SuperId); - var token = string.Format("{0}u____u{1}u____u{2}", admin.Email, admin.Username, admin.RawPasswordValue); + var token = $"{admin.Email}u____u{admin.Username}u____u{admin.RawPasswordValue}"; var encrypted = token.EncryptWithMachineKey(); var bytes = Encoding.UTF8.GetBytes(encrypted); var base64 = Convert.ToBase64String(bytes); - return string.Format("val=\"{0}\"", base64); + return $"val=\"{base64}\""; } - + /// /// Ensures that the user must be in the Administrator or the Install role /// @@ -73,14 +79,12 @@ namespace Umbraco.Web.Mvc /// protected override bool AuthorizeCore(HttpContextBase httpContext) { - if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); - var appContext = GetApplicationContext(); + // we need to that the app is configured and that a user is logged in + if (RuntimeState.Level != RuntimeLevel.Run) return false; - //we need to that the app is configured and that a user is logged in - if (appContext.IsConfigured == false) return false; - - //need the auth header + // need the auth header if (httpContext.Request.Headers["Authorization"] == null || httpContext.Request.Headers["Authorization"].IsNullOrWhiteSpace()) return false; var header = httpContext.Request.Headers["Authorization"]; @@ -90,7 +94,7 @@ namespace Umbraco.Web.Mvc if (keyVal.Count != 1) return false; if (keyVal[0].Groups.Count != 2) return false; - var admin = appContext.Services.UserService.GetUserById(0); + var admin = UserService.GetUserById(Core.Constants.Security.SuperId); if (admin == null) return false; try @@ -115,9 +119,9 @@ namespace Umbraco.Web.Mvc } catch (Exception ex) { - LogHelper.Error("Failed to format passed in token value", ex); + Logger.Error("Failed to format passed in token value", ex); return false; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs index f1c27ffb96..82904d53b6 100644 --- a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs +++ b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Web.Http; using System.Web.Mvc; @@ -6,6 +6,7 @@ using System.Web.Routing; using System.Web.SessionState; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Exceptions; using Umbraco.Web.WebApi; namespace Umbraco.Web.Mvc @@ -16,6 +17,7 @@ namespace Umbraco.Web.Mvc /// Creates a custom individual route for the specified controller plugin. Individual routes /// are required by controller plugins to map to a unique URL based on ID. /// + /// /// /// /// An existing route collection @@ -37,26 +39,28 @@ namespace Umbraco.Web.Mvc /// /// /// - internal static Route RouteControllerPlugin(this AreaRegistration area, string controllerName, Type controllerType, RouteCollection routes, - string controllerSuffixName, string defaultAction, object defaultId, - string umbracoTokenValue = "backoffice", - string routeTokens = "{action}/{id}", - bool isMvc = true, - string areaPathPrefix = "") + internal static Route RouteControllerPlugin(this AreaRegistration area, + IGlobalSettings globalSettings, + string controllerName, Type controllerType, RouteCollection routes, + string controllerSuffixName, string defaultAction, object defaultId, + string umbracoTokenValue = "backoffice", + string routeTokens = "{action}/{id}", + bool isMvc = true, + string areaPathPrefix = "") { - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - Mandate.ParameterNotNull(controllerSuffixName, "controllerSuffixName"); - - Mandate.ParameterNotNull(controllerType, "controllerType"); - Mandate.ParameterNotNull(routes, "routes"); - Mandate.ParameterNotNull(defaultId, "defaultId"); + if (string.IsNullOrEmpty(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); + if (controllerSuffixName == null) throw new ArgumentNullException(nameof(controllerSuffixName)); - var umbracoArea = GlobalSettings.UmbracoMvcArea; + if (controllerType == null) throw new ArgumentNullException(nameof(controllerType)); + if (routes == null) throw new ArgumentNullException(nameof(routes)); + if (defaultId == null) throw new ArgumentNullException(nameof(defaultId)); + + var umbracoArea = globalSettings.GetUmbracoMvcArea(); //routes are explicitly named with controller names and IDs - var url = umbracoArea + "/" + - (areaPathPrefix.IsNullOrWhiteSpace() ? "" : areaPathPrefix + "/") + - area.AreaName + "/" + controllerName + "/" + routeTokens; + var url = umbracoArea + "/" + + (areaPathPrefix.IsNullOrWhiteSpace() ? "" : areaPathPrefix + "/") + + area.AreaName + "/" + controllerName + "/" + routeTokens; Route controllerPluginRoute; //var meta = PluginController.GetMetadata(controllerType); @@ -87,12 +91,13 @@ namespace Umbraco.Web.Mvc string.Format("umbraco-{0}-{1}-{2}", "api", area.AreaName, controllerName), //url format url, - new { controller = controllerName, id = defaultId }); + new {controller = controllerName, id = defaultId}); //web api routes don't set the data tokens object if (controllerPluginRoute.DataTokens == null) { controllerPluginRoute.DataTokens = new RouteValueDictionary(); } + //look in this namespace to create the controller controllerPluginRoute.DataTokens.Add("Namespaces", new[] {controllerType.Namespace}); @@ -107,7 +112,7 @@ namespace Umbraco.Web.Mvc //Don't look anywhere else except this namespace! controllerPluginRoute.DataTokens.Add("UseNamespaceFallback", false); - //constraints: only match controllers ending with 'controllerSuffixName' and only match this controller's ID for this route + //constraints: only match controllers ending with 'controllerSuffixName' and only match this controller's ID for this route if (controllerSuffixName.IsNullOrWhiteSpace() == false) { controllerPluginRoute.Constraints = new RouteValueDictionary( @@ -124,4 +129,4 @@ namespace Umbraco.Web.Mvc return controllerPluginRoute; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/BackOfficeArea.cs b/src/Umbraco.Web/Mvc/BackOfficeArea.cs index 68afc3fc39..4f0ab36827 100644 --- a/src/Umbraco.Web/Mvc/BackOfficeArea.cs +++ b/src/Umbraco.Web/Mvc/BackOfficeArea.cs @@ -1,10 +1,6 @@ -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; +using System.Web.Mvc; using Umbraco.Core.Configuration; using Umbraco.Web.Editors; -using Umbraco.Web.Install; -using Umbraco.Web.Install.Controllers; namespace Umbraco.Web.Mvc { @@ -13,42 +9,51 @@ namespace Umbraco.Web.Mvc /// internal class BackOfficeArea : AreaRegistration { + private readonly IGlobalSettings _globalSettings; + + public BackOfficeArea(IGlobalSettings globalSettings) + { + _globalSettings = globalSettings; + } /// /// Create the routes for the area /// /// /// - /// By using the context to register the routes it means that the area is already applied to them all + /// By using the context to register the routes it means that the area is already applied to them all /// and that the namespaces searched for the controllers are ONLY the ones specified. /// public override void RegisterArea(AreaRegistrationContext context) { + context.MapRoute( + "Umbraco_preview", + _globalSettings.GetUmbracoMvcArea() + "/preview/{action}/{editor}", + new {controller = "Preview", action = "Index", editor = UrlParameter.Optional}, + new[] { "Umbraco.Web.Editors" }); + context.MapRoute( "Umbraco_back_office", - GlobalSettings.UmbracoMvcArea + "/{action}/{id}", + _globalSettings.GetUmbracoMvcArea() + "/{action}/{id}", new {controller = "BackOffice", action = "Default", id = UrlParameter.Optional}, - //limit the action/id to only allow characters - this is so this route doesn't hog all other + //limit the action/id to only allow characters - this is so this route doesn't hog all other // routes like: /umbraco/channels/word.aspx, etc... new { - action = @"[a-zA-Z]*", + action = @"[a-zA-Z]*", id = @"[a-zA-Z]*" }, new[] {typeof (BackOfficeController).Namespace}); - + //Create the REST/web/script service routes context.MapRoute( "Umbraco_web_services", - GlobalSettings.UmbracoMvcArea + "/RestServices/{controller}/{action}/{id}", + _globalSettings.GetUmbracoMvcArea() + "/RestServices/{controller}/{action}/{id}", new {controller = "SaveFileController", action = "Index", id = UrlParameter.Optional}, //look in this namespace for controllers new[] {"Umbraco.Web.WebServices"}); } - public override string AreaName - { - get { return GlobalSettings.UmbracoMvcArea; } - } + public override string AreaName => _globalSettings.GetUmbracoMvcArea(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/Constants.cs b/src/Umbraco.Web/Mvc/Constants.cs index e175ef2ea8..69a22c1545 100644 --- a/src/Umbraco.Web/Mvc/Constants.cs +++ b/src/Umbraco.Web/Mvc/Constants.cs @@ -1,12 +1,12 @@ -namespace Umbraco.Web.Mvc +namespace Umbraco.Web.Mvc { - /// - /// constants - /// + /// + /// constants + /// internal static class Constants { - internal const string ViewLocation = "~/Views"; + internal const string ViewLocation = "~/Views"; - internal const string DataTokenCurrentViewContext = "umbraco-current-view-context"; + internal const string DataTokenCurrentViewContext = "umbraco-current-view-context"; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/ContentModelBinder.cs b/src/Umbraco.Web/Mvc/ContentModelBinder.cs new file mode 100644 index 0000000000..b68d5a36f7 --- /dev/null +++ b/src/Umbraco.Web/Mvc/ContentModelBinder.cs @@ -0,0 +1,214 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Web; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; +using Umbraco.Web.Models; + +namespace Umbraco.Web.Mvc +{ + /// + /// Maps view models, supporting mapping to and from any IPublishedContent or IContentModel. + /// + public class ContentModelBinder : DefaultModelBinder, IModelBinderProvider + { + // use Instance + private ContentModelBinder() { } + + public static ContentModelBinder Instance = new ContentModelBinder(); + + /// + /// Binds the model to a value by using the specified controller context and binding context. + /// + /// + /// The bound value. + /// + /// The controller context.The binding context. + public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) + { + object model; + if (controllerContext.RouteData.DataTokens.TryGetValue(Core.Constants.Web.UmbracoDataToken, out model) == false) + return null; + + // this model binder deals with IContentModel and IPublishedContent by extracting the model from the route's + // datatokens. This data token is set in 2 places: RenderRouteHandler, UmbracoVirtualNodeRouteHandler + // and both always set the model to an instance of `ContentMOdel`. So if this isn't an instance of IContentModel then + // we need to let the DefaultModelBinder deal with the logic. + var contentModel = model as IContentModel; + if (contentModel == null) + { + model = base.BindModel(controllerContext, bindingContext); + if (model == null) return null; + } + + //if for any reason the model is not either IContentModel or IPublishedContent, then we return since those are the only + // types this binder is dealing with. + if ((model is IContentModel) == false && (model is IPublishedContent) == false) return null; + + return BindModel(model, bindingContext.ModelType); + } + + // source is the model that we have + // modelType is the type of the model that we need to bind to + // + // create a model object of the modelType by mapping: + // { ContentModel, ContentModel, IPublishedContent } + // to + // { ContentModel, ContentModel, IPublishedContent } + // + public static object BindModel(object source, Type modelType) + { + // null model, return + if (source == null) return null; + + // if types already match, return + var sourceType = source.GetType(); + if (sourceType.Inherits(modelType)) // includes == + return source; + + // try to grab the content + var sourceContent = source as IPublishedContent; // check if what we have is an IPublishedContent + if (sourceContent == null && sourceType.Implements()) + { + // else check if it's an IContentModel, and get the content + sourceContent = ((IContentModel)source).Content; + } + if (sourceContent == null) + { + // else check if we can convert it to a content + var attempt1 = source.TryConvertTo(); + if (attempt1.Success) sourceContent = attempt1.Result; + } + + // if we have a content + if (sourceContent != null) + { + // if model is IPublishedContent, check content type and return + if (modelType.Implements()) + { + if ((sourceContent.GetType().Inherits(modelType)) == false) + ThrowModelBindingException(true, false, sourceContent.GetType(), modelType); + return sourceContent; + } + + // if model is ContentModel, create and return + if (modelType == typeof(ContentModel)) + { + return new ContentModel(sourceContent); + } + + // if model is ContentModel, check content type, then create and return + if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(ContentModel<>)) + { + var targetContentType = modelType.GetGenericArguments()[0]; + if ((sourceContent.GetType().Inherits(targetContentType)) == false) + ThrowModelBindingException(true, true, sourceContent.GetType(), targetContentType); + return Activator.CreateInstance(modelType, sourceContent); + } + } + + // last chance : try to convert + var attempt2 = source.TryConvertTo(modelType); + if (attempt2.Success) return attempt2.Result; + + // fail + ThrowModelBindingException(false, false, sourceType, modelType); + return null; + } + + private static void ThrowModelBindingException(bool sourceContent, bool modelContent, Type sourceType, Type modelType) + { + var msg = new StringBuilder(); + + // prepare message + msg.Append("Cannot bind source"); + if (sourceContent) msg.Append(" content"); + msg.Append(" type "); + msg.Append(sourceType.FullName); + msg.Append(" to model"); + if (modelContent) msg.Append(" content"); + msg.Append(" type "); + msg.Append(modelType.FullName); + msg.Append("."); + +// raise event, to give model factories a chance at reporting + // the error with more details, and optionally request that + // the application restarts. + + var args = new ModelBindingArgs(sourceType, modelType, msg); + ModelBindingException?.Invoke(Instance, args); + + if (args.Restart) + { + msg.Append(" The application is restarting now."); + + var context = HttpContext.Current; + if (context == null) + AppDomain.Unload(AppDomain.CurrentDomain); + else + Current.RestartAppPool(new HttpContextWrapper(context)); + } + + throw new ModelBindingException(msg.ToString()); + } + + public IModelBinder GetBinder(Type modelType) + { + // can bind to ContentModel (exact type match) + if (modelType == typeof(ContentModel)) return this; + + // can bind to ContentModel (exact generic type match) + if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(ContentModel<>)) return this; + + // can bind to TContent where TContent : IPublishedContent (any IPublishedContent implementation) + if (typeof(IPublishedContent).IsAssignableFrom(modelType)) return this; + return null; + } + + /// + /// Contains event data for the event. + /// + public class ModelBindingArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + public ModelBindingArgs(Type sourceType, Type modelType, StringBuilder message) + { + SourceType = sourceType; + ModelType = modelType; + Message = message; + } + + /// + /// Gets the type of the source object. + /// + public Type SourceType { get; set; } + + /// + /// Gets the type of the view model. + /// + public Type ModelType { get; set; } + + /// + /// Gets the message string builder. + /// + /// Handlers of the event can append text to the message. + public StringBuilder Message { get; } + + /// + /// Gets or sets a value indicating whether the application should restart. + /// + public bool Restart { get; set; } + } + + /// + /// Occurs on model binding exceptions. + /// + public static event EventHandler ModelBindingException; + } +} diff --git a/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs b/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs index c998ed931e..393b6882c9 100644 --- a/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs @@ -1,50 +1,38 @@ -using System.Web.Mvc; +using System.Web.Mvc; +using Umbraco.Web.Composing; namespace Umbraco.Web.Mvc { public static class ControllerContextExtensions { /// - /// Tries to get the Umbraco context from the whole ControllerContext hierarchy based on data tokens and if that fails - /// it will attempt to fallback to retrieving it from the HttpContext. + /// Gets the Umbraco context from a controller context hierarchy, if any, else the 'current' Umbraco context. /// - /// - /// + /// The controller context. + /// The Umbraco context. public static UmbracoContext GetUmbracoContext(this ControllerContext controllerContext) { - var umbCtx = controllerContext.RouteData.GetUmbracoContext(); - if (umbCtx != null) return umbCtx; - - if (controllerContext.ParentActionViewContext != null) - { - //recurse - return controllerContext.ParentActionViewContext.GetUmbracoContext(); - } - - //fallback to getting from HttpContext - return controllerContext.HttpContext.GetUmbracoContext(); + var o = controllerContext.GetDataTokenInViewContextHierarchy(Core.Constants.Web.UmbracoContextDataToken); + return o != null ? o as UmbracoContext : Current.UmbracoContext; } /// - /// Find a data token in the whole ControllerContext hierarchy of execution + /// Recursively gets a data token from a controller context hierarchy. /// - /// - /// - /// + /// The controller context. + /// The name of the data token. + /// The data token, or null. internal static object GetDataTokenInViewContextHierarchy(this ControllerContext controllerContext, string dataTokenName) { - if (controllerContext.RouteData.DataTokens.ContainsKey(dataTokenName)) + var context = controllerContext; + while (context != null) { - return controllerContext.RouteData.DataTokens[dataTokenName]; + object token; + if (context.RouteData.DataTokens.TryGetValue(dataTokenName, out token)) + return token; + context = context.ParentActionViewContext; } - - if (controllerContext.ParentActionViewContext != null) - { - //recurse! - return controllerContext.ParentActionViewContext.GetDataTokenInViewContextHierarchy(dataTokenName); - } - return null; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/ControllerExtensions.cs b/src/Umbraco.Web/Mvc/ControllerExtensions.cs index 9e55745002..85fdb5f34b 100644 --- a/src/Umbraco.Web/Mvc/ControllerExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerExtensions.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Mvc /// /// /// - internal static string GetControllerName(Type controllerType) + internal static string GetControllerName(Type controllerType) { if (!controllerType.Name.EndsWith("Controller")) { @@ -27,92 +27,92 @@ namespace Umbraco.Web.Mvc /// /// /// - internal static string GetControllerName(this IController controllerInstance) - { - return GetControllerName(controllerInstance.GetType()); - } + internal static string GetControllerName(this IController controllerInstance) + { + return GetControllerName(controllerInstance.GetType()); + } - /// + /// /// Return the controller name from the controller type /// /// /// /// - internal static string GetControllerName() + internal static string GetControllerName() { return GetControllerName(typeof(T)); } - /// - /// This is generally used for proxying to a ChildAction which requires a ViewContext to be setup - /// but since the View isn't actually rendered the IView object is null, however the rest of the - /// properties are filled in. - /// - /// - /// - internal static ViewContext CreateEmptyViewContext(this ControllerBase controller) - { - return new ViewContext - { - Controller = controller, - HttpContext = controller.ControllerContext.HttpContext, - RequestContext = controller.ControllerContext.RequestContext, - RouteData = controller.ControllerContext.RouteData, - TempData = controller.TempData, - ViewData = controller.ViewData - }; - } + /// + /// This is generally used for proxying to a ChildAction which requires a ViewContext to be setup + /// but since the View isn't actually rendered the IView object is null, however the rest of the + /// properties are filled in. + /// + /// + /// + internal static ViewContext CreateEmptyViewContext(this ControllerBase controller) + { + return new ViewContext + { + Controller = controller, + HttpContext = controller.ControllerContext.HttpContext, + RequestContext = controller.ControllerContext.RequestContext, + RouteData = controller.ControllerContext.RouteData, + TempData = controller.TempData, + ViewData = controller.ViewData + }; + } - /// - /// Returns the string output from a ViewResultBase object - /// - /// - /// - /// - internal static string RenderViewResultAsString(this ControllerBase controller, ViewResultBase viewResult) - { - using (var sw = new StringWriter()) - { - controller.EnsureViewObjectDataOnResult(viewResult); + /// + /// Returns the string output from a ViewResultBase object + /// + /// + /// + /// + internal static string RenderViewResultAsString(this ControllerBase controller, ViewResultBase viewResult) + { + using (var sw = new StringWriter()) + { + controller.EnsureViewObjectDataOnResult(viewResult); - var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, viewResult.ViewData, viewResult.TempData, sw); - viewResult.View.Render(viewContext, sw); - foreach (var v in viewResult.ViewEngineCollection) - { - v.ReleaseView(controller.ControllerContext, viewResult.View); - } - return sw.ToString().Trim(); - } - } - - /// - /// Renders the partial view to string. - /// - /// The controller context. - /// Name of the view. - /// The model. - /// true if it is a Partial view, otherwise false for a normal view - /// - internal static string RenderViewToString(this ControllerBase controller, string viewName, object model, bool isPartial = false) - { - if (controller.ControllerContext == null) - throw new ArgumentException("The controller must have an assigned ControllerContext to execute this method."); + var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, viewResult.ViewData, viewResult.TempData, sw); + viewResult.View.Render(viewContext, sw); + foreach (var v in viewResult.ViewEngineCollection) + { + v.ReleaseView(controller.ControllerContext, viewResult.View); + } + return sw.ToString().Trim(); + } + } - controller.ViewData.Model = model; + /// + /// Renders the partial view to string. + /// + /// The controller context. + /// Name of the view. + /// The model. + /// true if it is a Partial view, otherwise false for a normal view + /// + internal static string RenderViewToString(this ControllerBase controller, string viewName, object model, bool isPartial = false) + { + if (controller.ControllerContext == null) + throw new ArgumentException("The controller must have an assigned ControllerContext to execute this method."); - using (var sw = new StringWriter()) - { - var viewResult = isPartial == false - ? ViewEngines.Engines.FindView(controller.ControllerContext, viewName, null) - : ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName); - if (viewResult.View == null) - throw new InvalidOperationException("No view could be found by name " + viewName); + controller.ViewData.Model = model; + + using (var sw = new StringWriter()) + { + var viewResult = isPartial == false + ? ViewEngines.Engines.FindView(controller.ControllerContext, viewName, null) + : ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName); + if (viewResult.View == null) + throw new InvalidOperationException("No view could be found by name " + viewName); var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw); - viewResult.View.Render(viewContext, sw); - viewResult.ViewEngine.ReleaseView(controller.ControllerContext, viewResult.View); - return sw.GetStringBuilder().ToString(); - } - } + viewResult.View.Render(viewContext, sw); + viewResult.ViewEngine.ReleaseView(controller.ControllerContext, viewResult.View); + return sw.GetStringBuilder().ToString(); + } + } /// /// Renders the partial view to string. @@ -125,7 +125,7 @@ namespace Umbraco.Web.Mvc /// true if it is a Partial view, otherwise false for a normal view /// internal static string RenderViewToString( - this RequestContext requestContext, + this RequestContext requestContext, ViewDataDictionary viewData, TempDataDictionary tempData, string viewName, object model, bool isPartial = false) @@ -134,23 +134,23 @@ namespace Umbraco.Web.Mvc if (viewData == null) throw new ArgumentNullException("viewData"); if (tempData == null) throw new ArgumentNullException("tempData"); - var routeData = requestContext.RouteData; + var routeData = requestContext.RouteData; if (routeData.Values.ContainsKey("controller") == false) routeData.Values.Add("controller", "Fake"); - viewData.Model = model; + viewData.Model = model; var controllerContext = new ControllerContext( requestContext.HttpContext, routeData, new FakeController { - ViewData = viewData + ViewData = viewData }); - + using (var sw = new StringWriter()) { var viewResult = isPartial == false ? ViewEngines.Engines.FindView(controllerContext, viewName, null) - : ViewEngines.Engines.FindPartialView(controllerContext, viewName); - if (viewResult.View == null) + : ViewEngines.Engines.FindPartialView(controllerContext, viewName); + if (viewResult.View == null) throw new InvalidOperationException("No view could be found by name " + viewName); var viewContext = new ViewContext(controllerContext, viewResult.View, viewData, tempData, sw); viewResult.View.Render(viewContext, sw); @@ -159,7 +159,7 @@ namespace Umbraco.Web.Mvc } } - private class FakeController : ControllerBase { protected override void ExecuteCore() { } } + private class FakeController : ControllerBase { protected override void ExecuteCore() { } } /// /// Normally in MVC the way that the View object gets assigned to the result is to Execute the ViewResult, this however @@ -170,7 +170,7 @@ namespace Umbraco.Web.Mvc /// /// private static void EnsureViewObjectDataOnResult(this ControllerBase controller, ViewResultBase result) - { + { if (result.View != null) return; if (string.IsNullOrEmpty(result.ViewName)) diff --git a/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs b/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs index 86f24ac452..862736287c 100644 --- a/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web.Mvc; using System.Web.Routing; @@ -23,16 +23,16 @@ namespace Umbraco.Web.Mvc var controllerFactory = factory as MasterControllerFactory; if (controllerFactory != null) { - return controllerFactory.GetControllerTypeInternal(requestContext, controllerName); + return controllerFactory.GetControllerTypeInternal(requestContext, controllerName); } //we have no choice but to instantiate the controller var instance = factory.CreateController(requestContext, controllerName); if (instance != null) { - return instance.GetType(); + return instance.GetType(); } return null; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/DefaultRenderMvcControllerResolver.cs b/src/Umbraco.Web/Mvc/DefaultRenderMvcControllerResolver.cs deleted file mode 100644 index 641491c5ba..0000000000 --- a/src/Umbraco.Web/Mvc/DefaultRenderMvcControllerResolver.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Web.Mvc; -using Umbraco.Core; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.Mvc -{ - /// - /// A resolver used to resolve the default RenderMvcController that is used to render any front-end - /// Umbraco page when using MVC when there are no routes hijacked. - /// - public class DefaultRenderMvcControllerResolver : SingleObjectResolverBase - { - /// - /// Constructor accepting the default RenderMvcController - /// - /// - public DefaultRenderMvcControllerResolver(Type value) - : base(value) - { - ValidateType(value); - } - - /// - /// Returns the Default RenderMvcController type - /// - /// - public Type GetDefaultControllerType() - { - return Value; - } - - /// - /// Returns an instance of the default controller instance. - /// - [Obsolete("This method will be removed in future versions and should not be used to resolve a controller instance, the IControllerFactory is used for that purpose")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IRenderMvcController GetControllerInstance() - { - //try the dependency resolver, then the activator - var instance = DependencyResolver.Current.GetService(Value) ?? Activator.CreateInstance(Value); - var result = instance as IRenderMvcController; - if (result == null) - { - throw new InvalidOperationException("Could not create an instance of " + Value + " for the default " + typeof(IRenderMvcController).Name); - } - return result; - } - - /// - /// Sets the default RenderMvcController type - /// - /// - public void SetDefaultControllerType(Type controllerType) - { - ValidateType(controllerType); - Value = controllerType; - } - - /// - /// Ensures that the type passed in is of type RenderMvcController - /// - /// - private void ValidateType(Type type) - { - if (TypeHelper.IsTypeAssignableFrom(type) == false) - { - throw new InvalidOperationException("The Type specified (" + type + ") is not of type " + typeof (IRenderController)); - } - } - - } -} diff --git a/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs index c1c2186171..bcf7b34b4f 100644 --- a/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web; using System.Web.Mvc; @@ -20,12 +20,12 @@ namespace Umbraco.Web.Mvc return; } - filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); - filterContext.HttpContext.Response.Cache.SetMaxAge(TimeSpan.Zero); + filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); + filterContext.HttpContext.Response.Cache.SetMaxAge(TimeSpan.Zero); filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches); filterContext.HttpContext.Response.Cache.SetNoStore(); filterContext.HttpContext.Response.AddHeader("Pragma", "no-cache"); filterContext.HttpContext.Response.Cache.SetExpires(new DateTime(1990, 1, 1, 0, 0, 0)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs b/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs index c266b1ed5e..a874d93d7a 100644 --- a/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs +++ b/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.Mvc { /// /// Will ensure that client-side cache does not occur by sending the correct response headers - /// + /// public class DisableClientCacheAttribute : ActionFilterAttribute { public override void OnResultExecuting(ResultExecutingContext filterContext) @@ -23,4 +23,4 @@ namespace Umbraco.Web.Mvc base.OnResultExecuting(filterContext); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs b/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs index 625b67b2b2..34b857dfb4 100644 --- a/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs +++ b/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs @@ -1,19 +1,19 @@ -using System.IO; +using System.IO; using System.Web.Mvc; namespace Umbraco.Web.Mvc { /// - /// This is a special filter which is required for the RTE to be able to render Partial View Macros that + /// This is a special filter which is required for the RTE to be able to render Partial View Macros that /// contain forms when the RTE value is resolved outside of an MVC view being rendered /// /// /// The entire way that we support partial view macros that contain forms isn't really great, these forms /// need to be executed as ChildActions so that the ModelState,ViewData,TempData get merged into that action /// so the form can show errors, viewdata, etc... - /// Under normal circumstances, macros will be rendered after a ViewContext is created but in some cases + /// Under normal circumstances, macros will be rendered after a ViewContext is created but in some cases /// developers will resolve the RTE value in the controller, in this case the Form won't be rendered correctly - /// with merged ModelState from the controller because the special DataToken hasn't been set yet (which is + /// with merged ModelState from the controller because the special DataToken hasn't been set yet (which is /// normally done in the UmbracoViewPageOfModel when a real ViewContext is available. /// So we need to detect if the currently rendering controller is IRenderController and if so we'll ensure that /// this DataToken exists before the action executes in case the developer resolves an RTE value that contains @@ -38,7 +38,7 @@ namespace Umbraco.Web.Mvc /// /// Ensures that the custom ViewContext datatoken is set after the RenderController action is invoked, - /// this ensures that any custom ModelState that may have been added in the RenderController itself is + /// this ensures that any custom ModelState that may have been added in the RenderController itself is /// passed onwards in case it is required when rendering a PartialViewMacro with a form /// /// The filter context. @@ -70,4 +70,4 @@ namespace Umbraco.Web.Mvc } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs index 463afe1900..92625afdde 100644 --- a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs +++ b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs @@ -1,11 +1,10 @@ -using System; -using System.Globalization; -using System.Linq; +using System; using System.Web.Mvc; -using Umbraco.Core.Models; using Umbraco.Web.Routing; using Umbraco.Core; -using Language = umbraco.cms.businesslogic.language.Language; +using Umbraco.Core.Models.PublishedContent; +using LightInject; +using Umbraco.Web.Composing; namespace Umbraco.Web.Mvc { @@ -16,14 +15,13 @@ namespace Umbraco.Web.Mvc /// /// This is inspired from this discussion: /// http://our.umbraco.org/forum/developers/extending-umbraco/41367-Umbraco-6-MVC-Custom-MVC-Route?p=3 - /// + /// /// which is based on custom routing found here: /// http://shazwazza.com/post/Custom-MVC-routing-in-Umbraco /// public class EnsurePublishedContentRequestAttribute : ActionFilterAttribute { private readonly string _dataTokenName; - private readonly string _culture; private UmbracoContext _umbracoContext; private readonly int? _contentId; private UmbracoHelper _helper; @@ -33,13 +31,11 @@ namespace Umbraco.Web.Mvc /// /// /// - /// - public EnsurePublishedContentRequestAttribute(UmbracoContext umbracoContext, int contentId, string culture = null) + public EnsurePublishedContentRequestAttribute(UmbracoContext umbracoContext, int contentId) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); _umbracoContext = umbracoContext; _contentId = contentId; - _culture = culture; } /// @@ -65,64 +61,57 @@ namespace Umbraco.Web.Mvc /// /// /// - /// - public EnsurePublishedContentRequestAttribute(UmbracoContext umbracoContext, string dataTokenName, string culture = null) + public EnsurePublishedContentRequestAttribute(UmbracoContext umbracoContext, string dataTokenName) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); _umbracoContext = umbracoContext; _dataTokenName = dataTokenName; - _culture = culture; } /// /// Exposes the UmbracoContext /// - protected UmbracoContext UmbracoContext - { - get { return _umbracoContext ?? (_umbracoContext = UmbracoContext.Current); } - } + protected UmbracoContext UmbracoContext => _umbracoContext ?? (_umbracoContext = UmbracoContext.Current); + + // todo - try lazy property injection? + private PublishedRouter PublishedRouter => Core.Composing.Current.Container.GetInstance(); /// /// Exposes an UmbracoHelper /// - protected UmbracoHelper Umbraco - { - get { return _helper ?? (_helper = new UmbracoHelper(UmbracoContext.Current)); } - } + protected UmbracoHelper Umbraco => _helper + ?? (_helper = new UmbracoHelper(Current.UmbracoContext, Current.Services, Current.ApplicationCache)); public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); //First we need to check if the pcr has been set, if it has we're going to ignore this and not actually do anything - if (UmbracoContext.Current.PublishedContentRequest != null) + if (UmbracoContext.Current.PublishedRequest != null) { return; } - UmbracoContext.Current.PublishedContentRequest = - new PublishedContentRequest( - UmbracoContext.Current.CleanedUmbracoUrl, UmbracoContext.Current.RoutingContext); - - ConfigurePublishedContentRequest(UmbracoContext.Current.PublishedContentRequest, filterContext); + UmbracoContext.Current.PublishedRequest = PublishedRouter.CreateRequest(UmbracoContext.Current); + ConfigurePublishedContentRequest(UmbracoContext.Current.PublishedRequest, filterContext); } /// - /// This assigns the published content to the request, developers can override this to specify + /// This assigns the published content to the request, developers can override this to specify /// any other custom attributes required. /// - /// + /// /// - protected virtual void ConfigurePublishedContentRequest(PublishedContentRequest pcr, ActionExecutedContext filterContext) + protected virtual void ConfigurePublishedContentRequest(PublishedRequest request, ActionExecutedContext filterContext) { if (_contentId.HasValue) { - var content = Umbraco.TypedContent(_contentId); + var content = Umbraco.Content(_contentId); if (content == null) { throw new InvalidOperationException("Could not resolve content with id " + _contentId); } - pcr.PublishedContent = content; + request.PublishedContent = content; } else if (_dataTokenName.IsNullOrWhiteSpace() == false) { @@ -131,15 +120,14 @@ namespace Umbraco.Web.Mvc { throw new InvalidOperationException("No data token could be found with the name " + _dataTokenName); } - if ((result is IPublishedContent) == false) + if (result is IPublishedContent == false) { throw new InvalidOperationException("The data token resolved with name " + _dataTokenName + " was not an instance of " + typeof(IPublishedContent)); } - pcr.PublishedContent = (IPublishedContent) result; + request.PublishedContent = (IPublishedContent) result; } - pcr.Prepare(); + PublishedRouter.PrepareRequest(request); } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/FilteredControllerFactoriesResolver.cs b/src/Umbraco.Web/Mvc/FilteredControllerFactoriesResolver.cs deleted file mode 100644 index 140c7738fa..0000000000 --- a/src/Umbraco.Web/Mvc/FilteredControllerFactoriesResolver.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.Mvc -{ - /// - /// A resolver for storing IFilteredControllerFactories - /// - public sealed class FilteredControllerFactoriesResolver : ManyObjectsResolverBase - { - /// - /// Constructor - /// - /// - /// - /// - internal FilteredControllerFactoriesResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable factories) - : base(serviceProvider, logger, factories) - { - - } - - public IEnumerable Factories - { - get { return Values; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/FilteredControllerFactoryCollection.cs b/src/Umbraco.Web/Mvc/FilteredControllerFactoryCollection.cs new file mode 100644 index 0000000000..1c2d140379 --- /dev/null +++ b/src/Umbraco.Web/Mvc/FilteredControllerFactoryCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Mvc +{ + public class FilteredControllerFactoryCollection : BuilderCollectionBase + { + public FilteredControllerFactoryCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Web/Mvc/FilteredControllerFactoryCollectionBuilder.cs b/src/Umbraco.Web/Mvc/FilteredControllerFactoryCollectionBuilder.cs new file mode 100644 index 0000000000..930b5a1eb2 --- /dev/null +++ b/src/Umbraco.Web/Mvc/FilteredControllerFactoryCollectionBuilder.cs @@ -0,0 +1,14 @@ +using LightInject; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Mvc +{ + public class FilteredControllerFactoryCollectionBuilder : OrderedCollectionBuilderBase + { + public FilteredControllerFactoryCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override FilteredControllerFactoryCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Web/Mvc/HtmlTagWrapper.cs b/src/Umbraco.Web/Mvc/HtmlTagWrapper.cs index 7a187afb57..fd8586ff48 100644 --- a/src/Umbraco.Web/Mvc/HtmlTagWrapper.cs +++ b/src/Umbraco.Web/Mvc/HtmlTagWrapper.cs @@ -10,20 +10,20 @@ namespace Umbraco.Web.Mvc public class HtmlTagWrapper : IHtmlTagWrapper, IHtmlString { public HtmlTagWrapper Parent; - - private readonly List _children; - public IEnumerable Children - { - get { return _children; } - } - private List> _attributes; - public IEnumerable> Attributes - { - get { return _attributes; } - } + private readonly List _children; + public IEnumerable Children + { + get { return _children; } + } - public void ReflectAttributesFromAnonymousType(List> newAttributes) + private List> _attributes; + public IEnumerable> Attributes + { + get { return _attributes; } + } + + public void ReflectAttributesFromAnonymousType(List> newAttributes) { List> mergedAttributes = newAttributes @@ -32,7 +32,7 @@ namespace Umbraco.Web.Mvc .Select(g => new KeyValuePair(g.Key, string.Join(" ", g.ToArray()))) .ToList(); - _attributes = mergedAttributes; + _attributes = mergedAttributes; } public void ReflectAttributesFromAnonymousType(object anonymousAttributes) { @@ -59,9 +59,9 @@ namespace Umbraco.Web.Mvc public HtmlTagWrapper(string tag) { this.Tag = tag; - this._children = new List(); + this._children = new List(); this.CssClasses = new List(); - this._attributes = new List>(); + this._attributes = new List>(); this.Visible = true; } public HtmlString Write() @@ -150,44 +150,44 @@ namespace Umbraco.Web.Mvc if (!string.IsNullOrWhiteSpace(value)) { var newAttributes = new List> {new KeyValuePair(name, value)}; - this.ReflectAttributesFromAnonymousType(newAttributes); + this.ReflectAttributesFromAnonymousType(newAttributes); } else { var existingKey = this._attributes.Find(item => item.Key == name); - _attributes.Remove(existingKey); + _attributes.Remove(existingKey); } return this; } public HtmlTagWrapper AddChild(IHtmlTagWrapper newChild) { - _children.Add(newChild); + _children.Add(newChild); return this; } public HtmlTagWrapper AddChildren(params IHtmlTagWrapper[] collection) { - _children.AddRange(collection); + _children.AddRange(collection); return this; } public HtmlTagWrapper AddChild(string text) { - _children.Add(new HtmlTagWrapperTextNode(text)); + _children.Add(new HtmlTagWrapperTextNode(text)); return this; } public HtmlTagWrapper AddChildAt(int index, IHtmlTagWrapper newChild) { - _children.Insert(index, newChild); + _children.Insert(index, newChild); return this; } public HtmlTagWrapper AddChildAt(int index, string text) { - _children.Insert(index, new HtmlTagWrapperTextNode(text)); + _children.Insert(index, new HtmlTagWrapperTextNode(text)); return this; } public HtmlTagWrapper AddChildrenAt(int index, params IHtmlTagWrapper[] collection) { - _children.InsertRange(index, collection); + _children.InsertRange(index, collection); return this; } public HtmlTagWrapper RemoveChildAt(int index) diff --git a/src/Umbraco.Web/Mvc/HtmlTagWrapperTextNode.cs b/src/Umbraco.Web/Mvc/HtmlTagWrapperTextNode.cs index d6d82512d0..c5ee99b1b4 100644 --- a/src/Umbraco.Web/Mvc/HtmlTagWrapperTextNode.cs +++ b/src/Umbraco.Web/Mvc/HtmlTagWrapperTextNode.cs @@ -4,7 +4,7 @@ namespace Umbraco.Web.Mvc { public class HtmlTagWrapperTextNode : IHtmlTagWrapper { - public string Content { get; private set; } + public string Content { get; private set; } public HtmlTagWrapperTextNode(string content) { this.Content = content; diff --git a/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs b/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs index 214532b6e4..82b16443c8 100644 --- a/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Web.Mvc; using System.Web.Routing; @@ -6,15 +6,15 @@ using System.Web.Routing; namespace Umbraco.Web.Mvc { - public interface IFilteredControllerFactory : IControllerFactory - { - /// - /// Determines whether this instance can handle the specified request. - /// - /// The request. - /// true if this instance can handle the specified request; otherwise, false. - /// - bool CanHandle(RequestContext request); + public interface IFilteredControllerFactory : IControllerFactory + { + /// + /// Determines whether this instance can handle the specified request. + /// + /// The request. + /// true if this instance can handle the specified request; otherwise, false. + /// + bool CanHandle(RequestContext request); - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/Mvc/IHtmlTagWrapper.cs b/src/Umbraco.Web/Mvc/IHtmlTagWrapper.cs index 2535b78d90..b37cf96d97 100644 --- a/src/Umbraco.Web/Mvc/IHtmlTagWrapper.cs +++ b/src/Umbraco.Web/Mvc/IHtmlTagWrapper.cs @@ -2,8 +2,8 @@ namespace Umbraco.Web.Mvc { - public interface IHtmlTagWrapper - { - void WriteToHtmlTextWriter(HtmlTextWriter html); - } + public interface IHtmlTagWrapper + { + void WriteToHtmlTextWriter(HtmlTextWriter html); + } } diff --git a/src/Umbraco.Web/Mvc/IRenderController.cs b/src/Umbraco.Web/Mvc/IRenderController.cs index e4303e06e5..103a484869 100644 --- a/src/Umbraco.Web/Mvc/IRenderController.cs +++ b/src/Umbraco.Web/Mvc/IRenderController.cs @@ -1,4 +1,4 @@ -using System.Web.Mvc; +using System.Web.Mvc; namespace Umbraco.Web.Mvc { @@ -7,6 +7,6 @@ namespace Umbraco.Web.Mvc /// public interface IRenderController : IController { - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/IRenderMvcController.cs b/src/Umbraco.Web/Mvc/IRenderMvcController.cs index 53865cd4ee..450ebbb9f8 100644 --- a/src/Umbraco.Web/Mvc/IRenderMvcController.cs +++ b/src/Umbraco.Web/Mvc/IRenderMvcController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web.Http.Filters; using System.Web.Mvc; using System.Web.Routing; @@ -17,6 +17,6 @@ namespace Umbraco.Web.Mvc /// /// /// - ActionResult Index(RenderModel model); + ActionResult Index(ContentModel model); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs index c60e9d883e..3ed178624f 100644 --- a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs @@ -1,51 +1,65 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; +using System; using System.Linq; -using System.Threading; using System.Web.Mvc; using System.Web.Routing; -using Umbraco.Core; namespace Umbraco.Web.Mvc { /// - /// A controller factory which uses an internal list of in order to invoke - /// different controller factories dependent upon their implementation of for the current - /// request. Allows circumvention of MVC3's singly registered IControllerFactory. - /// - /// - internal class MasterControllerFactory : DefaultControllerFactory - { - private readonly FilteredControllerFactoriesResolver _slaveFactories; + /// A controller factory which uses an internal list of in order to invoke + /// different controller factories dependent upon their implementation of for the current + /// request. Allows circumvention of MVC3's singly registered IControllerFactory. + /// + /// + internal class MasterControllerFactory : DefaultControllerFactory + { + private readonly Func _factoriesAccessor; + private FilteredControllerFactoryCollection _factories; - public MasterControllerFactory(FilteredControllerFactoriesResolver factoryResolver) - { - _slaveFactories = factoryResolver; - } + /// + /// Initializes a new instance of the with a factories accessor. + /// + /// The factories accessor. + public MasterControllerFactory(Func factoriesAccessor) + { + // note + // because the MasterControllerFactory needs to be ctored to be assigned to + // ControllerBuilder.Current when setting up Mvc and WebApi, it cannot be ctored by + // the IoC container - and yet we don't want that ctor to resolve the factories + // as that happen before everything is configured - so, passing a factories + // accessor func. - /// - /// Creates the specified controller by using the specified request context. - /// - /// The context of the HTTP request, which includes the HTTP context and route data. - /// The name of the controller. - /// The controller. - /// The parameter is null. - /// - /// The parameter is null or empty. - /// - public override IController CreateController(RequestContext requestContext, string controllerName) - { - var factory = _slaveFactories.Factories.FirstOrDefault(x => x.CanHandle(requestContext)); - return factory != null - ? factory.CreateController(requestContext, controllerName) - : base.CreateController(requestContext, controllerName); - } + _factoriesAccessor = factoriesAccessor; + } + + private IFilteredControllerFactory FactoryForRequest(RequestContext context) + { + if (_factories == null) _factories = _factoriesAccessor(); + return _factories.FirstOrDefault(x => x.CanHandle(context)); + } + + /// + /// Creates the specified controller by using the specified request context. + /// + /// The context of the HTTP request, which includes the HTTP context and route data. + /// The name of the controller. + /// The controller. + /// The parameter is null. + /// + /// The parameter is null or empty. + /// + public override IController CreateController(RequestContext requestContext, string controllerName) + { + var factory = FactoryForRequest(requestContext); + return factory != null + ? factory.CreateController(requestContext, controllerName) + : base.CreateController(requestContext, controllerName); + } /// /// Retrieves the controller type for the specified name and request context. /// - /// + /// /// /// The controller type. /// @@ -53,7 +67,7 @@ namespace Umbraco.Web.Mvc /// The name of the controller. internal Type GetControllerTypeInternal(RequestContext requestContext, string controllerName) { - var factory = _slaveFactories.Factories.FirstOrDefault(x => x.CanHandle(requestContext)); + var factory = FactoryForRequest(requestContext); if (factory != null) { //check to see if the factory is of type UmbracoControllerFactory which exposes the GetControllerType method so we don't have to create @@ -67,35 +81,33 @@ namespace Umbraco.Web.Mvc } //we have no choice but to instantiate the controller var instance = factory.CreateController(requestContext, controllerName); - if (instance != null) - { - return instance.GetType(); - } - return null; + return instance?.GetType(); } - return base.GetControllerType(requestContext, controllerName); + return GetControllerType(requestContext, controllerName); } - /// - /// Releases the specified controller. - /// - /// The controller to release. - /// - public override void ReleaseController(IController controller) - { - bool released = false; - if (controller is Controller) - { - var requestContext = ((Controller)controller).ControllerContext.RequestContext; - var factory = _slaveFactories.Factories.FirstOrDefault(x => x.CanHandle(requestContext)); - if (factory != null) - { - factory.ReleaseController(controller); - released = true; - } - } - if (!released) base.ReleaseController(controller); - } - } -} \ No newline at end of file + /// + /// Releases the specified controller. + /// + /// The controller to release. + /// + public override void ReleaseController(IController icontroller) + { + var released = false; + var controller = icontroller as Controller; + if (controller != null) + { + var requestContext = controller.ControllerContext.RequestContext; + var factory = FactoryForRequest(requestContext); + if (factory != null) + { + factory.ReleaseController(controller); + released = true; + } + } + if (released == false) + base.ReleaseController(icontroller); + } + } +} diff --git a/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs index 71ebfb8288..517fc9efdb 100644 --- a/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Web; using System.Web.Mvc; -using System.Web.Security; -using Umbraco.Web.Security; -using umbraco.cms.businesslogic.member; using AuthorizeAttribute = System.Web.Mvc.AuthorizeAttribute; using Umbraco.Core; +using Umbraco.Web.Composing; namespace Umbraco.Web.Mvc { @@ -17,13 +14,10 @@ namespace Umbraco.Web.Mvc /// public sealed class MemberAuthorizeAttribute : AuthorizeAttribute { - + // see note in HttpInstallAuthorizeAttribute private readonly UmbracoContext _umbracoContext; - private UmbracoContext GetUmbracoContext() - { - return _umbracoContext ?? UmbracoContext.Current; - } + private UmbracoContext UmbracoContext => _umbracoContext ?? Current.UmbracoContext; /// /// THIS SHOULD BE ONLY USED FOR UNIT TESTS @@ -83,7 +77,7 @@ namespace Umbraco.Web.Mvc } } - return GetUmbracoContext().Security.IsMemberAuthorized(AllowAll, + return UmbracoContext.Security.IsMemberAuthorized(AllowAll, AllowType.Split(','), AllowGroup.Split(','), members); diff --git a/src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs b/src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs index 7d2df43962..3b52e32c20 100644 --- a/src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs +++ b/src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs @@ -1,42 +1,42 @@ -using System.Linq; +using System.Linq; using System.Web.Mvc; namespace Umbraco.Web.Mvc { - /// - /// When a ChildAction is executing and we want the ModelState from the Parent context to be merged in - /// to help with validation, this filter can be used. - /// - /// - /// By default, this filter will only merge when an Http POST is detected but this can be modified in the ctor - /// - public class MergeModelStateToChildActionAttribute : ActionFilterAttribute - { - private readonly string[] _verb; + /// + /// When a ChildAction is executing and we want the ModelState from the Parent context to be merged in + /// to help with validation, this filter can be used. + /// + /// + /// By default, this filter will only merge when an Http POST is detected but this can be modified in the ctor + /// + public class MergeModelStateToChildActionAttribute : ActionFilterAttribute + { + private readonly string[] _verb; - public MergeModelStateToChildActionAttribute() - : this(HttpVerbs.Post) - { + public MergeModelStateToChildActionAttribute() + : this(HttpVerbs.Post) + { - } + } - public MergeModelStateToChildActionAttribute(params HttpVerbs[] verb) - { - _verb = verb.Select(x => x.ToString().ToUpper()).ToArray(); - } + public MergeModelStateToChildActionAttribute(params HttpVerbs[] verb) + { + _verb = verb.Select(x => x.ToString().ToUpper()).ToArray(); + } - public override void OnActionExecuting(ActionExecutingContext filterContext) - { - //check if the verb matches, if so merge the ModelState before the action is executed. - if (_verb.Contains(filterContext.HttpContext.Request.HttpMethod)) - { - if (filterContext.Controller.ControllerContext.IsChildAction) - { - filterContext.Controller.ViewData.ModelState.Merge( - filterContext.Controller.ControllerContext.ParentActionViewContext.ViewData.ModelState); - } - } - base.OnActionExecuting(filterContext); - } - } -} \ No newline at end of file + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + //check if the verb matches, if so merge the ModelState before the action is executed. + if (_verb.Contains(filterContext.HttpContext.Request.HttpMethod)) + { + if (filterContext.Controller.ControllerContext.IsChildAction) + { + filterContext.Controller.ViewData.ModelState.Merge( + filterContext.Controller.ControllerContext.ParentActionViewContext.ViewData.ModelState); + } + } + base.OnActionExecuting(filterContext); + } + } +} diff --git a/src/Umbraco.Web/Mvc/MergeParentContextViewDataAttribute.cs b/src/Umbraco.Web/Mvc/MergeParentContextViewDataAttribute.cs index 1b1783c04e..12938a0eca 100644 --- a/src/Umbraco.Web/Mvc/MergeParentContextViewDataAttribute.cs +++ b/src/Umbraco.Web/Mvc/MergeParentContextViewDataAttribute.cs @@ -1,20 +1,20 @@ -using System.Linq; +using System.Linq; using System.Web.Mvc; namespace Umbraco.Web.Mvc { /// - /// This attribute can be used for when child actions execute and will automatically merge in the viewdata from the parent context to the + /// This attribute can be used for when child actions execute and will automatically merge in the viewdata from the parent context to the /// child action result. /// /// /// This will retain any custom viewdata put into the child viewdata if the same key persists in the parent context's view data. You can always still - /// access the parent's view data normally. + /// access the parent's view data normally. /// This just simplifies working with ChildActions and view data. - /// + /// /// NOTE: This does not mean that the parent context's view data will be merged before the action executes, if you need access to the parent context's view /// data during controller execution you can access it normally. - /// + /// /// NOTE: This recursively merges in all ParentActionViewContext ancestry in case there's child actions inside of child actions. /// public class MergeParentContextViewDataAttribute : ActionFilterAttribute @@ -47,10 +47,10 @@ namespace Umbraco.Web.Mvc //Recurse! if (currentParent.IsChildAction) { - MergeCurrentParent(target, currentParent.ParentActionViewContext); + MergeCurrentParent(target, currentParent.ParentActionViewContext); } - + } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/MinifyJavaScriptResultAttribute.cs b/src/Umbraco.Web/Mvc/MinifyJavaScriptResultAttribute.cs index 8281af7412..8dee11b844 100644 --- a/src/Umbraco.Web/Mvc/MinifyJavaScriptResultAttribute.cs +++ b/src/Umbraco.Web/Mvc/MinifyJavaScriptResultAttribute.cs @@ -25,13 +25,13 @@ namespace Umbraco.Web.Mvc var jsResult = filterContext.Result as JavaScriptResult; if (jsResult == null) return; if (filterContext.HttpContext.IsDebuggingEnabled) return; - + //minify the result var result = jsResult.Script; var minifier = new JSMin(); - var minified = minifier.Minify(result); + var minified = minifier.Minify(result); jsResult.Script = minified; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/MvcVersionCheck.cs b/src/Umbraco.Web/Mvc/MvcVersionCheck.cs index 348ce05d7d..60da4d0a60 100644 --- a/src/Umbraco.Web/Mvc/MvcVersionCheck.cs +++ b/src/Umbraco.Web/Mvc/MvcVersionCheck.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Web.Mvc +namespace Umbraco.Web.Mvc { internal class MvcVersionCheck { @@ -7,4 +7,4 @@ namespace Umbraco.Web.Mvc get { return typeof (System.Web.Mvc.Controller).Assembly.GetName().Version; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/NotChildAction.cs b/src/Umbraco.Web/Mvc/NotChildAction.cs index 8106ac47b1..a06ff28c33 100644 --- a/src/Umbraco.Web/Mvc/NotChildAction.cs +++ b/src/Umbraco.Web/Mvc/NotChildAction.cs @@ -1,9 +1,9 @@ -using System.Web.Mvc; +using System.Web.Mvc; namespace Umbraco.Web.Mvc { /// - /// Used to ensure that actions with duplicate names that are not child actions don't get executed when + /// Used to ensure that actions with duplicate names that are not child actions don't get executed when /// we are Posting and not redirecting. /// /// @@ -17,4 +17,4 @@ namespace Umbraco.Web.Mvc return !isChildAction; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/NotFoundHandler.cs b/src/Umbraco.Web/Mvc/NotFoundHandler.cs index 1d73f9b5e6..bb98b42028 100644 --- a/src/Umbraco.Web/Mvc/NotFoundHandler.cs +++ b/src/Umbraco.Web/Mvc/NotFoundHandler.cs @@ -1,4 +1,4 @@ -using System.Web; +using System.Web; namespace Umbraco.Web.Mvc { @@ -14,4 +14,4 @@ namespace Umbraco.Web.Mvc get { return true; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/PluginController.cs b/src/Umbraco.Web/Mvc/PluginController.cs index 4ff4b961b8..6b5f111585 100644 --- a/src/Umbraco.Web/Mvc/PluginController.cs +++ b/src/Umbraco.Web/Mvc/PluginController.cs @@ -1,9 +1,12 @@ -using System; +using System; using System.Collections.Concurrent; using System.Web.Mvc; -using umbraco.interfaces; +using LightInject; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Composing; using Umbraco.Core.Services; using Umbraco.Web.Security; using Umbraco.Web.WebApi; @@ -11,139 +14,109 @@ using Umbraco.Web.WebApi; namespace Umbraco.Web.Mvc { /// - /// A base class for all plugin controllers to inherit from + /// Provides a base class for plugin controllers. /// public abstract class PluginController : Controller, IDiscoverable { - /// - /// stores the metadata about plugin controllers - /// - private static readonly ConcurrentDictionary MetadataStorage = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary MetadataStorage + = new ConcurrentDictionary(); - private UmbracoHelper _umbracoHelper; + private UmbracoHelper _umbracoHelper; + + // for debugging purposes + internal Guid InstanceId { get; } = Guid.NewGuid(); + + // note + // properties marked as [Inject] below will be property-injected (vs constructor-injected) in + // order to keep the constuctor as light as possible, so that ppl implementing eg a SurfaceController + // don't need to implement complex constructors + need to refactor them each time we change ours. + // this means that these properties have a setter. + // what can go wrong? /// - /// Default constructor + /// Gets or sets the Umbraco context. /// - /// - protected PluginController(UmbracoContext umbracoContext) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - UmbracoContext = umbracoContext; - InstanceId = Guid.NewGuid(); - } + [Inject] + public virtual UmbracoContext UmbracoContext { get; set; } - protected PluginController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) + /// + /// Gets or sets the database context. + /// + [Inject] + public IUmbracoDatabaseFactory DatabaseFactory { get; set; } + + /// + /// Gets or sets the services context. + /// + [Inject] + public ServiceContext Services { get; set; } + + /// + /// Gets or sets the application cache. + /// + [Inject] + public CacheHelper ApplicationCache { get; set; } + + /// + /// Gets or sets the logger. + /// + [Inject] + public ILogger Logger { get; set; } + + /// + /// Gets or sets the profiling logger. + /// + [Inject] + public ProfilingLogger ProfilingLogger { get; set; } + + /// + /// Gets the membership helper. + /// + public MembershipHelper Members => Umbraco.MembershipHelper; + + /// + /// Gets the Umbraco helper. + /// + public UmbracoHelper Umbraco { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (umbracoHelper == null) throw new ArgumentNullException("umbracoHelper"); - UmbracoContext = umbracoContext; - InstanceId = Guid.NewGuid(); - _umbracoHelper = umbracoHelper; + get + { + return _umbracoHelper + ?? (_umbracoHelper = new UmbracoHelper(UmbracoContext, Services, ApplicationCache)); + } + internal set // tests + { + _umbracoHelper = value; + } } /// - /// Useful for debugging + /// Gets metadata for this instance. /// - internal Guid InstanceId { get; private set; } + internal PluginControllerMetadata Metadata => GetMetadata(GetType()); /// - /// Returns the MemberHelper instance + /// Gets metadata for a controller type. /// - public MembershipHelper Members + /// The controller type. + /// Metadata for the controller type. + internal static PluginControllerMetadata GetMetadata(Type controllerType) { - get { return Umbraco.MembershipHelper; } - } + return MetadataStorage.GetOrAdd(controllerType, type => + { + // plugin controller? back-office controller? + var pluginAttribute = controllerType.GetCustomAttribute(false); + var backOfficeAttribute = controllerType.GetCustomAttribute(true); - /// - /// Returns an UmbracoHelper object - /// - public virtual UmbracoHelper Umbraco - { - get { return _umbracoHelper ?? (_umbracoHelper = new UmbracoHelper(UmbracoContext)); } - } - - /// - /// Returns an ILogger - /// - public ILogger Logger - { - get { return ProfilingLogger.Logger; } - } - - /// - /// Returns a ProfilingLogger - /// - public virtual ProfilingLogger ProfilingLogger - { - get { return UmbracoContext.Application.ProfilingLogger; } - } - - /// - /// Returns the current UmbracoContext - /// - public virtual UmbracoContext UmbracoContext { get; private set; } - - /// - /// Returns the current ApplicationContext - /// - public virtual ApplicationContext ApplicationContext - { - get { return UmbracoContext.Application; } - } - - /// - /// Returns a ServiceContext - /// - public ServiceContext Services - { - get { return ApplicationContext.Services; } - } - - /// - /// Returns a DatabaseContext - /// - public DatabaseContext DatabaseContext - { - get { return ApplicationContext.DatabaseContext; } - } - - /// - /// Returns the metadata for this instance - /// - internal PluginControllerMetadata Metadata - { - get { return GetMetadata(this.GetType()); } - } - - /// - /// Returns the metadata for a PluginController - /// - /// - /// - internal static PluginControllerMetadata GetMetadata(Type type) - { - - return MetadataStorage.GetOrAdd(type, type1 => + return new PluginControllerMetadata { - var pluginAttribute = type.GetCustomAttribute(false); - //check if any inherited class of this type contains the IsBackOffice attribute - var backOfficeAttribute = type.GetCustomAttribute(true); - - var meta = new PluginControllerMetadata() - { - AreaName = pluginAttribute == null ? null : pluginAttribute.AreaName, - ControllerName = ControllerExtensions.GetControllerName(type), - ControllerNamespace = type.Namespace, - ControllerType = type, - IsBackOffice = backOfficeAttribute != null - }; - - MetadataStorage.TryAdd(type, meta); - - return meta; - }); - + AreaName = pluginAttribute?.AreaName, + ControllerName = ControllerExtensions.GetControllerName(controllerType), + ControllerNamespace = controllerType.Namespace, + ControllerType = controllerType, + IsBackOffice = backOfficeAttribute != null + }; + }); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/PluginControllerArea.cs b/src/Umbraco.Web/Mvc/PluginControllerArea.cs index c6b3e7fe1e..4c240e976d 100644 --- a/src/Umbraco.Web/Mvc/PluginControllerArea.cs +++ b/src/Umbraco.Web/Mvc/PluginControllerArea.cs @@ -1,80 +1,84 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using System.Web.Routing; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Composing; using Umbraco.Web.WebApi; namespace Umbraco.Web.Mvc { - /// - /// A custom area for controllers that are plugins - /// - internal class PluginControllerArea : AreaRegistration - { - private readonly IEnumerable _surfaceControllers; + /// + /// A custom area for controllers that are plugins + /// + internal class PluginControllerArea : AreaRegistration + { + private readonly IGlobalSettings _globalSettings; + private readonly IEnumerable _surfaceControllers; private readonly IEnumerable _apiControllers; - private readonly string _areaName; + private readonly string _areaName; - /// - /// The constructor accepts all types of plugin controllers and will verify that ALL of them have the same areaName assigned to them - /// based on their PluginControllerAttribute. If they are not the same an exception will be thrown. - /// - /// - public PluginControllerArea(IEnumerable pluginControllers) - { - var controllers = pluginControllers.ToArray(); + /// + /// The constructor accepts all types of plugin controllers and will verify that ALL of them have the same areaName assigned to them + /// based on their PluginControllerAttribute. If they are not the same an exception will be thrown. + /// + /// + /// + public PluginControllerArea(IGlobalSettings globalSettings, IEnumerable pluginControllers) + { + _globalSettings = globalSettings; + var controllers = pluginControllers.ToArray(); - if (controllers.Any(x => x.AreaName.IsNullOrWhiteSpace())) - { - throw new InvalidOperationException("Cannot create a PluginControllerArea unless all plugin controllers assigned have a PluginControllerAttribute assigned"); - } - _areaName = controllers.First().AreaName; - foreach(var c in controllers) - { - if (c.AreaName != _areaName) - { - throw new InvalidOperationException("Cannot create a PluginControllerArea unless all plugin controllers assigned have the same AreaName. The first AreaName found was " + _areaName + " however, the controller of type " + c.GetType().FullName + " has an AreaName of " + c.AreaName); - } - } + if (controllers.Any(x => x.AreaName.IsNullOrWhiteSpace())) + { + throw new InvalidOperationException("Cannot create a PluginControllerArea unless all plugin controllers assigned have a PluginControllerAttribute assigned"); + } + _areaName = controllers.First().AreaName; + foreach(var c in controllers) + { + if (c.AreaName != _areaName) + { + throw new InvalidOperationException("Cannot create a PluginControllerArea unless all plugin controllers assigned have the same AreaName. The first AreaName found was " + _areaName + " however, the controller of type " + c.GetType().FullName + " has an AreaName of " + c.AreaName); + } + } - //get the controllers - _surfaceControllers = controllers.Where(x => TypeHelper.IsTypeAssignableFrom(x.ControllerType)); + //get the controllers + _surfaceControllers = controllers.Where(x => TypeHelper.IsTypeAssignableFrom(x.ControllerType)); _apiControllers = controllers.Where(x => TypeHelper.IsTypeAssignableFrom(x.ControllerType)); - } + } - public override void RegisterArea(AreaRegistrationContext context) - { - MapRouteSurfaceControllers(context.Routes, _surfaceControllers); - MapRouteApiControllers(context.Routes, _apiControllers); - } + public override void RegisterArea(AreaRegistrationContext context) + { + MapRouteSurfaceControllers(context.Routes, _surfaceControllers); + MapRouteApiControllers(context.Routes, _apiControllers); + } - public override string AreaName - { - get { return _areaName; } - } + public override string AreaName + { + get { return _areaName; } + } - /// - /// Registers all surface controller routes - /// - /// - /// - /// - /// The routes will be: - /// - /// /Umbraco/[AreaName]/[ControllerName]/[Action]/[Id] - /// - private void MapRouteSurfaceControllers(RouteCollection routes, IEnumerable surfaceControllers) - { - foreach (var s in surfaceControllers) - { - var route = this.RouteControllerPlugin(s.ControllerName, s.ControllerType, routes, "", "Index", UrlParameter.Optional, "surface"); + /// + /// Registers all surface controller routes + /// + /// + /// + /// + /// The routes will be: + /// + /// /Umbraco/[AreaName]/[ControllerName]/[Action]/[Id] + /// + private void MapRouteSurfaceControllers(RouteCollection routes, IEnumerable surfaceControllers) + { + foreach (var s in surfaceControllers) + { + var route = this.RouteControllerPlugin(_globalSettings, s.ControllerName, s.ControllerType, routes, "", "Index", UrlParameter.Optional, "surface"); //set the route handler to our SurfaceRouteHandler route.RouteHandler = new SurfaceRouteHandler(); - } - } + } + } /// /// Registers all api controller routes @@ -85,10 +89,10 @@ namespace Umbraco.Web.Mvc { foreach (var s in apiControllers) { - this.RouteControllerPlugin(s.ControllerName, s.ControllerType, routes, "", "", UrlParameter.Optional, "api", - isMvc: false, + this.RouteControllerPlugin(_globalSettings, s.ControllerName, s.ControllerType, routes, "", "", UrlParameter.Optional, "api", + isMvc: false, areaPathPrefix: s.IsBackOffice ? "backoffice" : null); } } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/Mvc/PluginControllerAttribute.cs b/src/Umbraco.Web/Mvc/PluginControllerAttribute.cs index 064f39826d..5e5262b2ed 100644 --- a/src/Umbraco.Web/Mvc/PluginControllerAttribute.cs +++ b/src/Umbraco.Web/Mvc/PluginControllerAttribute.cs @@ -1,26 +1,26 @@ -using System; +using System; using System.Linq; namespace Umbraco.Web.Mvc { - /// - /// An attribute applied to a plugin controller that requires that it is routed to its own area - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class PluginControllerAttribute : Attribute - { - public string AreaName { get; private set; } + /// + /// An attribute applied to a plugin controller that requires that it is routed to its own area + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class PluginControllerAttribute : Attribute + { + public string AreaName { get; private set; } - public PluginControllerAttribute(string areaName) - { - //validate this, only letters and digits allowed. - if (areaName.Any(c => !Char.IsLetterOrDigit(c))) - { - throw new FormatException("The areaName specified " + areaName + " can only contains letters and digits"); - } + public PluginControllerAttribute(string areaName) + { + //validate this, only letters and digits allowed. + if (areaName.Any(c => !Char.IsLetterOrDigit(c))) + { + throw new FormatException("The areaName specified " + areaName + " can only contains letters and digits"); + } - AreaName = areaName; - } + AreaName = areaName; + } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/Mvc/PluginControllerMetadata.cs b/src/Umbraco.Web/Mvc/PluginControllerMetadata.cs index 6de8525de9..a08b11dd7f 100644 --- a/src/Umbraco.Web/Mvc/PluginControllerMetadata.cs +++ b/src/Umbraco.Web/Mvc/PluginControllerMetadata.cs @@ -1,21 +1,21 @@ -using System; +using System; namespace Umbraco.Web.Mvc { - /// - /// Represents some metadata about the controller - /// - internal class PluginControllerMetadata - { - internal Type ControllerType { get; set; } - internal string ControllerName { get; set; } - internal string ControllerNamespace { get; set; } - internal string AreaName { get; set; } + /// + /// Represents some metadata about the controller + /// + internal class PluginControllerMetadata + { + internal Type ControllerType { get; set; } + internal string ControllerName { get; set; } + internal string ControllerNamespace { get; set; } + internal string AreaName { get; set; } /// - /// This is determined by another attribute [IsBackOffice] which slightly modifies the route path + /// This is determined by another attribute [IsBackOffice] which slightly modifies the route path /// allowing us to determine if it is indeed a back office request or not /// internal bool IsBackOffice { get; set; } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/Mvc/PluginViewEngine.cs b/src/Umbraco.Web/Mvc/PluginViewEngine.cs index b4b72e36d5..a1b2d886fd 100644 --- a/src/Umbraco.Web/Mvc/PluginViewEngine.cs +++ b/src/Umbraco.Web/Mvc/PluginViewEngine.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Linq; using System.Web.Mvc; using Lucene.Net.Util; @@ -7,100 +7,100 @@ using Umbraco.Core.IO; namespace Umbraco.Web.Mvc { /// - /// A view engine to look into the App_Plugins folder for views for packaged controllers - /// + /// A view engine to look into the App_Plugins folder for views for packaged controllers + /// public class PluginViewEngine : RazorViewEngine - { - - /// - /// Constructor - /// - public PluginViewEngine() - { - SetViewLocations(); - } + { - private void SetViewLocations() - { - //these are the originals: + /// + /// Constructor + /// + public PluginViewEngine() + { + SetViewLocations(); + } - //base.AreaViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; - //base.AreaMasterLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; - //base.AreaPartialViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; - //base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; - //base.MasterLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; - //base.PartialViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; - //base.FileExtensions = new string[] { "cshtml", "vbhtml" }; + private void SetViewLocations() + { + //these are the originals: - var viewLocationsArray = new[] - { - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.vbhtml") - }; + //base.AreaViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; + //base.AreaMasterLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; + //base.AreaPartialViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; + //base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; + //base.MasterLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; + //base.PartialViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; + //base.FileExtensions = new string[] { "cshtml", "vbhtml" }; - //set all of the area view locations to the plugin folder - AreaViewLocationFormats = viewLocationsArray - .Concat(new[] - { - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.cshtml"), - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.vbhtml") - }) - .ToArray(); + var viewLocationsArray = new[] + { + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.vbhtml") + }; + + //set all of the area view locations to the plugin folder + AreaViewLocationFormats = viewLocationsArray + .Concat(new[] + { + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.cshtml"), + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.vbhtml") + }) + .ToArray(); AreaMasterLocationFormats = viewLocationsArray; AreaPartialViewLocationFormats = new[] - { - //will be used when we have partial view and child action macros - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Partials/{0}.cshtml"), - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Partials/{0}.vbhtml"), - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/MacroPartials/{0}.cshtml"), - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/MacroPartials/{0}.vbhtml"), - //for partials - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.vbhtml"), - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.cshtml"), - string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.vbhtml") - }; + { + //will be used when we have partial view and child action macros + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Partials/{0}.cshtml"), + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Partials/{0}.vbhtml"), + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/MacroPartials/{0}.cshtml"), + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/MacroPartials/{0}.vbhtml"), + //for partials + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.vbhtml"), + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.cshtml"), + string.Concat(SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.vbhtml") + }; - } + } - /// - /// Ensures that the correct web.config for razor exists in the /Views folder. - /// - private void EnsureFolderAndWebConfig(ViewEngineResult result) - { - if (result.View == null) return; - var razorResult = result.View as RazorView; - if (razorResult == null) return; + /// + /// Ensures that the correct web.config for razor exists in the /Views folder. + /// + private void EnsureFolderAndWebConfig(ViewEngineResult result) + { + if (result.View == null) return; + var razorResult = result.View as RazorView; + if (razorResult == null) return; - var folder = Path.GetDirectoryName(IOHelper.MapPath(razorResult.ViewPath)); - //now we need to get the /View/ folder - var viewFolder = folder.Substring(0, folder.LastIndexOf("\\Views\\")) + "\\Views"; + var folder = Path.GetDirectoryName(IOHelper.MapPath(razorResult.ViewPath)); + //now we need to get the /View/ folder + var viewFolder = folder.Substring(0, folder.LastIndexOf("\\Views\\")) + "\\Views"; - //ensure the web.config file is in the ~/Views folder - Directory.CreateDirectory(viewFolder); - if (!File.Exists(Path.Combine(viewFolder, "web.config"))) - { - using (var writer = File.CreateText(Path.Combine(viewFolder, "web.config"))) - { + //ensure the web.config file is in the ~/Views folder + Directory.CreateDirectory(viewFolder); + if (!File.Exists(Path.Combine(viewFolder, "web.config"))) + { + using (var writer = File.CreateText(Path.Combine(viewFolder, "web.config"))) + { writer.Write(Strings.WebConfigTemplate); - } - } - } + } + } + } - public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) - { - var result = base.FindView(controllerContext, viewName, masterName, useCache); - EnsureFolderAndWebConfig(result); - return result; - } + public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) + { + var result = base.FindView(controllerContext, viewName, masterName, useCache); + EnsureFolderAndWebConfig(result); + return result; + } - public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) - { - var result = base.FindPartialView(controllerContext, partialViewName, useCache); - EnsureFolderAndWebConfig(result); - return result; - } - } -} \ No newline at end of file + public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) + { + var result = base.FindPartialView(controllerContext, partialViewName, useCache); + EnsureFolderAndWebConfig(result); + return result; + } + } +} diff --git a/src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs b/src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs index fef1ba8647..6253c55523 100644 --- a/src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs +++ b/src/Umbraco.Web/Mvc/PostedDataProxyInfo.cs @@ -1,12 +1,12 @@ -using System; +using System; namespace Umbraco.Web.Mvc { - /// - /// Represents the data required to proxy a request to a surface controller for posted data - /// - internal class PostedDataProxyInfo : RouteDefinition - { - public string Area { get; set; } - } -} \ No newline at end of file + /// + /// Represents the data required to proxy a request to a surface controller for posted data + /// + internal class PostedDataProxyInfo : RouteDefinition + { + public string Area { get; set; } + } +} diff --git a/src/Umbraco.Web/Mvc/PreRenderViewActionFilterAttribute.cs b/src/Umbraco.Web/Mvc/PreRenderViewActionFilterAttribute.cs index f41827965c..54e20f5d56 100644 --- a/src/Umbraco.Web/Mvc/PreRenderViewActionFilterAttribute.cs +++ b/src/Umbraco.Web/Mvc/PreRenderViewActionFilterAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web.Mvc; namespace Umbraco.Web.Mvc @@ -15,13 +15,13 @@ namespace Umbraco.Web.Mvc var result = filterContext.Result as ViewResultBase; if (result == null) - { + { return; } var model = result.Model; if (model == null) - { + { return; } @@ -45,4 +45,4 @@ namespace Umbraco.Web.Mvc if (handler != null) handler(null, e); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/ProfilingView.cs b/src/Umbraco.Web/Mvc/ProfilingView.cs index 59d85ba62c..af3daae75c 100644 --- a/src/Umbraco.Web/Mvc/ProfilingView.cs +++ b/src/Umbraco.Web/Mvc/ProfilingView.cs @@ -1,28 +1,31 @@ -using System.IO; +using System.IO; using System.Web.Mvc; +using Umbraco.Core.Logging; +using Umbraco.Web; +using Umbraco.Web.Composing; namespace Umbraco.Core.Profiling { - public class ProfilingView : IView - { - private readonly IView _inner; - private readonly string _name; - private readonly string _viewPath; + public class ProfilingView : IView + { + private readonly IView _inner; + private readonly string _name; + private readonly string _viewPath; - public ProfilingView(IView inner) - { - _inner = inner; - _name = inner.GetType().Name; - var razorView = inner as RazorView; - _viewPath = razorView != null ? razorView.ViewPath : "Unknown"; - } + public ProfilingView(IView inner) + { + _inner = inner; + _name = inner.GetType().Name; + var razorView = inner as RazorView; + _viewPath = razorView != null ? razorView.ViewPath : "Unknown"; + } - public void Render(ViewContext viewContext, TextWriter writer) - { - using (ProfilerResolver.Current.Profiler.Step(string.Format("{0}.Render: {1}", _name, _viewPath))) - { - _inner.Render(viewContext, writer); - } - } - } + public void Render(ViewContext viewContext, TextWriter writer) + { + using (Current.Profiler.Step(string.Format("{0}.Render: {1}", _name, _viewPath))) + { + _inner.Render(viewContext, writer); + } + } + } } diff --git a/src/Umbraco.Web/Mvc/ProfilingViewEngine.cs b/src/Umbraco.Web/Mvc/ProfilingViewEngine.cs index 7c5b7a2ce3..f7a7411a19 100644 --- a/src/Umbraco.Web/Mvc/ProfilingViewEngine.cs +++ b/src/Umbraco.Web/Mvc/ProfilingViewEngine.cs @@ -1,48 +1,51 @@ -using System.Web.Mvc; +using System.Web.Mvc; +using Umbraco.Core.Logging; +using Umbraco.Web; +using Umbraco.Web.Composing; namespace Umbraco.Core.Profiling { - public class ProfilingViewEngine: IViewEngine - { - internal readonly IViewEngine Inner; - private readonly string _name; + public class ProfilingViewEngine: IViewEngine + { + internal readonly IViewEngine Inner; + private readonly string _name; - public ProfilingViewEngine(IViewEngine inner) - { - Inner = inner; - _name = inner.GetType().Name; - } + public ProfilingViewEngine(IViewEngine inner) + { + Inner = inner; + _name = inner.GetType().Name; + } - public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) - { - using (ProfilerResolver.Current.Profiler.Step(string.Format("{0}.FindPartialView, {1}, {2}", _name, partialViewName, useCache))) - { - return WrapResult(Inner.FindPartialView(controllerContext, partialViewName, useCache)); - } - } + public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) + { + using (Current.Profiler.Step(string.Format("{0}.FindPartialView, {1}, {2}", _name, partialViewName, useCache))) + { + return WrapResult(Inner.FindPartialView(controllerContext, partialViewName, useCache)); + } + } - public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) - { - using (ProfilerResolver.Current.Profiler.Step(string.Format("{0}.FindView, {1}, {2}, {3}", _name, viewName, masterName, useCache))) - { - return WrapResult(Inner.FindView(controllerContext, viewName, masterName, useCache)); - } - } + public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) + { + using (Current.Profiler.Step(string.Format("{0}.FindView, {1}, {2}, {3}", _name, viewName, masterName, useCache))) + { + return WrapResult(Inner.FindView(controllerContext, viewName, masterName, useCache)); + } + } - private static ViewEngineResult WrapResult(ViewEngineResult innerResult) - { - var profiledResult = innerResult.View != null ? - new ViewEngineResult(new ProfilingView(innerResult.View), innerResult.ViewEngine) : - new ViewEngineResult(innerResult.SearchedLocations); - return profiledResult; - } + private static ViewEngineResult WrapResult(ViewEngineResult innerResult) + { + var profiledResult = innerResult.View != null ? + new ViewEngineResult(new ProfilingView(innerResult.View), innerResult.ViewEngine) : + new ViewEngineResult(innerResult.SearchedLocations); + return profiledResult; + } - public void ReleaseView(ControllerContext controllerContext, IView view) - { - using (ProfilerResolver.Current.Profiler.Step(string.Format("{0}.ReleaseView, {1}", _name, view.GetType().Name))) - { - Inner.ReleaseView(controllerContext, view); - } - } - } + public void ReleaseView(ControllerContext controllerContext, IView view) + { + using (Current.Profiler.Step(string.Format("{0}.ReleaseView, {1}", _name, view.GetType().Name))) + { + Inner.ReleaseView(controllerContext, view); + } + } + } } diff --git a/src/Umbraco.Web/Mvc/QueryStringFilterAttribute.cs b/src/Umbraco.Web/Mvc/QueryStringFilterAttribute.cs index db5ea4869d..fe364fb79b 100644 --- a/src/Umbraco.Web/Mvc/QueryStringFilterAttribute.cs +++ b/src/Umbraco.Web/Mvc/QueryStringFilterAttribute.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Mvc /// /// Just like you can POST an arbitrary number of parameters to an Action, you can't GET an arbitrary number /// but this will allow you to do it - /// + /// /// http://stackoverflow.com/questions/488061/passing-multiple-parameters-to-controller-in-asp-net-mvc-also-generating-on-the /// public class QueryStringFilterAttribute : ActionFilterAttribute diff --git a/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs b/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs index 747776c3b0..c73cc12362 100644 --- a/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs @@ -1,73 +1,74 @@ -using System; +using System; using System.Collections.Specialized; using System.Linq; using System.Web; using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Mvc { /// - /// Redirects to an Umbraco page by Id or Entity - /// - public class RedirectToUmbracoPageResult : ActionResult - { - private IPublishedContent _publishedContent; - private readonly int _pageId; + /// Redirects to an Umbraco page by Id or Entity + /// + public class RedirectToUmbracoPageResult : ActionResult + { + private IPublishedContent _publishedContent; + private readonly int _pageId; private NameValueCollection _queryStringValues; - private readonly UmbracoContext _umbracoContext; - private string _url; + private readonly UmbracoContext _umbracoContext; + private string _url; - public string Url - { - get - { - if (!_url.IsNullOrWhiteSpace()) return _url; + public string Url + { + get + { + if (!_url.IsNullOrWhiteSpace()) return _url; - if (PublishedContent == null) - { - throw new InvalidOperationException(string.Format("Cannot redirect, no entity was found for id {0}", _pageId)); - } + if (PublishedContent == null) + { + throw new InvalidOperationException(string.Format("Cannot redirect, no entity was found for id {0}", _pageId)); + } - var result = _umbracoContext.RoutingContext.UrlProvider.GetUrl(PublishedContent.Id); - if (result != "#") - { - _url = result; - return _url; - } + var result = _umbracoContext.UrlProvider.GetUrl(PublishedContent.Id); + if (result != "#") + { + _url = result; + return _url; + } - throw new InvalidOperationException(string.Format("Could not route to entity with id {0}, the NiceUrlProvider could not generate a URL", _pageId)); + throw new InvalidOperationException(string.Format("Could not route to entity with id {0}, the NiceUrlProvider could not generate a URL", _pageId)); - } - } + } + } public int PageId { get { return _pageId; } } - public IPublishedContent PublishedContent - { - get - { - if (_publishedContent != null) return _publishedContent; + public IPublishedContent PublishedContent + { + get + { + if (_publishedContent != null) return _publishedContent; - //need to get the URL for the page - _publishedContent = UmbracoContext.Current.ContentCache.GetById(_pageId); + //need to get the URL for the page + _publishedContent = UmbracoContext.Current.ContentCache.GetById(_pageId); - return _publishedContent; - } - } + return _publishedContent; + } + } - /// - /// Creates a new RedirectToUmbracoResult - /// - /// - public RedirectToUmbracoPageResult(int pageId) - : this(pageId, UmbracoContext.Current) - { - } + /// + /// Creates a new RedirectToUmbracoResult + /// + /// + public RedirectToUmbracoPageResult(int pageId) + : this(pageId, UmbracoContext.Current) + { + } /// /// Creates a new RedirectToUmbracoResult @@ -89,14 +90,14 @@ namespace Umbraco.Web.Mvc { } - /// - /// Creates a new RedirectToUmbracoResult - /// - /// - public RedirectToUmbracoPageResult(IPublishedContent publishedContent) - : this(publishedContent, UmbracoContext.Current) - { - } + /// + /// Creates a new RedirectToUmbracoResult + /// + /// + public RedirectToUmbracoPageResult(IPublishedContent publishedContent) + : this(publishedContent, UmbracoContext.Current) + { + } /// /// Creates a new RedirectToUmbracoResult @@ -118,16 +119,16 @@ namespace Umbraco.Web.Mvc { } - /// - /// Creates a new RedirectToUmbracoResult - /// - /// - /// - public RedirectToUmbracoPageResult(int pageId, UmbracoContext umbracoContext) - { - _pageId = pageId; - _umbracoContext = umbracoContext; - } + /// + /// Creates a new RedirectToUmbracoResult + /// + /// + /// + public RedirectToUmbracoPageResult(int pageId, UmbracoContext umbracoContext) + { + _pageId = pageId; + _umbracoContext = umbracoContext; + } /// /// Creates a new RedirectToUmbracoResult @@ -155,17 +156,17 @@ namespace Umbraco.Web.Mvc _umbracoContext = umbracoContext; } - /// - /// Creates a new RedirectToUmbracoResult - /// - /// - /// - public RedirectToUmbracoPageResult(IPublishedContent publishedContent, UmbracoContext umbracoContext) - { - _publishedContent = publishedContent; - _pageId = publishedContent.Id; - _umbracoContext = umbracoContext; - } + /// + /// Creates a new RedirectToUmbracoResult + /// + /// + /// + public RedirectToUmbracoPageResult(IPublishedContent publishedContent, UmbracoContext umbracoContext) + { + _publishedContent = publishedContent; + _pageId = publishedContent.Id; + _umbracoContext = umbracoContext; + } /// /// Creates a new RedirectToUmbracoResult @@ -195,16 +196,16 @@ namespace Umbraco.Web.Mvc _umbracoContext = umbracoContext; } - public override void ExecuteResult(ControllerContext context) - { - if (context == null) throw new ArgumentNullException("context"); + public override void ExecuteResult(ControllerContext context) + { + if (context == null) throw new ArgumentNullException("context"); - if (context.IsChildAction) - { - throw new InvalidOperationException("Cannot redirect from a Child Action"); - } + if (context.IsChildAction) + { + throw new InvalidOperationException("Cannot redirect from a Child Action"); + } - var destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext); + var destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext); if (_queryStringValues != null && _queryStringValues.Count > 0) { @@ -212,10 +213,10 @@ namespace Umbraco.Web.Mvc _queryStringValues.AllKeys.Select(x => x + "=" + HttpUtility.UrlEncode(_queryStringValues[x]))); } - context.Controller.TempData.Keep(); + context.Controller.TempData.Keep(); - context.HttpContext.Response.Redirect(destinationUrl, endResponse: false); - } + context.HttpContext.Response.Redirect(destinationUrl, endResponse: false); + } private NameValueCollection ParseQueryString(string queryString) { @@ -226,5 +227,5 @@ namespace Umbraco.Web.Mvc return null; } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/Mvc/RedirectToUmbracoUrlResult.cs b/src/Umbraco.Web/Mvc/RedirectToUmbracoUrlResult.cs index 342a22c829..ddc989eb57 100644 --- a/src/Umbraco.Web/Mvc/RedirectToUmbracoUrlResult.cs +++ b/src/Umbraco.Web/Mvc/RedirectToUmbracoUrlResult.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web.Mvc; namespace Umbraco.Web.Mvc @@ -7,8 +7,8 @@ namespace Umbraco.Web.Mvc /// Redirects to the current URL rendering an Umbraco page including it's query strings /// /// - /// This is useful if you need to redirect - /// to the current page but the current page is actually a rewritten URL normally done with something like + /// This is useful if you need to redirect + /// to the current page but the current page is actually a rewritten URL normally done with something like /// Server.Transfer. It is also handy if you want to persist the query strings. /// public class RedirectToUmbracoUrlResult : ActionResult @@ -17,7 +17,7 @@ namespace Umbraco.Web.Mvc /// /// Creates a new RedirectToUmbracoResult - /// + /// /// public RedirectToUmbracoUrlResult(UmbracoContext umbracoContext) { @@ -39,4 +39,4 @@ namespace Umbraco.Web.Mvc context.HttpContext.Response.Redirect(destinationUrl, endResponse: false); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs b/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs index 320e8c6d8a..b3a1904efe 100644 --- a/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs +++ b/src/Umbraco.Web/Mvc/ReflectedFixedRazorViewEngine.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; using System.Web.Mvc; @@ -90,10 +90,10 @@ namespace Umbraco.Web.Mvc { return _wrappedEngine.FindView(controllerContext, viewName, masterName, useCache); } - + public void ReleaseView(ControllerContext controllerContext, IView view) { _wrappedEngine.ReleaseView(controllerContext, view); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/RenderActionInvoker.cs b/src/Umbraco.Web/Mvc/RenderActionInvoker.cs index 55be6591ad..82354e4bad 100644 --- a/src/Umbraco.Web/Mvc/RenderActionInvoker.cs +++ b/src/Umbraco.Web/Mvc/RenderActionInvoker.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -8,35 +8,35 @@ using System.Web.Mvc.Async; namespace Umbraco.Web.Mvc { - /// - /// Ensures that if an action for the Template name is not explicitly defined by a user, that the 'Index' action will execute - /// + /// + /// Ensures that if an action for the Template name is not explicitly defined by a user, that the 'Index' action will execute + /// public class RenderActionInvoker : AsyncControllerActionInvoker - { + { - /// - /// Ensures that if an action for the Template name is not explicitly defined by a user, that the 'Index' action will execute - /// - /// - /// - /// - /// - protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName) - { - var ad = base.FindAction(controllerContext, controllerDescriptor, actionName); + /// + /// Ensures that if an action for the Template name is not explicitly defined by a user, that the 'Index' action will execute + /// + /// + /// + /// + /// + protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName) + { + var ad = base.FindAction(controllerContext, controllerDescriptor, actionName); - //now we need to check if it exists, if not we need to return the Index by default - if (ad == null) - { + //now we need to check if it exists, if not we need to return the Index by default + if (ad == null) + { //check if the controller is an instance of IRenderController and find the index if (controllerContext.Controller is IRenderController) - { - return controllerDescriptor.FindAction(controllerContext, "Index"); + { + return controllerDescriptor.FindAction(controllerContext, "Index"); } - } - return ad; - } - - } -} \ No newline at end of file + } + return ad; + } + + } +} diff --git a/src/Umbraco.Web/Mvc/RenderControllerFactory.cs b/src/Umbraco.Web/Mvc/RenderControllerFactory.cs index ccc80782a5..76e8ca92d3 100644 --- a/src/Umbraco.Web/Mvc/RenderControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/RenderControllerFactory.cs @@ -1,27 +1,27 @@ -using System.Web.Mvc; +using System.Web.Mvc; using System.Web.Routing; namespace Umbraco.Web.Mvc { /// - /// A controller factory for the render pipeline of Umbraco. This controller factory tries to create a controller with the supplied - /// name, and falls back to UmbracoController if none was found. - /// - /// + /// A controller factory for the render pipeline of Umbraco. This controller factory tries to create a controller with the supplied + /// name, and falls back to UmbracoController if none was found. + /// + /// public class RenderControllerFactory : UmbracoControllerFactory - { - - /// - /// Determines whether this instance can handle the specified request. - /// - /// The request. - /// true if this instance can handle the specified request; otherwise, false. - /// - public override bool CanHandle(RequestContext request) - { - var dataToken = request.RouteData.DataTokens["area"]; - return dataToken == null || string.IsNullOrWhiteSpace(dataToken.ToString()); - } + { + + /// + /// Determines whether this instance can handle the specified request. + /// + /// The request. + /// true if this instance can handle the specified request; otherwise, false. + /// + public override bool CanHandle(RequestContext request) + { + var dataToken = request.RouteData.DataTokens["area"]; + return dataToken == null || string.IsNullOrWhiteSpace(dataToken.ToString()); + } /// /// Creates the controller @@ -34,7 +34,7 @@ namespace Umbraco.Web.Mvc /// public override IController CreateController(RequestContext requestContext, string controllerName) { - var instance = base.CreateController(requestContext, controllerName); + var instance = base.CreateController(requestContext, controllerName); var controllerInstance = instance as Controller; if (controllerInstance != null) { @@ -44,6 +44,6 @@ namespace Umbraco.Web.Mvc return instance; } - - } -} \ No newline at end of file + + } +} diff --git a/src/Umbraco.Web/Mvc/RenderIndexActionSelectorAttribute.cs b/src/Umbraco.Web/Mvc/RenderIndexActionSelectorAttribute.cs index 1d1c56db11..fa80107181 100644 --- a/src/Umbraco.Web/Mvc/RenderIndexActionSelectorAttribute.cs +++ b/src/Umbraco.Web/Mvc/RenderIndexActionSelectorAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Linq; using System.Reflection; @@ -8,7 +8,7 @@ namespace Umbraco.Web.Mvc { /// /// A custom ActionMethodSelector which will ensure that the RenderMvcController.Index(RenderModel model) action will be executed - /// if the + /// if the /// internal class RenderIndexActionSelectorAttribute : ActionMethodSelectorAttribute { @@ -34,9 +34,9 @@ namespace Umbraco.Web.Mvc var controllerDesc = ControllerDescCache.GetOrAdd(currType, type => new ReflectedControllerDescriptor(currType)); var actions = controllerDesc.GetCanonicalActions(); - //If there are more than one Index action for this controller, then + //If there are more than one Index action for this controller, then // this base class one should not be matched return actions.Count(x => x.ActionName == "Index") <= 1; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/RenderModelBinder.cs b/src/Umbraco.Web/Mvc/RenderModelBinder.cs deleted file mode 100644 index f1dc2cafd8..0000000000 --- a/src/Umbraco.Web/Mvc/RenderModelBinder.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using System.Globalization; -using System.Text; -using System.Web; -using System.Web.Mvc; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Mvc -{ - /// - /// Allows for Model Binding any IPublishedContent or IRenderModel - /// - public class RenderModelBinder : DefaultModelBinder, IModelBinderProvider - { - public static RenderModelBinder Instance = new RenderModelBinder(); - - /// - /// Binds the model to a value by using the specified controller context and binding context. - /// - /// - /// The bound value. - /// - /// The controller context.The binding context. - public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) - { - object model; - if (controllerContext.RouteData.DataTokens.TryGetValue(Core.Constants.Web.UmbracoDataToken, out model) == false) - return null; - - //This model binder deals with IRenderModel and IPublishedContent by extracting the model from the route's - // datatokens. This data token is set in 2 places: RenderRouteHandler, UmbracoVirtualNodeRouteHandler - // and both always set the model to an instance of `RenderModel`. So if this isn't an instance of IRenderModel then - // we need to let the DefaultModelBinder deal with the logic. - var renderModel = model as IRenderModel; - if (renderModel == null) - { - model = base.BindModel(controllerContext, bindingContext); - if (model == null) return null; - } - - //if for any reason the model is not either IRenderModel or IPublishedContent, then we return since those are the only - // types this binder is dealing with. - if ((model is IRenderModel) == false && (model is IPublishedContent) == false) return null; - - //default culture - var culture = CultureInfo.CurrentCulture; - - var umbracoContext = controllerContext.GetUmbracoContext() - ?? UmbracoContext.Current; - - if (umbracoContext != null && umbracoContext.PublishedContentRequest != null) - { - culture = umbracoContext.PublishedContentRequest.Culture; - } - - return BindModel(model, bindingContext.ModelType, culture); - } - - // source is the model that we have - // modelType is the type of the model that we need to bind to - // culture is the CultureInfo that we have, used by RenderModel - // - // create a model object of the modelType by mapping: - // { RenderModel, RenderModel, IPublishedContent } - // to - // { RenderModel, RenderModel, IPublishedContent } - // - public static object BindModel(object source, Type modelType, CultureInfo culture) - { - // null model, return - if (source == null) return null; - - // if types already match, return - var sourceType = source.GetType(); - if (sourceType.Inherits(modelType)) // includes == - return source; - - // try to grab the content - var sourceContent = source as IPublishedContent; // check if what we have is an IPublishedContent - if (sourceContent == null && sourceType.Implements()) - { - // else check if it's an IRenderModel, and get the content - sourceContent = ((IRenderModel)source).Content; - } - if (sourceContent == null) - { - // else check if we can convert it to a content - var attempt1 = source.TryConvertTo(); - if (attempt1.Success) sourceContent = attempt1.Result; - } - - // if we have a content - if (sourceContent != null) - { - // try to grab the culture - // using supplied culture by default - var sourceRenderModel = source as RenderModel; - if (sourceRenderModel != null) - culture = sourceRenderModel.CurrentCulture; - - // if model is IPublishedContent, check content type and return - if (modelType.Implements()) - { - if ((sourceContent.GetType().Inherits(modelType)) == false) - ThrowModelBindingException(true, false, sourceContent.GetType(), modelType); - return sourceContent; - } - - // if model is RenderModel, create and return - if (modelType == typeof(RenderModel)) - { - return new RenderModel(sourceContent, culture); - } - - // if model is RenderModel, check content type, then create and return - if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(RenderModel<>)) - { - var targetContentType = modelType.GetGenericArguments()[0]; - if ((sourceContent.GetType().Inherits(targetContentType)) == false) - ThrowModelBindingException(true, true, sourceContent.GetType(), targetContentType); - return Activator.CreateInstance(modelType, sourceContent, culture); - } - } - - // last chance : try to convert - var attempt2 = source.TryConvertTo(modelType); - if (attempt2.Success) return attempt2.Result; - - // fail - ThrowModelBindingException(false, false, sourceType, modelType); - return null; - } - - public class ModelBindingArgs : EventArgs - { - public ModelBindingArgs(Type sourceType, Type modelType, StringBuilder message) - { - SourceType = sourceType; - ModelType = modelType; - Message = message; - } - - public bool SourceIsContent; - public bool ViewModelIsContent; - public Type SourceType { get; set; } - public Type ModelType { get; set; } - public StringBuilder Message { get; private set; } - public bool Restart { get; set; } - } - - public static event EventHandler ModelBindingException; - - private static void ThrowModelBindingException(bool sourceContent, bool modelContent, Type sourceType, Type modelType) - { - var msg = new StringBuilder(); - - // prepare message - msg.Append("Cannot bind source"); - if (sourceContent) msg.Append(" content"); - msg.Append(" type "); - msg.Append(sourceType.FullName); - msg.Append(" to model"); - if (modelContent) msg.Append(" content"); - msg.Append(" type "); - msg.Append(modelType.FullName); - msg.Append("."); - - // raise event, to give model factories a chance at reporting - // the error with more details, and optionally request that - // the application restarts. - - var args = new ModelBindingArgs(sourceType, modelType, msg); - if (ModelBindingException != null) - ModelBindingException(Instance, args); - - if (args.Restart) - { - msg.Append(" The application is restarting now."); - - var context = HttpContext.Current; - if (context == null) - AppDomain.Unload(AppDomain.CurrentDomain); - else - ApplicationContext.Current.RestartApplicationPool(new HttpContextWrapper(context)); - } - - throw new ModelBindingException(msg.ToString()); - } - - public IModelBinder GetBinder(Type modelType) - { - // can bind to RenderModel (exact type match) - // You might be tempted to change this to IRenderModel but do not change this: http://issues.umbraco.org/issue/U4-8216 - if (modelType == typeof(RenderModel)) return this; - - // can bind to RenderModel (exact generic type match) - if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(RenderModel<>)) return this; - - // can bind to TContent where TContent : IPublishedContent (any IPublishedContent implementation) - if (typeof(IPublishedContent).IsAssignableFrom(modelType)) return this; - return null; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderMvcController.cs b/src/Umbraco.Web/Mvc/RenderMvcController.cs index f3886d8ce1..e7f336dcde 100644 --- a/src/Umbraco.Web/Mvc/RenderMvcController.cs +++ b/src/Umbraco.Web/Mvc/RenderMvcController.cs @@ -1,98 +1,76 @@ -using System; +using System; using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; -using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; using Umbraco.Web.Models; using Umbraco.Web.Routing; namespace Umbraco.Web.Mvc { - /// - /// The default controller to render front-end requests + /// Represents the default front-end rendering controller. /// - [PreRenderViewActionFilter] + [PreRenderViewActionFilter] public class RenderMvcController : UmbracoController, IRenderMvcController { + private PublishedRequest _publishedRequest; public RenderMvcController() - : base() { ActionInvoker = new RenderActionInvoker(); } - public RenderMvcController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) - : base(umbracoContext, umbracoHelper) - { - } - - public RenderMvcController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - - } - - private PublishedContentRequest _publishedContentRequest; + /// + /// Gets the Umbraco context. + /// + public override UmbracoContext UmbracoContext => PublishedRequest.UmbracoContext; /// - /// Returns the current UmbracoContext + /// Gets the current content item. /// - public override UmbracoContext UmbracoContext - { - get { return PublishedContentRequest.RoutingContext.UmbracoContext; } - } + protected IPublishedContent CurrentPage => PublishedRequest.PublishedContent; /// - /// Returns the Current published content item for rendering the content + /// Gets the current published content request. /// - protected IPublishedContent CurrentPage - { - get { return PublishedContentRequest.PublishedContent; } - } - - /// - /// Returns the current PublishedContentRequest - /// - protected internal virtual PublishedContentRequest PublishedContentRequest + protected internal virtual PublishedRequest PublishedRequest { get { - if (_publishedContentRequest != null) - return _publishedContentRequest; + if (_publishedRequest != null) + return _publishedRequest; if (RouteData.DataTokens.ContainsKey(Core.Constants.Web.PublishedDocumentRequestDataToken) == false) { throw new InvalidOperationException("DataTokens must contain an 'umbraco-doc-request' key with a PublishedContentRequest object"); } - _publishedContentRequest = (PublishedContentRequest)RouteData.DataTokens[Core.Constants.Web.PublishedDocumentRequestDataToken]; - return _publishedContentRequest; + _publishedRequest = (PublishedRequest)RouteData.DataTokens[Core.Constants.Web.PublishedDocumentRequestDataToken]; + return _publishedRequest; } } /// - /// Checks to make sure the physical view file exists on disk + /// Ensures that a physical view file exists on disk. /// - /// - /// + /// The view name. protected bool EnsurePhsyicalViewExists(string template) { var result = ViewEngines.Engines.FindView(ControllerContext, template, null); - if (result.View == null) - { - LogHelper.Warn("No physical template file was found for template " + template); - return false; - } - return true; + if (result.View != null) return true; + + Logger.Warn("No physical template file was found for template " + template); + return false; } /// - /// Returns an ActionResult based on the template name found in the route values and the given model. + /// Gets an action result based on the template name found in the route values and a model. /// - /// - /// - /// - /// - /// If the template found in the route values doesn't physically exist, then an empty ContentResult will be returned. - /// + /// The type of the model. + /// The model. + /// The action result. + /// If the template found in the route values doesn't physically exist, then an empty ContentResult will be returned. protected ActionResult CurrentTemplate(T model) { var template = ControllerContext.RouteData.Values["action"].ToString(); @@ -102,14 +80,14 @@ namespace Umbraco.Web.Mvc } /// - /// The default action to render the front-end view + /// The default action to render the front-end view. /// /// /// [RenderIndexActionSelector] - public virtual ActionResult Index(RenderModel model) + public virtual ActionResult Index(ContentModel model) { return CurrentTemplate(model); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index d7ce36b4a4..3b0b6b35e2 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Linq; -using System.Text; using System.Web; using System.Web.Compilation; using System.Web.Mvc; @@ -8,12 +7,13 @@ using System.Web.Routing; using System.Web.SessionState; using Umbraco.Core; using Umbraco.Core.Logging; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; +using Umbraco.Core.Composing; using Umbraco.Web.Models; using Umbraco.Web.Routing; -using umbraco.cms.businesslogic.template; using System.Collections.Generic; +using Current = Umbraco.Web.Composing.Current; +using LightInject; +using Umbraco.Web.Features; namespace Umbraco.Web.Mvc { @@ -27,36 +27,31 @@ namespace Umbraco.Web.Mvc internal const string Area = "ar"; } - public RenderRouteHandler(IControllerFactory controllerFactory) - { - if (controllerFactory == null) throw new ArgumentNullException("controllerFactory"); - _controllerFactory = controllerFactory; - } - - /// - /// Contructor generally used for unit testing - /// - /// - /// - internal RenderRouteHandler(IControllerFactory controllerFactory, UmbracoContext umbracoContext) - { - if (controllerFactory == null) throw new ArgumentNullException("controllerFactory"); - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - _controllerFactory = controllerFactory; - _umbracoContext = umbracoContext; - } - private readonly IControllerFactory _controllerFactory; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly UmbracoContext _umbracoContext; - /// - /// Returns the current UmbracoContext - /// - public UmbracoContext UmbracoContext + // fixme - that one could / should accept a PublishedRouter (engine) to work on the PublishedRequest (published content request) + public RenderRouteHandler(IUmbracoContextAccessor umbracoContextAccessor, IControllerFactory controllerFactory) { - get { return _umbracoContext ?? UmbracoContext.Current; } + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _controllerFactory = controllerFactory ?? throw new ArgumentNullException(nameof(controllerFactory)); } + // fixme - what about that one? + // called by TemplateRenderer - which is created in + // library - could get an engine without problem it's all ugly anyways + // UmbracoComponentRenderer - ?? that one is not so obvious + public RenderRouteHandler(UmbracoContext umbracoContext, IControllerFactory controllerFactory) + { + _umbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); + _controllerFactory = controllerFactory ?? throw new ArgumentNullException(nameof(controllerFactory)); + } + + private UmbracoContext UmbracoContext => _umbracoContext ?? _umbracoContextAccessor.UmbracoContext; + + private UmbracoFeatures Features => Current.Container.GetInstance(); // fixme inject + #region IRouteHandler Members /// @@ -69,20 +64,20 @@ namespace Umbraco.Web.Mvc { if (UmbracoContext == null) { - throw new NullReferenceException("There is not current UmbracoContext, it must be initialized before the RenderRouteHandler executes"); + throw new NullReferenceException("There is no current UmbracoContext, it must be initialized before the RenderRouteHandler executes"); } - var docRequest = UmbracoContext.PublishedContentRequest; - if (docRequest == null) + var request = UmbracoContext.PublishedRequest; + if (request == null) { - throw new NullReferenceException("There is not current PublishedContentRequest, it must be initialized before the RenderRouteHandler executes"); + throw new NullReferenceException("There is no current PublishedContentRequest, it must be initialized before the RenderRouteHandler executes"); } SetupRouteDataForRequest( - new RenderModel(docRequest.PublishedContent, docRequest.Culture), + new ContentModel(request.PublishedContent), requestContext, - docRequest); + request); - return GetHandlerForRoute(requestContext, docRequest); + return GetHandlerForRoute(requestContext, request); } @@ -91,23 +86,23 @@ namespace Umbraco.Web.Mvc /// /// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views /// - /// + /// /// - /// - internal void SetupRouteDataForRequest(RenderModel renderModel, RequestContext requestContext, PublishedContentRequest docRequest) + /// + internal void SetupRouteDataForRequest(ContentModel contentModel, RequestContext requestContext, PublishedRequest frequest) { //put essential data into the data tokens, the 'umbraco' key is required to be there for the view engine - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, renderModel); //required for the RenderModelBinder and view engine - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, docRequest); //required for RenderMvcController - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, UmbracoContext); //required for UmbracoTemplatePage + requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, contentModel); //required for the RenderModelBinder and view engine + requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, frequest); //required for RenderMvcController + requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, UmbracoContext); //required for UmbracoViewPage } - private void UpdateRouteDataForRequest(RenderModel renderModel, RequestContext requestContext) + private void UpdateRouteDataForRequest(ContentModel contentModel, RequestContext requestContext) { - if (renderModel == null) throw new ArgumentNullException("renderModel"); - if (requestContext == null) throw new ArgumentNullException("requestContext"); + if (contentModel == null) throw new ArgumentNullException(nameof(contentModel)); + if (requestContext == null) throw new ArgumentNullException(nameof(requestContext)); - requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoDataToken] = renderModel; + requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoDataToken] = contentModel; // the rest should not change -- it's only the published content that has changed } @@ -119,7 +114,7 @@ namespace Umbraco.Web.Mvc /// internal static PostedDataProxyInfo GetFormInfo(RequestContext requestContext) { - if (requestContext == null) throw new ArgumentNullException("requestContext"); + if (requestContext == null) throw new ArgumentNullException(nameof(requestContext)); //if it is a POST/GET then a value must be in the request if (requestContext.HttpContext.Request.QueryString["ufprt"].IsNullOrWhiteSpace() @@ -153,7 +148,7 @@ namespace Umbraco.Web.Mvc } catch (FormatException) { - LogHelper.Warn("A value was detected in the ufprt parameter but Umbraco could not decrypt the string"); + Current.Logger.Warn("A value was detected in the ufprt parameter but Umbraco could not decrypt the string"); return null; } @@ -177,10 +172,10 @@ namespace Umbraco.Web.Mvc if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Area)) return null; - foreach (var item in decodedParts.Where(x => new[] { - ReservedAdditionalKeys.Controller, - ReservedAdditionalKeys.Action, - ReservedAdditionalKeys.Area }.Contains(x.Key) == false)) + foreach (var item in decodedParts.Where(x => new[] { + ReservedAdditionalKeys.Controller, + ReservedAdditionalKeys.Action, + ReservedAdditionalKeys.Area }.Contains(x.Key) == false)) { // Populate route with additional values which aren't reserved values so they eventually to action parameters requestContext.RouteData.Values[item.Key] = item.Value; @@ -203,8 +198,8 @@ namespace Umbraco.Web.Mvc /// internal static IHttpHandler HandlePostedValues(RequestContext requestContext, PostedDataProxyInfo postedInfo) { - if (requestContext == null) throw new ArgumentNullException("requestContext"); - if (postedInfo == null) throw new ArgumentNullException("postedInfo"); + if (requestContext == null) throw new ArgumentNullException(nameof(requestContext)); + if (postedInfo == null) throw new ArgumentNullException(nameof(postedInfo)); //set the standard route values/tokens requestContext.RouteData.Values["controller"] = postedInfo.ControllerName; @@ -226,7 +221,7 @@ namespace Umbraco.Web.Mvc x.DataTokens.ContainsKey("area") == false).ToList(); // If more than one route is found, find one with a matching action - if (surfaceRoutes.Count() > 1) + if (surfaceRoutes.Count > 1) { surfaceRoute = surfaceRoutes.FirstOrDefault(x => x.Defaults["action"] != null && @@ -274,38 +269,38 @@ namespace Umbraco.Web.Mvc /// Returns a RouteDefinition object based on the current renderModel /// /// - /// + /// /// - internal virtual RouteDefinition GetUmbracoRouteDefinition(RequestContext requestContext, PublishedContentRequest publishedContentRequest) + internal virtual RouteDefinition GetUmbracoRouteDefinition(RequestContext requestContext, PublishedRequest request) { - if (requestContext == null) throw new ArgumentNullException("requestContext"); - if (publishedContentRequest == null) throw new ArgumentNullException("publishedContentRequest"); + if (requestContext == null) throw new ArgumentNullException(nameof(requestContext)); + if (request == null) throw new ArgumentNullException(nameof(request)); - var defaultControllerType = DefaultRenderMvcControllerResolver.Current.GetDefaultControllerType(); + var defaultControllerType = Current.DefaultRenderMvcControllerType; var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType); //creates the default route definition which maps to the 'UmbracoController' controller var def = new RouteDefinition { ControllerName = defaultControllerName, ControllerType = defaultControllerType, - PublishedContentRequest = publishedContentRequest, + PublishedRequest = request, ActionName = ((Route)requestContext.RouteData.Route).Defaults["action"].ToString(), HasHijackedRoute = false }; //check that a template is defined), if it doesn't and there is a hijacked route it will just route // to the index Action - if (publishedContentRequest.HasTemplate) + if (request.HasTemplate) { //the template Alias should always be already saved with a safe name. //if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed // with the action name attribute. - var templateName = publishedContentRequest.TemplateAlias.Split('.')[0].ToSafeAlias(); + var templateName = request.TemplateAlias.Split('.')[0].ToSafeAlias(); def.ActionName = templateName; } //check if there's a custom controller assigned, base on the document type alias. - var controllerType = _controllerFactory.GetControllerTypeInternal(requestContext, publishedContentRequest.PublishedContent.DocumentTypeAlias); + var controllerType = _controllerFactory.GetControllerTypeInternal(requestContext, request.PublishedContent.DocumentTypeAlias); //check if that controller exists if (controllerType != null) @@ -324,12 +319,8 @@ namespace Umbraco.Web.Mvc } else { - LogHelper.Warn( - "The current Document Type {0} matches a locally declared controller of type {1}. Custom Controllers for Umbraco routing must implement '{2}' and inherit from '{3}'.", - () => publishedContentRequest.PublishedContent.DocumentTypeAlias, - () => controllerType.FullName, - () => typeof(IRenderController).FullName, - () => typeof(ControllerBase).FullName); + Current.Logger.Warn(() => + $"The current Document Type {request.PublishedContent.DocumentTypeAlias} matches a locally declared controller of type {controllerType.FullName}. Custom Controllers for Umbraco routing must implement '{typeof(IRenderController).FullName}' and inherit from '{typeof(ControllerBase).FullName}'."); //we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults // that have already been set above. @@ -337,33 +328,33 @@ namespace Umbraco.Web.Mvc } //store the route definition - requestContext.RouteData.DataTokens[Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; + requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; return def; } - internal IHttpHandler GetHandlerOnMissingTemplate(PublishedContentRequest pcr) + internal IHttpHandler GetHandlerOnMissingTemplate(PublishedRequest request) { - if (pcr == null) throw new ArgumentNullException("pcr"); + if (request == null) throw new ArgumentNullException(nameof(request)); // missing template, so we're in a 404 here // so the content, if any, is a custom 404 page of some sort - if (!pcr.HasPublishedContent) + if (request.HasPublishedContent == false) // means the builder could not find a proper document to handle 404 return new PublishedContentNotFoundHandler(); - if (!pcr.HasTemplate) + if (request.HasTemplate == false) // means the engine could find a proper document, but the document has no template // at that point there isn't much we can do and there is no point returning // to Mvc since Mvc can't do much return new PublishedContentNotFoundHandler("In addition, no template exists to render the custom 404."); // so we have a template, so we should have a rendering engine - if (pcr.RenderingEngine == RenderingEngine.WebForms) // back to webforms ? + if (request.RenderingEngine == RenderingEngine.WebForms) // back to webforms ? return GetWebFormsHandler(); - - if (pcr.RenderingEngine != RenderingEngine.Mvc) // else ? + + if (request.RenderingEngine != RenderingEngine.Mvc) // else ? return new PublishedContentNotFoundHandler("In addition, no rendering engine exists to render the custom 404."); return null; @@ -373,13 +364,13 @@ namespace Umbraco.Web.Mvc /// this will determine the controller and set the values in the route data /// /// - /// - internal IHttpHandler GetHandlerForRoute(RequestContext requestContext, PublishedContentRequest publishedContentRequest) + /// + internal IHttpHandler GetHandlerForRoute(RequestContext requestContext, PublishedRequest request) { - if (requestContext == null) throw new ArgumentNullException("requestContext"); - if (publishedContentRequest == null) throw new ArgumentNullException("publishedContentRequest"); + if (requestContext == null) throw new ArgumentNullException(nameof(requestContext)); + if (request == null) throw new ArgumentNullException(nameof(request)); - var routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); + var routeDef = GetUmbracoRouteDefinition(requestContext, request); //Need to check for a special case if there is form data being posted back to an Umbraco URL var postedInfo = GetFormInfo(requestContext); @@ -389,28 +380,33 @@ namespace Umbraco.Web.Mvc } //Now we can check if we are supposed to render WebForms when the route has not been hijacked - if (publishedContentRequest.RenderingEngine == RenderingEngine.WebForms - && publishedContentRequest.HasTemplate + if (request.RenderingEngine == RenderingEngine.WebForms + && request.HasTemplate && routeDef.HasHijackedRoute == false) { return GetWebFormsHandler(); } - //here we need to check if there is no hijacked route and no template assigned, if this is the case - //we want to return a blank page, but we'll leave that up to the NoTemplateHandler. - if (!publishedContentRequest.HasTemplate && !routeDef.HasHijackedRoute) + //Here we need to check if there is no hijacked route and no template assigned, + //if this is the case we want to return a blank page, but we'll leave that up to the NoTemplateHandler. + //We also check if templates have been disabled since if they are then we're allowed to render even though there's no template, + //for example for json rendering in headless. + if ((request.HasTemplate == false && Features.Disabled.DisableTemplates == false) + && routeDef.HasHijackedRoute == false) { - publishedContentRequest.UpdateOnMissingTemplate(); // will go 404 + // fixme - better find a way to inject that engine? or at least Current.Engine of some sort! + var engine = Core.Composing.Current.Container.GetInstance(); + request.UpdateOnMissingTemplate(); // request will go 404 // HandleHttpResponseStatus returns a value indicating that the request should // not be processed any further, eg because it has been redirect. then, exit. - if (UmbracoModule.HandleHttpResponseStatus(requestContext.HttpContext, publishedContentRequest)) + if (UmbracoModule.HandleHttpResponseStatus(requestContext.HttpContext, request, Current.Logger)) return null; - var handler = GetHandlerOnMissingTemplate(publishedContentRequest); + var handler = GetHandlerOnMissingTemplate(request); // if it's not null it can be either the PublishedContentNotFoundHandler (no document was - // found to handle 404, or document with no template was found) or the WebForms handler + // found to handle 404, or document with no template was found) or the WebForms handler // (a document was found and its template is WebForms) // if it's null it means that a document was found and its template is Mvc @@ -422,19 +418,17 @@ namespace Umbraco.Web.Mvc // else we are running Mvc // update the route data - because the PublishedContent has changed UpdateRouteDataForRequest( - new RenderModel(publishedContentRequest.PublishedContent, publishedContentRequest.Culture), + new ContentModel(request.PublishedContent), requestContext); // update the route definition - routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); + routeDef = GetUmbracoRouteDefinition(requestContext, request); } //no post values, just route to the controller/action requried (local) requestContext.RouteData.Values["controller"] = routeDef.ControllerName; - if (!string.IsNullOrWhiteSpace(routeDef.ActionName)) - { + if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false) requestContext.RouteData.Values["action"] = routeDef.ActionName; - } // Set the session state requirements requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext, routeDef.ControllerName)); @@ -459,7 +453,5 @@ namespace Umbraco.Web.Mvc { return _controllerFactory.GetControllerSessionBehavior(requestContext, controllerName); } - - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/RenderViewEngine.cs b/src/Umbraco.Web/Mvc/RenderViewEngine.cs index fdf61059e0..6725123a8f 100644 --- a/src/Umbraco.Web/Mvc/RenderViewEngine.cs +++ b/src/Umbraco.Web/Mvc/RenderViewEngine.cs @@ -1,115 +1,100 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web.Mvc; -using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Web.Models; namespace Umbraco.Web.Mvc { - /// - /// A view engine to look into the template location specified in the config for the front-end/Rendering part of the cms, - /// this includes paths to render partial macros and media item templates. - /// + /// + /// A view engine to look into the template location specified in the config for the front-end/Rendering part of the cms, + /// this includes paths to render partial macros and media item templates. + /// public class RenderViewEngine : ReflectedFixedRazorViewEngine - { + { + private readonly IEnumerable _supplementedViewLocations = new[] { "/{0}.cshtml" }; + //NOTE: we will make the main view location the last to be searched since if it is the first to be searched and there is both a view and a partial + // view in both locations and the main view is rendering a partial view with the same name, we will get a stack overflow exception. + // http://issues.umbraco.org/issue/U4-1287, http://issues.umbraco.org/issue/U4-1215 + private readonly IEnumerable _supplementedPartialViewLocations = new[] { "/Partials/{0}.cshtml", "/MacroPartials/{0}.cshtml", "/{0}.cshtml" }; - private readonly IEnumerable _supplementedViewLocations = new[] { "/{0}.cshtml" }; - //NOTE: we will make the main view location the last to be searched since if it is the first to be searched and there is both a view and a partial - // view in both locations and the main view is rendering a partial view with the same name, we will get a stack overflow exception. - // http://issues.umbraco.org/issue/U4-1287, http://issues.umbraco.org/issue/U4-1215 - private readonly IEnumerable _supplementedPartialViewLocations = new[] { "/Partials/{0}.cshtml", "/MacroPartials/{0}.cshtml", "/{0}.cshtml" }; + /// + /// Constructor + /// + public RenderViewEngine() + { + const string templateFolder = Constants.ViewLocation; - /// - /// Constructor - /// - public RenderViewEngine() - { - const string templateFolder = Constants.ViewLocation; + // the Render view engine doesn't support Area's so make those blank + ViewLocationFormats = _supplementedViewLocations.Select(x => templateFolder + x).ToArray(); + PartialViewLocationFormats = _supplementedPartialViewLocations.Select(x => templateFolder + x).ToArray(); - var replaceWithUmbracoFolder = _supplementedViewLocations.ForEach(location => templateFolder + location); - var replacePartialWithUmbracoFolder = _supplementedPartialViewLocations.ForEach(location => templateFolder + location); + AreaPartialViewLocationFormats = Array.Empty(); + AreaViewLocationFormats = Array.Empty(); - //The Render view engine doesn't support Area's so make those blank - ViewLocationFormats = replaceWithUmbracoFolder.ToArray(); - PartialViewLocationFormats = replacePartialWithUmbracoFolder.ToArray(); + EnsureFoldersAndFiles(); + } - AreaPartialViewLocationFormats = new string[] { }; - AreaViewLocationFormats = new string[] { }; + /// + /// Ensures that the correct web.config for razor exists in the /Views folder, the partials folder exist and the ViewStartPage exists. + /// + private static void EnsureFoldersAndFiles() + { + var viewFolder = IOHelper.MapPath(Constants.ViewLocation); - EnsureFoldersAndFiles(); - } + // ensure the web.config file is in the ~/Views folder + Directory.CreateDirectory(viewFolder); + var webConfigPath = Path.Combine(viewFolder, "web.config"); + if (File.Exists(webConfigPath) == false) + { + using (var writer = File.CreateText(webConfigPath)) + { + writer.Write(Strings.WebConfigTemplate); + } + } - /// - /// Ensures that the correct web.config for razor exists in the /Views folder, the partials folder exist and the ViewStartPage exists. - /// - private void EnsureFoldersAndFiles() - { - var viewFolder = IOHelper.MapPath(Constants.ViewLocation); - //ensure the web.config file is in the ~/Views folder - Directory.CreateDirectory(viewFolder); - if (!File.Exists(Path.Combine(viewFolder, "web.config"))) - { - using (var writer = File.CreateText(Path.Combine(viewFolder, "web.config"))) - { - writer.Write(Strings.WebConfigTemplate); - } - } - //auto create the partials folder - var partialsFolder = Path.Combine(viewFolder, "Partials"); - Directory.CreateDirectory(partialsFolder); + //auto create the partials folder + var partialsFolder = Path.Combine(viewFolder, "Partials"); + Directory.CreateDirectory(partialsFolder); - //We could create a _ViewStart page if it isn't there as well, but we may not allow editing of this page in the back office. - } + // We could create a _ViewStart page if it isn't there as well, but we may not allow editing of this page in the back office. + } - public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) - { - if (!ShouldFindView(controllerContext, false)) - { - return new ViewEngineResult(new string[] { }); - } + public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) + { + return ShouldFindView(controllerContext, false) + ? base.FindView(controllerContext, viewName, masterName, useCache) + : new ViewEngineResult(new string[] { }); + } - return base.FindView(controllerContext, viewName, masterName, useCache); - } + public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) + { + return ShouldFindView(controllerContext, true) + ? base.FindPartialView(controllerContext, partialViewName, useCache) + : new ViewEngineResult(new string[] { }); + } - public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) - { - if (!ShouldFindView(controllerContext, true)) - { - return new ViewEngineResult(new string[] { }); - } - - return base.FindPartialView(controllerContext, partialViewName, useCache); - } - - /// - /// Determines if the view should be found, this is used for view lookup performance and also to ensure - /// less overlap with other user's view engines. This will return true if the Umbraco back office is rendering - /// and its a partial view or if the umbraco front-end is rendering but nothing else. - /// - /// - /// - /// - private bool ShouldFindView(ControllerContext controllerContext, bool isPartial) + /// + /// Determines if the view should be found, this is used for view lookup performance and also to ensure + /// less overlap with other user's view engines. This will return true if the Umbraco back office is rendering + /// and its a partial view or if the umbraco front-end is rendering but nothing else. + /// + /// + /// + /// + private static bool ShouldFindView(ControllerContext controllerContext, bool isPartial) { var umbracoToken = controllerContext.GetDataTokenInViewContextHierarchy(Core.Constants.Web.UmbracoDataToken); - //first check if we're rendering a partial view for the back office, or surface controller, etc... - //anything that is not IUmbracoRenderModel as this should only pertain to Umbraco views. - if (isPartial && ((umbracoToken is RenderModel) == false)) - { + // first check if we're rendering a partial view for the back office, or surface controller, etc... + // anything that is not IUmbracoRenderModel as this should only pertain to Umbraco views. + if (isPartial && !(umbracoToken is ContentModel)) return true; - } - //only find views if we're rendering the umbraco front end - if (umbracoToken is RenderModel) - { - return true; - } - - return false; + // only find views if we're rendering the umbraco front end + return umbracoToken is ContentModel; } - - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/Mvc/RouteDefinition.cs b/src/Umbraco.Web/Mvc/RouteDefinition.cs index fa9443c19d..6d0a5e102b 100644 --- a/src/Umbraco.Web/Mvc/RouteDefinition.cs +++ b/src/Umbraco.Web/Mvc/RouteDefinition.cs @@ -1,31 +1,31 @@ -using System; +using System; using System.Web.Mvc; using Umbraco.Web.Routing; namespace Umbraco.Web.Mvc { - /// - /// Represents the data required to route to a specific controller/action during an Umbraco request - /// - public class RouteDefinition - { - public string ControllerName { get; set; } - public string ActionName { get; set; } - + /// + /// Represents the data required to route to a specific controller/action during an Umbraco request + /// + public class RouteDefinition + { + public string ControllerName { get; set; } + public string ActionName { get; set; } + /// /// The Controller type found for routing to /// public Type ControllerType { get; set; } - /// - /// The current RenderModel found for the request - /// - public PublishedContentRequest PublishedContentRequest { get; set; } + /// + /// The current RenderModel found for the request + /// + public PublishedRequest PublishedRequest { get; set; } - /// - /// Gets/sets whether the current request has a hijacked route/user controller routed for it - /// - public bool HasHijackedRoute { get; set; } - } -} \ No newline at end of file + /// + /// Gets/sets whether the current request has a hijacked route/user controller routed for it + /// + public bool HasHijackedRoute { get; set; } + } +} diff --git a/src/Umbraco.Web/Mvc/RouteValueDictionaryExtensions.cs b/src/Umbraco.Web/Mvc/RouteValueDictionaryExtensions.cs index ceb4012a66..decf039a51 100644 --- a/src/Umbraco.Web/Mvc/RouteValueDictionaryExtensions.cs +++ b/src/Umbraco.Web/Mvc/RouteValueDictionaryExtensions.cs @@ -1,41 +1,41 @@ -using System; +using System; using System.Linq; using System.Web.Mvc; using System.Web.Routing; namespace Umbraco.Web.Mvc { - internal static class RouteValueDictionaryExtensions - { + internal static class RouteValueDictionaryExtensions + { - /// - /// Converts a route value dictionary to a form collection - /// - /// - /// - public static FormCollection ToFormCollection(this RouteValueDictionary items) - { - var formCollection = new FormCollection(); - foreach (var i in items) - { - formCollection.Add(i.Key, i.Value != null ? i.Value.ToString() : null); - } - return formCollection; - } + /// + /// Converts a route value dictionary to a form collection + /// + /// + /// + public static FormCollection ToFormCollection(this RouteValueDictionary items) + { + var formCollection = new FormCollection(); + foreach (var i in items) + { + formCollection.Add(i.Key, i.Value != null ? i.Value.ToString() : null); + } + return formCollection; + } - /// - /// Returns the value of a mandatory item in the route items - /// - /// - /// - /// - public static object GetRequiredObject(this RouteValueDictionary items, string key) - { - if (key == null) throw new ArgumentNullException("key"); - if (items.Keys.Contains(key) == false) - throw new ArgumentNullException("The " + key + " parameter was not found but is required"); - return items[key]; - } + /// + /// Returns the value of a mandatory item in the route items + /// + /// + /// + /// + public static object GetRequiredObject(this RouteValueDictionary items, string key) + { + if (key == null) throw new ArgumentNullException("key"); + if (items.Keys.Contains(key) == false) + throw new ArgumentNullException("The " + key + " parameter was not found but is required"); + return items[key]; + } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/Mvc/Strings.Designer.cs b/src/Umbraco.Web/Mvc/Strings.Designer.cs index 243a7f7dd9..1c2e2bebbb 100644 --- a/src/Umbraco.Web/Mvc/Strings.Designer.cs +++ b/src/Umbraco.Web/Mvc/Strings.Designer.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Mvc { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs index 420573d745..043841f279 100644 --- a/src/Umbraco.Web/Mvc/SurfaceController.cs +++ b/src/Umbraco.Web/Mvc/SurfaceController.cs @@ -1,43 +1,17 @@ -using System; -using System.Web.Mvc; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; +using System; using Umbraco.Core; -using Umbraco.Web.Security; using System.Collections.Specialized; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Mvc { - /// - /// The base controller that all Presentation Add-in controllers should inherit from + /// Provides a base class for front-end add-in controllers. /// [MergeModelStateToChildAction] [MergeParentContextViewData] public abstract class SurfaceController : PluginController { - /// - /// Default constructor - /// - /// - protected SurfaceController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - - protected SurfaceController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) - : base(umbracoContext, umbracoHelper) - { - } - - /// - /// Empty constructor, uses Singleton to resolve the UmbracoContext - /// - protected SurfaceController() - : base(UmbracoContext.Current) - { - } - /// /// Redirects to the Umbraco page with the given id /// @@ -124,19 +98,20 @@ namespace Umbraco.Web.Mvc /// /// Redirects to the currently rendered Umbraco page and passes provided querystring /// - /// + /// /// protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage(string queryString) { return new RedirectToUmbracoPageResult(CurrentPage, queryString, UmbracoContext); } + /// /// Redirects to the currently rendered Umbraco URL /// /// /// - /// this is useful if you need to redirect - /// to the current page but the current page is actually a rewritten URL normally done with something like + /// this is useful if you need to redirect + /// to the current page but the current page is actually a rewritten URL normally done with something like /// Server.Transfer. /// protected RedirectToUmbracoUrlResult RedirectToCurrentUmbracoUrl() @@ -150,7 +125,7 @@ namespace Umbraco.Web.Mvc /// protected UmbracoPageResult CurrentUmbracoPage() { - return new UmbracoPageResult(ApplicationContext.ProfilingLogger); + return new UmbracoPageResult(ProfilingLogger); } /// @@ -161,13 +136,11 @@ namespace Umbraco.Web.Mvc get { var routeDefAttempt = TryGetRouteDefinitionFromAncestorViewContexts(); - if (!routeDefAttempt.Success) - { + if (routeDefAttempt.Success == false) throw routeDefAttempt.Exception; - } var routeDef = routeDefAttempt.Result; - return routeDef.PublishedContentRequest.PublishedContent; + return routeDef.PublishedRequest.PublishedContent; } } @@ -181,24 +154,16 @@ namespace Umbraco.Web.Mvc /// private Attempt TryGetRouteDefinitionFromAncestorViewContexts() { - ControllerContext currentContext = ControllerContext; + var currentContext = ControllerContext; while (currentContext != null) { var currentRouteData = currentContext.RouteData; if (currentRouteData.DataTokens.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken)) - { return Attempt.Succeed((RouteDefinition)currentRouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken]); - } - if (currentContext.IsChildAction) - { - //assign current context to parent - currentContext = currentContext.ParentActionViewContext; - } - else - { - //exit the loop - currentContext = null; - } + + currentContext = currentContext.IsChildAction + ? currentContext.ParentActionViewContext + : null; } return Attempt.Fail( new InvalidOperationException("Cannot find the Umbraco route definition in the route values, the request must be made in the context of an Umbraco request")); @@ -206,4 +171,4 @@ namespace Umbraco.Web.Mvc } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerFactory.cs b/src/Umbraco.Web/Mvc/SurfaceControllerFactory.cs index 8af018e833..abccbf648b 100644 --- a/src/Umbraco.Web/Mvc/SurfaceControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/SurfaceControllerFactory.cs @@ -1,68 +1,68 @@ -using System; +using System; using System.Web.Mvc; using System.Web.Routing; namespace Umbraco.Web.Mvc { - /// - /// Creates SurfaceControllers - /// - [Obsolete("This is not used in the codebase and will be removed from the core in future versions")] - public class SurfaceControllerFactory : RenderControllerFactory - { - /// - /// Check if the correct data tokens are in the route values so that we know its a surface controller route - /// - /// - /// - public override bool CanHandle(RequestContext request) - { - var area = request.RouteData.DataTokens["area"]; - - //if its a non-area route don't handle, all surface controllers will be in the 'umbraco' area - if (area == null || string.IsNullOrWhiteSpace(area.ToString())) - return false; + /// + /// Creates SurfaceControllers + /// + [Obsolete("This is not used in the codebase and will be removed from the core in future versions")] + public class SurfaceControllerFactory : RenderControllerFactory + { + /// + /// Check if the correct data tokens are in the route values so that we know its a surface controller route + /// + /// + /// + public override bool CanHandle(RequestContext request) + { + var area = request.RouteData.DataTokens["area"]; - //ensure there is an umbraco token set - var umbracoToken = request.RouteData.DataTokens[Core.Constants.Web.UmbracoDataToken]; - if (umbracoToken == null || string.IsNullOrWhiteSpace(umbracoToken.ToString())) - return false; + //if its a non-area route don't handle, all surface controllers will be in the 'umbraco' area + if (area == null || string.IsNullOrWhiteSpace(area.ToString())) + return false; - return true; - } + //ensure there is an umbraco token set + var umbracoToken = request.RouteData.DataTokens[Core.Constants.Web.UmbracoDataToken]; + if (umbracoToken == null || string.IsNullOrWhiteSpace(umbracoToken.ToString())) + return false; - /// - /// Create the controller - /// - /// - /// - /// - public override IController CreateController(RequestContext requestContext, string controllerName) - { - //first try to instantiate with the DependencyResolver, if that fails, try with the UmbracoContext as a param, if that fails try with no params. - var controllerType = GetControllerType(requestContext, controllerName); - if (controllerType == null) - throw new InvalidOperationException("Could not find a controller type for the controller name " + controllerName); + return true; + } - object controllerObject; - try - { - controllerObject = DependencyResolver.Current.GetService(controllerType); - } - catch (Exception) - { - try - { - controllerObject = Activator.CreateInstance(controllerType, UmbracoContext.Current); - } - catch (Exception) - { - //if this throws an exception, we'll let it - controllerObject = Activator.CreateInstance(controllerType); - } - } - //if an exception is thrown here, we want it to be thrown as its an invalid cast. - return (IController)controllerObject; - } - } -} \ No newline at end of file + /// + /// Create the controller + /// + /// + /// + /// + public override IController CreateController(RequestContext requestContext, string controllerName) + { + //first try to instantiate with the DependencyResolver, if that fails, try with the UmbracoContext as a param, if that fails try with no params. + var controllerType = GetControllerType(requestContext, controllerName); + if (controllerType == null) + throw new InvalidOperationException("Could not find a controller type for the controller name " + controllerName); + + object controllerObject; + try + { + controllerObject = DependencyResolver.Current.GetService(controllerType); + } + catch (Exception) + { + try + { + controllerObject = Activator.CreateInstance(controllerType, UmbracoContext.Current); + } + catch (Exception) + { + //if this throws an exception, we'll let it + controllerObject = Activator.CreateInstance(controllerType); + } + } + //if an exception is thrown here, we want it to be thrown as its an invalid cast. + return (IController)controllerObject; + } + } +} diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerResolver.cs b/src/Umbraco.Web/Mvc/SurfaceControllerResolver.cs deleted file mode 100644 index 69683261bc..0000000000 --- a/src/Umbraco.Web/Mvc/SurfaceControllerResolver.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Mvc; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.Mvc -{ - internal sealed class SurfaceControllerResolver : ManyObjectsResolverBase - { - public SurfaceControllerResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable surfaceControllers) - : base(serviceProvider, logger, surfaceControllers) - { - - } - - /// - /// Gets the surface controllers - /// - [Obsolete("This property should not be used in code, controllers are to be instantiated via MVC. To get a list of SurfaceController types use the RegisteredSurfaceControllers property.")] - public IEnumerable SurfaceControllers - { - get { return Values; } - } - - /// - /// Gets all of the surface controller types - /// - public IEnumerable RegisteredSurfaceControllers - { - get { return InstanceTypes; } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollection.cs b/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollection.cs new file mode 100644 index 0000000000..ff0288ffe1 --- /dev/null +++ b/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollection.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Mvc +{ + // unless we want to modify the content of the collection + // which we are not doing at the moment + // we can inherit from BuilderCollectionBase and just be enumerable + + internal class SurfaceControllerTypeCollection : BuilderCollectionBase + { + public SurfaceControllerTypeCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Web/Mvc/SurfaceRouteHandler.cs b/src/Umbraco.Web/Mvc/SurfaceRouteHandler.cs index 8e56cd715f..3998834223 100644 --- a/src/Umbraco.Web/Mvc/SurfaceRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/SurfaceRouteHandler.cs @@ -13,4 +13,4 @@ namespace Umbraco.Web.Mvc return new UmbracoMvcHandler(requestContext); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs index c5ebf24626..2bacd93a56 100644 --- a/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs @@ -2,70 +2,87 @@ using System.Web; using System.Web.Mvc; using Umbraco.Core; -using Umbraco.Web.Security; -using umbraco.BasePages; +using Umbraco.Web.Composing; +using Umbraco.Core.Configuration; namespace Umbraco.Web.Mvc { - /// - /// Ensures authorization is successful for a back office user - /// - public sealed class UmbracoAuthorizeAttribute : AuthorizeAttribute - { - private readonly ApplicationContext _applicationContext; + /// + /// Ensures authorization is successful for a back office user. + /// + public sealed class UmbracoAuthorizeAttribute : AuthorizeAttribute + { + // see note in HttpInstallAuthorizeAttribute private readonly UmbracoContext _umbracoContext; + private readonly IRuntimeState _runtimeState; + private readonly string _redirectUrl; - private ApplicationContext GetApplicationContext() - { - return _applicationContext ?? ApplicationContext.Current; - } + private IRuntimeState RuntimeState => _runtimeState ?? Current.RuntimeState; - private UmbracoContext GetUmbracoContext() - { - return _umbracoContext ?? UmbracoContext.Current; - } + private UmbracoContext UmbracoContext => _umbracoContext ?? Current.UmbracoContext; /// /// THIS SHOULD BE ONLY USED FOR UNIT TESTS /// /// - public UmbracoAuthorizeAttribute(UmbracoContext umbracoContext) + /// + public UmbracoAuthorizeAttribute(UmbracoContext umbracoContext, IRuntimeState runtimeState) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); _umbracoContext = umbracoContext; - _applicationContext = _umbracoContext.Application; + _runtimeState = runtimeState; } + /// + /// Default constructor + /// public UmbracoAuthorizeAttribute() + { } + + /// + /// Constructor specifying to redirect to the specified location if not authorized + /// + /// + public UmbracoAuthorizeAttribute(string redirectUrl) { + _redirectUrl = redirectUrl ?? throw new ArgumentNullException(nameof(redirectUrl)); } - /// - /// Ensures that the user must be in the Administrator or the Install role - /// - /// - /// - protected override bool AuthorizeCore(HttpContextBase httpContext) - { - if (httpContext == null) throw new ArgumentNullException("httpContext"); - - try - { - var appContext = GetApplicationContext(); - var umbContext = GetUmbracoContext(); + /// + /// Constructor specifying to redirect to the umbraco login page if not authorized + /// + /// + public UmbracoAuthorizeAttribute(bool redirectToUmbracoLogin) + { + if (redirectToUmbracoLogin) + { + _redirectUrl = UmbracoConfig.For.GlobalSettings().Path.EnsureStartsWith("~"); + } + } - //we need to that the app is configured and that a user is logged in - if (!appContext.IsConfigured) - return false; + /// + /// Ensures that the user must be in the Administrator or the Install role + /// + /// + /// + protected override bool AuthorizeCore(HttpContextBase httpContext) + { + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); - var isLoggedIn = umbContext.Security.ValidateCurrentUser(); - return isLoggedIn; - } - catch (Exception) - { - return false; - } - } + try + { + // if not configured (install or upgrade) then we can continue + // otherwise we need to ensure that a user is logged in + return RuntimeState.Level == RuntimeLevel.Install + || RuntimeState.Level == RuntimeLevel.Upgrade + || UmbracoContext.Security.ValidateCurrentUser(); + } + catch (Exception) + { + return false; + } + } /// /// Override to to ensure no redirect occurs @@ -73,11 +90,20 @@ namespace Umbraco.Web.Mvc /// protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { - filterContext.Result = (ActionResult)new HttpUnauthorizedResult("You must login to view this resource."); - - //DON'T do a FormsAuth redirect... argh!! thankfully we're running .Net 4.5 :) + if (_redirectUrl.IsNullOrWhiteSpace()) + { + filterContext.Result = (ActionResult)new HttpUnauthorizedResult("You must login to view this resource."); + + + } + else + { + filterContext.Result = new RedirectResult(_redirectUrl); + } + + // DON'T do a FormsAuth redirect... argh!! thankfully we're running .Net 4.5 :) filterContext.RequestContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true; } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/Mvc/UmbracoAuthorizedController.cs b/src/Umbraco.Web/Mvc/UmbracoAuthorizedController.cs index 9229939d0f..d0e3ec7b61 100644 --- a/src/Umbraco.Web/Mvc/UmbracoAuthorizedController.cs +++ b/src/Umbraco.Web/Mvc/UmbracoAuthorizedController.cs @@ -1,44 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.Configuration; -using Umbraco.Web.Routing; -using Umbraco.Web.Security; -using umbraco.BusinessLogic; - -namespace Umbraco.Web.Mvc +namespace Umbraco.Web.Mvc { /// - /// A base MVC controller for use in the back office that ensures that every call to it authorizes the current user. - /// - /// - /// This controller essentially just uses a global UmbracoAuthorizeAttribute, inheritors that require more granular control over the - /// authorization of each method can use this attribute instead of inheriting from this controller. - /// - [UmbracoAuthorize] + /// Provides a base class for authorized Umbraco controllers. + /// + /// + /// This controller essentially just uses a global UmbracoAuthorizeAttribute, inheritors that require more granular control over the + /// authorization of each method can use this attribute instead of inheriting from this controller. + /// + [UmbracoAuthorize] [DisableBrowserCache] public abstract class UmbracoAuthorizedController : UmbracoController - { - - private bool _userisValidated = false; - - /// - /// Returns the currently logged in Umbraco User - /// - [Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object")] - protected User UmbracoUser - { - get - { - if (!_userisValidated) - { - Security.ValidateCurrentUser(); - _userisValidated = true; - } - return new User(Security.CurrentUser); - } - } - - } + { } } diff --git a/src/Umbraco.Web/Mvc/UmbracoController.cs b/src/Umbraco.Web/Mvc/UmbracoController.cs index 37d7549d05..6f4034fe85 100644 --- a/src/Umbraco.Web/Mvc/UmbracoController.cs +++ b/src/Umbraco.Web/Mvc/UmbracoController.cs @@ -1,113 +1,93 @@ using System; using System.Web; using System.Web.Mvc; +using LightInject; using Microsoft.Owin; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Web.Security; namespace Umbraco.Web.Mvc { /// - /// A base controller class containing all of the Umbraco objects as properties that a developer requires + /// Provides a base class for Umbraco controllers. /// public abstract class UmbracoController : Controller { - protected UmbracoController(UmbracoContext umbracoContext) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - UmbracoContext = umbracoContext; - } + private UmbracoHelper _umbracoHelper; - protected UmbracoController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (umbracoHelper == null) throw new ArgumentNullException("umbracoHelper"); - UmbracoContext = umbracoContext; - _umbraco = umbracoHelper; - } + // for debugging purposes + internal Guid InstanceId { get; } = Guid.NewGuid(); - protected UmbracoController() - : this(UmbracoContext.Current) - { - - } - - protected IOwinContext OwinContext - { - get { return Request.GetOwinContext(); } - } - - private UmbracoHelper _umbraco; + // note + // properties marked as [Inject] below will be property-injected (vs constructor-injected) in + // order to keep the constuctor as light as possible, so that ppl implementing eg a SurfaceController + // don't need to implement complex constructors + need to refactor them each time we change ours. + // this means that these properties have a setter. + // what can go wrong? /// - /// Returns the MemberHelper instance + /// Gets or sets the Umbraco context. /// - public MembershipHelper Members - { - get { return Umbraco.MembershipHelper; } - } + [Inject] + public virtual IGlobalSettings GlobalSettings { get; set; } /// - /// Returns an UmbracoHelper object + /// Gets or sets the Umbraco context. /// - public virtual UmbracoHelper Umbraco - { - get { return _umbraco ?? (_umbraco = new UmbracoHelper(UmbracoContext)); } - } + [Inject] + public virtual UmbracoContext UmbracoContext { get; set; } /// - /// Returns an ILogger + /// Gets or sets the database context. /// - public ILogger Logger - { - get { return ProfilingLogger.Logger; } - } + [Inject] + public IUmbracoDatabaseFactory DatabaseFactory { get; set; } /// - /// Returns a ProfilingLogger + /// Gets or sets the services context. /// - public virtual ProfilingLogger ProfilingLogger - { - get { return UmbracoContext.Application.ProfilingLogger; } - } + [Inject] + public ServiceContext Services { get; set; } /// - /// Returns the current UmbracoContext + /// Gets or sets the application cache. /// - public virtual UmbracoContext UmbracoContext { get; private set; } + [Inject] + public CacheHelper ApplicationCache { get; set; } /// - /// Returns the current ApplicationContext + /// Gets or sets the logger. /// - public virtual ApplicationContext ApplicationContext - { - get { return UmbracoContext.Application; } - } + [Inject] + public ILogger Logger { get; set; } /// - /// Returns a ServiceContext + /// Gets or sets the profiling logger. /// - public ServiceContext Services - { - get { return ApplicationContext.Services; } - } + [Inject] + public ProfilingLogger ProfilingLogger { get; set; } + + protected IOwinContext OwinContext => Request.GetOwinContext(); /// - /// Returns a DatabaseContext + /// Gets the membership helper. /// - public DatabaseContext DatabaseContext - { - get { return ApplicationContext.DatabaseContext; } - } + public MembershipHelper Members => Umbraco.MembershipHelper; /// - /// Returns the WebSecurity instance + /// Gets the Umbraco helper. /// - public virtual WebSecurity Security - { - get { return UmbracoContext.Security; } - } + public UmbracoHelper Umbraco => _umbracoHelper + ?? (_umbracoHelper = new UmbracoHelper(UmbracoContext, Services, ApplicationCache)); + + /// + /// Gets the web security helper. + /// + public virtual WebSecurity Security => UmbracoContext.Security; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/UmbracoControllerFactory.cs b/src/Umbraco.Web/Mvc/UmbracoControllerFactory.cs index 02ff701c8b..01c73b92fc 100644 --- a/src/Umbraco.Web/Mvc/UmbracoControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/UmbracoControllerFactory.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Web.Mvc; using System.Web.Routing; using System.Web.SessionState; using Umbraco.Core; +using Umbraco.Web.Composing; namespace Umbraco.Web.Mvc { @@ -14,7 +15,7 @@ namespace Umbraco.Web.Mvc private readonly OverridenDefaultControllerFactory _innerFactory = new OverridenDefaultControllerFactory(); public abstract bool CanHandle(RequestContext request); - + public virtual Type GetControllerType(RequestContext requestContext, string controllerName) { return _innerFactory.GetControllerType(requestContext, controllerName); @@ -30,10 +31,9 @@ namespace Umbraco.Web.Mvc public virtual IController CreateController(RequestContext requestContext, string controllerName) { var controllerType = GetControllerType(requestContext, controllerName) ?? - _innerFactory.GetControllerType( - requestContext, - ControllerExtensions.GetControllerName( - DefaultRenderMvcControllerResolver.Current.GetDefaultControllerType())); + _innerFactory.GetControllerType( + requestContext, + ControllerExtensions.GetControllerName(Current.DefaultRenderMvcControllerType)); return _innerFactory.GetControllerInstance(requestContext, controllerType); } @@ -82,4 +82,4 @@ namespace Umbraco.Web.Mvc } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/UmbracoMvcHandler.cs b/src/Umbraco.Web/Mvc/UmbracoMvcHandler.cs index 6e7a92ea10..6238c3dbaf 100644 --- a/src/Umbraco.Web/Mvc/UmbracoMvcHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoMvcHandler.cs @@ -5,13 +5,13 @@ namespace Umbraco.Web.Mvc { /// /// MVC handler to facilitate the TemplateRenderer. This handler can execute an MVC request and return it as a string. - /// + /// /// Original: - /// + /// /// This handler also used to intercept creation of controllers and store it for later use. - /// This was needed for the 'return CurrentUmbracoPage()' surface controller functionality + /// This was needed for the 'return CurrentUmbracoPage()' surface controller functionality /// because it needs to send data back to the page controller. - /// + /// /// The creation of this controller has been moved to the UmbracoPageResult class which will create a controller when needed. /// internal class UmbracoMvcHandler : MvcHandler @@ -20,7 +20,7 @@ namespace Umbraco.Web.Mvc : base(requestContext) { } - + /// /// This is used internally purely to render an Umbraco MVC template to string and shouldn't be used for anything else. /// @@ -29,4 +29,4 @@ namespace Umbraco.Web.Mvc base.ProcessRequest(RequestContext.HttpContext); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs index da4cb198ac..98bd66e48e 100644 --- a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Web; using System.Web.Mvc; @@ -6,46 +6,47 @@ using System.Web.Routing; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Profiling; +using Umbraco.Web.Composing; namespace Umbraco.Web.Mvc { - /// - /// Used by posted forms to proxy the result to the page in which the current URL matches on - /// - public class UmbracoPageResult : ActionResult - { - private readonly ProfilingLogger _profilingLogger; + /// + /// Used by posted forms to proxy the result to the page in which the current URL matches on + /// + public class UmbracoPageResult : ActionResult + { + private readonly ProfilingLogger _profilingLogger; - public UmbracoPageResult(ProfilingLogger profilingLogger) - { - _profilingLogger = profilingLogger; - } + public UmbracoPageResult(ProfilingLogger profilingLogger) + { + _profilingLogger = profilingLogger; + } [Obsolete("Use the ctor specifying all depenendencies instead")] - public UmbracoPageResult() - : this(new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler)) - { - - } + public UmbracoPageResult() + : this(new ProfilingLogger(Current.Logger, Current.Profiler)) + { - public override void ExecuteResult(ControllerContext context) - { - ResetRouteData(context.RouteData); + } + + public override void ExecuteResult(ControllerContext context) + { + ResetRouteData(context.RouteData); ValidateRouteData(context.RouteData); - var routeDef = (RouteDefinition)context.RouteData.DataTokens[Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken]; + var routeDef = (RouteDefinition)context.RouteData.DataTokens[Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken]; - //Special case, if it is webforms but we're posting to an MVC surface controller, then we + //Special case, if it is webforms but we're posting to an MVC surface controller, then we // need to return the webforms result instead - if (routeDef.PublishedContentRequest.RenderingEngine == RenderingEngine.WebForms) - { - EnsureViewContextForWebForms(context); - var webFormsHandler = RenderRouteHandler.GetWebFormsHandler(); - webFormsHandler.ProcessRequest(HttpContext.Current); - } - else - { + if (routeDef.PublishedRequest.RenderingEngine == RenderingEngine.WebForms) + { + EnsureViewContextForWebForms(context); + var webFormsHandler = RenderRouteHandler.GetWebFormsHandler(); + webFormsHandler.ProcessRequest(HttpContext.Current); + } + else + { var factory = ControllerBuilder.Current.GetControllerFactory(); context.RouteData.Values["action"] = routeDef.ActionName; ControllerBase controller = null; @@ -61,30 +62,30 @@ namespace Umbraco.Web.Mvc finally { CleanupController(controller, factory); - } - } - } + } + } + } /// /// Executes the controller action /// - private void ExecuteControllerAction(ControllerContext context, IController controller) - { + private void ExecuteControllerAction(ControllerContext context, IController controller) + { using (_profilingLogger.TraceDuration("Executing Umbraco RouteDefinition controller", "Finished")) - { - controller.Execute(context.RequestContext); - } - } - - /// - /// Since we could be returning the current page from a surface controller posted values in which the routing values are changed, we + { + controller.Execute(context.RequestContext); + } + } + + /// + /// Since we could be returning the current page from a surface controller posted values in which the routing values are changed, we /// need to revert these values back to nothing in order for the normal page to render again. /// private static void ResetRouteData(RouteData routeData) - { + { routeData.DataTokens["area"] = null; routeData.DataTokens["Namespaces"] = null; - } + } /// /// Validate that the current page execution is not being handled by the normal umbraco routing system @@ -130,7 +131,7 @@ namespace Umbraco.Web.Mvc controller.ViewData[d.Key] = d.Value; //We cannot simply merge the temp data because during controller execution it will attempt to 'load' temp data - // but since it has not been saved, there will be nothing to load and it will revert to nothing, so the trick is + // but since it has not been saved, there will be nothing to load and it will revert to nothing, so the trick is // to Save the state of the temp data first then it will automatically be picked up. // http://issues.umbraco.org/issue/U4-1339 @@ -140,9 +141,9 @@ namespace Umbraco.Web.Mvc { targetController.TempDataProvider = sourceController.TempDataProvider; targetController.TempData = sourceController.TempData; - targetController.TempData.Save(sourceController.ControllerContext, sourceController.TempDataProvider); + targetController.TempData.Save(sourceController.ControllerContext, sourceController.TempDataProvider); } - + } /// @@ -170,11 +171,11 @@ namespace Umbraco.Web.Mvc controller.DisposeIfDisposable(); } - private class DummyView : IView - { - public void Render(ViewContext viewContext, TextWriter writer) - { - } - } - } -} \ No newline at end of file + private class DummyView : IView + { + public void Render(ViewContext viewContext, TextWriter writer) + { + } + } + } +} diff --git a/src/Umbraco.Web/Mvc/UmbracoRequireHttpsAttribute.cs b/src/Umbraco.Web/Mvc/UmbracoRequireHttpsAttribute.cs index 129e3a6596..589090c08e 100644 --- a/src/Umbraco.Web/Mvc/UmbracoRequireHttpsAttribute.cs +++ b/src/Umbraco.Web/Mvc/UmbracoRequireHttpsAttribute.cs @@ -1,4 +1,5 @@ using System.Web.Mvc; +using Umbraco.Core.Configuration; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; namespace Umbraco.Web.Mvc @@ -15,7 +16,7 @@ namespace Umbraco.Web.Mvc protected override void HandleNonHttpsRequest(AuthorizationContext filterContext) { // If umbracoUseSSL is set, let base method handle redirect. Otherwise, we don't care. - if (GlobalSettings.UseSSL) + if (UmbracoConfig.For.GlobalSettings().UseHttps) { base.HandleNonHttpsRequest(filterContext); } @@ -28,7 +29,7 @@ namespace Umbraco.Web.Mvc public override void OnAuthorization(AuthorizationContext filterContext) { // If umbracoSSL is set, let base method handle checking for HTTPS. Otherwise, we don't care. - if (GlobalSettings.UseSSL) + if (UmbracoConfig.For.GlobalSettings().UseHttps) { base.OnAuthorization(filterContext); } diff --git a/src/Umbraco.Web/Mvc/UmbracoTemplatePage.cs b/src/Umbraco.Web/Mvc/UmbracoTemplatePage.cs deleted file mode 100644 index b9f9f4690f..0000000000 --- a/src/Umbraco.Web/Mvc/UmbracoTemplatePage.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Umbraco.Core.Dynamics; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Mvc -{ - /// - /// The View that front-end templates inherit from - /// - public abstract class UmbracoTemplatePage : UmbracoViewPage - { - private object _currentPage; - - /// - /// Returns the content as a dynamic object - /// - public dynamic CurrentPage - { - get - { - // it's invalid to create a DynamicPublishedContent around a null content anyway - // fixme - should we return null or DynamicNull.Null? - if (Model == null || Model.Content == null) return null; - return _currentPage ?? (_currentPage = Model.Content.AsDynamic()); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/UmbracoTemplatePageOfTContent.cs b/src/Umbraco.Web/Mvc/UmbracoTemplatePageOfTContent.cs deleted file mode 100644 index 4e1a9aef38..0000000000 --- a/src/Umbraco.Web/Mvc/UmbracoTemplatePageOfTContent.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Umbraco.Core.Dynamics; -using Umbraco.Core.Models; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Mvc -{ - public abstract class UmbracoTemplatePage : UmbracoViewPage> - where TContent : IPublishedContent - { - private object _currentPage; - - /// - /// Returns the content as a dynamic object - /// - public dynamic CurrentPage - { - get - { - // it's invalid to create a DynamicPublishedContent around a null content anyway - // fixme - should we return null or DynamicNull.Null? - if (Model == null || Model.Content == null) return null; - return _currentPage ?? (_currentPage = Model.Content.AsDynamic()); - } - } - } -} diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPage.cs b/src/Umbraco.Web/Mvc/UmbracoViewPage.cs index 4e067c2c82..74d2da2420 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPage.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPage.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Mvc { diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs index 3d1a41d289..6857c0ce46 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs @@ -1,13 +1,16 @@ -using System; -using System.Globalization; +using System; using System.Text; using System.Web; using System.Web.Mvc; using System.Web.WebPages; +using LightInject; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.IO; -using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models; using Umbraco.Web.Routing; using Umbraco.Web.Security; @@ -15,99 +18,98 @@ using Umbraco.Web.Security; namespace Umbraco.Web.Mvc { /// - /// The View that umbraco front-end views inherit from + /// Represents the properties and methods that are needed in order to render an Umbraco view. /// public abstract class UmbracoViewPage : WebViewPage { - /// - /// Returns the current UmbracoContext - /// - public UmbracoContext UmbracoContext - { - get - { - //we should always try to return the context from the data tokens just in case its a custom context and not - //using the UmbracoContext.Current, we will fallback to the singleton if necessary. - var umbCtx = ViewContext.GetUmbracoContext() - //lastly, we will use the singleton, the only reason this should ever happen is is someone is rendering a page that inherits from this - //class and are rendering it outside of the normal Umbraco routing process. Very unlikely. - ?? UmbracoContext.Current; - - return umbCtx; - } - } - - /// - /// Returns the current ApplicationContext - /// - public ApplicationContext ApplicationContext - { - get { return UmbracoContext.Application; } - } - - /// - /// Returns the current PublishedContentRequest - /// - internal PublishedContentRequest PublishedContentRequest - { - get - { - //we should always try to return the object from the data tokens just in case its a custom object and not - //using the UmbracoContext.Current. - //we will fallback to the singleton if necessary. - if (ViewContext.RouteData.DataTokens.ContainsKey(Core.Constants.Web.PublishedDocumentRequestDataToken)) - { - return (PublishedContentRequest)ViewContext.RouteData.DataTokens.GetRequiredObject(Core.Constants.Web.PublishedDocumentRequestDataToken); - } - //next check if it is a child action and see if the parent has it set in data tokens - if (ViewContext.IsChildAction) - { - if (ViewContext.ParentActionViewContext.RouteData.DataTokens.ContainsKey(Core.Constants.Web.PublishedDocumentRequestDataToken)) - { - return (PublishedContentRequest)ViewContext.ParentActionViewContext.RouteData.DataTokens.GetRequiredObject(Core.Constants.Web.PublishedDocumentRequestDataToken); - } - } - - //lastly, we will use the singleton, the only reason this should ever happen is is someone is rendering a page that inherits from this - //class and are rendering it outside of the normal Umbraco routing process. Very unlikely. - return UmbracoContext.Current.PublishedContentRequest; - } - } - + private UmbracoContext _umbracoContext; private UmbracoHelper _helper; - private MembershipHelper _membershipHelper; + + // note + // properties marked as [Inject] below will be property-injected (vs constructor-injected) since + // we have no control over the view constructor (generated by eg the Razor engine). + // this means that these properties have a setter. + // what can go wrong? /// - /// Gets an UmbracoHelper + /// Gets or sets the database context. + /// + [Inject] + public ServiceContext Services { get; set; } + + /// + /// Gets or sets the application cache. + /// + [Inject] + public CacheHelper ApplicationCache { get; set; } + + // fixme + // previously, Services and ApplicationCache would derive from UmbracoContext.Application, which + // was an ApplicationContext - so that everything derived from UmbracoContext. + // UmbracoContext is fetched from the data tokens, thus allowing the view to be rendered with a + // custom context and NOT the Current.UmbracoContext - eg outside the normal Umbraco routing + // process. + // leaving it as-it for the time being but really - the UmbracoContext should be injected just + // like the Services & ApplicationCache properties, and have a setter for those special weird + // cases. + + /// + /// Gets the Umbraco context. + /// + public UmbracoContext UmbracoContext => _umbracoContext + ?? (_umbracoContext = ViewContext.GetUmbracoContext() ?? Current.UmbracoContext); + + /// + /// Gets the public content request. + /// + internal PublishedRequest PublishedRequest + { + get + { + const string token = Core.Constants.Web.PublishedDocumentRequestDataToken; + + // we should always try to return the object from the data tokens just in case its a custom object and not + // the one from UmbracoContext. Fallback to UmbracoContext if necessary. + + // try view context + if (ViewContext.RouteData.DataTokens.ContainsKey(token)) + return (PublishedRequest) ViewContext.RouteData.DataTokens.GetRequiredObject(token); + + // child action, try parent view context + if (ViewContext.IsChildAction && ViewContext.ParentActionViewContext.RouteData.DataTokens.ContainsKey(token)) + return (PublishedRequest) ViewContext.ParentActionViewContext.RouteData.DataTokens.GetRequiredObject(token); + + // fallback to UmbracoContext + return UmbracoContext.PublishedRequest; + } + } + + /// + /// Gets the Umbraco helper. /// - /// - /// This constructs the UmbracoHelper with the content model of the page routed to - /// public virtual UmbracoHelper Umbraco { get { - if (_helper == null) - { - var model = ViewData.Model; - var content = model as IPublishedContent; - if (content == null && model is IRenderModel) - content = ((IRenderModel) model).Content; - _helper = content == null - ? new UmbracoHelper(UmbracoContext) - : new UmbracoHelper(UmbracoContext, content); - } + if (_helper != null) return _helper; + + var model = ViewData.Model; + var content = model as IPublishedContent; + if (content == null && model is IContentModel) + content = ((IContentModel) model).Content; + _helper = content == null + ? new UmbracoHelper(UmbracoContext, Services, ApplicationCache) + : new UmbracoHelper(UmbracoContext, Services, ApplicationCache, content); return _helper; } } /// - /// Returns the MemberHelper instance + /// Gets the membership helper. /// - public MembershipHelper Members - { - get { return _membershipHelper ?? (_membershipHelper = new MembershipHelper(UmbracoContext)); } - } + public MembershipHelper Members => Umbraco.MembershipHelper; + + // view logic below: /// /// Ensure that the current view context is added to the route data tokens so we can extract it if we like @@ -118,17 +120,14 @@ namespace Umbraco.Web.Mvc protected override void InitializePage() { base.InitializePage(); - if (ViewContext.IsChildAction == false) - { - //this is used purely for partial view macros that contain forms - // and mostly just when rendered within the RTE - This should already be set with the - // EnsurePartialViewMacroViewContextFilterAttribute - if (ViewContext.RouteData.DataTokens.ContainsKey(Constants.DataTokenCurrentViewContext) == false) - { - ViewContext.RouteData.DataTokens.Add(Constants.DataTokenCurrentViewContext, ViewContext); - } - } + if (ViewContext.IsChildAction) return; + + // this is used purely for partial view macros that contain forms and mostly + // just when rendered within the RTE - this should already be set with the + // EnsurePartialViewMacroViewContextFilterAttribute + if (ViewContext.RouteData.DataTokens.ContainsKey(Constants.DataTokenCurrentViewContext) == false) + ViewContext.RouteData.DataTokens.Add(Constants.DataTokenCurrentViewContext, ViewContext); } // maps model @@ -140,11 +139,8 @@ namespace Umbraco.Web.Mvc // map the view data (may change its type, may set model to null) viewData = MapViewDataDictionary(viewData, typeof (TModel)); - var culture = CultureInfo.CurrentCulture; - // bind the model (use context culture as default, if available) - if (UmbracoContext.PublishedContentRequest != null && UmbracoContext.PublishedContentRequest.Culture != null) - culture = UmbracoContext.PublishedContentRequest.Culture; - viewData.Model = RenderModelBinder.BindModel(viewDataModel, typeof (TModel), culture); + // bind the model + viewData.Model = ContentModelBinder.BindModel(viewDataModel, typeof (TModel)); // set the view data base.SetViewData(viewData); @@ -208,7 +204,7 @@ namespace Umbraco.Web.Mvc { // creating previewBadge markup markupToInject = - String.Format(UmbracoConfig.For.UmbracoSettings().Content.PreviewBadge, + string.Format(UmbracoConfig.For.UmbracoSettings().Content.PreviewBadge, IOHelper.ResolveUrl(SystemDirectories.Umbraco), IOHelper.ResolveUrl(SystemDirectories.UmbracoClient), Server.UrlEncode(UmbracoContext.Current.HttpContext.Request.Path)); @@ -229,8 +225,6 @@ namespace Umbraco.Web.Mvc } base.WriteLiteral(value); - - } public HelperResult RenderSection(string name, Func defaultContents) @@ -247,7 +241,7 @@ namespace Umbraco.Web.Mvc { return WebViewPageExtensions.RenderSection(this, name, defaultContents); } - + public HelperResult RenderSection(string name, IHtmlString defaultContents) { return WebViewPageExtensions.RenderSection(this, name, defaultContents); diff --git a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeByIdRouteHandler.cs b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeByIdRouteHandler.cs index 94cd71673b..304f59b64b 100644 --- a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeByIdRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeByIdRouteHandler.cs @@ -1,5 +1,5 @@ using System.Web.Routing; -using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Mvc { @@ -15,9 +15,7 @@ namespace Umbraco.Web.Mvc protected sealed override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext) { var byId = umbracoContext.ContentCache.GetById(_realNodeId); - if (byId == null) return null; - - return FindContent(requestContext, umbracoContext, byId); + return byId == null ? null : FindContent(requestContext, umbracoContext, byId); } protected virtual IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext, IPublishedContent baseContent) @@ -25,4 +23,4 @@ namespace Umbraco.Web.Mvc return baseContent; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs index df64d5e75e..ab474fa699 100644 --- a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs @@ -1,18 +1,18 @@ -using System.Globalization; -using System.Linq; -using System.Web; +using System.Web; using System.Web.Mvc; using System.Web.Routing; -using System.Web.Security; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Models; using Umbraco.Web.Routing; +using LightInject; namespace Umbraco.Web.Mvc { public abstract class UmbracoVirtualNodeRouteHandler : IRouteHandler { + // todo - try lazy property injection? + private PublishedRouter PublishedRouter => Core.Composing.Current.Container.GetInstance(); + /// /// Returns the UmbracoContext for this route handler /// @@ -27,7 +27,7 @@ namespace Umbraco.Web.Mvc /// //check if context is null, we know it will be null if we are dealing with a request that /// //has an extension and by default no Umb ctx is created for the request /// if (ctx == null) { - /// //TODO: Here you can EnsureContext , please note that the requestContext is passed in + /// //TODO: Here you can EnsureContext , please note that the requestContext is passed in /// //therefore your should refrain from using other singletons like HttpContext.Current since /// //you will already have a reference to it. Also if you need an ApplicationContext you should /// //pass this in via a ctor instead of using the ApplicationContext.Current singleton. @@ -45,26 +45,23 @@ namespace Umbraco.Web.Mvc public IHttpHandler GetHttpHandler(RequestContext requestContext) { var umbracoContext = GetUmbracoContext(requestContext); - + var found = FindContent(requestContext, umbracoContext); if (found == null) return new NotFoundHandler(); - - umbracoContext.PublishedContentRequest = new PublishedContentRequest( - umbracoContext.CleanedUmbracoUrl, umbracoContext.RoutingContext, - UmbracoConfig.For.UmbracoSettings().WebRouting, s => Roles.Provider.GetRolesForUser(s)) - { - PublishedContent = found - }; + + var request = PublishedRouter.CreateRequest(umbracoContext); + request.PublishedContent = found; + umbracoContext.PublishedRequest = request; //allows inheritors to change the pcr - PreparePublishedContentRequest(umbracoContext.PublishedContentRequest); + PreparePublishedContentRequest(umbracoContext.PublishedRequest); //create the render model - var renderModel = new RenderModel(umbracoContext.PublishedContentRequest.PublishedContent, umbracoContext.PublishedContentRequest.Culture); + var renderModel = new ContentModel(umbracoContext.PublishedRequest.PublishedContent); //assigns the required tokens to the request requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, renderModel); - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, umbracoContext.PublishedContentRequest); + requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, umbracoContext.PublishedRequest); requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbracoContext); //this is used just for a flag that this is an umbraco custom route requestContext.RouteData.DataTokens.Add(Core.Constants.Web.CustomRouteDataToken, true); @@ -77,11 +74,11 @@ namespace Umbraco.Web.Mvc { ActionName = requestContext.RouteData.GetRequiredString("action"), ControllerName = requestContext.RouteData.GetRequiredString("controller"), - PublishedContentRequest = umbracoContext.PublishedContentRequest + PublishedRequest = umbracoContext.PublishedRequest }; //set the special data token to the current route definition - requestContext.RouteData.DataTokens[Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; + requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; return RenderRouteHandler.HandlePostedValues(requestContext, formInfo); } @@ -91,9 +88,9 @@ namespace Umbraco.Web.Mvc protected abstract IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext); - protected virtual void PreparePublishedContentRequest(PublishedContentRequest publishedContentRequest) + protected virtual void PreparePublishedContentRequest(PublishedRequest request) { - publishedContentRequest.Prepare(); + PublishedRouter.PrepareRequest(request); } } } diff --git a/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs b/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs index 543844ee08..19ae37b9cf 100644 --- a/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/Mvc/UrlHelperExtensions.cs @@ -1,28 +1,91 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System; using System.Web.Mvc; +using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Xml; -namespace Umbraco.Core +namespace Umbraco.Web.Mvc { - /// - /// Extension methods for UrlHelper - /// - public static class UrlHelperExtensions - { + /// + /// Extension methods for UrlHelper + /// + public static class UrlHelperExtensions + { + /// + /// Utility method for checking for valid proxy urls or redirect urls to prevent Open Redirect security issues + /// + /// + /// The url to validate + /// The url of the current local domain (to ensure we can validate if the requested url is local without dependency on the request) + /// True if it's an allowed url + public static bool ValidateProxyUrl(this UrlHelper urlHelper, string url, string callerUrl) + { + if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) == false) + { + return false; + } - /// - /// Returns the base path (not including the 'action') of the MVC controller "SaveFileController" - /// - /// - /// - public static string GetSaveFileServicePath(this UrlHelper url) - { - var result = url.Action("SavePartialView", "SaveFile", new {area = GlobalSettings.UmbracoMvcArea}); + if (url.StartsWith("//")) + return false; + + Uri requestUri; + if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out requestUri)) + { + if (string.IsNullOrEmpty(callerUrl) == false) + { + Uri localUri; + if (Uri.TryCreate(callerUrl, UriKind.RelativeOrAbsolute, out localUri)) + { + // check for local urls + + //Cannot start with // since that is not a local url + if (requestUri.OriginalString.StartsWith("//") == false + //cannot be non-absolute and also contain the char : since that will indicate a protocol + && (requestUri.IsAbsoluteUri == false && requestUri.OriginalString.Contains(":") == false) + //needs to be non-absolute or the hosts must match the current request + && (requestUri.IsAbsoluteUri == false || requestUri.Host == localUri.Host)) + { + return true; + } + } + else + { + return false; + } + } + + //we cannot continue if the url is not absolute + if (requestUri.IsAbsoluteUri == false) + { + return false; + } + + // check for valid proxy urls + var feedProxyXml = XmlHelper.OpenAsXmlDocument(IOHelper.MapPath(SystemFiles.FeedProxyConfig)); + if (feedProxyXml != null && + feedProxyXml.SelectSingleNode(string.Concat("//allow[@host = '", requestUri.Host, "']")) != null) + { + return true; + } + } + else + { + return false; + } + return false; + } + + /// + /// Returns the base path (not including the 'action') of the MVC controller "SaveFileController" + /// + /// + /// + public static string GetSaveFileServicePath(this UrlHelper url) + { + var result = url.Action("SavePartialView", "SaveFile", new {area = UmbracoConfig.For.GlobalSettings().GetUmbracoMvcArea()}); return result.TrimEnd("SavePartialView").EnsureEndsWith('/'); - } + } /// /// Returns the base path (not including the 'action') of the MVC controller "BulkPublishController" @@ -31,7 +94,7 @@ namespace Umbraco.Core /// public static string GetBulkPublishServicePath(this UrlHelper url) { - var result = url.Action("PublishDocument", "BulkPublish", new { area = GlobalSettings.UmbracoMvcArea }); + var result = url.Action("PublishDocument", "BulkPublish", new { area = UmbracoConfig.For.GlobalSettings().GetUmbracoMvcArea() }); return result.TrimEnd("PublishDocument").EnsureEndsWith('/'); } @@ -42,8 +105,8 @@ namespace Umbraco.Core /// The base path of the controller. public static string GetCoreStringsControllerPath(this UrlHelper url) { - var result = url.Action("ToSafeAlias", "CoreStrings", new { area = GlobalSettings.UmbracoMvcArea }); + var result = url.Action("ToSafeAlias", "CoreStrings", new { area = UmbracoConfig.For.GlobalSettings().GetUmbracoMvcArea() }); return result.TrimEnd("ToSafeAlias"); } - } + } } diff --git a/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs index 977f37473b..756ca7f05c 100644 --- a/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs +++ b/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Security.Claims; @@ -12,7 +12,7 @@ namespace Umbraco.Web.Mvc /// /// /// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/ - /// + /// /// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled /// public sealed class ValidateMvcAngularAntiForgeryTokenAttribute : ActionFilterAttribute @@ -58,4 +58,4 @@ namespace Umbraco.Web.Mvc base.OnActionExecuting(filterContext); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Mvc/ViewContextExtensions.cs b/src/Umbraco.Web/Mvc/ViewContextExtensions.cs index 1cb39e2487..ec470aa921 100644 --- a/src/Umbraco.Web/Mvc/ViewContextExtensions.cs +++ b/src/Umbraco.Web/Mvc/ViewContextExtensions.cs @@ -1,53 +1,53 @@ -using System.IO; +using System.IO; using System.Web.Mvc; namespace Umbraco.Web.Mvc { - internal static class ViewContextExtensions - { - /// - /// Creates a new ViewContext from an existing one but specifies a new Model for the ViewData - /// - /// - /// - /// - public static ViewContext CopyWithModel(this ViewContext vc, object model) - { - return new ViewContext - { - Controller = vc.Controller, - HttpContext = vc.HttpContext, - RequestContext = vc.RequestContext, - RouteData = vc.RouteData, - TempData = vc.TempData, - View = vc.View, - ViewData = new ViewDataDictionary(vc) - { - Model = model - }, - FormContext = vc.FormContext, - ClientValidationEnabled = vc.ClientValidationEnabled, - UnobtrusiveJavaScriptEnabled = vc.UnobtrusiveJavaScriptEnabled, - Writer = vc.Writer - }; - } + internal static class ViewContextExtensions + { + /// + /// Creates a new ViewContext from an existing one but specifies a new Model for the ViewData + /// + /// + /// + /// + public static ViewContext CopyWithModel(this ViewContext vc, object model) + { + return new ViewContext + { + Controller = vc.Controller, + HttpContext = vc.HttpContext, + RequestContext = vc.RequestContext, + RouteData = vc.RouteData, + TempData = vc.TempData, + View = vc.View, + ViewData = new ViewDataDictionary(vc) + { + Model = model + }, + FormContext = vc.FormContext, + ClientValidationEnabled = vc.ClientValidationEnabled, + UnobtrusiveJavaScriptEnabled = vc.UnobtrusiveJavaScriptEnabled, + Writer = vc.Writer + }; + } - //public static ViewContext CloneWithWriter(this ViewContext vc, TextWriter writer) - //{ - // return new ViewContext - // { - // Controller = vc.Controller, - // HttpContext = vc.HttpContext, - // RequestContext = vc.RequestContext, - // RouteData = vc.RouteData, - // TempData = vc.TempData, - // View = vc.View, - // ViewData = vc.ViewData, - // FormContext = vc.FormContext, - // ClientValidationEnabled = vc.ClientValidationEnabled, - // UnobtrusiveJavaScriptEnabled = vc.UnobtrusiveJavaScriptEnabled, - // Writer = writer - // }; - //} - } -} \ No newline at end of file + //public static ViewContext CloneWithWriter(this ViewContext vc, TextWriter writer) + //{ + // return new ViewContext + // { + // Controller = vc.Controller, + // HttpContext = vc.HttpContext, + // RequestContext = vc.RequestContext, + // RouteData = vc.RouteData, + // TempData = vc.TempData, + // View = vc.View, + // ViewData = vc.ViewData, + // FormContext = vc.FormContext, + // ClientValidationEnabled = vc.ClientValidationEnabled, + // UnobtrusiveJavaScriptEnabled = vc.UnobtrusiveJavaScriptEnabled, + // Writer = writer + // }; + //} + } +} diff --git a/src/Umbraco.Web/Mvc/ViewDataContainerExtensions.cs b/src/Umbraco.Web/Mvc/ViewDataContainerExtensions.cs index f8f627d39a..9fb301d8c5 100644 --- a/src/Umbraco.Web/Mvc/ViewDataContainerExtensions.cs +++ b/src/Umbraco.Web/Mvc/ViewDataContainerExtensions.cs @@ -1,49 +1,49 @@ -using System.Web.Mvc; +using System.Web.Mvc; namespace Umbraco.Web.Mvc { internal static class ViewDataContainerExtensions - { - private class ViewDataContainer : IViewDataContainer - { - public ViewDataContainer() - { - ViewData = new ViewDataDictionary(); - } - public ViewDataDictionary ViewData { get; set; } - } + { + private class ViewDataContainer : IViewDataContainer + { + public ViewDataContainer() + { + ViewData = new ViewDataDictionary(); + } + public ViewDataDictionary ViewData { get; set; } + } - /// - /// Creates a new IViewDataContainer but with a filtered ModelState - /// - /// - /// - /// - public static IViewDataContainer FilterContainer(this IViewDataContainer container, string prefix) - { - var newContainer = new ViewDataContainer(); - newContainer.ViewData.ModelState.Merge(container.ViewData.ModelState, prefix); + /// + /// Creates a new IViewDataContainer but with a filtered ModelState + /// + /// + /// + /// + public static IViewDataContainer FilterContainer(this IViewDataContainer container, string prefix) + { + var newContainer = new ViewDataContainer(); + newContainer.ViewData.ModelState.Merge(container.ViewData.ModelState, prefix); //change the html field name too - newContainer.ViewData.TemplateInfo.HtmlFieldPrefix = prefix; - return newContainer; - } + newContainer.ViewData.TemplateInfo.HtmlFieldPrefix = prefix; + return newContainer; + } - /// - /// Returns a new IViewContainer based on the current one but supplies a different model to the ViewData - /// - /// - /// - /// - public static IViewDataContainer CopyWithModel(this IViewDataContainer container, object model) - { - return new ViewDataContainer - { - ViewData = new ViewDataDictionary(container.ViewData) - { - Model = model - } - }; - } + /// + /// Returns a new IViewContainer based on the current one but supplies a different model to the ViewData + /// + /// + /// + /// + public static IViewDataContainer CopyWithModel(this IViewDataContainer container, object model) + { + return new ViewDataContainer + { + ViewData = new ViewDataDictionary(container.ViewData) + { + Model = model + } + }; + } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/Mvc/ViewDataDictionaryExtensions.cs b/src/Umbraco.Web/Mvc/ViewDataDictionaryExtensions.cs index 71cebe736a..1e088c3744 100644 --- a/src/Umbraco.Web/Mvc/ViewDataDictionaryExtensions.cs +++ b/src/Umbraco.Web/Mvc/ViewDataDictionaryExtensions.cs @@ -1,4 +1,4 @@ -using System.Web.Mvc; +using System.Web.Mvc; using Umbraco.Core; namespace Umbraco.Web.Mvc @@ -15,4 +15,4 @@ namespace Umbraco.Web.Mvc destination.MergeLeft(source); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/NotificationServiceExtensions.cs b/src/Umbraco.Web/NotificationServiceExtensions.cs index b6182590c8..a93930b7b8 100644 --- a/src/Umbraco.Web/NotificationServiceExtensions.cs +++ b/src/Umbraco.Web/NotificationServiceExtensions.cs @@ -1,147 +1,145 @@ -using System; +using System; using System.Globalization; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; -using umbraco; -using umbraco.BusinessLogic.Actions; -using umbraco.interfaces; +using Umbraco.Core.Models; +using Umbraco.Web._Legacy.Actions; using System.Collections.Generic; +using Umbraco.Core.Models.Entities; +using Umbraco.Web.Composing; namespace Umbraco.Web { - //NOTE: all of these require an UmbracoContext because currently to send the notifications we need an HttpContext, this is based on legacy code + // TODO: all of these require an UmbracoContext because currently to send the notifications we need an HttpContext, this is based on legacy code // for which probably requires updating so that these can be sent outside of the http context. internal static class NotificationServiceExtensions { - internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, ApplicationContext applicationContext) + internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action) { - if (UmbracoContext.Current == null) + if (Current.UmbracoContext == null) { - LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); return; } - service.SendNotification(entity, action, UmbracoContext.Current); + service.SendNotification(entity, action, Current.UmbracoContext, Current.Services.TextService, UmbracoConfig.For.GlobalSettings()); } - internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, ApplicationContext applicationContext) + internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action) { - if (UmbracoContext.Current == null) + if (Current.UmbracoContext == null) { - LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); return; } - service.SendNotification(entities, action, UmbracoContext.Current); + service.SendNotification(entities, action, Current.UmbracoContext, Current.Services.TextService, UmbracoConfig.For.GlobalSettings()); } - internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext) + //internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext) + //{ + // if (umbracoContext == null) + // { + // Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + // return; + // } + // service.SendNotification(entity, action, umbracoContext); + //} + + //internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext) + //{ + // if (umbracoContext == null) + // { + // Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + // return; + // } + // service.SendNotification(entities, action, umbracoContext); + //} + + internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) { if (umbracoContext == null) { - LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - return; - } - service.SendNotification(entity, action, umbracoContext, umbracoContext.Application); - } - - internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext) - { - if (umbracoContext == null) - { - LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); - return; - } - service.SendNotification(entities, action, umbracoContext, umbracoContext.Application); - } - - internal static void SendNotification(this INotificationService service, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) - { - if (umbracoContext == null) - { - LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); return; } var user = umbracoContext.Security.CurrentUser; + var userService = Current.Services.UserService; // fixme inject - //if there is no current user, then use the admin + //if there is no current user, then use the admin if (user == null) { - LogHelper.Debug(typeof(NotificationServiceExtensions), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); - user = applicationContext.Services.UserService.GetUserById(0); + Current.Logger.Debug(typeof(NotificationServiceExtensions), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); + user = userService.GetUserById(Constants.Security.SuperId); if (user == null) { - LogHelper.Warn(typeof(NotificationServiceExtensions), "Noticiations can not be sent, no admin user with id 0 could be resolved"); + Current.Logger.Warn(typeof(NotificationServiceExtensions), $"Noticiations can not be sent, no admin user with id {Constants.Security.SuperId} could be resolved"); return; } } - service.SendNotification(user, entity, action, umbracoContext, applicationContext); + service.SendNotification(user, entity, action, umbracoContext, textService, globalSettings); } - internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) + internal static void SendNotification(this INotificationService service, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) { if (umbracoContext == null) { - LogHelper.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); + Current.Logger.Warn(typeof(NotificationServiceExtensions), "Cannot send notifications, there is no current UmbracoContext"); return; } var user = umbracoContext.Security.CurrentUser; + var userService = Current.Services.UserService; // fixme inject - //if there is no current user, then use the admin + //if there is no current user, then use the admin if (user == null) { - LogHelper.Debug(typeof(NotificationServiceExtensions), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); - user = applicationContext.Services.UserService.GetUserById(0); + Current.Logger.Debug(typeof(NotificationServiceExtensions), "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); + user = userService.GetUserById(Constants.Security.SuperId); if (user == null) { - LogHelper.Warn(typeof(NotificationServiceExtensions), "Noticiations can not be sent, no admin user with id 0 could be resolved"); + Current.Logger.Warn(typeof(NotificationServiceExtensions), $"Noticiations can not be sent, no admin user with id {Constants.Security.SuperId} could be resolved"); return; } } - service.SendNotification(user, entities, action, umbracoContext, applicationContext); + service.SendNotification(user, entities, action, umbracoContext, textService, globalSettings); } - internal static void SendNotification(this INotificationService service, IUser sender, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) + internal static void SendNotification(this INotificationService service, IUser sender, IUmbracoEntity entity, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) { - if (sender == null) throw new ArgumentNullException("sender"); - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + if (sender == null) throw new ArgumentNullException(nameof(sender)); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); - - applicationContext.Services.NotificationService.SendNotifications( + service.SendNotifications( sender, entity, action.Letter.ToString(CultureInfo.InvariantCulture), - ui.Text("actions", action.Alias), + textService.Localize("actions", action.Alias), umbracoContext.HttpContext, - (mailingUser, strings) => ui.Text("notifications", "mailSubject", strings, mailingUser), + (mailingUser, strings) => textService.Localize("notifications/mailSubject", mailingUser.GetUserCulture(textService, globalSettings), strings), (mailingUser, strings) => UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail - ? ui.Text("notifications", "mailBody", strings, mailingUser) - : ui.Text("notifications", "mailBodyHtml", strings, mailingUser)); + ? textService.Localize("notifications/mailBody", mailingUser.GetUserCulture(textService, globalSettings), strings) + : textService.Localize("notifications/mailBodyHtml", mailingUser.GetUserCulture(textService, globalSettings), strings)); } - internal static void SendNotification(this INotificationService service, IUser sender, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ApplicationContext applicationContext) + internal static void SendNotification(this INotificationService service, IUser sender, IEnumerable entities, IAction action, UmbracoContext umbracoContext, ILocalizedTextService textService, IGlobalSettings globalSettings) { - if (sender == null) throw new ArgumentNullException("sender"); - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + if (sender == null) throw new ArgumentNullException(nameof(sender)); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); - - applicationContext.Services.NotificationService.SendNotifications( + service.SendNotifications( sender, entities, action.Letter.ToString(CultureInfo.InvariantCulture), - ui.Text("actions", action.Alias), + textService.Localize("actions", action.Alias), umbracoContext.HttpContext, - (mailingUser, strings) => ui.Text("notifications", "mailSubject", strings, mailingUser), + (mailingUser, strings) => textService.Localize("notifications/mailSubject", mailingUser.GetUserCulture(textService, globalSettings), strings), (mailingUser, strings) => UmbracoConfig.For.UmbracoSettings().Content.DisableHtmlEmail - ? ui.Text("notifications", "mailBody", strings, mailingUser) - : ui.Text("notifications", "mailBodyHtml", strings, mailingUser)); + ? textService.Localize("notifications/mailBody", mailingUser.GetUserCulture(textService, globalSettings), strings) + : textService.Localize("notifications/mailBodyHtml", mailingUser.GetUserCulture(textService, globalSettings), strings)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs b/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs index 03c2dc631d..8b4d46a373 100644 --- a/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs +++ b/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs @@ -1,4 +1,4 @@ -using System; +using System; using Owin; namespace Umbraco.Web @@ -12,4 +12,4 @@ namespace Umbraco.Web public IAppBuilder AppBuilder { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PluginManagerExtensions.cs b/src/Umbraco.Web/PluginManagerExtensions.cs deleted file mode 100644 index 76b71245e5..0000000000 --- a/src/Umbraco.Web/PluginManagerExtensions.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using Umbraco.Core; -using Umbraco.Core.Media; -using Umbraco.Web.Mvc; -using Umbraco.Web.Routing; -using Umbraco.Web.Trees; -using Umbraco.Web.WebApi; -using umbraco; -using umbraco.interfaces; -using Umbraco.Web.Search; - -namespace Umbraco.Web -{ - /// - /// Extension methods for the PluginTypeResolver - /// - public static class PluginManagerExtensions - { - - /// - /// Returns all available TreeApiController's in application that are attribute with TreeAttribute - /// - /// - /// - internal static IEnumerable ResolveAttributedTreeControllers(this PluginManager resolver) - { - return resolver.ResolveTypesWithAttribute(); - } - - internal static IEnumerable ResolveSurfaceControllers(this PluginManager resolver) - { - return resolver.ResolveTypes(); - } - - internal static IEnumerable ResolveUmbracoApiControllers(this PluginManager resolver) - { - return resolver.ResolveTypes(); - } - - /// - /// Returns all available ITrees in application - /// - /// - /// - internal static IEnumerable ResolveTrees(this PluginManager resolver) - { - return resolver.ResolveTypes(); - } - - /// - /// Returns all available in application - /// - /// - /// - internal static IEnumerable ResolveSearchableTrees(this PluginManager resolver) - { - return resolver.ResolveTypes(); - } - - /// - /// Returns all classes attributed with RestExtensionAttribute attribute - /// - /// - /// - internal static IEnumerable ResolveRestExtensions(this PluginManager resolver) - { - return resolver.ResolveAttributedTypes(); - } - - /// - /// Returns all classes attributed with XsltExtensionAttribute attribute - /// - /// - /// - internal static IEnumerable ResolveXsltExtensions(this PluginManager resolver) - { - return resolver.ResolveAttributedTypes(); - } - - /// - /// Returns all IThumbnailProvider classes - /// - /// - /// - internal static IEnumerable ResolveThumbnailProviders(this PluginManager resolver) - { - return resolver.ResolveTypes(); - } - - /// - /// Returns all IImageUrlProvider classes - /// - /// - /// - internal static IEnumerable ResolveImageUrlProviders(this PluginManager resolver) - { - return resolver.ResolveTypes(); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Profiling/WebProfiler.cs b/src/Umbraco.Web/Profiling/WebProfiler.cs deleted file mode 100644 index fd980db2d1..0000000000 --- a/src/Umbraco.Web/Profiling/WebProfiler.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System; -using System.Threading; -using System.Web; -using StackExchange.Profiling; -using StackExchange.Profiling.SqlFormatters; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Profiling; - -namespace Umbraco.Web.Profiling -{ - /// - /// A profiler used for web based activity based on the MiniProfiler framework - /// - internal class WebProfiler : IProfiler - { - private const string BootRequestItemKey = "Umbraco.Web.Profiling.WebProfiler__isBootRequest"; - private readonly WebProfilerProvider _provider; - private int _first; - - /// - /// Constructor - /// - internal WebProfiler() - { - // create our own provider, which can provide a profiler even during boot - // MiniProfiler's default cannot because there's no HttpRequest in HttpContext - _provider = new WebProfilerProvider(); - - // settings - MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); - MiniProfiler.Settings.StackMaxLength = 5000; - MiniProfiler.Settings.ProfilerProvider = _provider; - - //Binds to application events to enable the MiniProfiler with a real HttpRequest - UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; - } - - /// - /// Handle the Init event o fthe UmbracoApplication which allows us to subscribe to the HttpApplication events - /// - /// - /// - void UmbracoApplicationApplicationInit(object sender, EventArgs e) - { - var app = sender as HttpApplication; - if (app == null) return; - - if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) - { - //If we don't have a high enough trust level we cannot bind to the events - LogHelper.Info("Cannot start the WebProfiler since the application is running in Medium trust"); - } - else - { - app.BeginRequest += UmbracoApplicationBeginRequest; - app.EndRequest += UmbracoApplicationEndRequest; - } - } - - void UmbracoApplicationBeginRequest(object sender, EventArgs e) - { - // if this is the first request, notify our own provider that this request is the boot request - var first = Interlocked.Exchange(ref _first, 1) == 0; - if (first) - { - _provider.BeginBootRequest(); - ((HttpApplication)sender).Context.Items[BootRequestItemKey] = true; - // and no need to start anything, profiler is already there - } - // else start a profiler, the normal way - else if (ShouldProfile(sender)) - Start(); - } - - void UmbracoApplicationEndRequest(object sender, EventArgs e) - { - // if this is the boot request, or if we should profile this request, stop - // (the boot request is always profiled, no matter what) - var isBootRequest = ((HttpApplication)sender).Context.Items[BootRequestItemKey] != null; - if (isBootRequest) - _provider.EndBootRequest(); - if (isBootRequest || ShouldProfile(sender)) - Stop(); - } - - private bool ShouldProfile(object sender) - { - if (GlobalSettings.DebugMode == false) - return false; - - //will not run in medium trust - if (SystemUtilities.GetCurrentTrustLevel() < AspNetHostingPermissionLevel.High) - return false; - - var request = TryGetRequest(sender); - - if (request.Success == false || request.Result.Url.IsClientSideRequest()) - return false; - - //if there is an umbDebug query string than profile it - bool umbDebug; - if (string.IsNullOrEmpty(request.Result.QueryString["umbDebug"]) == false && bool.TryParse(request.Result.QueryString["umbDebug"], out umbDebug)) - return true; - - //if there is an umbDebug header than profile it - if (string.IsNullOrEmpty(request.Result.Headers["X-UMB-DEBUG"]) == false && bool.TryParse(request.Result.Headers["X-UMB-DEBUG"], out umbDebug)) - return true; - - //everything else is ok to profile - return false; - } - - /// - /// Render the UI to display the profiler - /// - /// - /// - /// Generally used for HTML displays - /// - public string Render() - { - return MiniProfiler.RenderIncludes(RenderPosition.Right).ToString(); - } - - /// - /// Profile a step - /// - /// - /// - /// - /// Use the 'using(' syntax - /// - public IDisposable Step(string name) - { - return GlobalSettings.DebugMode == false ? null : MiniProfiler.Current.Step(name); - } - - /// - /// Start the profiler - /// - public void Start() - { - MiniProfiler.Start(); - } - - /// - /// Start the profiler - /// - /// - /// set discardResults to false when you want to abandon all profiling, this is useful for - /// when someone is not authenticated or you want to clear the results based on some other mechanism. - /// - public void Stop(bool discardResults = false) - { - MiniProfiler.Stop(discardResults); - } - - /// - /// Gets the request object from the app instance if it is available - /// - /// The application object - /// - private Attempt TryGetRequest(object sender) - { - var app = sender as HttpApplication; - if (app == null) return Attempt.Fail(); - - try - { - var req = app.Request; - return Attempt.Succeed(new HttpRequestWrapper(req)); - } - catch (HttpException ex) - { - return Attempt.Fail(ex); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Profiling/WebProfilerProvider.cs b/src/Umbraco.Web/Profiling/WebProfilerProvider.cs deleted file mode 100644 index ffd1871ecc..0000000000 --- a/src/Umbraco.Web/Profiling/WebProfilerProvider.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Threading; -using System.Web; -using StackExchange.Profiling; - -namespace Umbraco.Web.Profiling -{ - /// - /// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows - /// us to profile items during app startup - before an HttpRequest is created - /// - /// - /// Once the boot phase is changed to StartupPhase.Request then the base class (default) provider will handle all - /// profiling data and this sub class no longer performs any logic. - /// - internal class WebProfilerProvider : WebRequestProfilerProvider - { - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); - private MiniProfiler _startupProfiler; - private int _first; - private volatile BootPhase _bootPhase; - - public WebProfilerProvider() - { - // booting... - _bootPhase = BootPhase.Boot; - } - - /// - /// Indicates the boot phase. - /// - private enum BootPhase - { - Boot = 0, // boot phase (before the 1st request even begins) - BootRequest = 1, // request boot phase (during the 1st request) - Booted = 2 // done booting - } - - public void BeginBootRequest() - { - _locker.EnterWriteLock(); - try - { - if (_bootPhase != BootPhase.Boot) - throw new InvalidOperationException("Invalid boot phase."); - _bootPhase = BootPhase.BootRequest; - - // assign the profiler to be the current MiniProfiler for the request - // is's already active, starting and all - HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; - } - finally - { - _locker.ExitWriteLock(); - } - } - - public void EndBootRequest() - { - _locker.EnterWriteLock(); - try - { - if (_bootPhase != BootPhase.BootRequest) - throw new InvalidOperationException("Invalid boot phase."); - _bootPhase = BootPhase.Booted; - - _startupProfiler = null; - } - finally - { - _locker.ExitWriteLock(); - } - } - - /// - /// Executed when a profiling operation is started - /// - /// - /// - /// - /// This checks if the startup phase is not None, if this is the case and the current profiler is NULL - /// then this sets the startup profiler to be active. Otherwise it just calls the base class Start method. - /// - public override MiniProfiler Start(ProfileLevel level) - { - var first = Interlocked.Exchange(ref _first, 1) == 0; - if (first == false) return base.Start(level); - - _startupProfiler = new MiniProfiler("http://localhost/umbraco-startup") { Name = "StartupProfiler" }; - SetProfilerActive(_startupProfiler); - return _startupProfiler; - } - - /// - /// This returns the current profiler - /// - /// - /// - /// If the boot phase is not None, then this will return the startup profiler (this), otherwise - /// returns the base class - /// - public override MiniProfiler GetCurrentProfiler() - { - // if not booting then just use base (fast) - // no lock, _bootPhase is volatile - if (_bootPhase == BootPhase.Booted) - return base.GetCurrentProfiler(); - - // else - try - { - var current = base.GetCurrentProfiler(); - return current ?? _startupProfiler; - } - catch - { - return _startupProfiler; - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index 9f235e00d5..b7d046787e 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -1,53 +1,41 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Security; -// -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -// -[assembly: AssemblyTitle("umbraco.presentation")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyTitle("Umbraco.Web")] +[assembly: AssemblyDescription("Umbraco Web")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyProduct("Umbraco CMS")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("ce9d3539-299e-40d3-b605-42ac423e24fa")] -//This is required so that Medium trust works and this is because of this class: -// umbraco.presentation.templateControls.ItemDesigner since this class cannot inherit from -// the System.Web.UI.Design.ControlDesigner in partial trust (or something along those lines) -// if we remove this class then we won't need to do this. -[assembly: System.Security.SecurityRules(System.Security.SecurityRuleSet.Level1)] +// Umbraco Cms +[assembly: InternalsVisibleTo("Umbraco.Web.UI")] [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] -[assembly: InternalsVisibleTo("umbraco.MacroEngines")] -[assembly: InternalsVisibleTo("Umbraco.Web.UI")] -[assembly: InternalsVisibleTo("umbraco.webservices")] -[assembly: InternalsVisibleTo("Concorde.Sync")] -[assembly: InternalsVisibleTo("Umbraco.Courier.Core")] -[assembly: InternalsVisibleTo("Umbraco.Courier.Persistence")] + +[assembly: InternalsVisibleTo("Umbraco.VisualStudio")] // fixme - what is this? +[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder")] // fixme - why? +[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.AspNet")] // fixme - why? + +// Allow this to be mocked in our unit tests +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] + +// Umbraco Deploy [assembly: InternalsVisibleTo("Umbraco.Deploy")] [assembly: InternalsVisibleTo("Umbraco.Deploy.UI")] [assembly: InternalsVisibleTo("Umbraco.Deploy.Cloud")] -[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder")] -[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.AspNet")] -[assembly: InternalsVisibleTo("Umbraco.Headless")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +// Umbraco Forms [assembly: InternalsVisibleTo("Umbraco.Forms.Core")] [assembly: InternalsVisibleTo("Umbraco.Forms.Core.Providers")] [assembly: InternalsVisibleTo("Umbraco.Forms.Web")] +// Umbraco Headless +[assembly: InternalsVisibleTo("Umbraco.Headless")] + +// v8 +[assembly: InternalsVisibleTo("Umbraco.Compat7")] -//allow custom unit-testing code to access internals through custom adapters -[assembly: InternalsVisibleTo("Umbraco.VisualStudio")] // backwards compat. -[assembly: InternalsVisibleTo("Umbraco.UnitTesting.Adapter")] // new, more imperative name diff --git a/src/Umbraco.Web/Properties/Settings1.Designer.cs b/src/Umbraco.Web/Properties/Settings1.Designer.cs index 1b72efc115..368d6bdae3 100644 --- a/src/Umbraco.Web/Properties/Settings1.Designer.cs +++ b/src/Umbraco.Web/Properties/Settings1.Designer.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.6.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs index 4407b2fcdf..dc16dfb3df 100644 --- a/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs @@ -1,5 +1,7 @@ -using Umbraco.Core; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -7,34 +9,28 @@ namespace Umbraco.Web.PropertyEditors /// A property editor to allow multiple checkbox selection of pre-defined items. /// /// - /// Due to remaining backwards compatible, this stores the id of the checkbox items in the database + /// Due to remaining backwards compatible, this stores the id of the checkbox items in the database /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the string value is published /// in cache and not the int ID. /// - [PropertyEditor(Constants.PropertyEditors.CheckBoxListAlias, "Checkbox list", "checkboxlist", Icon="icon-bulleted-list", Group="lists")] - public class CheckBoxListPropertyEditor : PropertyEditor + [DataEditor(Constants.PropertyEditors.Aliases.CheckBoxList, "Checkbox list", "checkboxlist", Icon="icon-bulleted-list", Group="lists")] + public class CheckBoxListPropertyEditor : DataEditor { + private readonly ILocalizedTextService _textService; /// - /// Return a custom pre-value editor + /// The constructor will setup the property editor based on the attribute if one is found /// - /// - /// - /// We are just going to re-use the ValueListPreValueEditor - /// - protected override PreValueEditor CreatePreValueEditor() + public CheckBoxListPropertyEditor(ILogger logger, ILocalizedTextService textService) + : base(logger) { - return new ValueListPreValueEditor(); + _textService = textService; } - /// - /// We need to override the value editor so that we can ensure the string value is published in cache and not the integer ID value. - /// - /// - protected override PropertyValueEditor CreateValueEditor() - { - return new PublishValuesMultipleValueEditor(false, base.CreateValueEditor()); - } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new ValueListConfigurationEditor(_textService); + /// + protected override IDataValueEditor CreateValueEditor() => new PublishValuesMultipleValueEditor(false, Attribute); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs deleted file mode 100644 index a504ed0431..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - internal class ColorListPreValueEditor : ValueListPreValueEditor - { - - public ColorListPreValueEditor() - { - var field = Fields.First(); - - //use a custom editor too - field.View = "views/propertyeditors/colorpicker/colorpicker.prevalues.html"; - //change the description - field.Description = "Add and remove colors"; - //change the label - field.Name = "Add color"; - //need to have some custom validation happening here - field.Validators.Add(new ColorListValidator()); - - Fields.Insert(0, new PreValueField - { - Name = "Include labels?", - View = "boolean", - Key = "useLabel", - Description = "Stores colors as a Json object containing both the color hex string and label, rather than just the hex string." - }); - } - - public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) - { - var dictionary = persistedPreVals.FormatAsDictionary(); - var items = dictionary - .Where(x => x.Key != "useLabel") - .ToDictionary(x => x.Value.Id, x => x.Value.Value); - - var items2 = new Dictionary(); - foreach (var item in items) - { - if (item.Value.DetectIsJson() == false) - { - items2[item.Key] = item.Value; - continue; - } - - try - { - items2[item.Key] = JsonConvert.DeserializeObject(item.Value); - } - catch - { - // let's say parsing Json failed, so what we have is the string - build json - items2[item.Key] = new JObject { { "color", item.Value }, { "label", item.Value } }; - } - } - - var result = new Dictionary { { "items", items2 } }; - var useLabel = dictionary.ContainsKey("useLabel") && dictionary["useLabel"].Value == "1"; - if (useLabel) - result["useLabel"] = dictionary["useLabel"].Value; - - return result; - } - - public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue) - { - var val = editorValue["items"] as JArray; - var result = new Dictionary(); - if (val == null) return result; - - try - { - object useLabelObj; - var useLabel = false; - if (editorValue.TryGetValue("useLabel", out useLabelObj)) - { - useLabel = useLabelObj is string && (string) useLabelObj == "1"; - result["useLabel"] = new PreValue(useLabel ? "1" : "0"); - } - - // get all non-empty values - var index = 0; - foreach (var preValue in val.OfType() - .Where(x => x["value"] != null) - .Select(x => - { - var idString = x["id"] == null ? "0" : x["id"].ToString(); - int id; - if (int.TryParse(idString, out id) == false) id = 0; - - var color = x["value"].ToString(); - if (string.IsNullOrWhiteSpace(color)) return null; - - var label = x["label"].ToString(); - return new PreValue(id, useLabel - ? JsonConvert.SerializeObject(new { value = color, label = label }) - : color); - }) - .WhereNotNull()) - { - result.Add(index.ToInvariantString(), preValue); - index++; - } - } - catch (Exception ex) - { - LogHelper.Error("Could not deserialize the posted value: " + val, ex); - } - - return result; - } - - internal class ColorListValidator : IPropertyValidator - { - public IEnumerable Validate(object value, PreValueCollection preValues, PropertyEditor editor) - { - var json = value as JArray; - if (json == null) yield break; - - //validate each item which is a json object - for (var index = 0; index < json.Count; index++) - { - var i = json[index]; - var jItem = i as JObject; - if (jItem == null || jItem["value"] == null) continue; - - //NOTE: we will be removing empty values when persisting so no need to validate - var asString = jItem["value"].ToString(); - if (asString.IsNullOrWhiteSpace()) continue; - - if (Regex.IsMatch(asString, "^([0-9a-f]{3}|[0-9a-f]{6})$", RegexOptions.IgnoreCase) == false) - { - yield return new ValidationResult("The value " + asString + " is not a valid hex color", new[] - { - //we'll make the server field the index number of the value so it can be wired up to the view - "item_" + index.ToInvariantString() - }); - } - } - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs new file mode 100644 index 0000000000..ddc4b50858 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + internal class ColorPickerConfigurationEditor : ConfigurationEditor + { + public ColorPickerConfigurationEditor() + { + var items = Fields.First(x => x.Key == "items"); + + // customize the items field + items.View = "views/propertyeditors/colorpicker/colorpicker.prevalues.html"; + items.Description = "Add and remove colors"; + items.Name = "Add color"; + items.Validators.Add(new ColorListValidator()); + } + + public override Dictionary ToConfigurationEditor(ColorPickerConfiguration configuration) + { + var items = configuration?.Items.ToDictionary(x => x.Id.ToString(), x => GetItemValue(x, configuration.UseLabel)) ?? new object(); + var useLabel = configuration?.UseLabel ?? false; + + return new Dictionary + { + { "items", items }, + { "useLabel", useLabel } + }; + } + + private object GetItemValue(ValueListConfiguration.ValueListItem item, bool useLabel) + { + if (useLabel) + { + return item.Value.DetectIsJson() + ? JsonConvert.DeserializeObject(item.Value) + : new JObject { { "color", item.Value }, { "label", item.Value } }; + } + + if (!item.Value.DetectIsJson()) + return item.Value; + + var jobject = (JObject) JsonConvert.DeserializeObject(item.Value); + return jobject.Property("color").Value.Value(); + } + + public override ColorPickerConfiguration FromConfigurationEditor(IDictionary editorValues, ColorPickerConfiguration configuration) + { + var output = new ColorPickerConfiguration(); + + if (!editorValues.TryGetValue("items", out var jjj) || !(jjj is JArray jItems)) + return output; // oops + + // handle useLabel + if (editorValues.TryGetValue("useLabel", out var useLabelObj)) + output.UseLabel = useLabelObj.TryConvertTo(); + + // auto-assigning our ids, get next id from existing values + var nextId = 1; + if (configuration?.Items != null && configuration.Items.Count > 0) + nextId = configuration.Items.Max(x => x.Id) + 1; + + // create ValueListItem instances - sortOrder is ignored here + foreach (var item in jItems.OfType()) + { + var value = item.Property("value")?.Value?.Value(); + if (string.IsNullOrWhiteSpace(value)) continue; + + var id = item.Property("id")?.Value?.Value() ?? 0; + if (id >= nextId) nextId = id + 1; + + // if using a label, replace color by json blob + // (a pity we have to serialize here!) + if (output.UseLabel) + { + var label = item.Property("label")?.Value?.Value(); + value = JsonConvert.SerializeObject(new { value, label }); + } + + output.Items.Add(new ValueListConfiguration.ValueListItem { Id = id, Value = value }); + } + + // ensure ids + foreach (var item in output.Items) + if (item.Id == 0) + item.Id = nextId++; + + return output; + } + + internal class ColorListValidator : IValueValidator + { + public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) + { + if (!(value is JArray json)) yield break; + + //validate each item which is a json object + for (var index = 0; index < json.Count; index++) + { + var i = json[index]; + if (!(i is JObject jItem) || jItem["value"] == null) continue; + + //NOTE: we will be removing empty values when persisting so no need to validate + var asString = jItem["value"].ToString(); + if (asString.IsNullOrWhiteSpace()) continue; + + if (Regex.IsMatch(asString, "^([0-9a-f]{3}|[0-9a-f]{6})$", RegexOptions.IgnoreCase) == false) + { + yield return new ValidationResult("The value " + asString + " is not a valid hex color", new[] + { + //we'll make the server field the index number of the value so it can be wired up to the view + "item_" + index.ToInvariantString() + }); + } + } + } + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs index 0b94ddd993..5e7275adb0 100644 --- a/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs @@ -1,22 +1,17 @@ -using Umbraco.Core; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.ColorPickerAlias, "Color Picker", "colorpicker", Icon="icon-colorpicker", Group="Pickers")] - public class ColorPickerPropertyEditor : PropertyEditor + [DataEditor(Constants.PropertyEditors.Aliases.ColorPicker, "Color Picker", "colorpicker", Icon="icon-colorpicker", Group="Pickers")] + public class ColorPickerPropertyEditor : DataEditor { - /// - /// Return a custom pre-value editor - /// - /// - /// - /// ColorListPreValueEditor uses the ValueListPreValueEditor with a custom view and controller. - /// - protected override PreValueEditor CreatePreValueEditor() - { - return new ColorListPreValueEditor(); - } + public ColorPickerPropertyEditor(ILogger logger) + : base(logger) + { } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new ColorPickerConfigurationEditor(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs deleted file mode 100644 index 5b99264113..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - /// - /// Content property editor that stores UDI - /// - [PropertyEditor(Constants.PropertyEditors.ContentPicker2Alias, "Content Picker", PropertyEditorValueTypes.String, "contentpicker", IsParameterEditor = true, Group = "Pickers")] - public class ContentPicker2PropertyEditor : PropertyEditor - { - - public ContentPicker2PropertyEditor() - { - InternalPreValues = new Dictionary - { - {"startNodeId", "-1"}, - {"showOpenButton", "0"}, - {"showEditButton", "0"}, - {"showPathOnHover", "0"}, - {"idType", "udi"} - }; - } - - internal IDictionary InternalPreValues; - public override IDictionary DefaultPreValues - { - get { return InternalPreValues; } - set { InternalPreValues = value; } - } - - protected override PreValueEditor CreatePreValueEditor() - { - return new ContentPickerPreValueEditor(); - } - - internal class ContentPickerPreValueEditor : PreValueEditor - { - public ContentPickerPreValueEditor() - { - //create the fields - Fields.Add(new PreValueField() - { - Key = "showOpenButton", - View = "boolean", - Name = "Show open button (this feature is in preview!)", - Description = "Opens the node in a dialog" - }); - Fields.Add(new PreValueField() - { - Key = "startNodeId", - View = "treepicker", - Name = "Start node", - Config = new Dictionary - { - {"idType", "udi"} - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs new file mode 100644 index 0000000000..fe6ab6a054 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs @@ -0,0 +1,13 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + public class ContentPickerConfiguration + { + [ConfigurationField("showOpenButton", "Show open button (this feature is in beta!)", "boolean", Description = "Opens the node in a dialog")] + public bool ShowOpenButton { get; set; } + + [ConfigurationField("startNodeId", "Start node", "treepicker")] // + config in configuration editor ctor + public int StartNodeId { get; set; } = -1; // default value is -1 + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerConfigurationEditor.cs new file mode 100644 index 0000000000..5be97e5f76 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerConfigurationEditor.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + internal class ContentPickerConfigurationEditor : ConfigurationEditor + { + public ContentPickerConfigurationEditor() + { + // configure fields + Field(nameof(ContentPickerConfiguration.StartNodeId)) + .Config = new Dictionary { { "idType", "udi" } }; + } + + public override IDictionary ToValueEditor(object configuration) + { + // these are not configuration fields, but constants required by the value editor + var d = base.ToValueEditor(configuration); + d["showEditButton"] = false; + d["showPathOnHover"] = false; + d["idType"] = "udi"; + return d; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs index 52e6e1d6bd..391257ca58 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerPropertyEditor.cs @@ -1,32 +1,23 @@ -using System; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - - /// - /// Legacy content property editor that stores Integer Ids - /// - [Obsolete("This editor is obsolete, use ContentPickerPropertyEditor2 instead which stores UDI")] - [PropertyEditor(Constants.PropertyEditors.ContentPickerAlias, "(Obsolete) Content Picker", PropertyEditorValueTypes.Integer, "contentpicker", IsParameterEditor = true, Group = "Pickers", IsDeprecated = true)] - public class ContentPickerPropertyEditor : ContentPicker2PropertyEditor - { - public ContentPickerPropertyEditor() - { - InternalPreValues["idType"] = "int"; - } - - /// - /// overridden to change the pre-value picker to use INT ids - /// - /// - protected override PreValueEditor CreatePreValueEditor() - { - var preValEditor = base.CreatePreValueEditor(); - preValEditor.Fields.Single(x => x.Key == "startNodeId").Config["idType"] = "int"; - return preValEditor; - } - } -} \ No newline at end of file +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Content property editor that stores UDI + /// + [DataEditor(Constants.PropertyEditors.Aliases.ContentPicker, EditorType.PropertyValue | EditorType.MacroParameter, "Content Picker", "contentpicker", ValueType = ValueTypes.String, Group = "Pickers")] + public class ContentPickerPropertyEditor : DataEditor + { + public ContentPickerPropertyEditor(ILogger logger) + : base(logger) + { } + + protected override IConfigurationEditor CreateConfigurationEditor() + { + return new ContentPickerConfigurationEditor(); + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/DateConfiguration.cs b/src/Umbraco.Web/PropertyEditors/DateConfiguration.cs new file mode 100644 index 0000000000..d5409e0c92 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DateConfiguration.cs @@ -0,0 +1,13 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the date value editor. + /// + public class DateConfiguration + { + [ConfigurationField("format", "Date format", "textstring", Description = "If left empty then the format is YYYY-MM-DD. (see momentjs.com for supported formats)")] + public string Format { get; set; } = "YYYY-MM-DD"; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DateConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/DateConfigurationEditor.cs new file mode 100644 index 0000000000..b0839c5157 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DateConfigurationEditor.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the date value editor. + /// + public class DateConfigurationEditor : ConfigurationEditor + { + public override IDictionary ToValueEditor(object configuration) + { + var d = base.ToValueEditor(configuration); + d["pickTime"] = false; + return d; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/DatePreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/DatePreValueEditor.cs deleted file mode 100644 index dde4d6c2f1..0000000000 --- a/src/Umbraco.Web/PropertyEditors/DatePreValueEditor.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - internal class DatePreValueEditor : PreValueEditor - { - [PreValueField("format", "Date format", "textstring", Description = "If left empty then the format is YYYY-MM-DD. (see momentjs.com for supported formats)")] - public string DefaultValue { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs index a98c96e759..7f65658b13 100644 --- a/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs @@ -1,66 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.DateAlias, "Date", PropertyEditorValueTypes.Date, "datepicker", Icon="icon-calendar")] - public class DatePropertyEditor : PropertyEditor + [DataEditor(Constants.PropertyEditors.Aliases.Date, "Date", "datepicker", ValueType = ValueTypes.Date, Icon="icon-calendar")] + public class DatePropertyEditor : DataEditor { - public DatePropertyEditor() - { - _defaultPreVals = new Dictionary - { - {"format", "YYYY-MM-DD"}, - {"pickTime", false} - }; - } + public DatePropertyEditor(ILogger logger): base(logger) + { } - private IDictionary _defaultPreVals; + /// + protected override IDataValueEditor CreateValueEditor() => new DateValueEditor(Attribute); - public override IDictionary DefaultPreValues - { - get { return _defaultPreVals; } - set { _defaultPreVals = value; } - } - - protected override PropertyValueEditor CreateValueEditor() - { - return new DatePropertyValueEditor(base.CreateValueEditor()); - } - - /// - /// CUstom value editor so we can serialize with the correct date format (excluding time) - /// and includes the date validator - /// - internal class DatePropertyValueEditor : PropertyValueEditorWrapper - { - public DatePropertyValueEditor(PropertyValueEditor wrapped) : base(wrapped) - { - Validators.Add(new DateTimeValidator()); - } - - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) - { - var date = property.Value.TryConvertTo(); - if (date.Success == false || date.Result == null) - { - return string.Empty; - } - //Dates will be formatted as yyyy-MM-dd - return date.Result.Value.ToString("yyyy-MM-dd"); - } - - } - - protected override PreValueEditor CreatePreValueEditor() - { - return new DatePreValueEditor(); - } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new DateConfigurationEditor(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/DateTimeConfiguration.cs b/src/Umbraco.Web/PropertyEditors/DateTimeConfiguration.cs new file mode 100644 index 0000000000..39eec34ba6 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DateTimeConfiguration.cs @@ -0,0 +1,19 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the datetime value editor. + /// + public class DateTimeConfiguration : DateConfiguration + { + public DateTimeConfiguration() + { + // different default values + Format = "YYYY-MM-DD HH:mm:ss"; + } + + [ConfigurationField("offsetTime", "Offset time", "boolean", Description = "When enabled the time displayed will be offset with the server's timezone, this is useful for scenarios like scheduled publishing when an editor is in a different timezone than the hosted server")] + public bool OffsetTime { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DateTimeConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/DateTimeConfigurationEditor.cs new file mode 100644 index 0000000000..494d8814f4 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DateTimeConfigurationEditor.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the datetime value editor. + /// + public class DateTimeConfigurationEditor : ConfigurationEditor + { + public override IDictionary ToValueEditor(object configuration) + { + var d = base.ToValueEditor(configuration); + d["pickTime"] = true; + return d; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/DateTimePreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/DateTimePreValueEditor.cs deleted file mode 100644 index 242893cbca..0000000000 --- a/src/Umbraco.Web/PropertyEditors/DateTimePreValueEditor.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - internal class DateTimePreValueEditor : DatePreValueEditor - { - [PreValueField("offsetTime", "Offset time", "boolean", Description = "When enabled the time displayed will be offset with the server's timezone, this is useful for scenarios like scheduled publishing when an editor is in a different timezone than the hosted server")] - public bool OffsetTime { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs index 64cdba4df6..64c3d8e969 100644 --- a/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs @@ -1,55 +1,32 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Editors; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.DateTimeAlias, "Date/Time", "datepicker", ValueType = PropertyEditorValueTypes.DateTime, Icon="icon-time")] - public class DateTimePropertyEditor : PropertyEditor + /// + /// Represents a date and time property editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.DateTime, "Date/Time", "datepicker", ValueType = ValueTypes.DateTime, Icon="icon-time")] + public class DateTimePropertyEditor : DataEditor { - public DateTimePropertyEditor() - { - _defaultPreVals = new Dictionary - { - //NOTE: This is very important that we do not use .Net format's there, this format - // is the correct format for the JS picker we are using so you cannot capitalize the HH, they need to be 'hh' - {"format", "YYYY-MM-DD HH:mm:ss"}, - //a pre-value indicating if the client/server time should be offset, when set to true the date/time seen - // by the client will be offset with the server time. - // For example, this is forced to true for scheduled publishing date/time pickers - {"offsetTime", "0"} - }; - } - - private IDictionary _defaultPreVals; - /// - /// Overridden because we ONLY support Date + Time format + /// Initializes a new instance of the class. /// - public override IDictionary DefaultPreValues - { - get { return _defaultPreVals; } - set { _defaultPreVals = value; } - } + /// + public DateTimePropertyEditor(ILogger logger) + : base(logger) + { } - protected override PropertyValueEditor CreateValueEditor() + /// + protected override IDataValueEditor CreateValueEditor() { var editor = base.CreateValueEditor(); - editor.Validators.Add(new DateTimeValidator()); - return editor; } - protected override PreValueEditor CreatePreValueEditor() - { - return new DateTimePreValueEditor(); - } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new DateTimeConfigurationEditor(); } - - -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/DateTimeValidator.cs b/src/Umbraco.Web/PropertyEditors/DateTimeValidator.cs index 8a41f3c399..9e2205ca65 100644 --- a/src/Umbraco.Web/PropertyEditors/DateTimeValidator.cs +++ b/src/Umbraco.Web/PropertyEditors/DateTimeValidator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Umbraco.Core.Models; @@ -10,9 +10,9 @@ namespace Umbraco.Web.PropertyEditors /// /// Used to validate if the value is a valid date/time /// - internal class DateTimeValidator : IPropertyValidator + internal class DateTimeValidator : IValueValidator { - public IEnumerable Validate(object value, PreValueCollection preValues, PropertyEditor editor) + public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) { //don't validate if empty if (value == null || value.ToString().IsNullOrWhiteSpace()) @@ -26,11 +26,11 @@ namespace Umbraco.Web.PropertyEditors yield return new ValidationResult(string.Format("The string value {0} cannot be parsed into a DateTime", value), new[] { - //we only store a single value for this editor so the 'member' or 'field' + //we only store a single value for this editor so the 'member' or 'field' // we'll associate this error with will simply be called 'value' - "value" + "value" }); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs b/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs new file mode 100644 index 0000000000..31425fdadd --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs @@ -0,0 +1,32 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// CUstom value editor so we can serialize with the correct date format (excluding time) + /// and includes the date validator + /// + internal class DateValueEditor : DataValueEditor + { + public DateValueEditor(DataEditorAttribute attribute) + : base(attribute) + { + Validators.Add(new DateTimeValidator()); + } + + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) + { + var date = property.GetValue(languageId, segment).TryConvertTo(); + if (date.Success == false || date.Result == null) + { + return String.Empty; + } + //Dates will be formatted as yyyy-MM-dd + return date.Result.Value.ToString("yyyy-MM-dd"); + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/DecimalConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/DecimalConfigurationEditor.cs new file mode 100644 index 0000000000..22b1123b7c --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DecimalConfigurationEditor.cs @@ -0,0 +1,38 @@ +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. + /// + internal class DecimalConfigurationEditor : ConfigurationEditor + { + public DecimalConfigurationEditor() + { + Fields.Add(new ConfigurationField(new DecimalValidator()) + { + Description = "Enter the minimum amount of number to be entered", + Key = "min", + View = "decimal", + Name = "Minimum" + }); + + Fields.Add(new ConfigurationField(new DecimalValidator()) + { + Description = "Enter the intervals amount between each step of number to be entered", + Key = "step", + View = "decimal", + Name = "Step Size" + }); + + Fields.Add(new ConfigurationField(new DecimalValidator()) + { + Description = "Enter the maximum amount of number to be entered", + Key = "max", + View = "decimal", + Name = "Maximum" + }); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DecimalPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DecimalPropertyEditor.cs index 63c1964668..15b33f28b7 100644 --- a/src/Umbraco.Web/PropertyEditors/DecimalPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DecimalPropertyEditor.cs @@ -1,57 +1,32 @@ -using Umbraco.Core; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.DecimalAlias, "Decimal", PropertyEditorValueTypes.Decimal, "decimal", IsParameterEditor = true)] - public class DecimalPropertyEditor : PropertyEditor + /// + /// Represents a decimal property and parameter editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.Decimal, EditorType.PropertyValue | EditorType.MacroParameter, "Decimal", "decimal", ValueType = ValueTypes.Decimal)] + public class DecimalPropertyEditor : DataEditor { /// - /// Overridden to ensure that the value is validated + /// Initializes a new instance of the class. /// - /// - protected override PropertyValueEditor CreateValueEditor() + public DecimalPropertyEditor(ILogger logger) + : base(logger) + { } + + /// + protected override IDataValueEditor CreateValueEditor() { var editor = base.CreateValueEditor(); editor.Validators.Add(new DecimalValidator()); return editor; } - - protected override PreValueEditor CreatePreValueEditor() - { - return new DecimalPreValueEditor(); - } - /// - /// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. - /// - internal class DecimalPreValueEditor : PreValueEditor - { - public DecimalPreValueEditor() - { - //create the fields - Fields.Add(new PreValueField(new DecimalValidator()) - { - Description = "Enter the minimum amount of number to be entered", - Key = "min", - View = "decimal", - Name = "Minimum" - }); - Fields.Add(new PreValueField(new DecimalValidator()) - { - Description = "Enter the intervals amount between each step of number to be entered", - Key = "step", - View = "decimal", - Name = "Step Size" - }); - Fields.Add(new PreValueField(new DecimalValidator()) - { - Description = "Enter the maximum amount of number to be entered", - Key = "max", - View = "decimal", - Name = "Maximum" - }); - } - } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new DecimalConfigurationEditor(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/DropDownFlexibleConfiguration.cs b/src/Umbraco.Web/PropertyEditors/DropDownFlexibleConfiguration.cs new file mode 100644 index 0000000000..4d4d47ad5b --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DropDownFlexibleConfiguration.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + internal class DropDownFlexibleConfiguration : ValueListConfiguration + { + [ConfigurationField("multiple", "Enable multiple choice", "boolean", Description = "When checked, the dropdown will be a select multiple / combo box style dropdown.")] + public bool Multiple { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DropDownFlexibleConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownFlexibleConfigurationEditor.cs new file mode 100644 index 0000000000..5821823576 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DropDownFlexibleConfigurationEditor.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Web.PropertyEditors +{ + internal class DropDownFlexibleConfigurationEditor : ConfigurationEditor + { + public DropDownFlexibleConfigurationEditor(ILocalizedTextService textService) + { + var items = Fields.First(x => x.Key == "items"); + + // customize the items field + items.Name = textService.Localize("editdatatype/addPrevalue"); + items.Validators.Add(new ValueListUniqueValueValidator()); + } + + public override DropDownFlexibleConfiguration FromConfigurationEditor(IDictionary editorValues, DropDownFlexibleConfiguration configuration) + { + var output = new DropDownFlexibleConfiguration(); + + if (!editorValues.TryGetValue("items", out var jjj) || !(jjj is JArray jItems)) + return output; // oops + + // handle multiple + if (editorValues.TryGetValue("multiple", out var multipleObj)) + output.Multiple = multipleObj.TryConvertTo(); + + // auto-assigning our ids, get next id from existing values + var nextId = 1; + if (configuration?.Items != null && configuration.Items.Count > 0) + nextId = configuration.Items.Max(x => x.Id) + 1; + + // create ValueListItem instances - sortOrder is ignored here + foreach (var item in jItems.OfType()) + { + var value = item.Property("value")?.Value?.Value(); + if (string.IsNullOrWhiteSpace(value)) continue; + + var id = item.Property("id")?.Value?.Value() ?? 0; + if (id >= nextId) nextId = id + 1; + + output.Items.Add(new ValueListConfiguration.ValueListItem { Id = id, Value = value }); + } + + // ensure ids + foreach (var item in output.Items) + if (item.Id == 0) + item.Id = nextId++; + + return output; + } + + public override Dictionary ToConfigurationEditor(DropDownFlexibleConfiguration configuration) + { + var items = configuration?.Items.ToDictionary(x => x.Id.ToString(), x => x.Value) ?? new object(); + var multiple = configuration?.Multiple ?? false; + + return new Dictionary + { + { "items", items }, + { "multiple", multiple } + }; + } + + } +} diff --git a/src/Umbraco.Web/PropertyEditors/DropDownFlexiblePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownFlexiblePropertyEditor.cs new file mode 100644 index 0000000000..6acbedef63 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DropDownFlexiblePropertyEditor.cs @@ -0,0 +1,26 @@ +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Web.PropertyEditors +{ + [DataEditor(Constants.PropertyEditors.Aliases.DropDownListFlexible, "Dropdown", "dropdownFlexible", Group = "lists", Icon = "icon-indent")] + public class DropDownFlexiblePropertyEditor : DataEditor + { + private readonly ILocalizedTextService _textService; + + public DropDownFlexiblePropertyEditor(ILocalizedTextService textService, ILogger logger) + : base(logger) + { + _textService = textService; + } + + protected override IDataValueEditor CreateValueEditor() + { + return new PublishValuesMultipleValueEditor(false, Attribute); + } + + protected override IConfigurationEditor CreateConfigurationEditor() => new DropDownFlexibleConfigurationEditor(_textService); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultipleConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultipleConfigurationEditor.cs new file mode 100644 index 0000000000..a8956f9c4f --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultipleConfigurationEditor.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents a configuration editor for the "drop down list multiple" property editor. + /// + /// + /// Ensures that 'multiple' is saved for the config in the db but is not a configuration field. + /// This is mostly to maintain backwards compatibility with old property editors. Devs can now simply + /// use the "drop down" property editor and check the "multiple" configuration checkbox + /// fixme what is multiple exactly?! + /// + internal class DropDownMultipleConfigurationEditor : ValueListConfigurationEditor + { + public DropDownMultipleConfigurationEditor(ILocalizedTextService textService) + : base(textService) + { + Fields.Add(new ConfigurationField + { + Key = "multiple", + Name = "multiple", + View = "hidden", // so it does not show in the configuration editor + HideLabel = true + }); + } + + // editor... + // + // receives: + // "preValues":[ + // { + // "label":"Add prevalue", + // "description":"Add and remove values for the list", + // "hideLabel":false, + // "view":"multivalues", + // "config":{}, + // "key":"items", + // "value":{"169":{"value":"a","sortOrder":1},"170":{"value":"b","sortOrder":2},"171":{"value":"c","sortOrder":3}} + // }, + // { + // "label":"multiple", + // "description":null, + // "hideLabel":true, + // "view":"hidden", + // "config":{}, + // "key":"multiple", + // "value":"1" + // }] + // + // posts ('d' being a new value): + // [{key: "items", value: [{value: "a", sortOrder: 1, id: "169"}, {value: "c", sortOrder: 3, id: "171"}, {value: "d"}]}, {key: "multiple", value: "1"}] + // + // the 'multiple' thing never goes to DB + // values go to DB with alias 0, 1, 2 + their ID + value + // the sort order that comes back makes no sense + // + // FromEditor can totally ignore 'multiple' + + /// + public override Dictionary ToConfigurationEditor(ValueListConfiguration configuration) + { + var dictionary = base.ToConfigurationEditor(configuration); + + // always add the multiple field, as 'true' + dictionary["multiple"] = 1; + + return dictionary; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs index a4dfd6c2ad..43091b547a 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs @@ -1,7 +1,9 @@ +using System.Collections.Generic; using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -9,23 +11,20 @@ namespace Umbraco.Web.PropertyEditors /// A property editor to allow multiple selection of pre-defined items /// /// - /// Due to maintaining backwards compatibility this data type stores the value as a string which is a comma separated value of the + /// Due to maintaining backwards compatibility this data type stores the value as a string which is a comma separated value of the /// ids of the individual items so we have logic in here to deal with that. /// - [ParameterEditor("propertyTypePickerMultiple", "Name", "textbox")] - [ParameterEditor("contentTypeMultiple", "Name", "textbox")] - [ParameterEditor("tabPickerMultiple", "Name", "textbox")] - [PropertyEditor(Constants.PropertyEditors.DropDownListMultipleAlias, "Dropdown list multiple", "dropdown", Group = "lists", Icon="icon-bulleted-list")] + [DataEditor(Constants.PropertyEditors.Aliases.DropDownListMultiple, "Dropdown list multiple", "dropdown", Group = "lists", Icon="icon-bulleted-list", IsDeprecated = true)] public class DropDownMultiplePropertyEditor : DropDownMultipleWithKeysPropertyEditor { - protected override PropertyValueEditor CreateValueEditor() - { - return new PublishValuesMultipleValueEditor(false, base.CreateValueEditor()); - } + /// + /// The constructor will setup the property editor based on the attribute if one is found + /// + public DropDownMultiplePropertyEditor(ILogger logger, ILocalizedTextService textService) + : base(logger, textService) + { } + /// + protected override IDataValueEditor CreateValueEditor() => new PublishValuesMultipleValueEditor(false, Attribute); } - - - - -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs index 05ba3e2644..8b0759ea70 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs @@ -1,65 +1,37 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { /// - /// A property editor to allow multiple selection of pre-defined items + /// Represents a property editor that allows multiple selection of pre-defined items. /// /// - /// Due to maintaining backwards compatibility this data type stores the value as a string which is a comma separated value of the - /// ids of the individual items so we have logic in here to deal with that. + /// Due to backwards compatibility, this editor stores the value as a CSV string listing + /// the ids of individual items. /// - [PropertyEditor(Constants.PropertyEditors.DropdownlistMultiplePublishKeysAlias, "Dropdown list multiple, publish keys", "dropdown", Group = "lists", Icon = "icon-bulleted-list")] + [DataEditor(Constants.PropertyEditors.Aliases.DropdownlistMultiplePublishKeys, "Dropdown list multiple, publish keys", "dropdown", Group = "lists", Icon = "icon-bulleted-list", IsDeprecated = true)] public class DropDownMultipleWithKeysPropertyEditor : DropDownPropertyEditor { - protected override PropertyValueEditor CreateValueEditor() - { - return new PublishValuesMultipleValueEditor(true, base.CreateValueEditor()); - } - - protected override PreValueEditor CreatePreValueEditor() - { - return new DropDownMultiplePreValueEditor(); - } + private readonly ILocalizedTextService _textService; /// - /// A pre-value editor for the 'drop down list multiple' property editor that ensures that 'multiple' is saved for the config in the db but is not - /// rendered as a pre-value field. + /// Initializes a new instance of the class. /// - /// - /// This is mostly to maintain backwards compatibility with old property editors. Devs can now simply use the Drop down property editor and check the multiple pre-value checkbox - /// - internal class DropDownMultiplePreValueEditor : ValueListPreValueEditor + public DropDownMultipleWithKeysPropertyEditor(ILogger logger, ILocalizedTextService textService) + : base(logger, textService) { - public DropDownMultiplePreValueEditor() - { - //add the multiple field, we'll make it hidden so it is not seen in the pre-value editor - Fields.Add(new PreValueField - { - Key = "multiple", - Name = "multiple", - View = "hidden", - HideLabel = true - }); - } - - /// - /// Always - /// - /// - /// - /// - public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) - { - var returnVal = base.ConvertDbToEditor(defaultPreVals, persistedPreVals); - //always add the multiple param to true - returnVal["multiple"] = "1"; - return returnVal; - } + _textService = textService; } + /// + protected override IDataValueEditor CreateValueEditor() => new PublishValuesMultipleValueEditor(true, Attribute); + + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new DropDownMultipleConfigurationEditor(_textService); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs index 115b99dea4..b3d687d196 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs @@ -1,9 +1,11 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; using umbraco; using ClientDependency.Core; +using Umbraco.Core.Services; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.PropertyEditors @@ -16,17 +18,20 @@ namespace Umbraco.Web.PropertyEditors /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the string value is published /// in cache and not the int ID. /// - [PropertyEditor(Constants.PropertyEditors.DropDownListAlias, "Dropdown list", "dropdown", ValueType = PropertyEditorValueTypes.String, Group = "lists", Icon = "icon-indent")] + [DataEditor(Constants.PropertyEditors.Aliases.DropDownList, "Dropdown list", "dropdown", ValueType = ValueTypes.String, Group = "lists", Icon = "icon-indent", IsDeprecated = true)] public class DropDownPropertyEditor : DropDownWithKeysPropertyEditor { + /// + /// The constructor will setup the property editor based on the attribute if one is found + /// + public DropDownPropertyEditor(ILogger logger, ILocalizedTextService textService) + : base(logger, textService) + { } + /// /// We need to override the value editor so that we can ensure the string value is published in cache and not the integer ID value. /// /// - protected override PropertyValueEditor CreateValueEditor() - { - return new PublishValueValueEditor(base.CreateValueEditor()); - } - + protected override IDataValueEditor CreateValueEditor() => new PublishValueValueEditor(Attribute, Logger); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs index a33115003c..2a21860d71 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs @@ -1,5 +1,7 @@ -using Umbraco.Core; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -11,17 +13,24 @@ namespace Umbraco.Web.PropertyEditors /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the INT ID value is published /// in cache and not the string value. /// - [PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeysAlias, "Dropdown list, publishing keys", "dropdown", ValueType = PropertyEditorValueTypes.Integer, Group = "lists", Icon = "icon-indent")] - public class DropDownWithKeysPropertyEditor : PropertyEditor + [DataEditor(Constants.PropertyEditors.Aliases.DropdownlistPublishKeys, "Dropdown list, publishing keys", "dropdown", ValueType = ValueTypes.Integer, Group = "lists", Icon = "icon-indent", IsDeprecated = true)] + public class DropDownWithKeysPropertyEditor : DataEditor { + private readonly ILocalizedTextService _textService; + + /// + /// The constructor will setup the property editor based on the attribute if one is found + /// + public DropDownWithKeysPropertyEditor(ILogger logger, ILocalizedTextService textService) + : base(logger) + { + _textService = textService; + } /// /// Return a custom pre-value editor /// /// - protected override PreValueEditor CreatePreValueEditor() - { - return new ValueListPreValueEditor(); - } + protected override IConfigurationEditor CreateConfigurationEditor() => new ValueListConfigurationEditor(_textService); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/EmailAddressConfiguration.cs b/src/Umbraco.Web/PropertyEditors/EmailAddressConfiguration.cs new file mode 100644 index 0000000000..de7756266f --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/EmailAddressConfiguration.cs @@ -0,0 +1,13 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the email address value editor. + /// + public class EmailAddressConfiguration + { + [ConfigurationField("Required?", "boolean")] + public bool IsRequired { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/EmailAddressConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/EmailAddressConfigurationEditor.cs new file mode 100644 index 0000000000..27e7bffab8 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/EmailAddressConfigurationEditor.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the email address value editor. + /// + public class EmailAddressConfigurationEditor : ConfigurationEditor + { } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs index 8d141fd1e9..d870d2df5f 100644 --- a/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs @@ -1,12 +1,21 @@ -using Umbraco.Core; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.EmailAddressAlias, "Email address", "email", Icon="icon-message")] - public class EmailAddressPropertyEditor : PropertyEditor + [DataEditor(Constants.PropertyEditors.Aliases.EmailAddress, "Email address", "email", Icon="icon-message")] + public class EmailAddressPropertyEditor : DataEditor { - protected override PropertyValueEditor CreateValueEditor() + /// + /// The constructor will setup the property editor based on the attribute if one is found + /// + public EmailAddressPropertyEditor(ILogger logger) : base(logger) + { + } + + protected override IDataValueEditor CreateValueEditor() { var editor = base.CreateValueEditor(); //add an email address validator @@ -14,18 +23,9 @@ namespace Umbraco.Web.PropertyEditors return editor; } - protected override PreValueEditor CreatePreValueEditor() + protected override IConfigurationEditor CreateConfigurationEditor() { - return new EmailAddressePreValueEditor(); + return new EmailAddressConfigurationEditor(); } - - internal class EmailAddressePreValueEditor : PreValueEditor - { - //TODO: This doesn't seem necessary since it can be specified at the property type level - this will however be useful if/when - // we support overridden property value pre-value options. - [PreValueField("Required?", "boolean")] - public bool IsRequired { get; set; } - } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index 9ff4aaa1a2..e969d2e45b 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -1,81 +1,105 @@ -using System; +using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Linq; -using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.UploadFieldAlias, "File upload", "fileupload", Icon = "icon-download-alt", Group = "media")] - public class FileUploadPropertyEditor : PropertyEditor, IApplicationEventHandler + [DataEditor(Constants.PropertyEditors.Aliases.UploadField, "File upload", "fileupload", Icon = "icon-download-alt", Group = "media")] + public class FileUploadPropertyEditor : DataEditor { - private static MediaFileSystem MediaFileSystem + private readonly MediaFileSystem _mediaFileSystem; + + public FileUploadPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem) + : base(logger) { - // v8 will get rid of singletons - get { return FileSystemProviderManager.Current.MediaFileSystem; } + _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); } /// /// Creates the corresponding property value editor. /// /// The corresponding property value editor. - protected override PropertyValueEditor CreateValueEditor() + protected override IDataValueEditor CreateValueEditor() { - var baseEditor = base.CreateValueEditor(); - baseEditor.Validators.Add(new UploadFileTypeValidator()); - return new FileUploadPropertyValueEditor(baseEditor, MediaFileSystem); + var editor = new FileUploadPropertyValueEditor(Attribute, _mediaFileSystem); + editor.Validators.Add(new UploadFileTypeValidator()); + return editor; } - + /// /// Gets a value indicating whether a property is an upload field. /// /// The property. - /// A value indicating whether to check that the property has a non-empty value. /// A value indicating whether a property is an upload field, and (optionaly) has a non-empty value. - private static bool IsUploadField(Property property, bool ensureValue) + private static bool IsUploadField(Property property) { - if (property.PropertyType.PropertyEditorAlias != Constants.PropertyEditors.UploadFieldAlias) - return false; - if (ensureValue == false) - return true; - return property.Value is string && string.IsNullOrWhiteSpace((string) property.Value) == false; + return property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField; } - + /// - /// Gets the files that need to be deleted when entities are deleted. + /// Ensures any files associated are removed /// - /// The properties that were deleted. - static IEnumerable GetFilesToDelete(IEnumerable properties) + /// + internal IEnumerable ServiceDeleted(IEnumerable deletedEntities) { - return properties - .Where(x => IsUploadField(x, true)) - .Select(x => MediaFileSystem.GetRelativePath((string) x.Value)) - .ToList(); + return deletedEntities.SelectMany(x => x.Properties) + .Where(IsUploadField) + .SelectMany(GetFilePathsFromPropertyValues) + .Distinct(); } + + /// + /// Look through all propery values stored against the property and resolve any file paths stored + /// + /// + /// + private IEnumerable GetFilePathsFromPropertyValues(Property prop) + { + var propVals = prop.Values; + foreach (var propertyValue in propVals) + { + //check if the published value contains data and return it + var propVal = propertyValue.PublishedValue; + if (propVal != null && propVal is string str1 && !str1.IsNullOrWhiteSpace()) + yield return _mediaFileSystem.GetRelativePath(str1); + + //check if the edited value contains data and return it + propVal = propertyValue.EditedValue; + if (propVal != null && propVal is string str2 && !str2.IsNullOrWhiteSpace()) + yield return _mediaFileSystem.GetRelativePath(str2); + } + } /// /// After a content has been copied, also copy uploaded files. /// /// The event sender. /// The event arguments. - static void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs args) + internal void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs args) { // get the upload field properties with a value - var properties = args.Original.Properties.Where(x => IsUploadField(x, true)); + var properties = args.Original.Properties.Where(IsUploadField); // copy files var isUpdated = false; foreach (var property in properties) { - var sourcePath = MediaFileSystem.GetRelativePath((string) property.Value); - var copyPath = MediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); - args.Copy.SetValue(property.Alias, MediaFileSystem.GetUrl(copyPath)); - isUpdated = true; + //copy each of the property values (variants, segments) to the destination + foreach (var propertyValue in property.Values) + { + var propVal = property.GetValue(propertyValue.LanguageId, propertyValue.Segment); + if (propVal == null || !(propVal is string str) || str.IsNullOrWhiteSpace()) continue; + var sourcePath = _mediaFileSystem.GetRelativePath(str); + var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); + args.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath), propertyValue.LanguageId, propertyValue.Segment); + isUpdated = true; + } } // if updated, re-save the copy with the updated value @@ -88,7 +112,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The event sender. /// The event arguments. - static void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs args) + internal void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs args) { AutoFillProperties(args.Entity); } @@ -98,7 +122,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The event sender. /// The event arguments. - static void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs args) + internal void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs args) { foreach (var entity in args.SavedEntities) AutoFillProperties(entity); @@ -109,7 +133,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The event sender. /// The event arguments. - static void ContentServiceSaving(IContentService sender, Core.Events.SaveEventArgs args) + internal void ContentServiceSaving(IContentService sender, Core.Events.SaveEventArgs args) { foreach (var entity in args.SavedEntities) AutoFillProperties(entity); @@ -118,85 +142,24 @@ namespace Umbraco.Web.PropertyEditors /// /// Auto-fill properties (or clear). /// - /// The content. - static void AutoFillProperties(IContentBase content) + private void AutoFillProperties(IContentBase model) { - var properties = content.Properties.Where(x => IsUploadField(x, false)); + var properties = model.Properties.Where(IsUploadField); foreach (var property in properties) { - var autoFillConfig = MediaFileSystem.UploadAutoFillProperties.GetConfig(property.Alias); + var autoFillConfig = _mediaFileSystem.UploadAutoFillProperties.GetConfig(property.Alias); if (autoFillConfig == null) continue; - var svalue = property.Value as string; - if (string.IsNullOrWhiteSpace(svalue)) - MediaFileSystem.UploadAutoFillProperties.Reset(content, autoFillConfig); - else - MediaFileSystem.UploadAutoFillProperties.Populate(content, autoFillConfig, MediaFileSystem.GetRelativePath(svalue)); + foreach (var pvalue in property.Values) + { + var svalue = property.GetValue(pvalue.LanguageId, pvalue.Segment) as string; + if (string.IsNullOrWhiteSpace(svalue)) + _mediaFileSystem.UploadAutoFillProperties.Reset(model, autoFillConfig, pvalue.LanguageId, pvalue.Segment); + else + _mediaFileSystem.UploadAutoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(svalue), pvalue.LanguageId, pvalue.Segment); + } } - } - - #region Application event handler, used to bind to events on startup - - // The FileUploadPropertyEditor properties own files and as such must manage these files, - // so we are binding to events in order to make sure that - // - files are deleted when the owning content/media is - // - files are copied when the owning content is - // - populate the auto-fill properties when the owning content/media is saved - // - // NOTE: - // although some code fragments seem to want to support uploading multiple files, - // this is NOT a feature of the FileUploadPropertyEditor and is NOT supported - // - // auto-fill properties are recalculated EVERYTIME the content/media is saved, - // even if the property has NOT been modified (it could be the same filename but - // a different file) - this is accepted (auto-fill props should die) - // - // TODO in v8: - // for some weird backward compatibility reasons, - // - media copy is not supported - // - auto-fill properties are not supported for content items - // - auto-fill runs on MediaService.Created which makes no sense (no properties yet) - - public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - // nothing } - - public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - // nothing - } - - public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - // only if the app is configured - // see ApplicationEventHandler.ShouldExecute - if (applicationContext.IsConfigured == false || applicationContext.DatabaseContext.IsDatabaseConfigured == false) - return; - - MediaService.Created += MediaServiceCreated; // see above - makes no sense - MediaService.Saving += MediaServiceSaving; - //MediaService.Copied += MediaServiceCopied; // see above - missing - - ContentService.Copied += ContentServiceCopied; - //ContentService.Saving += ContentServiceSaving; // see above - missing - MediaService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( - GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); - - MediaService.EmptiedRecycleBin += (sender, args) => args.Files.AddRange( - GetFilesToDelete(args.AllPropertyData.SelectMany(x => x.Value))); - - ContentService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( - GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); - - ContentService.EmptiedRecycleBin += (sender, args) => args.Files.AddRange( - GetFilesToDelete(args.AllPropertyData.SelectMany(x => x.Value))); - - MemberService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( - GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); - } - - #endregion } } diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs index 777a14b768..011e46475c 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -1,27 +1,25 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core.IO; using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.PropertyEditors { /// /// The value editor for the file upload property editor. /// - internal class FileUploadPropertyValueEditor : PropertyValueEditorWrapper + internal class FileUploadPropertyValueEditor : DataValueEditor { private readonly MediaFileSystem _mediaFileSystem; - public FileUploadPropertyValueEditor(PropertyValueEditor wrapped, MediaFileSystem mediaFileSystem) - : base(wrapped) + public FileUploadPropertyValueEditor(DataEditorAttribute attribute, MediaFileSystem mediaFileSystem) + : base(attribute) { - _mediaFileSystem = mediaFileSystem; + _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); } /// @@ -42,7 +40,7 @@ namespace Umbraco.Web.PropertyEditors /// Other places (FileUploadPropertyEditor...) do NOT deal with multiple files, and our logic for reusing /// folders would NOT work, etc. /// - public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) + public override object FromEditor(ContentPropertyData editorValue, object currentValue) { currentValue = currentValue ?? string.Empty; @@ -54,7 +52,7 @@ namespace Umbraco.Web.PropertyEditors // check the editorValue value to see whether we need to clear files var editorJsonValue = editorValue.Value as JObject; var clears = editorJsonValue != null && editorJsonValue["clearFiles"] != null && editorJsonValue["clearFiles"].Value(); - var uploads = editorValue.AdditionalData.ContainsKey("files") && editorValue.AdditionalData["files"] is IEnumerable; + var uploads = editorValue.Files != null && editorValue.Files.Length > 0; // nothing = no changes, return what we have already (leave existing files intact) if (clears == false && uploads == false) @@ -70,25 +68,19 @@ namespace Umbraco.Web.PropertyEditors if (clears) { foreach (var pathToRemove in currentPaths) - _mediaFileSystem.DeleteFile(pathToRemove, true); + _mediaFileSystem.DeleteFile(pathToRemove); return string.Empty; // no more files } // ensure we have the required guids - if (editorValue.AdditionalData.ContainsKey("cuid") == false // for the content item - || editorValue.AdditionalData.ContainsKey("puid") == false) // and the property type - throw new Exception("Missing cuid/puid additional data."); - var cuido = editorValue.AdditionalData["cuid"]; - var puido = editorValue.AdditionalData["puid"]; - if ((cuido is Guid) == false || (puido is Guid) == false) - throw new Exception("Invalid cuid/puid additional data."); - var cuid = (Guid) cuido; - var puid = (Guid) puido; - if (cuid == Guid.Empty || puid == Guid.Empty) - throw new Exception("Invalid cuid/puid additional data."); + var cuid = editorValue.ContentKey; + if (cuid == Guid.Empty) throw new Exception("Invalid content key."); + var puid = editorValue.PropertyTypeKey; + if (puid == Guid.Empty) throw new Exception("Invalid property type key."); // process the files - var files = ((IEnumerable) editorValue.AdditionalData["files"]).ToArray(); + var files = editorValue.Files; + if (files == null) throw new Exception("Invalid files."); var newPaths = new List(); const int maxLength = 1; // we only process ONE file @@ -109,14 +101,15 @@ namespace Umbraco.Web.PropertyEditors { _mediaFileSystem.AddFile(filepath, filestream, true); // must overwrite! - var ext = _mediaFileSystem.GetExtension(filepath); - if (_mediaFileSystem.IsImageFile(ext)) - { - var preValues = editorValue.PreValues.FormatAsDictionary(); - var sizes = preValues.Any() ? preValues.First().Value.Value : string.Empty; - using (var image = Image.FromStream(filestream)) - _mediaFileSystem.GenerateThumbnails(image, filepath, sizes); - } + // fixme - remove this code + //var ext = _mediaFileSystem.GetExtension(filepath); + //if (_mediaFileSystem.IsImageFile(ext)) + //{ + // var preValues = editorValue.PreValues.FormatAsDictionary(); + // var sizes = preValues.Any() ? preValues.First().Value.Value : string.Empty; + // using (var image = Image.FromStream(filestream)) + // _mediaFileSystem.GenerateThumbnails(image, filepath, sizes); + //} // all related properties (auto-fill) are managed by FileUploadPropertyEditor // when the content is saved (through event handlers) @@ -131,10 +124,10 @@ namespace Umbraco.Web.PropertyEditors // remove files that are not there anymore foreach (var pathToRemove in currentPaths.Except(newPaths)) - _mediaFileSystem.DeleteFile(pathToRemove, true); + _mediaFileSystem.DeleteFile(pathToRemove); return string.Join(",", newPaths.Select(x => _mediaFileSystem.GetUrl(x))); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs deleted file mode 100644 index 5c89b868b8..0000000000 --- a/src/Umbraco.Web/PropertyEditors/FolderBrowserPropertyEditor.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Web.Mvc; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - [Obsolete("This is no longer used by default, use the ListViewPropertyEditor instead")] - [PropertyEditor(Constants.PropertyEditors.FolderBrowserAlias, "(Obsolete) Folder Browser", "folderbrowser", HideLabel=true, Icon="icon-folder", Group="media", IsDeprecated = true)] - public class FolderBrowserPropertyEditor : PropertyEditor - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs new file mode 100644 index 0000000000..23cc722e6f --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the gid value editor. + /// + public class GridConfiguration + { + [ConfigurationField("items", "Grid", "views/propertyeditors/grid/grid.prevalues.html", Description = "Grid configuration")] + public string Items { get; set; } + + [ConfigurationField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] + public string Rte { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/GridConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/GridConfigurationEditor.cs new file mode 100644 index 0000000000..4a74925816 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/GridConfigurationEditor.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the gid value editor. + /// + public class GridConfigurationEditor : ConfigurationEditor + { } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index af30b4ceeb..0f3211b743 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -1,107 +1,106 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading.Tasks; +using Umbraco.Core.Logging; using Examine; using Lucene.Net.Documents; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using UmbracoExamine; +using Umbraco.Core.Xml; +using Umbraco.Examine; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Core.Constants.PropertyEditors.GridAlias, "Grid layout", "grid", HideLabel = true, IsParameterEditor = false, ValueType = PropertyEditorValueTypes.Json, Group="rich content", Icon="icon-layout")] - public class GridPropertyEditor : PropertyEditor, IApplicationEventHandler - { + using Examine = global::Examine; - private static void DocumentWriting(object sender, Examine.LuceneEngine.DocumentWritingEventArgs e) + /// + /// Represents a grid property and parameter editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.Grid, "Grid layout", "grid", HideLabel = true, ValueType = ValueTypes.Json, Group="rich content", Icon="icon-layout")] + public class GridPropertyEditor : DataEditor + { + public GridPropertyEditor(ILogger logger) + : base(logger) + { } + + internal void DocumentWriting(object sender, Examine.LuceneEngine.DocumentWritingEventArgs e) { - var indexer = (BaseUmbracoIndexer)sender; - foreach (var field in indexer.IndexerData.UserFields) + foreach (var value in e.ValueSet.Values) { - if (e.Fields.ContainsKey(field.Name)) + //if there is a value, it's a string and it's detected as json + if (value.Value.Count > 0 && value.Value[0] != null && (value.Value[0] is string firstVal) && firstVal.DetectIsJson()) { - if (e.Fields[field.Name].DetectIsJson()) + try { - try + //TODO: We should deserialize this to Umbraco.Core.Models.GridValue instead of doing the below + var json = JsonConvert.DeserializeObject(firstVal); + + //check if this is formatted for grid json + if (json.HasValues && json.TryGetValue("name", out _) && json.TryGetValue("sections", out _)) { - //TODO: We should deserialize this to Umbraco.Core.Models.GridValue instead of doing the below - - var json = JsonConvert.DeserializeObject(e.Fields[field.Name]); - - //check if this is formatted for grid json - JToken name; - JToken sections; - if (json.HasValues && json.TryGetValue("name", out name) && json.TryGetValue("sections", out sections)) + //get all values and put them into a single field (using JsonPath) + var sb = new StringBuilder(); + foreach (var row in json.SelectTokens("$.sections[*].rows[*]")) { - //get all values and put them into a single field (using JsonPath) - var sb = new StringBuilder(); - foreach (var row in json.SelectTokens("$.sections[*].rows[*]")) - { - var rowName = row["name"].Value(); - var areaVals = row.SelectTokens("$.areas[*].controls[*].value"); + var rowName = row["name"].Value(); + var areaVals = row.SelectTokens("$.areas[*].controls[*].value"); - foreach (var areaVal in areaVals) + foreach (var areaVal in areaVals) + { + //TODO: If it's not a string, then it's a json formatted value - + // we cannot really index this in a smart way since it could be 'anything' + if (areaVal.Type == JTokenType.String) { - //TODO: If it's not a string, then it's a json formatted value - - // we cannot really index this in a smart way since it could be 'anything' - if (areaVal.Type == JTokenType.String) - { - var str = areaVal.Value(); - str = XmlHelper.CouldItBeXml(str) ? str.StripHtml() : str; - sb.Append(str); - sb.Append(" "); - - //add the row name as an individual field - e.Document.Add( - new Field( - string.Format("{0}.{1}", field.Name, rowName), str, Field.Store.YES, Field.Index.ANALYZED)); - } + var str = areaVal.Value(); + str = XmlHelper.CouldItBeXml(str) ? str.StripHtml() : str; + sb.Append(str); + sb.Append(" "); + //add the row name as an individual field + e.Document.Add( + new Field( + $"{value.Key}.{rowName}", str, Field.Store.YES, Field.Index.ANALYZED)); } - } - if (sb.Length > 0) - { - //First save the raw value to a raw field - e.Document.Add( - new Field( - string.Format("{0}{1}", UmbracoContentIndexer.RawFieldPrefix, field.Name), - e.Fields[field.Name], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO)); - - //now replace the original value with the combined/cleaned value - e.Document.RemoveField(field.Name); - e.Document.Add( - new Field( - field.Name, - sb.ToString(), Field.Store.YES, Field.Index.ANALYZED)); } } - } - catch (InvalidCastException) - { - //swallow...on purpose, there's a chance that this isn't the json format we are looking for - // and we don't want that to affect the website. - } - catch (JsonException) - { - //swallow...on purpose, there's a chance that this isn't json and we don't want that to affect - // the website. - } - catch (ArgumentException) - { - //swallow on purpose to prevent this error: - // Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject. + if (sb.Length > 0) + { + //First save the raw value to a raw field + e.Document.Add( + new Field( + $"{UmbracoExamineIndexer.RawFieldPrefix}{value.Key}", + firstVal, Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO)); + + //now replace the original value with the combined/cleaned value + e.Document.RemoveField(value.Key); + e.Document.Add( + new Field( + value.Key, + sb.ToString(), Field.Store.YES, Field.Index.ANALYZED)); + } } } + catch (InvalidCastException) + { + //swallow...on purpose, there's a chance that this isn't the json format we are looking for + // and we don't want that to affect the website. + } + catch (JsonException) + { + //swallow...on purpose, there's a chance that this isn't json and we don't want that to affect + // the website. + } + catch (ArgumentException) + { + //swallow on purpose to prevent this error: + // Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject. + } } + } } @@ -109,73 +108,15 @@ namespace Umbraco.Web.PropertyEditors /// Overridden to ensure that the value is validated /// /// - protected override PropertyValueEditor CreateValueEditor() + protected override IDataValueEditor CreateValueEditor() => new GridPropertyValueEditor(Attribute); + + protected override IConfigurationEditor CreateConfigurationEditor() => new GridConfigurationEditor(); + + internal class GridPropertyValueEditor : DataValueEditor { - var baseEditor = base.CreateValueEditor(); - return new GridPropertyValueEditor(baseEditor); + public GridPropertyValueEditor(DataEditorAttribute attribute) + : base(attribute) + { } } - - protected override PreValueEditor CreatePreValueEditor() - { - return new GridPreValueEditor(); - } - - internal class GridPropertyValueEditor : PropertyValueEditorWrapper - { - public GridPropertyValueEditor(PropertyValueEditor wrapped) - : base(wrapped) - { - } - - } - - internal class GridPreValueEditor : PreValueEditor - { - [PreValueField("items", "Grid", "views/propertyeditors/grid/grid.prevalues.html", Description = "Grid configuration")] - public string Items { get; set; } - - [PreValueField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] - public string Rte { get; set; } - } - - #region Application event handler, used to bind to events on startup - - private readonly GridPropertyEditorApplicationStartup _applicationStartup = new GridPropertyEditorApplicationStartup(); - - /// - /// we're using a sub -class because this has the logic to prevent it from executing if the application is not configured - /// - private class GridPropertyEditorApplicationStartup : ApplicationEventHandler - { - /// - /// We're going to bind to the Examine events so we can ensure grid data is index nicely. - /// - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - foreach (var i in ExamineManager.Instance.IndexProviderCollection.OfType()) - { - i.DocumentWriting += DocumentWriting; - } - } - } - - public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - //wrap - _applicationStartup.OnApplicationInitialized(umbracoApplication, applicationContext); - } - public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - //wrap - _applicationStartup.OnApplicationStarting(umbracoApplication, applicationContext); - } - public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - //wrap - _applicationStartup.OnApplicationStarted(umbracoApplication, applicationContext); - } - #endregion } - - } diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperConfigurationEditor.cs new file mode 100644 index 0000000000..2751b3bdc1 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperConfigurationEditor.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the image cropper value editor. + /// + internal class ImageCropperConfigurationEditor : ConfigurationEditor + { + /// + public override IDictionary ToValueEditor(object configuration) + { + var d = base.ToValueEditor(configuration); + if (!d.ContainsKey("focalPoint")) d["focalPoint"] = new { left = 0.5, top = 0.5 }; + if (!d.ContainsKey("src")) d["src"] = ""; + return d; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index 5196fda0ee..6d3c5b4f61 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Media; @@ -13,68 +14,47 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.ImageCropperAlias, "Image Cropper", "imagecropper", ValueType = PropertyEditorValueTypes.Json, HideLabel = false, Group="media", Icon="icon-crop")] - public class ImageCropperPropertyEditor : PropertyEditor, IApplicationEventHandler + /// + /// Represents an image cropper property editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.ImageCropper, "Image Cropper", "imagecropper", ValueType = ValueTypes.Json, HideLabel = false, Group="media", Icon="icon-crop")] + public class ImageCropperPropertyEditor : DataEditor { - // preValues - private IDictionary _internalPreValues; - - public override IDictionary DefaultPreValues - { - get { return _internalPreValues; } - set { _internalPreValues = value; } - } + private readonly MediaFileSystem _mediaFileSystem; + private readonly UploadAutoFillProperties _autoFillProperties; /// /// Initializes a new instance of the class. /// - public ImageCropperPropertyEditor() + public ImageCropperPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings) + : base(logger) { - _internalPreValues = new Dictionary - { - {"focalPoint", "{left: 0.5, top: 0.5}"}, - {"src", ""} - }; - } + _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); + var contentSettings1 = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); - private static MediaFileSystem MediaFileSystem - { - // v8 will get rid of singletons - get { return FileSystemProviderManager.Current.MediaFileSystem; } + _autoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, Logger, contentSettings1); } /// /// Creates the corresponding property value editor. /// /// The corresponding property value editor. - protected override PropertyValueEditor CreateValueEditor() - { - var baseEditor = base.CreateValueEditor(); - return new ImageCropperPropertyValueEditor(baseEditor, MediaFileSystem); - } + protected override IDataValueEditor CreateValueEditor() => new ImageCropperPropertyValueEditor(Attribute, Logger, _mediaFileSystem); /// /// Creates the corresponding preValue editor. /// /// The corresponding preValue editor. - protected override PreValueEditor CreatePreValueEditor() - { - return new ImageCropperPreValueEditor(); - } + protected override IConfigurationEditor CreateConfigurationEditor() => new ImageCropperConfigurationEditor(); /// /// Gets a value indicating whether a property is an image cropper field. /// - /// The property. - /// A value indicating whether to check that the property has a non-empty value. + /// The property. /// A value indicating whether a property is an image cropper field, and (optionaly) has a non-empty value. - private static bool IsCropperField(Property property, bool ensureValue) + private static bool IsCropperField(Property property) { - if (property.PropertyType.PropertyEditorAlias != Constants.PropertyEditors.ImageCropperAlias) - return false; - if (ensureValue == false) - return true; - return property.Value is string && string.IsNullOrWhiteSpace((string)property.Value) == false; + return property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper; } /// @@ -84,7 +64,7 @@ namespace Umbraco.Web.PropertyEditors /// A value indicating whether to log the error. /// The json object corresponding to the property value. /// In case of an error, optionaly logs the error and returns null. - private static JObject GetJObject(string value, bool writeLog) + private JObject GetJObject(string value, bool writeLog) { if (string.IsNullOrWhiteSpace(value)) return null; @@ -96,24 +76,59 @@ namespace Umbraco.Web.PropertyEditors catch (Exception ex) { if (writeLog) - LogHelper.Error("Could not parse image cropper value \"" + value + "\"", ex); + Logger.Error($"Could not parse image cropper value \"{value}\"", ex); return null; } } /// - /// Gets the files that need to be deleted when entities are deleted. + /// Ensures any files associated are removed /// - /// The properties that were deleted. - static IEnumerable GetFilesToDelete(IEnumerable properties) + /// + internal IEnumerable ServiceDeleted(IEnumerable deletedEntities) { - return properties.Where(x => IsCropperField(x, true)).Select(x => + return deletedEntities.SelectMany(x => x.Properties) + .Where(IsCropperField) + .SelectMany(GetFilePathsFromPropertyValues) + .Distinct(); + } + + /// + /// Look through all propery values stored against the property and resolve any file paths stored + /// + /// + /// + private IEnumerable GetFilePathsFromPropertyValues(Property prop) + { + //parses out the src from a json string + + foreach (var propertyValue in prop.Values) { - var jo = GetJObject((string) x.Value, true); - if (jo == null || jo["src"] == null) return null; - var src = jo["src"].Value(); - return string.IsNullOrWhiteSpace(src) ? null : MediaFileSystem.GetRelativePath(src); - }).WhereNotNull(); + //check if the published value contains data and return it + var src = GetFileSrcFromPropertyValue(propertyValue.PublishedValue, out var _); + if (src != null) yield return _mediaFileSystem.GetRelativePath(src); + + //check if the edited value contains data and return it + src = GetFileSrcFromPropertyValue(propertyValue.EditedValue, out var _); + if (src != null) yield return _mediaFileSystem.GetRelativePath(src); + } + } + + /// + /// Returns the "src" property from the json structure if the value is formatted correctly + /// + /// + /// The deserialized value + /// + private string GetFileSrcFromPropertyValue(object propVal, out JObject deserializedValue) + { + deserializedValue = null; + if (propVal == null || !(propVal is string str)) return null; + if (!str.DetectIsJson()) return null; + deserializedValue = GetJObject(str, true); + if (deserializedValue?["src"] == null) return null; + var src = deserializedValue["src"].Value(); + return _mediaFileSystem.GetRelativePath(src); } /// @@ -121,28 +136,28 @@ namespace Umbraco.Web.PropertyEditors /// /// The event sender. /// The event arguments. - static void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs args) + public void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs args) { - // get the image cropper field properties with a value - var properties = args.Original.Properties.Where(x => IsCropperField(x, true)); + // get the image cropper field properties + var properties = args.Original.Properties.Where(IsCropperField); // copy files var isUpdated = false; foreach (var property in properties) { - var jo = GetJObject((string) property.Value, true); - if (jo == null || jo["src"] == null) continue; - - var src = jo["src"].Value(); - if (string.IsNullOrWhiteSpace(src)) continue; - - var sourcePath = MediaFileSystem.GetRelativePath(src); - var copyPath = MediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); - jo["src"] = MediaFileSystem.GetUrl(copyPath); - args.Copy.SetValue(property.Alias, jo.ToString()); - isUpdated = true; + //copy each of the property values (variants, segments) to the destination by using the edited value + foreach (var propertyValue in property.Values) + { + var propVal = property.GetValue(propertyValue.LanguageId, propertyValue.Segment); + var src = GetFileSrcFromPropertyValue(propVal, out var jo); + if (src == null) continue; + var sourcePath = _mediaFileSystem.GetRelativePath(src); + var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); + jo["src"] = _mediaFileSystem.GetUrl(copyPath); + args.Copy.SetValue(property.Alias, jo.ToString(), propertyValue.LanguageId, propertyValue.Segment); + isUpdated = true; + } } - // if updated, re-save the copy with the updated value if (isUpdated) sender.Save(args.Copy); @@ -153,7 +168,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The event sender. /// The event arguments. - static void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs args) + public void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs args) { AutoFillProperties(args.Entity); } @@ -163,7 +178,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The event sender. /// The event arguments. - static void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs args) + public void MediaServiceSaving(IMediaService sender, Core.Events.SaveEventArgs args) { foreach (var entity in args.SavedEntities) AutoFillProperties(entity); @@ -174,7 +189,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The event sender. /// The event arguments. - static void ContentServiceSaving(IContentService sender, Core.Events.SaveEventArgs args) + public void ContentServiceSaving(IContentService sender, Core.Events.SaveEventArgs args) { foreach (var entity in args.SavedEntities) AutoFillProperties(entity); @@ -183,118 +198,57 @@ namespace Umbraco.Web.PropertyEditors /// /// Auto-fill properties (or clear). /// - /// The content. - static void AutoFillProperties(IContentBase content) + private void AutoFillProperties(IContentBase model) { - var properties = content.Properties.Where(x => IsCropperField(x, false)); + var properties = model.Properties.Where(IsCropperField); foreach (var property in properties) { - var autoFillConfig = MediaFileSystem.UploadAutoFillProperties.GetConfig(property.Alias); + var autoFillConfig = _autoFillProperties.GetConfig(property.Alias); if (autoFillConfig == null) continue; - var svalue = property.Value as string; - if (string.IsNullOrWhiteSpace(svalue)) + foreach (var pvalue in property.Values) { - MediaFileSystem.UploadAutoFillProperties.Reset(content, autoFillConfig); - continue; - } + var svalue = property.GetValue(pvalue.LanguageId, pvalue.Segment) as string; + if (string.IsNullOrWhiteSpace(svalue)) + { + _autoFillProperties.Reset(model, autoFillConfig, pvalue.LanguageId, pvalue.Segment); + continue; + } - var jo = GetJObject(svalue, false); - string src; - if (jo == null) - { - // so we have a non-empty string value that cannot be parsed into a json object - // see http://issues.umbraco.org/issue/U4-4756 - // it can happen when an image is uploaded via the folder browser, in which case - // the property value will be the file source eg '/media/23454/hello.jpg' and we - // are fixing that anomaly here - does not make any sense at all but... bah... - var config = ApplicationContext.Current.Services.DataTypeService - .GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId).FirstOrDefault(); - var crops = string.IsNullOrWhiteSpace(config) ? "[]" : config; - src = svalue; - property.Value = "{\"src\": \"" + svalue + "\", \"crops\": " + crops + "}"; - } - else - { - src = jo["src"] == null ? null : jo["src"].Value(); - } + // FIXME VERY TEMP + // we should kill all auto-fill properties + // BUT that being said what would be the right way to do this? + /* + var v = JsonConvert.DeserializeObject() - if (src == null) - MediaFileSystem.UploadAutoFillProperties.Reset(content, autoFillConfig); - else - MediaFileSystem.UploadAutoFillProperties.Populate(content, autoFillConfig, MediaFileSystem.GetRelativePath(src)); + var jo = GetJObject(svalue, false); + string src; + if (jo == null) + { + // so we have a non-empty string value that cannot be parsed into a json object + // see http://issues.umbraco.org/issue/U4-4756 + // it can happen when an image is uploaded via the folder browser, in which case + // the property value will be the file source eg '/media/23454/hello.jpg' and we + // are fixing that anomaly here - does not make any sense at all but... bah... + var config = _dataTypeService + .GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId).FirstOrDefault(); + var crops = string.IsNullOrWhiteSpace(config) ? "[]" : config; + src = svalue; + property.SetValue("{\"src\": \"" + svalue + "\", \"crops\": " + crops + "}"); + } + else + { + src = jo["src"]?.Value(); + } + + if (src == null) + _autoFillProperties.Reset(model, autoFillConfig, pvalue.LanguageId, pvalue.Segment); + else + _autoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(src), pvalue.LanguageId, pvalue.Segment); + */ + } } } - - internal class ImageCropperPreValueEditor : PreValueEditor - { - [PreValueField("crops", "Crop sizes", "views/propertyeditors/imagecropper/imagecropper.prevalues.html")] - public string Crops { get; set; } - } - - #region Application event handler, used to bind to events on startup - - // The ImageCropperPropertyEditor properties own files and as such must manage these files, - // so we are binding to events in order to make sure that - // - files are deleted when the owning content/media is - // - files are copied when the owning content is (NOTE: not supporting media copy here!) - // - populate the auto-fill properties when files are changing - // - populate the auto-fill properties when the owning content/media is saved - // - // NOTE: - // uploading multiple files is NOT a feature of the ImageCropperPropertyEditor - // - // auto-fill properties are recalculated EVERYTIME the content/media is saved, - // even if the property has NOT been modified (it could be the same filename but - // a different file) - this is accepted (auto-fill props should die) - // - // TODO in v8: - // for some weird backward compatibility reasons, - // - media copy is not supported - // - auto-fill properties are not supported for content items - // - auto-fill runs on MediaService.Created which makes no sense (no properties yet) - - public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - // nothing - } - - public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - // nothing - } - - public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - // only if the app is configured - // see ApplicationEventHandler.ShouldExecute - if (applicationContext.IsConfigured == false || applicationContext.DatabaseContext.IsDatabaseConfigured == false) - return; - - MediaService.Created += MediaServiceCreated; // see above - makes no sense - MediaService.Saving += MediaServiceSaving; - //MediaService.Copied += MediaServiceCopied; // see above - missing - - ContentService.Copied += ContentServiceCopied; - //ContentService.Saving += ContentServiceSaving; // see above - missing - - MediaService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( - GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); - - MediaService.EmptiedRecycleBin += (sender, args) => args.Files.AddRange( - GetFilesToDelete(args.AllPropertyData.SelectMany(x => x.Value))); - - ContentService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( - GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); - - ContentService.EmptiedRecycleBin += (sender, args) => args.Files.AddRange( - GetFilesToDelete(args.AllPropertyData.SelectMany(x => x.Value))); - - MemberService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange( - GetFilesToDelete(args.DeletedEntities.SelectMany(x => x.Properties))); - } - - #endregion } } diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index 0e6500f3d8..bce989fb18 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -1,8 +1,6 @@ using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; +using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -11,7 +9,6 @@ using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; using File = System.IO.File; namespace Umbraco.Web.PropertyEditors @@ -19,32 +16,42 @@ namespace Umbraco.Web.PropertyEditors /// /// The value editor for the image cropper property editor. /// - internal class ImageCropperPropertyValueEditor : PropertyValueEditorWrapper + internal class ImageCropperPropertyValueEditor : DataValueEditor // fixme core vs web? { + private readonly ILogger _logger; private readonly MediaFileSystem _mediaFileSystem; - public ImageCropperPropertyValueEditor(PropertyValueEditor wrapped, MediaFileSystem mediaFileSystem) - : base(wrapped) + public ImageCropperPropertyValueEditor(DataEditorAttribute attribute, ILogger logger, MediaFileSystem mediaFileSystem) + : base(attribute) { - _mediaFileSystem = mediaFileSystem; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); } /// /// This is called to merge in the prevalue crops with the value that is saved - similar to the property value converter for the front-end /// - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - var val = base.ConvertDbToEditor(property, propertyType, dataTypeService); + var val = property.GetValue(languageId, segment); + if (val == null) return null; - var json = val as JObject; - if (json != null) + ImageCropperValue value; + try { - ImageCropperValueConverter.MergePreValues(json, dataTypeService, propertyType.DataTypeDefinitionId); - return json; + value = JsonConvert.DeserializeObject(val.ToString()); + } + catch + { + value = new ImageCropperValue { Src = val.ToString() }; } - return val; + var dataType = dataTypeService.GetDataType(property.PropertyType.DataTypeId); + if (dataType?.Configuration != null) + value.ApplyConfiguration(dataType.ConfigurationAs()); + + return value; } /// /// Converts the value received from the editor into the value can be stored in the database. @@ -57,7 +64,7 @@ namespace Umbraco.Web.PropertyEditors /// editorValue.Value is used to figure out editorFile and, if it has been cleared, remove the old file - but /// it is editorValue.AdditionalData["files"] that is used to determine the actual file that has been uploaded. /// - public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) + public override object FromEditor(ContentPropertyData editorValue, object currentValue) { // get the current path var currentPath = string.Empty; @@ -71,7 +78,7 @@ namespace Umbraco.Web.PropertyEditors catch (Exception ex) { // for some reason the value is invalid so continue as if there was no value there - LogHelper.WarnWithException("Could not parse current db value to a JObject.", ex); + _logger.Warn(ex, "Could not parse current db value to a JObject."); } if (string.IsNullOrWhiteSpace(currentPath) == false) currentPath = _mediaFileSystem.GetRelativePath(currentPath); @@ -87,25 +94,18 @@ namespace Umbraco.Web.PropertyEditors } // ensure we have the required guids - if (editorValue.AdditionalData.ContainsKey("cuid") == false // for the content item - || editorValue.AdditionalData.ContainsKey("puid") == false) // and the property type - throw new Exception("Missing cuid/puid additional data."); - var cuido = editorValue.AdditionalData["cuid"]; - var puido = editorValue.AdditionalData["puid"]; - if ((cuido is Guid) == false || (puido is Guid) == false) - throw new Exception("Invalid cuid/puid additional data."); - var cuid = (Guid)cuido; - var puid = (Guid)puido; - if (cuid == Guid.Empty || puid == Guid.Empty) - throw new Exception("Invalid cuid/puid additional data."); + var cuid = editorValue.ContentKey; + if (cuid == Guid.Empty) throw new Exception("Invalid content key."); + var puid = editorValue.PropertyTypeKey; + if (puid == Guid.Empty) throw new Exception("Invalid property type key."); // editorFile is empty whenever a new file is being uploaded // or when the file is cleared (in which case editorJson is null) // else editorFile contains the unchanged value - var uploads = editorValue.AdditionalData.ContainsKey("files") && editorValue.AdditionalData["files"] is IEnumerable; - var files = uploads ? ((IEnumerable)editorValue.AdditionalData["files"]).ToArray() : new ContentItemFile[0]; - var file = uploads ? files.FirstOrDefault() : null; + var uploads = editorValue.Files; + if (uploads == null) throw new Exception("Invalid files."); + var file = uploads.Length > 0 ? uploads[0] : null; if (file == null) // not uploading a file { @@ -114,23 +114,23 @@ namespace Umbraco.Web.PropertyEditors // value is unchanged. if (string.IsNullOrWhiteSpace(editorFile) && string.IsNullOrWhiteSpace(currentPath) == false) { - _mediaFileSystem.DeleteFile(currentPath, true); + _mediaFileSystem.DeleteFile(currentPath); return null; // clear } - return editorJson == null ? null : editorJson.ToString(); // unchanged + return editorJson?.ToString(); // unchanged } // process the file var filepath = editorJson == null ? null : ProcessFile(editorValue, file, currentPath, cuid, puid); // remove all temp files - foreach (var f in files) + foreach (var f in uploads) File.Delete(f.TempFilePath); // remove current file if replaced if (currentPath != filepath && string.IsNullOrWhiteSpace(currentPath) == false) - _mediaFileSystem.DeleteFile(currentPath, true); + _mediaFileSystem.DeleteFile(currentPath); // update json and return if (editorJson == null) return null; @@ -138,7 +138,7 @@ namespace Umbraco.Web.PropertyEditors return editorJson.ToString(); } - private string ProcessFile(ContentPropertyData editorValue, ContentItemFile file, string currentPath, Guid cuid, Guid puid) + private string ProcessFile(ContentPropertyData editorValue, ContentPropertyFile file, string currentPath, Guid cuid, Guid puid) { // process the file // no file, invalid file, reject change @@ -152,38 +152,26 @@ namespace Umbraco.Web.PropertyEditors using (var filestream = File.OpenRead(file.TempFilePath)) { _mediaFileSystem.AddFile(filepath, filestream, true); // must overwrite! - - var ext = _mediaFileSystem.GetExtension(filepath); - if (_mediaFileSystem.IsImageFile(ext)) - { - var preValues = editorValue.PreValues.FormatAsDictionary(); - var sizes = preValues.Any() ? preValues.First().Value.Value : string.Empty; - using (var image = Image.FromStream(filestream)) - _mediaFileSystem.GenerateThumbnails(image, filepath, sizes); - } - - // all related properties (auto-fill) are managed by ImageCropperPropertyEditor - // when the content is saved (through event handlers) } return filepath; } - public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + + public override string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService) { - if (property.Value == null || string.IsNullOrEmpty(property.Value.ToString())) - return null; + if (value == null || string.IsNullOrEmpty(value.ToString())) + return null; // if we dont have a json structure, we will get it from the property type - var val = property.Value.ToString(); + var val = value.ToString(); if (val.DetectIsJson()) return val; // more magic here ;-( - var config = dataTypeService.GetPreValuesByDataTypeId(propertyType.DataTypeDefinitionId).FirstOrDefault(); - var crops = string.IsNullOrEmpty(config) ? "[]" : config; - var newVal = "{src: '" + val + "', crops: " + crops + "}"; - return newVal; + var configuration = dataTypeService.GetDataType(propertyType.DataTypeId).ConfigurationAs(); + var crops = configuration?.Crops ?? Array.Empty(); + return "{src: '" + val + "', crops: " + crops + "}"; } } } diff --git a/src/Umbraco.Web/PropertyEditors/IntegerConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/IntegerConfigurationEditor.cs new file mode 100644 index 0000000000..7c5b8e5133 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/IntegerConfigurationEditor.cs @@ -0,0 +1,38 @@ +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. + /// + internal class IntegerConfigurationEditor : ConfigurationEditor + { + public IntegerConfigurationEditor() + { + Fields.Add(new ConfigurationField(new IntegerValidator()) + { + Description = "Enter the minimum amount of number to be entered", + Key = "min", + View = "number", + Name = "Minimum" + }); + + Fields.Add(new ConfigurationField(new IntegerValidator()) + { + Description = "Enter the intervals amount between each step of number to be entered", + Key = "step", + View = "number", + Name = "Step Size" + }); + + Fields.Add(new ConfigurationField(new IntegerValidator()) + { + Description = "Enter the maximum amount of number to be entered", + Key = "max", + View = "number", + Name = "Maximum" + }); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/IntegerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/IntegerPropertyEditor.cs index 026aaf115f..a0a70f08a2 100644 --- a/src/Umbraco.Web/PropertyEditors/IntegerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/IntegerPropertyEditor.cs @@ -1,57 +1,29 @@ -using Umbraco.Core; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.IntegerAlias, "Numeric", "integer", IsParameterEditor = true, ValueType = PropertyEditorValueTypes.IntegerAlternative)] - public class IntegerPropertyEditor : PropertyEditor + /// + /// Represents an integer property and parameter editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.Integer, EditorType.PropertyValue | EditorType.MacroParameter, "Numeric", "integer", ValueType = ValueTypes.Integer)] + public class IntegerPropertyEditor : DataEditor { - /// - /// Overridden to ensure that the value is validated - /// - /// - protected override PropertyValueEditor CreateValueEditor() + public IntegerPropertyEditor(ILogger logger) + : base(logger) + { } + + /// + protected override IDataValueEditor CreateValueEditor() { var editor = base.CreateValueEditor(); - editor.Validators.Add(new IntegerValidator()); + editor.Validators.Add(new IntegerValidator()); // ensure the value is validated return editor; } - - protected override PreValueEditor CreatePreValueEditor() - { - return new IntegerPreValueEditor(); - } - /// - /// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. - /// - internal class IntegerPreValueEditor : PreValueEditor - { - public IntegerPreValueEditor() - { - //create the fields - Fields.Add(new PreValueField(new IntegerValidator()) - { - Description = "Enter the minimum amount of number to be entered", - Key = "min", - View = "number", - Name = "Minimum" - }); - Fields.Add(new PreValueField(new IntegerValidator()) - { - Description = "Enter the intervals amount between each step of number to be entered", - Key = "step", - View = "number", - Name = "Step Size" - }); - Fields.Add(new PreValueField(new IntegerValidator()) - { - Description = "Enter the maximum amount of number to be entered", - Key = "max", - View = "number", - Name = "Maximum" - }); - } - } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new IntegerConfigurationEditor(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/LabelConfiguration.cs b/src/Umbraco.Web/PropertyEditors/LabelConfiguration.cs new file mode 100644 index 0000000000..47e4e8ef9b --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/LabelConfiguration.cs @@ -0,0 +1,14 @@ +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the label value editor. + /// + public class LabelConfiguration : IConfigureValueType + { + [ConfigurationField(Constants.PropertyEditors.ConfigurationKeys.DataValueType, "Value type", "valuetype")] + public string ValueType { get; set; } = ValueTypes.String; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/LabelConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/LabelConfigurationEditor.cs new file mode 100644 index 0000000000..c248eb1f71 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/LabelConfigurationEditor.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the label value editor. + /// + public class LabelConfigurationEditor : ConfigurationEditor + { + /// + public override LabelConfiguration FromConfigurationEditor(IDictionary editorValues, LabelConfiguration configuration) + { + var newConfiguration = new LabelConfiguration(); + + // get the value type + // not simply deserializing Json because we want to validate the valueType + + if (editorValues.TryGetValue(Constants.PropertyEditors.ConfigurationKeys.DataValueType, out var valueTypeObj) + && valueTypeObj is string stringValue) + { + if (!string.IsNullOrWhiteSpace(stringValue) && ValueTypes.IsValue(stringValue)) // validate + newConfiguration.ValueType = stringValue; + } + + return newConfiguration; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs index c5482695a3..0e1a031284 100644 --- a/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs @@ -1,148 +1,37 @@ -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.NoEditAlias, "Label", "readonlyvalue", Icon = "icon-readonly")] - public class LabelPropertyEditor : PropertyEditor + /// + /// Represents a property editor for label properties. + /// + [DataEditor(Constants.PropertyEditors.Aliases.NoEdit, "Label", "readonlyvalue", Icon = "icon-readonly")] + public class LabelPropertyEditor : DataEditor { - protected override PropertyValueEditor CreateValueEditor() - { - return new LabelPropertyValueEditor(base.CreateValueEditor()); - } - - protected override PreValueEditor CreatePreValueEditor() - { - return new LabelPreValueEditor(); - } - /// - /// Custom value editor to mark it as readonly + /// Initializes a new instance of the class. /// - internal class LabelPropertyValueEditor : PropertyValueEditorWrapper + public LabelPropertyEditor(ILogger logger) + : base(logger) + { } + + /// + protected override IDataValueEditor CreateValueEditor() => new LabelPropertyValueEditor(Attribute); + + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new LabelConfigurationEditor(); + + // provides the property value editor + internal class LabelPropertyValueEditor : DataValueEditor { - public LabelPropertyValueEditor(PropertyValueEditor wrapped) - : base(wrapped) - { - } + public LabelPropertyValueEditor(DataEditorAttribute attribute) + : base(attribute) + { } - /// - /// This editor is for display purposes only, any values bound to it will not be saved back to the database - /// - public override bool IsReadOnly - { - get { return true; } - } - } - - internal class LabelPreValueEditor : PreValueEditor - { - private const string LegacyPropertyEditorValuesKey = "values"; - - public LabelPreValueEditor() - { - Fields.Add(new PreValueField() - { - HideLabel = true, - View = "readonlykeyvalues", - Key = LegacyPropertyEditorValuesKey - }); - - ValueType = PropertyEditorValueTypes.String; - } - - [PreValueField(Constants.PropertyEditors.PreValueKeys.DataValueType, "Value type", "valuetype")] - public string ValueType { get; set; } - - /// - /// Other than for the pre-value fields defined on this property editor, chuck all the values into one field so devs can see what is stored there. - /// We want this in case we've converted a legacy property editor over to a label as we should still show the pre-values stored for the data type. - /// - /// - /// - /// - public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) - { - var existing = base.ConvertDbToEditor(defaultPreVals, persistedPreVals); - - // Check for a saved value type. If not found set to default string type. - var valueType = PropertyEditorValueTypes.String; - if (existing.ContainsKey(Constants.PropertyEditors.PreValueKeys.DataValueType)) - { - valueType = (string)existing[Constants.PropertyEditors.PreValueKeys.DataValueType]; - } - - // Convert any other values from a legacy property editor to a list, easier to enumerate on the editor. - // Make sure to exclude values defined on the label property editor itself. - var asList = existing - .Select(e => new KeyValuePair(e.Key, e.Value)) - .Where(e => e.Key != Constants.PropertyEditors.PreValueKeys.DataValueType) - .ToList(); - - var result = new Dictionary { { Constants.PropertyEditors.PreValueKeys.DataValueType, valueType } }; - if (asList.Any()) - { - result.Add(LegacyPropertyEditorValuesKey, asList); - } - - return result; - } - - /// - /// When saving we want to avoid saving an empty "legacy property editor values" field if there are none. - /// - /// - /// - /// - public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue) - { - // notes (from the PR): - // - // "All stemmed from the fact that even though the label property editor could have pre-values (from a legacy type), - // they couldn't up to now be edited and saved through the UI. Now that a "true" pre-value has been added, it can, - // which led to some odd behaviour. - // - // Firstly there would be a pre-value record saved for legacy values even if there aren't any (the key would exist - // but with no value). In that case I remove that pre-value so it's not saved(likely does no harm, but it's not - // necessary - we only need this legacy values pre-value record if there are any). - // - // Secondly if there are legacy values, I found on each save the JSON structure containing them would get repeatedly - // nested (an outer JSON wrapper would be added each time). So what I'm doing is if there are legacy pre-values, - // I'm converting what comes in "wrapped" like (below) into the legacy property editor values." - - if (editorValue.ContainsKey(LegacyPropertyEditorValuesKey)) - { - // If provided value contains an empty legacy property editor values, don't save it - if (editorValue[LegacyPropertyEditorValuesKey] == null) - { - editorValue.Remove(LegacyPropertyEditorValuesKey); - } - else - { - // If provided value contains legacy property editor values, unwrap the value to save so it doesn't get repeatedly nested on saves. - // This is a bit funky - but basically needing to parse out the original value from a JSON structure that is passed in - // looking like: - // Value = {[ - // { - // "Key": "values", - // "Value": { - // - // }} - // ]} - var values = editorValue[LegacyPropertyEditorValuesKey] as JArray; - if (values != null && values.Count == 1 && values.First.Values().Count() == 2) - { - editorValue[LegacyPropertyEditorValuesKey] = values.First.Values().Last(); - } - } - } - - return base.ConvertEditorToDb(editorValue, currentValue); - } + /// + public override bool IsReadOnly => true; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs new file mode 100644 index 0000000000..cec8f232d3 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs @@ -0,0 +1,86 @@ +using Newtonsoft.Json; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the listview value editor. + /// + public class ListViewConfiguration + { + [ConfigurationField("pageSize", "Page Size", "number", Description = "Number of items per page")] + public int PageSize { get; set; } + + [ConfigurationField("displayAtTabNumber", "Display At Tab Number", "number", Description = "Which tab position that the list of child items will be displayed")] + public int DisplayAtTabNumber { get; set; } + + [ConfigurationField("orderBy", "Order By", "views/propertyeditors/listview/sortby.prevalues.html", + Description = "The default sort order for the list")] + public string OrderBy { get; set; } + + [ConfigurationField("orderDirection", "Order Direction", "views/propertyeditors/listview/orderdirection.prevalues.html")] + public string OrderDirection { get; set; } + + [ConfigurationField("includeProperties", "Columns Displayed", "views/propertyeditors/listview/includeproperties.prevalues.html", + Description = "The properties that will be displayed for each column")] + public Property[] IncludeProperties { get; set; } + + [ConfigurationField("layouts", "Layouts", "views/propertyeditors/listview/layouts.prevalues.html")] + public Layout[] Layouts { get; set; } + + [ConfigurationField("bulkActionPermissions", "Bulk Action Permissions", "views/propertyeditors/listview/bulkactionpermissions.prevalues.html", + Description = "The bulk actions that are allowed from the list view")] + public BulkActionPermissionSettings BulkActionPermissions { get; set; } = new BulkActionPermissionSettings(); // fixme managing defaults? + + [ConfigurationField("tabName", "Tab Name", "textstring", Description = "The name of the listview tab (default if empty: 'Child Items')")] + public int TabName { get; set; } + + public class Property + { + [JsonProperty("alias")] + public string Alias { get; set; } + + [JsonProperty("header")] + public string Header { get; set; } + + [JsonProperty("isSystem")] + public int IsSystem { get; set; } // fixme bool + } + + public class Layout + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("path")] + public string Path { get; set; } + + [JsonProperty("icon")] + public string Icon { get; set; } + + [JsonProperty("isSystem")] + public int IsSystem { get; set; } // fixme bool + + [JsonProperty("selected")] + public bool Selected { get; set; } + } + + public class BulkActionPermissionSettings + { + [JsonProperty("allowBulkPublish")] + public bool AllowBulkPublish { get; set; } = true; + + [JsonProperty("allowBulkUnpublish")] + public bool AllowBulkUnpublish { get; set; } = true; + + [JsonProperty("allowBulkCopy")] + public bool AllowBulkCopy { get; set; } = true; + + [JsonProperty("allowBulkMove")] + public bool AllowBulkMove { get; set; } = true; + + [JsonProperty("allowBulkDelete")] + public bool AllowBulkDelete { get; set; } = true; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ListViewConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/ListViewConfigurationEditor.cs new file mode 100644 index 0000000000..a94945df0b --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ListViewConfigurationEditor.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the listview value editor. + /// + public class ListViewConfigurationEditor : ConfigurationEditor + { + public override IDictionary DefaultConfiguration => new Dictionary + { + {"pageSize", "10"}, + {"displayAtTabNumber", "1"}, + {"orderBy", "SortOrder"}, + {"orderDirection", "asc"}, + { + "includeProperties", new[] + { + new {alias = "sortOrder", header = "Sort order", isSystem = 1}, + new {alias = "updateDate", header = "Last edited", isSystem = 1}, + new {alias = "owner", header = "Created by", isSystem = 1} + } + }, + { + "layouts", new[] + { + new {name = "List", path = "views/propertyeditors/listview/layouts/list/list.html", icon = "icon-list", isSystem = 1, selected = true}, + new {name = "Grid", path = "views/propertyeditors/listview/layouts/grid/grid.html", icon = "icon-thumbnails-small", isSystem = 1, selected = true} + } + }, + {"bulkActionPermissions", new + { + allowBulkPublish = true, + allowBulkUnpublish = true, + allowBulkCopy = true, + allowBulkMove = false, + allowBulkDelete = true + }} + }; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs index 2ee208ed48..6d537d0aca 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs @@ -1,95 +1,25 @@ using System.Collections.Generic; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.ListViewAlias, "List view", "listview", HideLabel = true, Group = "lists", Icon = "icon-item-arrangement")] - public class ListViewPropertyEditor : PropertyEditor + /// + /// Represents a list-view editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.ListView, "List view", "listview", HideLabel = true, Group = "lists", Icon = "icon-item-arrangement")] + public class ListViewPropertyEditor : DataEditor { - protected override PreValueEditor CreatePreValueEditor() - { - return new ListViewPreValueEditor(); - } + /// + /// Initializes a new instance of the class. + /// + /// + public ListViewPropertyEditor(ILogger logger) + : base(logger) + { } - public override IDictionary DefaultPreValues - { - get - { - return new Dictionary - { - {"pageSize", "10"}, - {"displayAtTabNumber", "1"}, - {"orderBy", "SortOrder"}, - {"orderDirection", "asc"}, - { - "includeProperties", new[] - { - new {alias = "sortOrder", header = "Sort order", isSystem = 1}, - new {alias = "updateDate", header = "Last edited", isSystem = 1}, - new {alias = "owner", header = "Created by", isSystem = 1} - } - }, - { - "layouts", new[] - { - new {name = "List", path = "views/propertyeditors/listview/layouts/list/list.html", icon = "icon-list", isSystem = 1, selected = true}, - new {name = "Grid", path = "views/propertyeditors/listview/layouts/grid/grid.html", icon = "icon-thumbnails-small", isSystem = 1, selected = true} - } - }, - {"bulkActionPermissions", new - { - allowBulkPublish = true, - allowBulkUnpublish = true, - allowBulkCopy = true, - allowBulkMove = false, - allowBulkDelete = true - }} - }; - } - } - - internal class ListViewPreValueEditor : PreValueEditor - { - [PreValueField("tabName", "Tab Name", "textstring", Description = "The name of the listview tab (default if empty: 'Child Items')")] - public int TabName { get; set; } - - [PreValueField("displayAtTabNumber", "Display At Tab Number", "number", Description = "Which tab position that the list of child items will be displayed")] - public int DisplayAtTabNumber { get; set; } - - [PreValueField("pageSize", "Page Size", "number", Description = "Number of items per page")] - public int PageSize { get; set; } - - [PreValueField("layouts", "Layouts", "views/propertyeditors/listview/layouts.prevalues.html")] - public int Layouts { get; set; } - - [PreValueField("includeProperties", "Columns Displayed", "views/propertyeditors/listview/includeproperties.prevalues.html", - Description = "The properties that will be displayed for each column")] - public object IncludeProperties { get; set; } - - [PreValueField("orderBy", "Order By", "views/propertyeditors/listview/sortby.prevalues.html", - Description = "The default sort order for the list")] - public int OrderBy { get; set; } - - [PreValueField("orderDirection", "Order Direction", "views/propertyeditors/listview/orderdirection.prevalues.html")] - public int OrderDirection { get; set; } - - [PreValueField("bulkActionPermissions", "Bulk Action Permissions", "views/propertyeditors/listview/bulkactionpermissions.prevalues.html", - Description = "The bulk actions that are allowed from the list view")] - public BulkActionPermissionSettings BulkActionPermissions { get; set; } - - internal class BulkActionPermissionSettings - { - public bool AllowBulkPublish { get; set; } - - public bool AllowBulkUnpublish { get; set; } - - public bool AllowBulkCopy { get; set; } - - public bool AllowBulkMove { get; set; } - - public bool AllowBulkDelete { get; set; } - } - } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new ListViewConfigurationEditor(); } } diff --git a/src/Umbraco.Web/PropertyEditors/MacroContainerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MacroContainerConfiguration.cs new file mode 100644 index 0000000000..1490c4d2a8 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MacroContainerConfiguration.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the macro container value editor. + /// + public class MacroContainerConfiguration + { + [ConfigurationField("max", "Max items", "number", Description = "The maximum number of macros that are allowed in the container")] + public int MaxItems { get; set; } + + [ConfigurationField("allowed", "Allowed items", "views/propertyeditors/macrocontainer/macrolist.prevalues.html", Description = "The macro types allowed, if none are selected all macros will be allowed")] + public object AllowedItems { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MacroContainerConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/MacroContainerConfigurationEditor.cs new file mode 100644 index 0000000000..1ac4f5c874 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MacroContainerConfigurationEditor.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the macro container value editor. + /// + public class MacroContainerConfigurationEditor : ConfigurationEditor + { } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs index b3e5390b64..a30a48a241 100644 --- a/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs @@ -1,41 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; - namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MacroContainerAlias, "Macro Picker", "macrocontainer", ValueType = PropertyEditorValueTypes.Text, Group="rich content", Icon="icon-settings-alt", IsDeprecated = true)] - public class MacroContainerPropertyEditor : PropertyEditor + // fixme - MacroContainerPropertyEditor is deprecated, but what's the alternative? + [DataEditor(Constants.PropertyEditors.Aliases.MacroContainer, "(Obsolete) Macro Picker", "macrocontainer", ValueType = ValueTypes.Text, Group="rich content", Icon="icon-settings-alt", IsDeprecated = true)] + public class MacroContainerPropertyEditor : DataEditor { - /// - /// Creates a pre value editor instance - /// - /// - protected override PreValueEditor CreatePreValueEditor() - { - return new MacroContainerPreValueEditor(); - } - - protected override PropertyValueEditor CreateValueEditor() - { - //TODO: Need to add some validation to the ValueEditor to ensure that any media chosen actually exists! - - return base.CreateValueEditor(); - } - - internal class MacroContainerPreValueEditor : PreValueEditor - { - [PreValueField("max", "Max items", "number", Description = "The maximum number of macros that are allowed in the container")] - public int MaxItems { get; set; } - - [PreValueField("allowed", "Allowed items", "views/propertyeditors/macrocontainer/macrolist.prevalues.html", Description = "The macro types allowed, if none are selected all macros will be allowed")] - public object AllowedItems { get; set; } - } + public MacroContainerPropertyEditor(ILogger logger) + : base(logger) + { } + protected override IConfigurationEditor CreateConfigurationEditor() => new MacroContainerConfigurationEditor(); } } diff --git a/src/Umbraco.Web/PropertyEditors/MarkdownConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MarkdownConfiguration.cs new file mode 100644 index 0000000000..4c2374ccd5 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MarkdownConfiguration.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the markdown value editor. + /// + public class MarkdownConfiguration + { + [ConfigurationField("preview", "Preview", "boolean", Description = "Display a live preview")] + public bool DisplayLivePreview { get; set; } + + [ConfigurationField("defaultValue", "Default value", "textarea", Description = "If value is blank, the editor will show this")] + public string DefaultValue { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MarkdownConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/MarkdownConfigurationEditor.cs new file mode 100644 index 0000000000..9b08c3ac7b --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MarkdownConfigurationEditor.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editorfor the markdown value editor. + /// + internal class MarkdownConfigurationEditor : ConfigurationEditor + { } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MarkdownPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MarkdownPropertyEditor.cs index 9fa8f2d588..1de60980e0 100644 --- a/src/Umbraco.Web/PropertyEditors/MarkdownPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MarkdownPropertyEditor.cs @@ -1,23 +1,23 @@ -using Umbraco.Core; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MarkdownEditorAlias, "Markdown editor", "markdowneditor", ValueType = PropertyEditorValueTypes.Text, Icon="icon-code", Group="rich content")] - public class MarkdownPropertyEditor : PropertyEditor + /// + /// Represents a markdown editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.MarkdownEditor, "Markdown editor", "markdowneditor", ValueType = ValueTypes.Text, Icon="icon-code", Group="rich content")] + public class MarkdownPropertyEditor : DataEditor { - protected override PreValueEditor CreatePreValueEditor() - { - return new MarkdownPreValueEditor(); - } + /// + /// Initializes a new instance of the class. + /// + public MarkdownPropertyEditor(ILogger logger) + : base(logger) + { } - internal class MarkdownPreValueEditor : PreValueEditor - { - [PreValueField("preview", "Preview", "boolean", Description = "Display a live preview")] - public bool DisplayLivePreview { get; set; } - - [PreValueField("defaultValue", "Default value", "textarea", Description = "If value is blank, the editor will show this")] - public string DefaultValue { get; set; } - } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new MarkdownConfigurationEditor(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs deleted file mode 100644 index 2d9ee68b3b..0000000000 --- a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - /// - /// Media picker property editors that stores UDI - /// - [PropertyEditor(Constants.PropertyEditors.MediaPicker2Alias, "Media Picker", PropertyEditorValueTypes.Text, "mediapicker", IsParameterEditor = true, Group = "media", Icon = "icon-picture")] - public class MediaPicker2PropertyEditor : PropertyEditor - { - public MediaPicker2PropertyEditor() - { - InternalPreValues = new Dictionary - { - {"idType", "udi"} - }; - } - - internal IDictionary InternalPreValues; - - public override IDictionary DefaultPreValues - { - get { return InternalPreValues; } - set { InternalPreValues = value; } - } - - protected override PreValueEditor CreatePreValueEditor() - { - return new MediaPickerPreValueEditor(); - } - - internal class MediaPickerPreValueEditor : PreValueEditor - { - public MediaPickerPreValueEditor() - { - //create the fields - Fields.Add(new PreValueField() - { - Key = "multiPicker", - View = "boolean", - Name = "Pick multiple items" - }); - Fields.Add(new PreValueField() - { - Key = "onlyImages", - View = "boolean", - Name = "Pick only images", - Description = "Only let the editor choose images from media." - }); - Fields.Add(new PreValueField() - { - Key = "disableFolderSelect", - View = "boolean", - Name = "Disable folder select", - Description = "Do not allow folders to be picked." - }); - Fields.Add(new PreValueField() - { - Key = "startNodeId", - View = "mediapicker", - Name = "Start node", - Config = new Dictionary - { - {"idType", "udi"} - } - }); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs new file mode 100644 index 0000000000..382db7e414 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs @@ -0,0 +1,22 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the media picker value editor. + /// + public class MediaPickerConfiguration + { + [ConfigurationField("multiPicker", "Pick multiple items", "boolean")] + public bool Multiple { get; set; } + + [ConfigurationField("onlyImages", "Pick only images", "boolean", Description = "Only let the editor choose images from media.")] + public bool OnlyImages { get; set; } + + [ConfigurationField("disableFolderSelect", "Disable folder select", "boolean", Description = "Do not allow folders to be picked.")] + public bool DisableFolderSelect { get; set; } + + [ConfigurationField("startNodeId", "Start node", "mediapicker")] + public int StartNodeId { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerConfigurationEditor.cs new file mode 100644 index 0000000000..e810a65fb6 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerConfigurationEditor.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the media picker value editor. + /// + public class MediaPickerConfigurationEditor : ConfigurationEditor + { + /// + /// Initializes a new instance of the class. + /// + public MediaPickerConfigurationEditor() + { + Field(nameof(MediaPickerConfiguration.StartNodeId)) + .Config = new Dictionary + { + { "idType", "udi" } + }; + } + + public override IDictionary ToValueEditor(object configuration) + { + var d = base.ToValueEditor(configuration); + d["idType"] = "udi"; + return d; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index 8828f89a0b..6c768f4932 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -1,51 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - - -namespace Umbraco.Web.PropertyEditors -{ - /// - /// Legacy media property editor that stores Integer Ids - /// - [Obsolete("This editor is obsolete, use ContentPicker2PropertyEditor instead which stores UDI")] - [PropertyEditor(Constants.PropertyEditors.MediaPickerAlias, "(Obsolete) Media Picker", PropertyEditorValueTypes.Integer, "mediapicker", Group = "media", Icon = "icon-picture", IsDeprecated = true)] - public class MediaPickerPropertyEditor : MediaPicker2PropertyEditor - { - public MediaPickerPropertyEditor() - { - InternalPreValues = new Dictionary - { - {"multiPicker", "0"}, - {"onlyImages", "0"}, - {"idType", "int"} - }; - } - - protected override PreValueEditor CreatePreValueEditor() - { - return new SingleMediaPickerPreValueEditor(); - } - - internal class SingleMediaPickerPreValueEditor : PreValueEditor - { - public SingleMediaPickerPreValueEditor() - { - Fields.Add(new PreValueField() - { - Key = "startNodeId", - View = "mediapicker", - Name = "Start node", - Config = new Dictionary - { - {"idType", "int"} - } - }); - } - } - } -} +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents a media picker property editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.MediaPicker, EditorType.PropertyValue | EditorType.MacroParameter, + "Media Picker", "mediapicker", ValueType = ValueTypes.Text, Group = "media", Icon = "icon-picture")] + public class MediaPickerPropertyEditor : DataEditor + { + /// + /// Initializes a new instance of the class. + /// + public MediaPickerPropertyEditor(ILogger logger) + : base(logger) + { } + + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new MediaPickerConfigurationEditor(); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs index 433199c536..9c67838e0c 100644 --- a/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs @@ -1,15 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MemberGroupPickerAlias, "Member Group Picker", "membergrouppicker", Group="People", Icon="icon-users")] - public class MemberGroupPickerPropertyEditor : PropertyEditor + [DataEditor(Constants.PropertyEditors.Aliases.MemberGroupPicker, "Member Group Picker", "membergrouppicker", Group="People", Icon="icon-users")] + public class MemberGroupPickerPropertyEditor : DataEditor { + public MemberGroupPickerPropertyEditor(ILogger logger) + : base(logger) + { } } } diff --git a/src/Umbraco.Web/PropertyEditors/MemberPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberPicker2PropertyEditor.cs deleted file mode 100644 index eefa4debe5..0000000000 --- a/src/Umbraco.Web/PropertyEditors/MemberPicker2PropertyEditor.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - [PropertyEditor(Constants.PropertyEditors.MemberPicker2Alias, "Member Picker", PropertyEditorValueTypes.String, "memberpicker", Group = "People", Icon = "icon-user")] - public class MemberPicker2PropertyEditor : PropertyEditor - { - public MemberPicker2PropertyEditor() - { - InternalPreValues = new Dictionary - { - {"idType", "udi"} - }; - } - - internal IDictionary InternalPreValues; - public override IDictionary DefaultPreValues - { - get { return InternalPreValues; } - set { InternalPreValues = value; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MemberPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MemberPickerConfiguration.cs new file mode 100644 index 0000000000..d8a6a97d04 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MemberPickerConfiguration.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + public class MemberPickerConfiguration : ConfigurationEditor + { + public override IDictionary DefaultConfiguration => new Dictionary + { + { "idType", "udi" } + }; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs index f39f824ca1..a0705fb373 100644 --- a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs @@ -1,20 +1,16 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - - [Obsolete("This editor is obsolete, use MemberPickerPropertyEditor2 instead which stores UDI")] - [PropertyEditor(Constants.PropertyEditors.MemberPickerAlias, "(Obsolete) Member Picker", PropertyEditorValueTypes.Integer, "memberpicker", Group = "People", Icon = "icon-user", IsDeprecated = true)] - public class MemberPickerPropertyEditor : MemberPicker2PropertyEditor - { - public MemberPickerPropertyEditor() - { - InternalPreValues["idType"] = "int"; - } - } -} +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + [DataEditor(Constants.PropertyEditors.Aliases.MemberPicker, "Member Picker", "memberpicker", ValueType = ValueTypes.String, Group = "People", Icon = "icon-user")] + public class MemberPickerPropertyEditor : DataEditor + { + public MemberPickerPropertyEditor(ILogger logger) + : base(logger) + { } + + protected override IConfigurationEditor CreateConfigurationEditor() => new MemberPickerConfiguration(); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs new file mode 100644 index 0000000000..b6333c3140 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json.Linq; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the multinode picker value editor. + /// + public class MultiNodePickerConfiguration + { + [ConfigurationField("startNode", "Node type", "treesource")] + public MultiNodePickerConfigurationTreeSource TreeSource { get; set; } + + [ConfigurationField("filter", "Allow items of type", "textstring", Description = "Separate with comma")] + public string Filter { get; set; } + + [ConfigurationField("minNumber", "Minimum number of items", "number")] + public int MinNumber { get; set; } + + [ConfigurationField("maxNumber", "Maximum number of items", "number")] + public int MaxNumber { get; set; } + + [ConfigurationField("showOpenButton", "Show open button (this feature is in preview!)", "boolean", Description = "Opens the node in a dialog")] + public bool ShowOpen { get; set; } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfigurationEditor.cs new file mode 100644 index 0000000000..a52ee2138e --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfigurationEditor.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the multinode picker value editor. + /// + public class MultiNodePickerConfigurationEditor : ConfigurationEditor + { + public MultiNodePickerConfigurationEditor() + { + Field(nameof(MultiNodePickerConfiguration.TreeSource)) + .Config = new Dictionary + { + { "idType", "udi" } + }; + } + + /// + public override Dictionary ToConfigurationEditor(MultiNodePickerConfiguration configuration) + { + // sanitize configuraiton + var output = base.ToConfigurationEditor(configuration); + + output["multiPicker"] = configuration.MaxNumber > 1 ? true : false; + + return output; + } + + /// + public override IDictionary ToValueEditor(object configuration) + { + var d = base.ToValueEditor(configuration); + d["multiPicker"] = true; + d["showEditButton"] = false; + d["showPathOnHover"] = false; + d["idType"] = "udi"; + return d; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs new file mode 100644 index 0000000000..ffedf6c7dc --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the 'startNode' value for the + /// + [JsonObject] + public class MultiNodePickerConfigurationTreeSource + { + [JsonProperty("type")] + public string ObjectType {get;set;} + + [JsonProperty("query")] + public string StartNodeQuery {get;set;} + + [JsonProperty("id")] + public int? StartNodeId {get;set;} + } +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs deleted file mode 100644 index f43ecd48be..0000000000 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePicker2Alias, "Multinode Treepicker", PropertyEditorValueTypes.Text, "contentpicker", Group="pickers", Icon="icon-page-add")] - public class MultiNodeTreePicker2PropertyEditor : PropertyEditor - { - public MultiNodeTreePicker2PropertyEditor() - { - InternalPreValues = new Dictionary - { - {"multiPicker", "1"}, - {"showOpenButton", "0"}, - {"showEditButton", "0"}, - {"showPathOnHover", "0"}, - {"idType", "udi"} - }; - } - - protected override PreValueEditor CreatePreValueEditor() - { - return new MultiNodePickerPreValueEditor(); - } - - internal IDictionary InternalPreValues; - public override IDictionary DefaultPreValues - { - get { return InternalPreValues; } - set { InternalPreValues = value; } - } - - internal class MultiNodePickerPreValueEditor : PreValueEditor - { - public MultiNodePickerPreValueEditor() - { - //create the fields - Fields.Add(new PreValueField() - { - Key = "startNode", - View = "treesource", - Name = "Node type", - Config = new Dictionary - { - {"idType", "udi"} - } - }); - Fields.Add(new PreValueField() - { - Key = "filter", - View = "textstring", - Name = "Allow items of type", - Description = "Separate with comma" - }); - Fields.Add(new PreValueField() - { - Key = "minNumber", - View = "number", - Name = "Minimum number of items" - }); - Fields.Add(new PreValueField() - { - Key = "maxNumber", - View = "number", - Name = "Maximum number of items" - }); - Fields.Add(new PreValueField() - { - Key = "showOpenButton", - View = "boolean", - Name = "Show open button (this feature is in preview!)", - Description = "Opens the node in a dialog" - }); - } - - /// - /// This ensures the multiPicker pre-val is set based on the maxNumber of nodes set - /// - /// - /// - /// - /// - /// Due to compatibility with 7.0.0 the multiPicker pre-val might already exist in the db, but we've removed that setting in 7.0.1 so we need to detect it and if it is - /// there, then we'll set the maxNumber to '1' - /// - public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) - { - var result = base.ConvertDbToEditor(defaultPreVals, persistedPreVals); - - //backwards compatibility check - if (result.ContainsKey("multiPicker") && result["multiPicker"].ToString() == "0") - { - result["maxNumber"] = "1"; - } - - //set the multiPicker val correctly depending on the maxNumber - if (result.ContainsKey("maxNumber")) - { - var asNumber = result["maxNumber"].TryConvertTo(); - if (asNumber.Success) - { - if (asNumber.Result <= 1) - { - result["multiPicker"] = "0"; - } - else - { - result["multiPicker"] = "1"; - } - } - } - - - return result; - } - - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index a3c1e64af0..ad75e16717 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -1,28 +1,16 @@ -using System; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - [Obsolete("This editor is obsolete, use MultiNodeTreePickerPropertyEditor2 instead which stores UDI")] - [PropertyEditor(Constants.PropertyEditors.MultiNodeTreePickerAlias, "(Obsolete) Multinode Treepicker", "contentpicker", Group = "pickers", Icon = "icon-page-add", IsDeprecated = true)] - public class MultiNodeTreePickerPropertyEditor : MultiNodeTreePicker2PropertyEditor - { - public MultiNodeTreePickerPropertyEditor() - { - InternalPreValues["idType"] = "int"; - } - - /// - /// overridden to change the pre-value picker to use INT ids - /// - /// - protected override PreValueEditor CreatePreValueEditor() - { - var preValEditor = base.CreatePreValueEditor(); - preValEditor.Fields.Single(x => x.Key == "startNode").Config["idType"] = "int"; - return preValEditor; - } - } -} +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + [DataEditor(Constants.PropertyEditors.Aliases.MultiNodeTreePicker, "Multinode Treepicker", "contentpicker", ValueType = ValueTypes.Text, Group = "pickers", Icon = "icon-page-add")] + public class MultiNodeTreePickerPropertyEditor : DataEditor + { + public MultiNodeTreePickerPropertyEditor(ILogger logger) + : base(logger) + { } + + protected override IConfigurationEditor CreateConfigurationEditor() => new MultiNodePickerConfigurationEditor(); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs deleted file mode 100644 index 268ad30ffb..0000000000 --- a/src/Umbraco.Web/PropertyEditors/MultipleMediaPickerPropertyEditor.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - [Obsolete("This editor is obsolete, use MultipleMediaPickerPropertyEditor2 instead which stores UDI")] - [PropertyEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "(Obsolete) Media Picker", "mediapicker", Group = "media", Icon = "icon-pictures-alt-2", IsDeprecated = true)] - public class MultipleMediaPickerPropertyEditor : MediaPicker2PropertyEditor - { - public MultipleMediaPickerPropertyEditor() - { - //default it to multi picker - InternalPreValues["multiPicker"] = "1"; - InternalPreValues["idType"] = "int"; - } - - /// - /// overridden to change the pre-value picker to use INT ids - /// - /// - protected override PreValueEditor CreatePreValueEditor() - { - var preValEditor = base.CreatePreValueEditor(); - preValEditor.Fields.Single(x => x.Key == "startNodeId").Config["idType"] = "int"; - return preValEditor; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTestStringConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultipleTestStringConfiguration.cs new file mode 100644 index 0000000000..028bc9c13d --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MultipleTestStringConfiguration.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for a multiple testring value editor. + /// + public class MultipleTestStringConfiguration + { + // fields are configured in the editor + + public int Minimum { get; set; } + + public int Maximum {get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTextStringConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleTextStringConfigurationEditor.cs new file mode 100644 index 0000000000..6a10a103ba --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/MultipleTextStringConfigurationEditor.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for a multiple testring value editor. + /// + internal class MultipleTextStringConfigurationEditor : ConfigurationEditor + { + public MultipleTextStringConfigurationEditor() + { + Fields.Add(new ConfigurationField(new IntegerValidator()) + { + Description = "Enter the minimum amount of text boxes to be displayed", + Key = "min", + View = "requiredfield", + Name = "Minimum", + PropertyName = nameof(MultipleTestStringConfiguration.Minimum) + }); + + Fields.Add(new ConfigurationField(new IntegerValidator()) + { + Description = "Enter the maximum amount of text boxes to be displayed, enter 0 for unlimited", + Key = "max", + View = "requiredfield", + Name = "Maximum", + PropertyName = nameof(MultipleTestStringConfiguration.Maximum) + }); + } + + /// + public override MultipleTestStringConfiguration FromConfigurationEditor(IDictionary editorValues, MultipleTestStringConfiguration configuration) + { + // fixme this isn't pretty + //the values from the editor will be min/max fieds and we need to format to json in one field + // is the editor sending strings or ints or?! + var min = (editorValues.ContainsKey("min") ? editorValues["min"].ToString() : "0").TryConvertTo(); + var max = (editorValues.ContainsKey("max") ? editorValues["max"].ToString() : "0").TryConvertTo(); + + return new MultipleTestStringConfiguration + { + Minimum = min ? min.Result : 0, + Maximum = max ? max.Result : 0 + }; + } + + /// + public override Dictionary ToConfigurationEditor(MultipleTestStringConfiguration configuration) + { + return new Dictionary + { + { "min", configuration.Minimum }, + { "max", configuration.Maximum } + }; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs index 96288c9857..a6373bd87d 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -1,9 +1,5 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System; using System.Linq; -using System.Collections; -using System.Collections.Generic; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Logging; @@ -14,107 +10,34 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.MultipleTextstringAlias, "Repeatable textstrings", "multipletextbox", ValueType = PropertyEditorValueTypes.Text, Icon="icon-ordered-list", Group="lists")] - public class MultipleTextStringPropertyEditor : PropertyEditor + /// + /// Represents a multiple text string property editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.MultipleTextstring, "Repeatable textstrings", "multipletextbox", ValueType = ValueTypes.Text, Icon="icon-ordered-list", Group="lists")] + public class MultipleTextStringPropertyEditor : DataEditor { - protected override PropertyValueEditor CreateValueEditor() - { - return new MultipleTextStringPropertyValueEditor(base.CreateValueEditor()); - } - - protected override PreValueEditor CreatePreValueEditor() - { - return new MultipleTextStringPreValueEditor(); - } - /// - /// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. + /// Initializes a new instance of the class. /// - internal class MultipleTextStringPreValueEditor : PreValueEditor - { - public MultipleTextStringPreValueEditor() - { - //create the fields - Fields.Add(new PreValueField(new IntegerValidator()) - { - Description = "Enter the minimum amount of text boxes to be displayed", - Key = "min", - View = "requiredfield", - Name = "Minimum" - }); - Fields.Add(new PreValueField(new IntegerValidator()) - { - Description = "Enter the maximum amount of text boxes to be displayed, enter 0 for unlimited", - Key = "max", - View = "requiredfield", - Name = "Maximum" - }); - } + public MultipleTextStringPropertyEditor(ILogger logger) + : base(logger) + { } - /// - /// Need to change how we persist the values so they are compatible with the legacy way we store values - /// - /// - /// - /// - public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue) - { - //the values from the editor will be min/max fieds and we need to format to json in one field - var min = (editorValue.ContainsKey("min") ? editorValue["min"].ToString() : "0").TryConvertTo(); - var max = (editorValue.ContainsKey("max") ? editorValue["max"].ToString() : "0").TryConvertTo(); + /// + protected override IDataValueEditor CreateValueEditor() => new MultipleTextStringPropertyValueEditor(Attribute); - var json = JObject.FromObject(new {Minimum = min.Success ? min.Result : 0, Maximum = max.Success ? max.Result : 0}); - - return new Dictionary { { "0", new PreValue(json.ToString(Formatting.None)) } }; - } - - /// - /// Need to deal with the legacy way of storing pre-values and turn them into nice values for the editor - /// - /// - /// - /// - public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) - { - var preVals = persistedPreVals.FormatAsDictionary(); - var stringVal = preVals.Any() ? preVals.First().Value.Value : ""; - var returnVal = new Dictionary { { "min", 0 }, { "max", 0 } }; - if (stringVal.IsNullOrWhiteSpace() == false) - { - try - { - var json = JsonConvert.DeserializeObject(stringVal); - if (json["Minimum"] != null) - { - //by default pre-values are sent out with an id/value pair - returnVal["min"] = json["Minimum"].Value(); - } - if (json["Maximum"] != null) - { - returnVal["max"] = json["Maximum"].Value(); - } - } - catch (Exception e) - { - //this shouldn't happen unless there's already a bad formatted pre-value - LogHelper.WarnWithException("Could not deserialize value to json " + stringVal, e); - return returnVal; - } - } - - return returnVal; - } - } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new MultipleTextStringConfigurationEditor(); /// /// Custom value editor so we can format the value for the editor and the database /// - internal class MultipleTextStringPropertyValueEditor : PropertyValueEditorWrapper + internal class MultipleTextStringPropertyValueEditor : DataValueEditor { - public MultipleTextStringPropertyValueEditor(PropertyValueEditor wrapped) : base(wrapped) - { - } - + public MultipleTextStringPropertyValueEditor(DataEditorAttribute attribute) + : base(attribute) + { } + /// /// The value passed in from the editor will be an array of simple objects so we'll need to parse them to get the string /// @@ -124,7 +47,7 @@ namespace Umbraco.Web.PropertyEditors /// /// We will also check the pre-values here, if there are more items than what is allowed we'll just trim the end /// - public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) + public override object FromEditor(ContentPropertyData editorValue, object currentValue) { var asArray = editorValue.Value as JArray; if (asArray == null) @@ -132,33 +55,21 @@ namespace Umbraco.Web.PropertyEditors return null; } - var preVals = editorValue.PreValues.FormatAsDictionary(); - var max = -1; - if (preVals.Any()) - { - try - { - var json = JsonConvert.DeserializeObject(preVals.First().Value.Value); - max = int.Parse(json["Maximum"].ToString()); - } - catch (Exception) - { - //swallow - max = -1; - } - } + if (!(editorValue.DataTypeConfiguration is MultipleTestStringConfiguration config)) + throw new Exception("panic"); + var max = config.Maximum; //The legacy property editor saved this data as new line delimited! strange but we have to maintain that. var array = asArray.OfType() .Where(x => x["value"] != null) .Select(x => x["value"].Value()); - + //only allow the max if over 0 if (max > 0) { - return string.Join(Environment.NewLine, array.Take(max)); + return string.Join(Environment.NewLine, array.Take(max)); } - + return string.Join(Environment.NewLine, array); } @@ -167,18 +78,18 @@ namespace Umbraco.Web.PropertyEditors /// cannot have 2 way binding, so to get around that each item in the array needs to be an object with a string. /// /// - /// /// + /// + /// /// /// /// The legacy property editor saved this data as new line delimited! strange but we have to maintain that. /// - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - return property.Value == null - ? new JObject[] {} - : property.Value.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => JObject.FromObject(new {value = x})); + var val = property.GetValue(languageId, segment); + return val?.ToString().Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries) + .Select(x => JObject.FromObject(new {value = x})) ?? new JObject[] { }; } diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs b/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs new file mode 100644 index 0000000000..44648ee859 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs @@ -0,0 +1,41 @@ +using Newtonsoft.Json; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the nested content value editor. + /// + public class NestedContentConfiguration + { + [ConfigurationField("contentTypes", "Doc Types", "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", Description = "Select the doc types to use as the data blueprint.")] + public ContentType[] ContentTypes { get; set; } + + [ConfigurationField("minItems", "Min Items", "number", Description = "Set the minimum number of items allowed.")] + public int? MinItems { get; set; } + + [ConfigurationField("maxItems", "Max Items", "number", Description = "Set the maximum number of items allowed.")] + public int? MaxItems { get; set; } + + [ConfigurationField("confirmDeletes", "Confirm Deletes", "boolean", Description = "Set whether item deletions should require confirming.")] + public bool ConfirmDeletes { get; set; } = true; + + [ConfigurationField("showIcons", "Show Icons", "boolean", Description = "Set whether to show the items doc type icon in the list.")] + public bool ShowIcons { get; set; } = true; + + [ConfigurationField("hideLabel", "Hide Label", "boolean", Description = "Set whether to hide the editor label and have the list take up the full width of the editor window.")] + public bool HideLabel { get; set; } + + public class ContentType + { + [JsonProperty("ncAlias")] + public string Alias { get; set; } + + [JsonProperty("ncTabAlias")] + public string TabAlias { get; set; } + + [JsonProperty("nameTemplate")] + public string Template { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentConfigurationEditor.cs new file mode 100644 index 0000000000..5daed5c0aa --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/NestedContentConfigurationEditor.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the nested content value editor. + /// + public class NestedContentConfigurationEditor : ConfigurationEditor + { } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentController.cs b/src/Umbraco.Web/PropertyEditors/NestedContentController.cs index 3e8b0027fd..103967d20a 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentController.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentController.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.PropertyEditors [System.Web.Http.HttpGet] public IEnumerable GetContentTypes() { - return Services.ContentTypeService.GetAllContentTypes() + return Services.ContentTypeService.GetAll() .OrderBy(x => x.SortOrder) .Select(x => new { diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs b/src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs deleted file mode 100644 index 079a2e33a4..0000000000 --- a/src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Linq; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; - -namespace Umbraco.Web.PropertyEditors -{ - internal static class NestedContentHelper - { - private const string CacheKeyPrefix = "Umbraco.Web.PropertyEditors.NestedContent.GetPreValuesCollectionByDataTypeId_"; - - public static PreValueCollection GetPreValuesCollectionByDataTypeId(int dtdId) - { - var preValueCollection = (PreValueCollection)ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( - string.Concat(CacheKeyPrefix, dtdId), - () => ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dtdId)); - - return preValueCollection; - } - - public static void ClearCache(int id) - { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem( - string.Concat(CacheKeyPrefix, id)); - } - - public static string GetContentTypeAliasFromItem(JObject item) - { - var contentTypeAliasProperty = item[NestedContentPropertyEditor.ContentTypeAliasPropertyKey]; - if (contentTypeAliasProperty == null) - { - return null; - } - - return contentTypeAliasProperty.ToObject(); - } - - public static IContentType GetContentTypeFromItem(JObject item) - { - var contentTypeAlias = GetContentTypeAliasFromItem(item); - if (string.IsNullOrEmpty(contentTypeAlias)) - { - return null; - } - - return ApplicationContext.Current.Services.ContentTypeService.GetContentType(contentTypeAlias); - } - - #region Conversion from v0.1.1 data formats - - public static void ConvertItemValueFromV011(JObject item, int dtdId, ref PreValueCollection preValues) - { - var contentTypeAlias = GetContentTypeAliasFromItem(item); - if (contentTypeAlias != null) - { - // the item is already in >v0.1.1 format - return; - } - - // old style (v0.1.1) data, let's attempt a conversion - // - get the prevalues (if they're not loaded already) - preValues = preValues ?? GetPreValuesCollectionByDataTypeId(dtdId); - - // - convert the prevalues (if necessary) - ConvertPreValueCollectionFromV011(preValues); - - // - get the content types prevalue as JArray - var preValuesAsDictionary = preValues.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); - if (!preValuesAsDictionary.ContainsKey(ContentTypesPreValueKey) || string.IsNullOrEmpty(preValuesAsDictionary[ContentTypesPreValueKey]) != false) - { - return; - } - - var preValueContentTypes = JArray.Parse(preValuesAsDictionary[ContentTypesPreValueKey]); - if (preValueContentTypes.Any()) - { - // the only thing we can really do is assume that the item is the first available content type - item[NestedContentPropertyEditor.ContentTypeAliasPropertyKey] = preValueContentTypes.First().Value("ncAlias"); - } - } - - public static void ConvertPreValueCollectionFromV011(PreValueCollection preValueCollection) - { - if (preValueCollection == null) - { - return; - } - - var persistedPreValuesAsDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); - - // do we have a "docTypeGuid" prevalue and no "contentTypes" prevalue? - if (persistedPreValuesAsDictionary.ContainsKey("docTypeGuid") == false || persistedPreValuesAsDictionary.ContainsKey(ContentTypesPreValueKey)) - { - // the prevalues are already in >v0.1.1 format - return; - } - - // attempt to parse the doc type guid - Guid guid; - if (Guid.TryParse(persistedPreValuesAsDictionary["docTypeGuid"], out guid) == false) - { - // this shouldn't happen... but just in case. - return; - } - - // find the content type - var contentType = ApplicationContext.Current.Services.ContentTypeService.GetAllContentTypes().FirstOrDefault(c => c.Key == guid); - if (contentType == null) - { - return; - } - - // add a prevalue in the format expected by the new (>0.1.1) content type picker/configurator - preValueCollection.PreValuesAsDictionary[ContentTypesPreValueKey] = new PreValue( - string.Format(@"[{{""ncAlias"": ""{0}"", ""ncTabAlias"": ""{1}"", ""nameTemplate"": ""{2}"", }}]", - contentType.Alias, - persistedPreValuesAsDictionary["tabAlias"], - persistedPreValuesAsDictionary["nameTemplate"] - ) - ); - } - - private static string ContentTypesPreValueKey - { - get { return NestedContentPropertyEditor.NestedContentPreValueEditor.ContentTypesPreValueKey; } - } - - #endregion - } -} diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 38944d0a34..52933e5846 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; @@ -15,263 +15,188 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.NestedContentAlias, "Nested Content", "nestedcontent", ValueType = "JSON", Group = "lists", Icon = "icon-thumbnail-list")] - public class NestedContentPropertyEditor : PropertyEditor + /// + /// Represents a nested content property editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.NestedContent, "Nested Content", "nestedcontent", ValueType = "JSON", Group = "lists", Icon = "icon-thumbnail-list")] + public class NestedContentPropertyEditor : DataEditor { + private readonly Lazy _propertyEditors; + internal const string ContentTypeAliasPropertyKey = "ncContentTypeAlias"; - private IDictionary _defaultPreValues; - public override IDictionary DefaultPreValues + public NestedContentPropertyEditor(ILogger logger, Lazy propertyEditors) + : base (logger) { - get { return _defaultPreValues; } - set { _defaultPreValues = value; } + _propertyEditors = propertyEditors; } - public NestedContentPropertyEditor() + // has to be lazy else circular dep in ctor + private PropertyEditorCollection PropertyEditors => _propertyEditors.Value; + + private static IContentType GetElementType(JObject item) { - // Setup default values - _defaultPreValues = new Dictionary - { - {NestedContentPreValueEditor.ContentTypesPreValueKey, ""}, - {"minItems", 0}, - {"maxItems", 0}, - {"confirmDeletes", "1"}, - {"showIcons", "1"} - }; + var contentTypeAlias = item[ContentTypeAliasPropertyKey]?.ToObject(); + return string.IsNullOrEmpty(contentTypeAlias) + ? null + : Current.Services.ContentTypeService.Get(contentTypeAlias); } #region Pre Value Editor - protected override PreValueEditor CreatePreValueEditor() - { - return new NestedContentPreValueEditor(); - } - - internal class NestedContentPreValueEditor : PreValueEditor - { - internal const string ContentTypesPreValueKey = "contentTypes"; - - [PreValueField(ContentTypesPreValueKey, "Doc Types", "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", Description = "Select the doc types to use as the data blueprint.")] - public string[] ContentTypes { get; set; } - - [PreValueField("minItems", "Min Items", "number", Description = "Set the minimum number of items allowed.")] - public string MinItems { get; set; } - - [PreValueField("maxItems", "Max Items", "number", Description = "Set the maximum number of items allowed.")] - public string MaxItems { get; set; } - - [PreValueField("confirmDeletes", "Confirm Deletes", "boolean", Description = "Set whether item deletions should require confirming.")] - public string ConfirmDeletes { get; set; } - - [PreValueField("showIcons", "Show Icons", "boolean", Description = "Set whether to show the items doc type icon in the list.")] - public string ShowIcons { get; set; } - - [PreValueField("hideLabel", "Hide Label", "boolean", Description = "Set whether to hide the editor label and have the list take up the full width of the editor window.")] - public string HideLabel { get; set; } - - public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) - { - // re-format old style (v0.1.1) pre values if necessary - NestedContentHelper.ConvertPreValueCollectionFromV011(persistedPreVals); - - return base.ConvertDbToEditor(defaultPreVals, persistedPreVals); - } - } + protected override IConfigurationEditor CreateConfigurationEditor() => new NestedContentConfigurationEditor(); #endregion #region Value Editor - protected override PropertyValueEditor CreateValueEditor() - { - return new NestedContentPropertyValueEditor(base.CreateValueEditor()); - } + protected override IDataValueEditor CreateValueEditor() => new NestedContentPropertyValueEditor(Attribute, PropertyEditors); - internal class NestedContentPropertyValueEditor : PropertyValueEditorWrapper + internal class NestedContentPropertyValueEditor : DataValueEditor { - public NestedContentPropertyValueEditor(PropertyValueEditor wrapped) - : base(wrapped) + private readonly PropertyEditorCollection _propertyEditors; + + public NestedContentPropertyValueEditor(DataEditorAttribute attribute, PropertyEditorCollection propertyEditors) + : base(attribute) { - Validators.Add(new NestedContentValidator()); + _propertyEditors = propertyEditors; + Validators.Add(new NestedContentValidator(propertyEditors)); } - internal ServiceContext Services - { - get { return ApplicationContext.Current.Services; } - } + internal ServiceContext Services => Current.Services; - public override void ConfigureForDisplay(PreValueCollection preValues) + /// + public override object Configuration { - base.ConfigureForDisplay(preValues); - - var asDictionary = preValues.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); - if (asDictionary.ContainsKey("hideLabel")) + get => base.Configuration; + set { - var boolAttempt = asDictionary["hideLabel"].TryConvertTo(); - if (boolAttempt.Success) - { - HideLabel = boolAttempt.Result; - } + if (value == null) + throw new ArgumentNullException(nameof(value)); + if (!(value is NestedContentConfiguration configuration)) + throw new ArgumentException($"Expected a {typeof(NestedContentConfiguration).Name} instance, but got {value.GetType().Name}.", nameof(value)); + base.Configuration = value; + + HideLabel = configuration.HideLabel.TryConvertTo().Result; } } #region DB to String - public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override string ConvertDbToString(PropertyType propertyType, object propertyValue, IDataTypeService dataTypeService) { - // Convert / validate value - if (property.Value == null || string.IsNullOrWhiteSpace(property.Value.ToString())) + if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString())) return string.Empty; - var value = JsonConvert.DeserializeObject>(property.Value.ToString()); + var value = JsonConvert.DeserializeObject>(propertyValue.ToString()); if (value == null) return string.Empty; - // Process value - PreValueCollection preValues = null; - for (var i = 0; i < value.Count; i++) + foreach (var o in value) { - var o = value[i]; - var propValues = ((JObject)o); + var propValues = (JObject) o; - // convert from old style (v0.1.1) data format if necessary - NestedContentHelper.ConvertItemValueFromV011(propValues, propertyType.DataTypeDefinitionId, ref preValues); - - var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); + var contentType = GetElementType(propValues); if (contentType == null) - { continue; - } - var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray(); - - foreach (var propKey in propValueKeys) + var propAliases = propValues.Properties().Select(x => x.Name).ToArray(); + foreach (var propAlias in propAliases) { - var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey); + var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propAlias); if (propType == null) { - if (IsSystemPropertyKey(propKey) == false) - { - // Property missing so just delete the value - propValues[propKey] = null; - } + // type not found, and property is not system: just delete the value + if (IsSystemPropertyKey(propAlias) == false) + propValues[propAlias] = null; } else { try { - // Create a fake property using the property abd stored value - var prop = new Property(propType, propValues[propKey] == null ? null : propValues[propKey].ToString()); - - // Lookup the property editor - var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); - - // Get the editor to do it's conversion, and store it back - propValues[propKey] = propEditor.ValueEditor.ConvertDbToString(prop, propType, dataTypeService); + // convert the value, and store the converted value + var propEditor = _propertyEditors[propType.PropertyEditorAlias]; + var tempConfig = dataTypeService.GetDataType(propType.DataTypeId).Configuration; + var valEditor = propEditor.GetValueEditor(tempConfig); + var convValue = valEditor.ConvertDbToString(propType, propValues[propAlias]?.ToString(), dataTypeService); + propValues[propAlias] = convValue; } catch (InvalidOperationException) { - // https://github.com/umco/umbraco-nested-content/issues/111 - // Catch any invalid cast operations as likely means courier failed due to missing - // or trashed item so couldn't convert a guid back to an int + // deal with weird situations by ignoring them (no comment) + propValues[propAlias] = null; + } + } + } + } - propValues[propKey] = null; + return JsonConvert.SerializeObject(value).ToXmlString(); + } + + #endregion + + #region Convert database // editor + + // note: there is NO variant support here + + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) + { + var val = property.GetValue(languageId, segment); + if (val == null || string.IsNullOrWhiteSpace(val.ToString())) + return string.Empty; + + var value = JsonConvert.DeserializeObject>(val.ToString()); + if (value == null) + return string.Empty; + + foreach (var o in value) + { + var propValues = (JObject) o; + + var contentType = GetElementType(propValues); + if (contentType == null) + continue; + + var propAliases = propValues.Properties().Select(x => x.Name).ToArray(); + foreach (var propAlias in propAliases) + { + var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propAlias); + if (propType == null) + { + // type not found, and property is not system: just delete the value + if (IsSystemPropertyKey(propAlias) == false) + propValues[propAlias] = null; + } + else + { + try + { + // create a temp property with the value + var tempProp = new Property(propType); + tempProp.SetValue(propValues[propAlias] == null ? null : propValues[propAlias].ToString()); + + // convert that temp property, and store the converted value + var propEditor = _propertyEditors[propType.PropertyEditorAlias]; + var tempConfig = dataTypeService.GetDataType(propType.DataTypeId).Configuration; + var valEditor = propEditor.GetValueEditor(tempConfig); + var convValue = valEditor.ToEditor(tempProp, dataTypeService); + propValues[propAlias] = convValue == null ? null : JToken.FromObject(convValue); + } + catch (InvalidOperationException) + { + // deal with weird situations by ignoring them (no comment) + propValues[propAlias] = null; } } } } - // Update the value on the property - property.Value = JsonConvert.SerializeObject(value); - - // Pass the call down - return base.ConvertDbToString(property, propertyType, dataTypeService); + // return json + return value; } - #endregion - - #region DB to Editor - - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) - { - if (property.Value == null || string.IsNullOrWhiteSpace(property.Value.ToString())) - return string.Empty; - - var value = JsonConvert.DeserializeObject>(property.Value.ToString()); - if (value == null) - return string.Empty; - - // Process value - PreValueCollection preValues = null; - for (var i = 0; i < value.Count; i++) - { - var o = value[i]; - var propValues = ((JObject)o); - - // convert from old style (v0.1.1) data format if necessary - NestedContentHelper.ConvertItemValueFromV011(propValues, propertyType.DataTypeDefinitionId, ref preValues); - - var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); - if (contentType == null) - { - continue; - } - - var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray(); - - foreach (var propKey in propValueKeys) - { - var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey); - if (propType == null) - { - if (IsSystemPropertyKey(propKey) == false) - { - // Property missing so just delete the value - propValues[propKey] = null; - } - } - else - { - try - { - // Create a fake property using the property and stored value - var prop = new Property(propType, propValues[propKey] == null ? null : propValues[propKey].ToString()); - - // Lookup the property editor - var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); - - // Get the editor to do it's conversion - var newValue = propEditor.ValueEditor.ConvertDbToEditor(prop, propType, dataTypeService); - - // Store the value back - propValues[propKey] = (newValue == null) ? null : JToken.FromObject(newValue); - } - catch (InvalidOperationException) - { - // https://github.com/umco/umbraco-nested-content/issues/111 - // Catch any invalid cast operations as likely means courier failed due to missing - // or trashed item so couldn't convert a guid back to an int - - propValues[propKey] = null; - } - } - - } - } - - // Update the value on the property - property.Value = JsonConvert.SerializeObject(value); - - // Pass the call down - return base.ConvertDbToEditor(property, propertyType, dataTypeService); - } - - #endregion - - #region Editor to DB - - public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) + public override object FromEditor(ContentPropertyData editorValue, object currentValue) { if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString())) return null; @@ -290,7 +215,7 @@ namespace Umbraco.Web.PropertyEditors var o = value[i]; var propValues = ((JObject)o); - var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); + var contentType = GetElementType(propValues); if (contentType == null) { continue; @@ -312,19 +237,16 @@ namespace Umbraco.Web.PropertyEditors else { // Fetch the property types prevalue - var propPreValues = Services.DataTypeService.GetPreValuesCollectionByDataTypeId( - propType.DataTypeDefinitionId); + var propConfiguration = Services.DataTypeService.GetDataType(propType.DataTypeId).Configuration; // Lookup the property editor - var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); + var propEditor = _propertyEditors[propType.PropertyEditorAlias]; // Create a fake content property data object - var contentPropData = new ContentPropertyData( - propValues[propKey], propPreValues, - new Dictionary()); + var contentPropData = new ContentPropertyData(propValues[propKey], propConfiguration); // Get the property editor to do it's conversion - var newValue = propEditor.ValueEditor.ConvertEditorToDb(contentPropData, propValues[propKey]); + var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, propValues[propKey]); // Store the value back propValues[propKey] = (newValue == null) ? null : JToken.FromObject(newValue); @@ -339,25 +261,29 @@ namespace Umbraco.Web.PropertyEditors #endregion } - internal class NestedContentValidator : IPropertyValidator + internal class NestedContentValidator : IValueValidator { - public IEnumerable Validate(object rawValue, PreValueCollection preValues, PropertyEditor editor) + private readonly PropertyEditorCollection _propertyEditors; + + public NestedContentValidator(PropertyEditorCollection propertyEditors) + { + _propertyEditors = propertyEditors; + } + + public IEnumerable Validate(object rawValue, string valueType, object dataTypeConfiguration) { var value = JsonConvert.DeserializeObject>(rawValue.ToString()); if (value == null) yield break; - IDataTypeService dataTypeService = ApplicationContext.Current.Services.DataTypeService; + var dataTypeService = Current.Services.DataTypeService; for (var i = 0; i < value.Count; i++) { var o = value[i]; - var propValues = ((JObject)o); + var propValues = (JObject) o; - var contentType = NestedContentHelper.GetContentTypeFromItem(propValues); - if (contentType == null) - { - continue; - } + var contentType = GetElementType(propValues); + if (contentType == null) continue; var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray(); @@ -366,12 +292,12 @@ namespace Umbraco.Web.PropertyEditors var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey); if (propType != null) { - PreValueCollection propPrevalues = dataTypeService.GetPreValuesCollectionByDataTypeId(propType.DataTypeDefinitionId); - PropertyEditor propertyEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); + var config = dataTypeService.GetDataType(propType.DataTypeId).Configuration; + var propertyEditor = _propertyEditors[propType.PropertyEditorAlias]; - foreach (IPropertyValidator validator in propertyEditor.ValueEditor.Validators) + foreach (var validator in propertyEditor.GetValueEditor().Validators) { - foreach (ValidationResult result in validator.Validate(propValues[propKey], propPrevalues, propertyEditor)) + foreach (var result in validator.Validate(propValues[propKey], propertyEditor.GetValueEditor().ValueType, config)) { result.ErrorMessage = "Item " + (i + 1) + " '" + propType.Name + "' " + result.ErrorMessage; yield return result; diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs index 212f0351b1..cb016c97b9 100644 --- a/src/Umbraco.Web/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs @@ -1,20 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors.ParameterEditors { - [ParameterEditor("contentType", "Content Type Picker", "entitypicker")] - public class ContentTypeParameterEditor : ParameterEditor + /// + /// Represents a content type parameter editor. + /// + [DataEditor("contentType", EditorType.MacroParameter, "Content Type Picker", "entitypicker")] + public class ContentTypeParameterEditor : DataEditor { - public ContentTypeParameterEditor() + /// + /// Initializes a new instance of the class. + /// + public ContentTypeParameterEditor(ILogger logger) + : base(logger) { - Configuration.Add("multiple", "0"); - Configuration.Add("entityType", "DocumentType"); + // configure + DefaultConfiguration.Add("multiple", false); + DefaultConfiguration.Add("entityType", "DocumentType"); } } } diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs deleted file mode 100644 index 1fb9b267ab..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors.ParameterEditors -{ - [ParameterEditor(Constants.PropertyEditors.MultiNodeTreePickerAlias, "Multiple Content Picker", "contentpicker")] - public class MultipleContentPickerParameterEditor : ParameterEditor - { - public MultipleContentPickerParameterEditor() - { - Configuration.Add("multiPicker", "1"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs index 6e4645db1e..d51be8961a 100644 --- a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs @@ -1,14 +1,17 @@ -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors.ParameterEditors { - [ParameterEditor("contentTypeMultiple", "Multiple Content Type Picker", "entitypicker")] - public class MultipleContentTypeParameterEditor : ParameterEditor + [DataEditor("contentTypeMultiple", EditorType.MacroParameter, "Multiple Content Type Picker", "entitypicker")] + public class MultipleContentTypeParameterEditor : DataEditor { - public MultipleContentTypeParameterEditor() + public MultipleContentTypeParameterEditor(ILogger logger) + : base(logger) { - Configuration.Add("multiple", "1"); - Configuration.Add("entityType", "DocumentType"); + // configure + DefaultConfiguration.Add("multiple", true); + DefaultConfiguration.Add("entityType", "DocumentType"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs deleted file mode 100644 index 038552798d..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors.ParameterEditors -{ - [ParameterEditor(Constants.PropertyEditors.MultipleMediaPickerAlias, "Multiple Media Picker", "mediapicker")] - public class MultipleMediaPickerParameterEditor : ParameterEditor - { - public MultipleMediaPickerParameterEditor() - { - Configuration.Add("multiPicker", "1"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs index d1b6d11ca7..11bf64c044 100644 --- a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs @@ -1,16 +1,19 @@ -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors.ParameterEditors { - [ParameterEditor("tabPickerMultiple", "Multiple Tab Picker", "entitypicker")] - public class MultiplePropertyGroupParameterEditor : ParameterEditor + [DataEditor("tabPickerMultiple", EditorType.MacroParameter, "Multiple Tab Picker", "entitypicker")] + public class MultiplePropertyGroupParameterEditor : DataEditor { - public MultiplePropertyGroupParameterEditor() + public MultiplePropertyGroupParameterEditor(ILogger logger) + : base(logger) { - Configuration.Add("multiple", "1"); - Configuration.Add("entityType", "PropertyGroup"); - //don't publish the id for a property group, publish it's alias (which is actually just it's lower cased name) - Configuration.Add("publishBy", "alias"); + // configure + DefaultConfiguration.Add("multiple", true); + DefaultConfiguration.Add("entityType", "PropertyGroup"); + //don't publish the id for a property group, publish its alias, which is actually just its lower cased name + DefaultConfiguration.Add("publishBy", "alias"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs index f2872dfb32..3d519c127f 100644 --- a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs @@ -1,16 +1,19 @@ -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors.ParameterEditors { - [ParameterEditor("propertyTypePickerMultiple", "Multiple Property Type Picker", "entitypicker")] - public class MultiplePropertyTypeParameterEditor : ParameterEditor + [DataEditor("propertyTypePickerMultiple", EditorType.MacroParameter, "Multiple Property Type Picker", "entitypicker")] + public class MultiplePropertyTypeParameterEditor : DataEditor { - public MultiplePropertyTypeParameterEditor() + public MultiplePropertyTypeParameterEditor(ILogger logger) + : base(logger) { - Configuration.Add("multiple", "1"); - Configuration.Add("entityType", "PropertyType"); - //don't publish the id for a property type, publish it's alias - Configuration.Add("publishBy", "alias"); + // configure + DefaultConfiguration.Add("multiple", "1"); + DefaultConfiguration.Add("entityType", "PropertyType"); + //don't publish the id for a property type, publish its alias + DefaultConfiguration.Add("publishBy", "alias"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs index 6310abe020..028db9d2a5 100644 --- a/src/Umbraco.Web/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs @@ -1,16 +1,19 @@ -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors.ParameterEditors { - [ParameterEditor("tabPicker", "Tab Picker", "entitypicker")] - public class PropertyGroupParameterEditor : ParameterEditor + [DataEditor("tabPicker", EditorType.MacroParameter, "Tab Picker", "entitypicker")] + public class PropertyGroupParameterEditor : DataEditor { - public PropertyGroupParameterEditor() + public PropertyGroupParameterEditor(ILogger logger) + : base(logger) { - Configuration.Add("multiple", "0"); - Configuration.Add("entityType", "PropertyGroup"); + // configure + DefaultConfiguration.Add("multiple", "0"); + DefaultConfiguration.Add("entityType", "PropertyGroup"); //don't publish the id for a property group, publish it's alias (which is actually just it's lower cased name) - Configuration.Add("publishBy", "alias"); + DefaultConfiguration.Add("publishBy", "alias"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs index f19dd27c8c..e24534a81b 100644 --- a/src/Umbraco.Web/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs @@ -1,16 +1,19 @@ -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors.ParameterEditors { - [ParameterEditor("propertyTypePicker", "Property Type Picker", "entitypicker")] - public class PropertyTypeParameterEditor : ParameterEditor + [DataEditor("propertyTypePicker", EditorType.MacroParameter, "Property Type Picker", "entitypicker")] + public class PropertyTypeParameterEditor : DataEditor { - public PropertyTypeParameterEditor() + public PropertyTypeParameterEditor(ILogger logger) + : base(logger) { - Configuration.Add("multiple", "0"); - Configuration.Add("entityType", "PropertyType"); - //don't publish the id for a property type, publish it's alias - Configuration.Add("publishBy", "alias"); + // configure + DefaultConfiguration.Add("multiple", "0"); + DefaultConfiguration.Add("entityType", "PropertyType"); + //don't publish the id for a property type, publish its alias + DefaultConfiguration.Add("publishBy", "alias"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/SingleMediaPickerParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/SingleMediaPickerParameterEditor.cs deleted file mode 100644 index 918387d0b2..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ParameterEditors/SingleMediaPickerParameterEditor.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors.ParameterEditors -{ - [ParameterEditor(Constants.PropertyEditors.MediaPickerAlias, "Single Media Picker", "mediapicker")] - public class SingleMediaPickerParameterEditor : ParameterEditor - { - public SingleMediaPickerParameterEditor() - { - Configuration.Add("multiPicker", "0"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/PropertyEditorAssetAttribute.cs b/src/Umbraco.Web/PropertyEditors/PropertyEditorAssetAttribute.cs index db4b37a066..0953fa2e4c 100644 --- a/src/Umbraco.Web/PropertyEditors/PropertyEditorAssetAttribute.cs +++ b/src/Umbraco.Web/PropertyEditors/PropertyEditorAssetAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using ClientDependency.Core; namespace Umbraco.Web.PropertyEditors @@ -15,7 +15,7 @@ namespace Umbraco.Web.PropertyEditors public ClientDependencyType AssetType { get; private set; } public string FilePath { get; private set; } public int Priority { get; set; } - + /// /// Returns a CDF file reference /// @@ -37,4 +37,4 @@ namespace Umbraco.Web.PropertyEditors Priority = int.MinValue; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs b/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs new file mode 100644 index 0000000000..c4f9f6be15 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs @@ -0,0 +1,56 @@ +using System.Linq; +using Examine; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services.Implement; +using Umbraco.Examine; + +namespace Umbraco.Web.PropertyEditors +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + internal class PropertyEditorsComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public void Initialize(IRuntimeState runtime, PropertyEditorCollection propertyEditors) + { + var fileUpload = propertyEditors.OfType().FirstOrDefault(); + if (fileUpload != null) Initialize(fileUpload); + + var imageCropper = propertyEditors.OfType().FirstOrDefault(); + if (imageCropper != null) Initialize(imageCropper); + + // grid/examine moved to ExamineComponent + } + + // as long as these methods are private+static they won't be executed by the boot loader + + private static void Initialize(FileUploadPropertyEditor fileUpload) + { + MediaService.Saving += fileUpload.MediaServiceSaving; + MediaService.Created += fileUpload.MediaServiceCreated; + ContentService.Copied += fileUpload.ContentServiceCopied; + + MediaService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + MemberService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + } + + private static void Initialize(ImageCropperPropertyEditor imageCropper) + { + MediaService.Saving += imageCropper.MediaServiceSaving; + MediaService.Created += imageCropper.MediaServiceCreated; + ContentService.Copied += imageCropper.ContentServiceCopied; + + MediaService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + MemberService.Deleted += (sender, args) + => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/PropertyValueEditorWrapper.cs b/src/Umbraco.Web/PropertyEditors/PropertyValueEditorWrapper.cs deleted file mode 100644 index 8bd7c7b146..0000000000 --- a/src/Umbraco.Web/PropertyEditors/PropertyValueEditorWrapper.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - /// - /// Useful when returning a custom value editor when your property editor is attributed, it ensures the attribute - /// values are copied across to your custom value editor. - /// - public class PropertyValueEditorWrapper : PropertyValueEditor - { - public PropertyValueEditorWrapper(PropertyValueEditor wrapped) - { - this.HideLabel = wrapped.HideLabel; - this.View = wrapped.View; - this.ValueType = wrapped.ValueType; - foreach (var v in wrapped.Validators) - { - Validators.Add(v); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs b/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs index 98a1ecefa1..0d7e7dea3b 100644 --- a/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/PublishValueValueEditor.cs @@ -1,10 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.Composing; namespace Umbraco.Web.PropertyEditors { @@ -15,61 +16,42 @@ namespace Umbraco.Web.PropertyEditors /// This is required for legacy/backwards compatibility, otherwise we'd just store the string version and cache the string version without /// needing additional lookups. /// - internal class PublishValueValueEditor : PropertyValueEditorWrapper + internal class PublishValueValueEditor : DataValueEditor { - private readonly IDataTypeService _dataTypeService; + private readonly ILogger _logger; - internal PublishValueValueEditor(IDataTypeService dataTypeService, PropertyValueEditor wrapped) - : base(wrapped) + internal PublishValueValueEditor(DataEditorAttribute attribute, ILogger logger) + : base(attribute) { - _dataTypeService = dataTypeService; + _logger = logger; } - public PublishValueValueEditor(PropertyValueEditor wrapped) - : this(ApplicationContext.Current.Services.DataTypeService, wrapped) + /// + public override string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService) { - } - - /// - /// Need to lookup the pre-values and put the string version in cache, not the ID (which is what is stored in the db) - /// - /// - /// - /// - /// - public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) - { - if (property.Value == null) + if (value == null) return null; - var idAttempt = property.Value.TryConvertTo(); + // get the configuration items + // if none, fallback to base + var configuration = dataTypeService.GetDataType(propertyType.DataTypeId).ConfigurationAs(); + if (configuration == null) + return base.ConvertDbToString(propertyType, value, dataTypeService); + + var items = configuration.Items; + + var idAttempt = value.TryConvertTo(); if (idAttempt.Success) { - var preValId = idAttempt.Result; - var preVals = GetPreValues(property); - if (preVals != null) - { - if (preVals.Any(x => x.Value.Id == preValId)) - { - return preVals.Single(x => x.Value.Id == preValId).Value.Value; - } + var itemId = idAttempt.Result; + var item = items.FirstOrDefault(x => x.Id == itemId); + if (item != null) return item.Value; - LogHelper.Warn("Could not find a pre value with ID " + preValId + " for property alias " + property.Alias); - } + _logger.Warn("Could not find a configuration item with ID " + itemId + " for property alias " + propertyType.Alias); } - - return base.ConvertDbToString(property, propertyType, dataTypeService); - } - protected IDictionary GetPreValues(Property property) - { - var preVals = _dataTypeService.GetPreValuesCollectionByDataTypeId(property.PropertyType.DataTypeDefinitionId); - if (preVals != null) - { - var dictionary = preVals.FormatAsDictionary(); - return dictionary; - } - return null; + // fallback to default + return base.ConvertDbToString(propertyType, value, dataTypeService); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs index 044a958deb..6cd9675f67 100644 --- a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs @@ -1,10 +1,12 @@ -using System; +using System; using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.Composing; namespace Umbraco.Web.PropertyEditors { @@ -19,75 +21,72 @@ namespace Umbraco.Web.PropertyEditors { private readonly bool _publishIds; - internal PublishValuesMultipleValueEditor(bool publishIds, IDataTypeService dataTypeService, PropertyValueEditor wrapped) - : base(dataTypeService, wrapped) + internal PublishValuesMultipleValueEditor(bool publishIds, ILogger logger, DataEditorAttribute attribute) + : base(attribute, logger) { _publishIds = publishIds; } - public PublishValuesMultipleValueEditor(bool publishIds, PropertyValueEditor wrapped) - : this(publishIds, ApplicationContext.Current.Services.DataTypeService, wrapped) - { - } + public PublishValuesMultipleValueEditor(bool publishIds, DataEditorAttribute attribute) + : this(publishIds, Current.Logger, attribute) + { } /// /// If publishing ids, we don't need to do anything, otherwise we need to look up the pre-values and get the string values /// - /// /// + /// /// /// - public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override string ConvertDbToString(PropertyType propertyType, object propertyValue, IDataTypeService dataTypeService) { - if (property.Value == null) + if (propertyValue == null) return null; //publishing ids, so just need to return the value as-is if (_publishIds) { - return property.Value.ToString(); + return propertyValue.ToString(); } - //get the multiple ids - var selectedIds = property.Value.ToString().Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + // get the multiple ids + // if none, fallback to base + var selectedIds = propertyValue.ToString().Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); if (selectedIds.Any() == false) - { - //nothing there - return base.ConvertDbToString(property, propertyType, dataTypeService); - } + return base.ConvertDbToString(propertyType, propertyValue, dataTypeService); - var preValues = GetPreValues(property); - if (preValues != null) - { - //get all pre-values matching our Ids - return string.Join(",", - preValues.Where(x => selectedIds.Contains(x.Value.Id.ToInvariantString())).Select(x => x.Value.Value)); - } + // get the configuration items + // if none, fallback to base + var configuration = dataTypeService.GetDataType(propertyType.DataTypeId).ConfigurationAs(); + if (configuration == null) + return base.ConvertDbToString(propertyType, propertyValue, dataTypeService); - return base.ConvertDbToString(property, propertyType, dataTypeService); + var items = configuration.Items.Where(x => selectedIds.Contains(x.Id.ToInvariantString())).Select(x => x.Value); + return string.Join(",", items); } /// /// Override so that we can return a json array to the editor for multi-select values /// /// - /// /// + /// + /// /// - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - var delimited = base.ConvertDbToEditor(property, propertyType, dataTypeService).ToString(); + var delimited = base.ToEditor(property, dataTypeService, languageId, segment).ToString(); return delimited.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); } /// - /// When multiple values are selected a json array will be posted back so we need to format for storage in + /// When multiple values are selected a json array will be posted back so we need to format for storage in /// the database which is a comma separated ID value /// /// /// /// - public override object ConvertEditorToDb(Core.Models.Editors.ContentPropertyData editorValue, object currentValue) + public override object FromEditor(Core.Models.Editors.ContentPropertyData editorValue, object currentValue) { var json = editorValue.Value as JArray; if (json == null) @@ -100,4 +99,4 @@ namespace Umbraco.Web.PropertyEditors return string.Join(",", values); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs index 2bcd8fcbd8..75c8390d30 100644 --- a/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs @@ -1,5 +1,7 @@ -using Umbraco.Core; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -11,9 +13,14 @@ namespace Umbraco.Web.PropertyEditors /// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the INT ID value is published /// in cache and not the string value. /// - [PropertyEditor(Constants.PropertyEditors.RadioButtonListAlias, "Radio button list", "radiobuttons", ValueType = PropertyEditorValueTypes.Integer, Group="lists", Icon="icon-target")] + [DataEditor(Constants.PropertyEditors.Aliases.RadioButtonList, "Radio button list", "radiobuttons", ValueType = ValueTypes.Integer, Group="lists", Icon="icon-target")] public class RadioButtonsPropertyEditor : DropDownWithKeysPropertyEditor { - + /// + /// The constructor will setup the property editor based on the attribute if one is found + /// + public RadioButtonsPropertyEditor(ILogger logger, ILocalizedTextService textService) + : base(logger, textService) + { } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs deleted file mode 100644 index 3aaf191fd2..0000000000 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - [PropertyEditor(Constants.PropertyEditors.RelatedLinks2Alias, "Related links", "relatedlinks", ValueType = PropertyEditorValueTypes.Json, Icon = "icon-thumbnail-list", Group = "pickers")] - public class RelatedLinks2PropertyEditor : PropertyEditor - { - public RelatedLinks2PropertyEditor() - { - InternalPreValues = new Dictionary - { - {"idType", "udi"} - }; - } - - internal IDictionary InternalPreValues; - public override IDictionary DefaultPreValues - { - get { return InternalPreValues; } - set { InternalPreValues = value; } - } - - protected override PreValueEditor CreatePreValueEditor() - { - return new RelatedLinksPreValueEditor(); - } - - internal class RelatedLinksPreValueEditor : PreValueEditor - { - [PreValueField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] - public int Maximum { get; set; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksConfiguration.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksConfiguration.cs new file mode 100644 index 0000000000..5db14c6842 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksConfiguration.cs @@ -0,0 +1,13 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the related links value editor. + /// + public class RelatedLinksConfiguration + { + [ConfigurationField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] + public int Maximum { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksConfigurationEditor.cs new file mode 100644 index 0000000000..07ff359a82 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksConfigurationEditor.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the related links value editor. + /// + public class RelatedLinksConfigurationEditor : ConfigurationEditor + { + public override IDictionary ToValueEditor(object configuration) + { + var d = base.ToValueEditor(configuration); + d["idType"] = "udi"; + return d; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs index e4a7d99d07..b450fcc67f 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs @@ -1,23 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - [Obsolete("This editor is obsolete, use RelatedLinks2PropertyEditor instead which stores UDI")] - [PropertyEditor(Constants.PropertyEditors.RelatedLinksAlias, "(Obsolete) Related links", "relatedlinks", ValueType = PropertyEditorValueTypes.Json, Icon="icon-thumbnail-list", Group="pickers", IsDeprecated = true)] - public class RelatedLinksPropertyEditor : RelatedLinks2PropertyEditor - { - public RelatedLinksPropertyEditor() - { - InternalPreValues = new Dictionary - { - {"idType", "int"} - }; - } - } -} +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + [DataEditor(Constants.PropertyEditors.Aliases.RelatedLinks, "Related links", "relatedlinks", ValueType = ValueTypes.Json, Icon = "icon-thumbnail-list", Group = "pickers")] + public class RelatedLinksPropertyEditor : DataEditor + { + public RelatedLinksPropertyEditor(ILogger logger) + : base(logger) + { } + + protected override IConfigurationEditor CreateConfigurationEditor() => new RelatedLinksConfigurationEditor(); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs b/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs new file mode 100644 index 0000000000..420abe61cc --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the rich text value editor. + /// + public class RichTextConfiguration + { + [ConfigurationField("editor", "Editor", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] + public string Editor { get; set; } + + [ConfigurationField("hideLabel", "Hide Label", "boolean")] + public bool HideLabel { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RichTextConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextConfigurationEditor.cs new file mode 100644 index 0000000000..4777b5ebb0 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/RichTextConfigurationEditor.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the rich text value editor. + /// + internal class RichTextConfigurationEditor : ConfigurationEditor + { } +} diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPreValueController.cs b/src/Umbraco.Web/PropertyEditors/RichTextPreValueController.cs index ca8df2b9be..96863c3ff9 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPreValueController.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPreValueController.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using System.Xml; using Umbraco.Core.IO; using Umbraco.Web.Editors; @@ -18,16 +13,15 @@ namespace Umbraco.Web.PropertyEditors [PluginController("UmbracoApi")] public class RichTextPreValueController : UmbracoAuthorizedJsonController { - private static volatile bool _init = false; + private static volatile bool _init; private static readonly object Locker = new object(); private static readonly Dictionary Commands = new Dictionary(); private static readonly Dictionary Plugins = new Dictionary(); private static readonly Dictionary ConfigOptions = new Dictionary(); - + private static string _invalidElements = ""; private static string _validElements = ""; - public RichTextEditorConfiguration GetConfiguration() { EnsureInit(); @@ -131,8 +125,8 @@ namespace Umbraco.Web.PropertyEditors } } } - + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs deleted file mode 100644 index cd08a8ad45..0000000000 --- a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - //need to figure out how to use this... - internal class RichTextPreValueEditor : PreValueEditor - { - public RichTextPreValueEditor() - { - //SD: You can add pre-val fields here like you are doing, or you can add fields using attributes (http://issues.umbraco.org/issue/U4-2692), - // see below for examples. - - //use a custom editor too - Fields.Add(new PreValueField() - { - View = "views/propertyeditors/rte/rte.prevalues.html", - HideLabel = true, - Key = "editor" - }); - - Fields.Add(new PreValueField() - { - Name = "Hide Label", - View = "boolean", - Key = "hideLabel" - }); - } - - //SD: You can declare a field like this if you want to instead of in the ctor, there's some options here: - //#1 - the property name becomes the Key: - // - // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public string Editor { get; set; } - - //#2 - You can specify a custom Key: - // - // [PreValueField("editor", "", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public string Editor { get; set; } - - //#3 - If you require custom server side validation for your field then you have to specify a custom PreValueField type to use, - // this is why in this case I find it easier to use the ctor logic but thats just an opinion - // - Any value specified for this property attribute will override the values set in on the class instance of the field, this - // allows you to re-use class instances of fields if you want. - // - // [PreValueField(typeof(EditorPreValueField))] - // public string Editor { get; set; } - - // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public class EditorPreValueField : PreValueField - // { - // public EditorPreValueField() - // { - // //add any required server validators for this field - // Validators.Add(new RegexValidator("^\\d*$")); - // //You could also set the field properties directly here if you wanted instead of the attribute - // } - // } - - } -} diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index de65a326f7..181471b315 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Macros; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -7,49 +9,50 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.TinyMCEAlias, "Rich Text Editor", "rte", ValueType = PropertyEditorValueTypes.Text, HideLabel = false, Group="Rich Content", Icon="icon-browser-window")] - public class RichTextPropertyEditor : PropertyEditor + /// + /// Represents a rich text property editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.TinyMce, "Rich Text Editor", "rte", ValueType = ValueTypes.Text, HideLabel = false, Group="Rich Content", Icon="icon-browser-window")] + public class RichTextPropertyEditor : DataEditor { + /// + /// The constructor will setup the property editor based on the attribute if one is found + /// + public RichTextPropertyEditor(ILogger logger) : base(logger) + { + } + /// /// Create a custom value editor /// /// - protected override PropertyValueEditor CreateValueEditor() - { - return new RichTextPropertyValueEditor(base.CreateValueEditor()); - } + protected override IDataValueEditor CreateValueEditor() => new RichTextPropertyValueEditor(Attribute); - protected override PreValueEditor CreatePreValueEditor() - { - return new RichTextPreValueEditor(); - } + protected override IConfigurationEditor CreateConfigurationEditor() => new RichTextConfigurationEditor(); /// /// A custom value editor to ensure that macro syntax is parsed when being persisted and formatted correctly for display in the editor /// - internal class RichTextPropertyValueEditor : PropertyValueEditorWrapper + internal class RichTextPropertyValueEditor : DataValueEditor { - public RichTextPropertyValueEditor(PropertyValueEditor wrapped) - : base(wrapped) - { - } + public RichTextPropertyValueEditor(DataEditorAttribute attribute) + : base(attribute) + { } - /// - /// override so that we can hide the label based on the pre-value - /// - /// - public override void ConfigureForDisplay(Core.Models.PreValueCollection preValues) + /// + public override object Configuration { - base.ConfigureForDisplay(preValues); - var asDictionary = preValues.FormatAsDictionary(); - if (asDictionary.ContainsKey("hideLabel")) + get => base.Configuration; + set { - var boolAttempt = asDictionary["hideLabel"].Value.TryConvertTo(); - if (boolAttempt.Success) - { - HideLabel = boolAttempt.Result; - } + if (value == null) + throw new ArgumentNullException(nameof(value)); + if (!(value is RichTextConfiguration configuration)) + throw new ArgumentException($"Expected a {typeof(RichTextConfiguration).Name} instance, but got {value.GetType().Name}.", nameof(value)); + base.Configuration = value; + + HideLabel = configuration.HideLabel; } } @@ -57,15 +60,17 @@ namespace Umbraco.Web.PropertyEditors /// Format the data for the editor /// /// - /// /// + /// + /// /// - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - if (property.Value == null) + var val = property.GetValue(languageId, segment); + if (val == null) return null; - var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(property.Value.ToString(), new Dictionary()); + var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(val.ToString(), new Dictionary()); return parsed; } @@ -75,7 +80,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - public override object ConvertEditorToDb(Core.Models.Editors.ContentPropertyData editorValue, object currentValue) + public override object FromEditor(Core.Models.Editors.ContentPropertyData editorValue, object currentValue) { if (editorValue.Value == null) return null; @@ -86,5 +91,5 @@ namespace Umbraco.Web.PropertyEditors } } - -} \ No newline at end of file + +} diff --git a/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs b/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs index 6e6417615d..2d381e1f8f 100644 --- a/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs +++ b/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs @@ -1,19 +1,14 @@ using System; using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Web.Http; using System.Xml; -using umbraco.BusinessLogic; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Web.Editors; using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; -using Constants = Umbraco.Core.Constants; using Umbraco.Core.Media; using System.IO; +using Umbraco.Core.IO; namespace Umbraco.Web.PropertyEditors { @@ -23,14 +18,13 @@ namespace Umbraco.Web.PropertyEditors [PluginController("UmbracoApi")] public class RteEmbedController : UmbracoAuthorizedJsonController { - public Result GetEmbed(string url, int width, int height) { var result = new Result(); //todo cache embed doc var xmlConfig = new XmlDocument(); - xmlConfig.Load(GlobalSettings.FullpathToRoot + Path.DirectorySeparatorChar + "config" + Path.DirectorySeparatorChar + "EmbeddedMedia.config"); + xmlConfig.Load(IOHelper.GetRootDirectorySafe() + Path.DirectorySeparatorChar + "config" + Path.DirectorySeparatorChar + "EmbeddedMedia.config"); foreach (XmlNode node in xmlConfig.SelectNodes("//provider")) { @@ -69,7 +63,7 @@ namespace Umbraco.Web.PropertyEditors } catch(Exception ex) { - LogHelper.Error(string.Format("Error embedding url {0} - width: {1} height: {2}", url, width, height), ex); + Logger.Error(string.Format("Error embedding url {0} - width: {1} height: {2}", url, width, height), ex); result.Status = Status.Error; } diff --git a/src/Umbraco.Web/PropertyEditors/SliderConfiguration.cs b/src/Umbraco.Web/PropertyEditors/SliderConfiguration.cs new file mode 100644 index 0000000000..f91da8e68d --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/SliderConfiguration.cs @@ -0,0 +1,64 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the slider value editor. + /// + public class SliderConfiguration + { + [ConfigurationField("enableRange", "Enable range", "boolean")] + public string Range { get; set; } + + [ConfigurationField("orientation", "Orientation", "views/propertyeditors/slider/orientation.prevalues.html")] + public string Orientation { get; set; } + + [ConfigurationField("initVal1", "Initial value", "number")] + public int InitialValue { get; set; } + + [ConfigurationField("initVal2", "Initial value 2", "number", Description = "Used when range is enabled")] + public int InitialValue2 { get; set; } + + [ConfigurationField("minVal", "Minimum value", "number")] + public int MinimumValue { get; set; } + + [ConfigurationField("maxVal", "Maximum value", "number")] + public int MaximumValue { get; set; } + + [ConfigurationField("step", "Step increments", "number")] + public int StepIncrements { get; set; } + + [ConfigurationField("precision", "Precision", "number", Description = "The number of digits shown after the decimal. Defaults to the number of digits after the decimal of step value.")] + public int Precision { get; set; } + + [ConfigurationField("handle", "Handle", "views/propertyeditors/slider/handle.prevalues.html", Description = "Handle shape. Default is 'round\'")] + public string Handle { get; set; } + + [ConfigurationField("tooltip", "Tooltip", "views/propertyeditors/slider/tooltip.prevalues.html", Description = "Whether to show the tooltip on drag, hide the tooltip, or always show the tooltip. Accepts: 'show', 'hide', or 'always'")] + public string Tooltip { get; set; } + + [ConfigurationField("tooltipSplit", "Tooltip split", "boolean", Description = "If false show one tootip if true show two tooltips one for each handler")] + public string TooltipSplit { get; set; } // fixme bool? + + [ConfigurationField("tooltipFormat", "Tooltip format", "textstring", Description = "The value wanted to be displayed in the tooltip. Use {0} and {1} for current values - {1} is only for range slider and if not using tooltip split.")] + public string TooltipFormat { get; set; } + + [ConfigurationField("tooltipPosition", "Tooltip position", "textstring", Description = "Position of tooltip, relative to slider. Accepts 'top'/'bottom' for horizontal sliders and 'left'/'right' for vertically orientated sliders. Default positions are 'top' for horizontal and 'right' for vertical slider.")] + public string TooltipPosition { get; set; } + + [ConfigurationField("reversed", "Reversed", "boolean", Description = "Whether or not the slider should be reversed")] + public string Reversed { get; set; } // fixme bool? + + [ConfigurationField("ticks", "Ticks", "textstring", Description = "Comma-separated values. Used to define the values of ticks. Tick marks are indicators to denote special values in the range. This option overwrites min and max options.")] + public string Ticks { get; set; } + + [ConfigurationField("ticksPositions", "Ticks positions", "textstring", Description = "Comma-separated values. Defines the positions of the tick values in percentages. The first value should always be 0, the last value should always be 100 percent.")] + public string TicksPositions { get; set; } + + [ConfigurationField("ticksLabels", "Ticks labels", "textstring", Description = "Comma-separated values. Defines the labels below the tick marks. Accepts HTML input.")] + public string TicksLabels { get; set; } + + [ConfigurationField("ticksSnapBounds", "Ticks snap bounds", "number", Description = "Used to define the snap bounds of a tick. Snaps to the tick if value is within these bounds.")] + public int TicksSnapBounds { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/SliderConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/SliderConfigurationEditor.cs new file mode 100644 index 0000000000..c243c05985 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/SliderConfigurationEditor.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the slider value editor. + /// + public class SliderConfigurationEditor : ConfigurationEditor + { } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/SliderPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/SliderPropertyEditor.cs index c9a08de894..d6684c3f61 100644 --- a/src/Umbraco.Web/PropertyEditors/SliderPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/SliderPropertyEditor.cs @@ -1,72 +1,26 @@ using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.SliderAlias, "Slider", "slider", Icon="icon-navigation-horizontal")] - public class SliderPropertyEditor : PropertyEditor + /// + /// Represents a slider editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.Slider, "Slider", "slider", Icon = "icon-navigation-horizontal")] + public class SliderPropertyEditor : DataEditor { - protected override PreValueEditor CreatePreValueEditor() + /// + /// Initializes a new instance of the class. + /// + public SliderPropertyEditor(ILogger logger) + : base(logger) + { } + + /// + protected override IConfigurationEditor CreateConfigurationEditor() { - return new SliderPreValueEditor(); - } - - internal class SliderPreValueEditor : PreValueEditor - { - - [PreValueField("enableRange", "Enable range", "boolean")] - public string Range { get; set; } - - [PreValueField("orientation", "Orientation", "views/propertyeditors/slider/orientation.prevalues.html")] - public string Orientation { get; set; } - - [PreValueField("initVal1", "Initial value", "number")] - public int InitialValue { get; set; } - - [PreValueField("initVal2", "Initial value 2", "number", Description = "Used when range is enabled")] - public int InitialValue2 { get; set; } - - [PreValueField("minVal", "Minimum value", "number")] - public int MinimumValue { get; set; } - - [PreValueField("maxVal", "Maximum value", "number")] - public int MaximumValue { get; set; } - - [PreValueField("step", "Step increments", "number")] - public int StepIncrements { get; set; } - - [PreValueField("precision", "Precision", "number", Description = "The number of digits shown after the decimal. Defaults to the number of digits after the decimal of step value.")] - public int Precision { get; set; } - - [PreValueField("handle", "Handle", "views/propertyeditors/slider/handle.prevalues.html", Description = "Handle shape. Default is 'round\'")] - public string Handle { get; set; } - - [PreValueField("tooltip", "Tooltip", "views/propertyeditors/slider/tooltip.prevalues.html", Description = "Whether to show the tooltip on drag, hide the tooltip, or always show the tooltip. Accepts: 'show', 'hide', or 'always'")] - public string Tooltip { get; set; } - - [PreValueField("tooltipSplit", "Tooltip split", "boolean", Description = "If false show one tootip if true show two tooltips one for each handler")] - public string TooltipSplit { get; set; } - - [PreValueField("tooltipFormat", "Tooltip format", "textstring", Description = "The value wanted to be displayed in the tooltip. Use {0} and {1} for current values - {1} is only for range slider and if not using tooltip split.")] - public string TooltipFormat { get; set; } - - [PreValueField("tooltipPosition", "Tooltip position", "textstring", Description = "Position of tooltip, relative to slider. Accepts 'top'/'bottom' for horizontal sliders and 'left'/'right' for vertically orientated sliders. Default positions are 'top' for horizontal and 'right' for vertical slider.")] - public string TooltipPosition { get; set; } - - [PreValueField("reversed", "Reversed", "boolean", Description = "Whether or not the slider should be reversed")] - public string Reversed { get; set; } - - [PreValueField("ticks", "Ticks", "textstring", Description = "Comma-separated values. Used to define the values of ticks. Tick marks are indicators to denote special values in the range. This option overwrites min and max options.")] - public string Ticks { get; set; } - - [PreValueField("ticksPositions", "Ticks positions", "textstring", Description = "Comma-separated values. Defines the positions of the tick values in percentages. The first value should always be 0, the last value should always be 100 percent.")] - public string TicksPositions { get; set; } - - [PreValueField("ticksLabels", "Ticks labels", "textstring", Description = "Comma-separated values. Defines the labels below the tick marks. Accepts HTML input.")] - public string TicksLabels { get; set; } - - [PreValueField("ticksSnapBounds", "Ticks snap bounds", "number", Description = "Used to define the snap bounds of a tick. Snaps to the tick if value is within these bounds.")] - public string TicksSnapBounds { get; set; } + return new SliderConfigurationEditor(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/TagConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/TagConfigurationEditor.cs new file mode 100644 index 0000000000..00ff2cf136 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/TagConfigurationEditor.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the tag value editor. + /// + public class TagConfigurationEditor : ConfigurationEditor + { + public TagConfigurationEditor(ManifestValueValidatorCollection validators) + { + Fields.Add(new ConfigurationField(new RequiredValidator()) + { + Description = "Define a tag group", + Key = "group", + Name = "Tag group", + PropertyName = nameof(TagConfiguration.Group), + View = "requiredfield" + }); + + Fields.Add(new ConfigurationField(new RequiredValidator()) + { + Description = "Select whether to store the tags in cache as CSV (default) or as JSON. The only benefits of storage as JSON is that you are able to have commas in a tag value but this will require parsing the json in your views or using a property value converter", + Key = "storageType", + Name = "Storage Type", + PropertyName = nameof(TagConfiguration.StorageType), + View = "views/propertyeditors/tags/tags.prevalues.html" + }); + } + + public override Dictionary ToConfigurationEditor(TagConfiguration configuration) + { + var dictionary = base.ToConfigurationEditor(configuration); + + // the front-end editor expects the string value of the storage type + if (!dictionary.TryGetValue("storageType", out var storageType)) + storageType = TagsStorageType.Csv; + dictionary["storageType"] = storageType.ToString(); + + return dictionary; + } + + public override TagConfiguration FromConfigurationEditor(IDictionary editorValues, TagConfiguration configuration) + { + // the front-end editor retuns the string value of the storage type + // pure Json could do with + // [JsonConverter(typeof(StringEnumConverter))] + // but here we're only deserializing to object and it's too late + + editorValues["storageType"] = Enum.Parse(typeof(TagsStorageType), (string) editorValues["storageType"]); + return base.FromConfigurationEditor(editorValues, configuration); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/TagPropertyEditorTagDefinition.cs b/src/Umbraco.Web/PropertyEditors/TagPropertyEditorTagDefinition.cs deleted file mode 100644 index 8e721366b8..0000000000 --- a/src/Umbraco.Web/PropertyEditors/TagPropertyEditorTagDefinition.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Editors; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - /// - /// Used to dynamically change the tag group and storage type based on the pre-values - /// - internal class TagPropertyEditorTagDefinition : TagPropertyDefinition - { - public TagPropertyEditorTagDefinition(ContentPropertyData propertySaving, SupportTagsAttribute tagsAttribute) - : base(propertySaving, tagsAttribute) - { - } - - public override string TagGroup - { - get - { - var preVals = PropertySaving.PreValues.FormatAsDictionary(); - return preVals.ContainsKey("group") ? preVals["group"].Value : "default"; - } - } - - public override TagCacheStorageType StorageType - { - get - { - var preVals = PropertySaving.PreValues.FormatAsDictionary(); - return preVals.ContainsKey("storageType") - ? Enum.Parse(preVals["storageType"].Value) - : TagCacheStorageType.Csv; - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/TagsDataController.cs b/src/Umbraco.Web/PropertyEditors/TagsDataController.cs index 5d8e799c6c..dc0a0787dd 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsDataController.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsDataController.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using Umbraco.Web.Editors; +using System.Collections.Generic; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -21,4 +20,4 @@ namespace Umbraco.Web.PropertyEditors return Umbraco.TagQuery.GetAllTags(tagGroup); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs index b44c65b157..88a8c92ba3 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs @@ -1,157 +1,74 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Runtime.InteropServices; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [SupportTags(typeof(TagPropertyEditorTagDefinition), ValueType = TagValueType.CustomTagList)] - [PropertyEditor(Constants.PropertyEditors.TagsAlias, "Tags", "tags", Icon="icon-tags")] - public class TagsPropertyEditor : PropertyEditor + /// + /// Represents a tags property editor. + /// + [TagsPropertyEditor] + [DataEditor(Constants.PropertyEditors.Aliases.Tags, "Tags", "tags", Icon="icon-tags")] + public class TagsPropertyEditor : DataEditor { - public TagsPropertyEditor() + private readonly ManifestValueValidatorCollection _validators; + + public TagsPropertyEditor(ManifestValueValidatorCollection validators, ILogger logger) + : base(logger) { - _defaultPreVals = new Dictionary - { - {"group", "default"}, - {"storageType", TagCacheStorageType.Csv.ToString()} - }; + _validators = validators; } - private IDictionary _defaultPreVals; + protected override IDataValueEditor CreateValueEditor() => new TagPropertyValueEditor(Attribute); - /// - /// Override to supply the default group - /// - public override IDictionary DefaultPreValues - { - get { return _defaultPreVals; } - set { _defaultPreVals = value; } - } + protected override IConfigurationEditor CreateConfigurationEditor() => new TagConfigurationEditor(_validators); - protected override PropertyValueEditor CreateValueEditor() + internal class TagPropertyValueEditor : DataValueEditor { - return new TagPropertyValueEditor(base.CreateValueEditor()); - } + public TagPropertyValueEditor(DataEditorAttribute attribute) + : base(attribute) + { } - protected override PreValueEditor CreatePreValueEditor() - { - return new TagPreValueEditor(); - } - - internal class TagPropertyValueEditor : PropertyValueEditorWrapper - { - public TagPropertyValueEditor(PropertyValueEditor wrapped) - : base(wrapped) + /// + public override object FromEditor(ContentPropertyData editorValue, object currentValue) { + return editorValue.Value is JArray json + ? json.Select(x => x.Value()) + : null; } - /// - /// This needs to return IEnumerable{string} - /// - /// - /// - /// - public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) - { - var json = editorValue.Value as JArray; - return json == null ? null : json.Select(x => x.Value()); - } + /// + public override IValueRequiredValidator RequiredValidator => new RequiredJsonValueValidator(); /// - /// Returns the validator used for the required field validation which is specified on the PropertyType + /// Custom validator to validate a required value against an empty json value. /// /// - /// This will become legacy as soon as we implement overridable pre-values. - /// - /// The default validator used is the RequiredValueValidator but this can be overridden by property editors - /// if they need to do some custom validation, or if the value being validated is a json object. + /// This validator is required because the default RequiredValidator uses ValueType to + /// determine whether a property value is JSON, and for tags the ValueType is string although + /// the underlying data is JSON. Yes, this makes little sense. /// - public override ManifestValueValidator RequiredValidator + private class RequiredJsonValueValidator : IValueRequiredValidator { - get { return new RequiredTagsValueValidator(); } - } - - /// - /// Custom validator to validate a required value against an empty json value - /// - /// - /// This is required because the Tags property editor is not of type 'JSON', it's just string so the underlying - /// validator does not validate against an empty json string - /// - [ValueValidator("Required")] - private class RequiredTagsValueValidator : ManifestValueValidator - { - /// - /// Validates a null value or an empty json value - /// - /// - /// - /// - /// - /// - public override IEnumerable Validate(object value, string config, PreValueCollection preValues, PropertyEditor editor) + /// + public IEnumerable ValidateRequired(object value, string valueType) { if (value == null) { - yield return new ValidationResult("Value cannot be null", new[] { "value" }); + yield return new ValidationResult("Value cannot be null", new[] {"value"}); + yield break; } - else - { - var asString = value.ToString(); - if (asString.DetectIsEmptyJson()) - { - yield return new ValidationResult("Value cannot be empty", new[] { "value" }); - } - - if (asString.IsNullOrWhiteSpace()) - { - yield return new ValidationResult("Value cannot be empty", new[] { "value" }); - } - } + if (value.ToString().DetectIsEmptyJson()) + yield return new ValidationResult("Value cannot be empty", new[] { "value" }); } } } - - internal class TagPreValueEditor : PreValueEditor - { - public TagPreValueEditor() - { - Fields.Add(new PreValueField(new ManifestPropertyValidator { Type = "Required" }) - { - Description = "Define a tag group", - Key = "group", - Name = "Tag group", - View = "requiredfield" - }); - - Fields.Add(new PreValueField(new ManifestPropertyValidator {Type = "Required"}) - { - Description = "Select whether to store the tags in cache as CSV (default) or as JSON. The only benefits of storage as JSON is that you are able to have commas in a tag value but this will require parsing the json in your views or using a property value converter", - Key = "storageType", - Name = "Storage Type", - View = "views/propertyeditors/tags/tags.prevalues.html" - }); - } - - public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) - { - var result = base.ConvertDbToEditor(defaultPreVals, persistedPreVals); - - //This is required because we've added this pre-value so old installs that don't have it will need to have a default. - if (result.ContainsKey("storageType") == false || result["storageType"] == null || result["storageType"].ToString().IsNullOrWhiteSpace()) - { - result["storageType"] = TagCacheStorageType.Csv.ToString(); - } - - return result; - } - } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/TextAreaConfiguration.cs b/src/Umbraco.Web/PropertyEditors/TextAreaConfiguration.cs new file mode 100644 index 0000000000..a773dbc7d2 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/TextAreaConfiguration.cs @@ -0,0 +1,13 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the textarea value editor. + /// + public class TextAreaConfiguration + { + [ConfigurationField("maxChars", "Maximum allowed characters", "number", Description = "If empty - no character limit")] + public int MaxChars { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/TextAreaConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/TextAreaConfigurationEditor.cs new file mode 100644 index 0000000000..d02a222590 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/TextAreaConfigurationEditor.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the textarea value editor. + /// + public class TextAreaConfigurationEditor : ConfigurationEditor + { } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs index 659b1b3224..041ca3f624 100644 --- a/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextAreaPropertyEditor.cs @@ -1,25 +1,26 @@ using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.TextboxMultipleAlias, "Textarea", "textarea", IsParameterEditor = true, ValueType = PropertyEditorValueTypes.Text, Icon="icon-application-window-alt")] - public class TextAreaPropertyEditor : PropertyEditor + /// + /// Represents a textarea property and parameter editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.TextArea, EditorType.PropertyValue | EditorType.MacroParameter, "Textarea", "textarea", ValueType = ValueTypes.Text, Icon="icon-application-window-alt")] + public class TextAreaPropertyEditor : DataEditor { - protected override PropertyValueEditor CreateValueEditor() - { - return new TextOnlyValueEditor(base.CreateValueEditor()); - } + /// + /// Initializes a new instance of the class. + /// + public TextAreaPropertyEditor(ILogger logger) + : base(logger) + { } - protected override PreValueEditor CreatePreValueEditor() - { - return new TextAreaPreValueEditor(); - } + /// + protected override IDataValueEditor CreateValueEditor() => new TextOnlyValueEditor(Attribute); - internal class TextAreaPreValueEditor : PreValueEditor - { - [PreValueField("maxChars", "Maximum allowed characters", "number", Description = "If empty - no character limit")] - public bool MaxChars { get; set; } - } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new TextAreaConfigurationEditor(); } } diff --git a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs index 7222ee13e4..f860a5abe7 100644 --- a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; @@ -6,41 +6,44 @@ using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { /// - /// Custom value editor which ensures that the value stored is just plain text and that + /// Custom value editor which ensures that the value stored is just plain text and that /// no magic json formatting occurs when translating it to and from the database values /// - public class TextOnlyValueEditor : PropertyValueEditorWrapper + public class TextOnlyValueEditor : DataValueEditor { - public TextOnlyValueEditor(PropertyValueEditor wrapped) : base(wrapped) - { - } + public TextOnlyValueEditor(DataEditorAttribute attribute) + : base(attribute) + { } /// /// A method used to format the database value to a value that can be used by the editor /// /// - /// /// + /// + /// /// /// /// The object returned will always be a string and if the database type is not a valid string type an exception is thrown /// - public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - if (property.Value == null) return string.Empty; + var val = property.GetValue(languageId, segment); - switch (GetDatabaseType()) + if (val == null) return string.Empty; + + switch (ValueTypes.ToStorageType(ValueType)) { - case DataTypeDatabaseType.Ntext: - case DataTypeDatabaseType.Nvarchar: - return property.Value.ToString(); - case DataTypeDatabaseType.Integer: - case DataTypeDatabaseType.Decimal: - case DataTypeDatabaseType.Date: + case ValueStorageType.Ntext: + case ValueStorageType.Nvarchar: + return val.ToString(); + case ValueStorageType.Integer: + case ValueStorageType.Decimal: + case ValueStorageType.Date: default: throw new InvalidOperationException("The " + typeof(TextOnlyValueEditor) + " can only be used with string based property editors"); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/TextboxConfiguration.cs b/src/Umbraco.Web/PropertyEditors/TextboxConfiguration.cs new file mode 100644 index 0000000000..c9f15e2e2f --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/TextboxConfiguration.cs @@ -0,0 +1,13 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the textbox value editor. + /// + public class TextboxConfiguration + { + [ConfigurationField("maxChars", "Maximum allowed characters", "textstringlimited", Description = "If empty, 500 character limit")] + public int? MaxChars { get; set; } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/TextboxConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/TextboxConfigurationEditor.cs new file mode 100644 index 0000000000..aee2a8261d --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/TextboxConfigurationEditor.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the textbox value editor. + /// + public class TextboxConfigurationEditor : ConfigurationEditor + { } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs index 72748c17ed..9d35f6f336 100644 --- a/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextboxPropertyEditor.cs @@ -1,31 +1,26 @@ -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Umbraco.Core; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.TextboxAlias, "Textbox", "textbox", IsParameterEditor = true, Group = "Common")] - public class TextboxPropertyEditor : PropertyEditor + /// + /// Represents a textbox property and parameter editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.TextBox, EditorType.PropertyValue | EditorType.MacroParameter, "Textbox", "textbox", Group = "Common")] + public class TextboxPropertyEditor : DataEditor { - protected override PropertyValueEditor CreateValueEditor() - { - return new TextOnlyValueEditor(base.CreateValueEditor()); - } + /// + /// Initializes a new instance of the class. + /// + public TextboxPropertyEditor(ILogger logger) + : base(logger) + { } - protected override PreValueEditor CreatePreValueEditor() - { - return new TextboxPreValueEditor(); - } + /// + protected override IDataValueEditor CreateValueEditor() => new TextOnlyValueEditor(Attribute); - internal class TextboxPreValueEditor : PreValueEditor - { - [PreValueField("maxChars", "Maximum allowed characters", "textstringlimited", Description = "If empty - 500 character limit")] - public bool MaxChars { get; set; } - } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new TextboxConfigurationEditor(); } } diff --git a/src/Umbraco.Web/PropertyEditors/TrueFalseConfiguration.cs b/src/Umbraco.Web/PropertyEditors/TrueFalseConfiguration.cs new file mode 100644 index 0000000000..2dfbf9b0b6 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/TrueFalseConfiguration.cs @@ -0,0 +1,13 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration for the boolean value editor. + /// + public class TrueFalseConfiguration + { + [ConfigurationField("default", "Default Value", "boolean")] + public string Default { get; set; } // fixme - well, true or false?! + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/TrueFalseConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/TrueFalseConfigurationEditor.cs new file mode 100644 index 0000000000..73fb9b9c3f --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/TrueFalseConfigurationEditor.cs @@ -0,0 +1,10 @@ +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents the configuration editor for the boolean value editor. + /// + public class TrueFalseConfigurationEditor : ConfigurationEditor + { } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs index 0dc181cbc3..79456aa8ca 100644 --- a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs @@ -1,20 +1,23 @@ using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.TrueFalseAlias, "True/False", PropertyEditorValueTypes.Integer, "boolean", IsParameterEditor = true, Group = "Common", Icon="icon-checkbox")] - public class TrueFalsePropertyEditor : PropertyEditor + /// + /// Represents a boolean property and parameter editor. + /// + [DataEditor(Constants.PropertyEditors.Aliases.Boolean, EditorType.PropertyValue | EditorType.MacroParameter, "True/False", "boolean", ValueType = ValueTypes.Integer, Group = "Common", Icon="icon-checkbox")] + public class TrueFalsePropertyEditor : DataEditor { - protected override PreValueEditor CreatePreValueEditor() - { - return new TrueFalsePreValueEditor(); - } + /// + /// Initializes a new instance of the class. + /// + public TrueFalsePropertyEditor(ILogger logger) + : base(logger) + { } - internal class TrueFalsePreValueEditor : PreValueEditor - { - [PreValueField("default", "Default Value", "boolean")] - public string Default { get; set; } - } + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new TrueFalseConfigurationEditor(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs index 8a96c7ac3e..f49800f9d5 100644 --- a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs +++ b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs @@ -1,51 +1,41 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; -using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Services; using Umbraco.Core.Configuration; -using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; -using umbraco; -using Umbraco.Core.Configuration.UmbracoSettings; - +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Web.Composing; + namespace Umbraco.Web.PropertyEditors { - internal class UploadFileTypeValidator : IPropertyValidator + internal class UploadFileTypeValidator : IValueValidator { - public IEnumerable Validate(object value, PreValueCollection preValues, PropertyEditor editor) + public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) { + if (!(value is JObject jobject) || jobject["selectedFiles"] == null) yield break; + + var fileNames = jobject["selectedFiles"].ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - //now check the file type - var asJson = value as JObject; - if (asJson == null) yield break; - if (asJson["selectedFiles"] == null) yield break; - var fileNames = asJson["selectedFiles"].ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (var fileName in fileNames) { if (ValidateFileExtension(fileName) == false) { - yield return new ValidationResult(ui.Text("errors", "dissallowedMediaType"), - new[] - { - //we only store a single value for this editor so the 'member' or 'field' - // we'll associate this error with will simply be called 'value' - "value" - }); + //we only store a single value for this editor so the 'member' or 'field' + // we'll associate this error with will simply be called 'value' + yield return new ValidationResult(Current.Services.TextService.Localize("errors/dissallowedMediaType"), new[] { "value" }); } } - } internal static bool ValidateFileExtension(string fileName) { if (fileName.IndexOf('.') <= 0) return false; - var extension = Path.GetExtension(fileName).TrimStart("."); - + var extension = Path.GetExtension(fileName).TrimStart("."); return UmbracoConfig.For.UmbracoSettings().Content.IsFileAllowedForUpload(extension); } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/UserPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/UserPickerConfiguration.cs new file mode 100644 index 0000000000..f7bd0ceb7d --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/UserPickerConfiguration.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + public class UserPickerConfiguration : ConfigurationEditor + { + public override IDictionary DefaultConfiguration => new Dictionary + { + {"entityType", "User"} + }; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs index e4a070cb29..e1e01ff437 100644 --- a/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs @@ -1,28 +1,18 @@ -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Web.Mvc; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [PropertyEditor(Constants.PropertyEditors.UserPickerAlias, "User picker", PropertyEditorValueTypes.Integer, "entitypicker", Group="People", Icon="icon-user")] - public class UserPickerPropertyEditor : PropertyEditor + [DataEditor(Constants.PropertyEditors.Aliases.UserPicker, "User picker", "entitypicker", ValueType = ValueTypes.Integer, Group = "People", Icon = "icon-user")] + public class UserPickerPropertyEditor : DataEditor { - private IDictionary _defaultPreValues; + public UserPickerPropertyEditor(ILogger logger) + : base(logger) + { } - public UserPickerPropertyEditor() - { - _defaultPreValues = new Dictionary - { - {"entityType", "User"} - }; - } - - public override IDictionary DefaultPreValues - { - get { return _defaultPreValues; } - set { _defaultPreValues = value; } - } + protected override IConfigurationEditor CreateConfigurationEditor() => new UserPickerConfiguration(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerPropertyConverter.cs deleted file mode 100644 index 9876868af1..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerPropertyConverter.cs +++ /dev/null @@ -1,157 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Umbraco -// -// -// The content picker property editor converter. -// -// -------------------------------------------------------------------------------------------------------------------- - -using System; -using System.Collections.Generic; -using System.Globalization; - -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Extensions; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - /// - /// The content picker property value converter. - /// - [DefaultPropertyValueConverter] - [PropertyValueType(typeof(IPublishedContent))] - [PropertyValueCache(PropertyCacheValue.Object, PropertyCacheLevel.ContentCache)] - [PropertyValueCache(PropertyCacheValue.Source, PropertyCacheLevel.Content)] - [PropertyValueCache(PropertyCacheValue.XPath, PropertyCacheLevel.Content)] - public class ContentPickerPropertyConverter : PropertyValueConverterBase - { - /// - /// The properties to exclude. - /// - private static readonly List PropertiesToExclude = new List() - { - Constants.Conventions.Content.InternalRedirectId.ToLower(CultureInfo.InvariantCulture), - Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) - }; - - /// - /// Checks if this converter can convert the property editor and registers if it can. - /// - /// - /// The published property type. - /// - /// - /// The . - /// - public override bool IsConverter(PublishedPropertyType propertyType) - { - if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.ContentPicker2Alias)) - return true; - - if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters) - { - return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.ContentPickerAlias); - } - return false; - } - - /// - /// Convert the raw string into a nodeId integer - /// - /// - /// The published property type. - /// - /// - /// The value of the property - /// - /// - /// The preview. - /// - /// - /// The . - /// - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) - { - var attemptConvertInt = source.TryConvertTo(); - if (attemptConvertInt.Success) - return attemptConvertInt.Result; - var attemptConvertUdi = source.TryConvertTo(); - if (attemptConvertUdi.Success) - return attemptConvertUdi.Result; - return null; - } - - /// - /// Convert the source nodeId into a IPublishedContent (or DynamicPublishedContent) - /// - /// - /// The published property type. - /// - /// - /// The value of the property - /// - /// - /// The preview. - /// - /// - /// The . - /// - public override object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) - { - return null; - } - - if (UmbracoContext.Current == null) return source; - - if ((propertyType.PropertyTypeAlias != null && PropertiesToExclude.Contains(propertyType.PropertyTypeAlias.ToLower(CultureInfo.InvariantCulture))) == false) - { - IPublishedContent content; - if (source is int) - { - var sourceInt = (int)source; - content = UmbracoContext.Current.ContentCache.GetById(sourceInt); - if(content != null) - return content; - } - else - { - var sourceUdi = source as Udi; - if (sourceUdi == null) return null; - var umbHelper = new UmbracoHelper(UmbracoContext.Current); - content = umbHelper.TypedContent(sourceUdi); - if (content != null) - return content; - } - } - return source; - } - - /// - /// The convert source to xPath. - /// - /// - /// The property type. - /// - /// - /// The source. - /// - /// - /// The preview. - /// - /// - /// The . - /// - public override object ConvertSourceToXPath(PublishedPropertyType propertyType, object source, bool preview) - { - return source.ToString(); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs new file mode 100644 index 0000000000..ce3fef228f --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + internal class ContentPickerValueConverter : PropertyValueConverterBase + { + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + + private static readonly List PropertiesToExclude = new List + { + Constants.Conventions.Content.InternalRedirectId.ToLower(CultureInfo.InvariantCulture), + Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) + }; + + public ContentPickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + } + + public override bool IsConverter(PublishedPropertyType propertyType) + => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.ContentPicker); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (IPublishedContent); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Elements; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + if (source == null) return null; + + var attemptConvertInt = source.TryConvertTo(); + if (attemptConvertInt.Success) + return attemptConvertInt.Result; + var attemptConvertUdi = source.TryConvertTo(); + if (attemptConvertUdi.Success) + return attemptConvertUdi.Result; + return null; + } + + public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + if (inter == null) + return null; + + if ((propertyType.Alias != null && PropertiesToExclude.Contains(propertyType.Alias.ToLower(CultureInfo.InvariantCulture))) == false) + { + IPublishedContent content; + if (inter is int id) + { + content = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(id); + if (content != null) + return content; + } + else + { + var udi = inter as GuidUdi; + if (udi == null) + return null; + content = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(udi.Guid); + if (content != null) + return content; + } + } + + return inter; + } + + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + return inter.ToString(); + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropDataSetConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropDataSetConverter.cs deleted file mode 100644 index 19e87f7d23..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropDataSetConverter.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Web.Models; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - /// - /// Used to do some type conversions from ImageCropDataSet to string and JObject - /// - public class ImageCropDataSetConverter : TypeConverter - { - private static readonly Type[] ConvertableTypes = new[] - { - typeof(JObject) - }; - - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - return ConvertableTypes.Any(x => TypeHelper.IsTypeAssignableFrom(x, destinationType)) - || base.CanConvertFrom(context, destinationType); - } - - public override object ConvertTo( - ITypeDescriptorContext context, - CultureInfo culture, - object value, - Type destinationType) - { - var cropDataSet = value as ImageCropDataSet; - if (cropDataSet == null) - return null; - - //JObject - if (TypeHelper.IsTypeAssignableFrom(destinationType)) - { - return JObject.FromObject(cropDataSet); - } - - return base.ConvertTo(context, culture, value, destinationType); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs deleted file mode 100644 index c959d26721..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Globalization; -using System.Xml; -using System.Xml.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Core.Services; -using Umbraco.Web.Models; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - /// - /// Used to strongly type the value for the image cropper - /// - [DefaultPropertyValueConverter(typeof (JsonValueConverter))] //this shadows the JsonValueConverter - [PropertyValueType(typeof (ImageCropDataSet))] - [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] - public class ImageCropperValueConverter : Core.PropertyEditors.ValueConverters.ImageCropperValueConverter - { - public ImageCropperValueConverter() - { - } - - public ImageCropperValueConverter(IDataTypeService dataTypeService) : base(dataTypeService) - { - } - - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) - { - var baseVal = base.ConvertDataToSource(propertyType, source, preview); - var json = baseVal as JObject; - if (json == null) return baseVal; - - var serializer = new JsonSerializer - { - Culture = CultureInfo.InvariantCulture, - FloatParseHandling = FloatParseHandling.Decimal - }; - - //return the strongly typed model - return json.ToObject(serializer); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/LegacyMediaPickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/LegacyMediaPickerPropertyConverter.cs deleted file mode 100644 index 1f9291d629..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/LegacyMediaPickerPropertyConverter.cs +++ /dev/null @@ -1,277 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Umbraco -// -// -// The multiple media picker property editor converter. -// -// -------------------------------------------------------------------------------------------------------------------- - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; - -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Core.Services; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - /// - /// The multiple media picker and double legacy media picker property value converter, should be deleted for v8 - /// - [DefaultPropertyValueConverter(typeof(MustBeStringValueConverter))] - public class LegacyMediaPickerPropertyConverter : PropertyValueConverterBase, IPropertyValueConverterMeta - { - private readonly IDataTypeService _dataTypeService; - - //TODO: Remove this ctor in v8 since the other one will use IoC - public LegacyMediaPickerPropertyConverter() - : this(ApplicationContext.Current.Services.DataTypeService) - { - } - - public LegacyMediaPickerPropertyConverter(IDataTypeService dataTypeService) - { - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - _dataTypeService = dataTypeService; - } - - /// - /// Checks if this converter can convert the property editor and registers if it can. - /// - /// - /// The property type. - /// - /// - /// The . - /// - public override bool IsConverter(PublishedPropertyType propertyType) - { - if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters && propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultipleMediaPickerAlias)) - { - return true; - } - - if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters && propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MediaPickerAlias)) - { - // this is the double legacy media picker, it can pick only single media items - return true; - } - - return false; - } - - /// - /// Convert the raw string into a nodeId integer array or a single integer - /// - /// - /// The published property type. - /// - /// - /// The value of the property - /// - /// - /// The preview. - /// - /// - /// The . - /// - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) - return null; - - if (IsMultipleDataType(propertyType.DataTypeId, propertyType.PropertyEditorAlias)) - { - var nodeIds = - source.ToString() - .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) - .Select(int.Parse) - .ToArray(); - return nodeIds; - } - - var attemptConvertInt = source.TryConvertTo(); - if (attemptConvertInt.Success) - { - return attemptConvertInt.Result; - } - else - { - var nodeIds = - source.ToString() - .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) - .Select(int.Parse) - .ToArray(); - - if (nodeIds.Length > 0) - { - var error = - string.Format( - "Data type \"{0}\" is not set to allow multiple items but appears to contain multiple items, check the setting and save the data type again", - ApplicationContext.Current.Services.DataTypeService.GetDataTypeDefinitionById( - propertyType.DataTypeId).Name); - - LogHelper.Warn(error); - throw new Exception(error); - } - } - - return null; - } - - /// - /// Convert the source nodeId into a IPublishedContent or IEnumerable of IPublishedContent (or DynamicPublishedContent) depending on data type setting - /// - /// - /// The published property type. - /// - /// - /// The value of the property - /// - /// - /// The preview. - /// - /// - /// The . - /// - public override object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) - { - return null; - } - - if (UmbracoContext.Current == null) - { - return null; - } - - var umbHelper = new UmbracoHelper(UmbracoContext.Current); - - if (IsMultipleDataType(propertyType.DataTypeId, propertyType.PropertyEditorAlias)) - { - var nodeIds = (int[])source; - var multiMediaPicker = Enumerable.Empty(); - if (nodeIds.Length > 0) - { - multiMediaPicker = umbHelper.TypedMedia(nodeIds).Where(x => x != null); - } - - // in v8 should return multiNodeTreePickerEnumerable but for v7 need to return as PublishedContentEnumerable so that string can be returned for legacy compatibility - return new PublishedContentEnumerable(multiMediaPicker); - } - - // single value picker - var nodeId = (int)source; - - return umbHelper.TypedMedia(nodeId); - } - - /// - /// The get property cache level. - /// - /// - /// The property type. - /// - /// - /// The cache value. - /// - /// - /// The . - /// - public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue) - { - PropertyCacheLevel returnLevel; - switch (cacheValue) - { - case PropertyCacheValue.Object: - returnLevel = PropertyCacheLevel.ContentCache; - break; - case PropertyCacheValue.Source: - returnLevel = PropertyCacheLevel.Content; - break; - case PropertyCacheValue.XPath: - returnLevel = PropertyCacheLevel.Content; - break; - default: - returnLevel = PropertyCacheLevel.None; - break; - } - - return returnLevel; - } - - /// - /// The get property value type. - /// - /// - /// The property type. - /// - /// - /// The . - /// - public Type GetPropertyValueType(PublishedPropertyType propertyType) - { - return IsMultipleDataType(propertyType.DataTypeId, propertyType.PropertyEditorAlias) ? typeof(IEnumerable) : typeof(IPublishedContent); - } - - /// - /// The is multiple data type. - /// - /// - /// The data type id. - /// - /// - /// - /// The . - /// - private bool IsMultipleDataType(int dataTypeId, string propertyEditorAlias) - { - // GetPreValuesCollectionByDataTypeId is cached at repository level; - // still, the collection is deep-cloned so this is kinda expensive, - // better to cache here + trigger refresh in DataTypeCacheRefresher - - return Storages.GetOrAdd(dataTypeId, id => - { - var preVals = _dataTypeService.GetPreValuesCollectionByDataTypeId(id).PreValuesAsDictionary; - - if (preVals.ContainsKey("multiPicker")) - { - var preValue = preVals - .FirstOrDefault(x => string.Equals(x.Key, "multiPicker", StringComparison.InvariantCultureIgnoreCase)) - .Value; - - return preValue != null && preValue.Value.TryConvertTo().Result; - } - - //in some odd cases, the pre-values in the db won't exist but their default pre-values contain this key so check there - var propertyEditor = PropertyEditorResolver.Current.GetByAlias(propertyEditorAlias); - if (propertyEditor != null) - { - var preValue = propertyEditor.DefaultPreValues - .FirstOrDefault(x => string.Equals(x.Key, "multiPicker", StringComparison.InvariantCultureIgnoreCase)) - .Value; - - return preValue != null && preValue.TryConvertTo().Result; - } - - return false; - }); - } - - private static readonly ConcurrentDictionary Storages = new ConcurrentDictionary(); - - internal static void ClearCaches() - { - Storages.Clear(); - } - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/LegacyRelatedLinksEditorValueConvertor.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/LegacyRelatedLinksEditorValueConvertor.cs deleted file mode 100644 index df4a4356b6..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/LegacyRelatedLinksEditorValueConvertor.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Linq; -using System.Xml; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Web.Extensions; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - [DefaultPropertyValueConverter(typeof(JsonValueConverter))] //this shadows the JsonValueConverter - [PropertyValueType(typeof(JArray))] - [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] - public class LegacyRelatedLinksEditorValueConvertor : PropertyValueConverterBase - { - - public override bool IsConverter(PublishedPropertyType propertyType) - { - if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters == false) - { - return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.RelatedLinksAlias); - } - return false; - } - - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) return null; - var sourceString = source.ToString(); - - if (sourceString.DetectIsJson()) - { - try - { - var obj = JsonConvert.DeserializeObject(sourceString); - - //update the internal links if we have a context - if (UmbracoContext.Current == null) return obj; - - var helper = new UmbracoHelper(UmbracoContext.Current); - foreach (var a in obj) - { - var type = a.Value("type"); - if (type.IsNullOrWhiteSpace() == false) - { - if (type == "internal") - { - switch (propertyType.PropertyEditorAlias) - { - case Constants.PropertyEditors.RelatedLinksAlias: - var intLinkId = a.Value("link"); - var intLink = helper.NiceUrl(intLinkId); - a["link"] = intLink; - break; - case Constants.PropertyEditors.RelatedLinks2Alias: - var strLinkId = a.Value("link"); - var udiAttempt = strLinkId.TryConvertTo(); - if (udiAttempt) - { - var content = helper.TypedContent(udiAttempt.Result); - if (content == null) break; - a["link"] = helper.NiceUrl(content.Id); - } - break; - } - } - } - } - return obj; - } - catch (Exception ex) - { - LogHelper.Error("Could not parse the string " + sourceString + " to a json object", ex); - } - } - - //it's not json, just return the string - return sourceString; - } - - public override object ConvertSourceToXPath(PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) return null; - var sourceString = source.ToString(); - - if (sourceString.DetectIsJson()) - { - try - { - var obj = JsonConvert.DeserializeObject(sourceString); - - var d = new XmlDocument(); - var e = d.CreateElement("links"); - d.AppendChild(e); - - foreach (dynamic link in obj) - { - var ee = d.CreateElement("link"); - ee.SetAttribute("title", link.title); - ee.SetAttribute("link", link.link); - ee.SetAttribute("type", link.type); - ee.SetAttribute("newwindow", link.newWindow); - - e.AppendChild(ee); - } - - return d.CreateNavigator(); - } - catch (Exception ex) - { - LogHelper.Error("Could not parse the string " + sourceString + " to a json object", ex); - } - } - - //it's not json, just return the string - return sourceString; - } - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs index 436d131bcd..e6d44c0a8f 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs @@ -1,10 +1,13 @@ -using System; +using System; +using System.Collections.Generic; using System.Text; using System.Web; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Macros; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors.ValueConverters { @@ -12,31 +15,41 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// Ensures macro syntax is parsed for the macro container which will work when getting the field /// values in any way (i.e. dynamically, using Field(), or IPublishedContent) /// - [PropertyValueType(typeof (IHtmlString))] - [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Request)] [DefaultPropertyValueConverter] public class MacroContainerValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ServiceContext _services; + private readonly CacheHelper _appCache; + + public MacroContainerValueConverter(IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, CacheHelper appCache) { - return propertyType.PropertyEditorAlias == Constants.PropertyEditors.MacroContainerAlias; + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _services = services ?? throw new ArgumentNullException(nameof(services)); + _appCache = appCache ?? throw new ArgumentNullException(nameof(appCache)); } + public override bool IsConverter(PublishedPropertyType propertyType) + => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.MacroContainer; + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (IHtmlString); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; + // NOT thread-safe over a request because it modifies the // global UmbracoContext.Current.InPreviewMode status. So it // should never execute in // over the same UmbracoContext with // different preview modes. - static string RenderMacros(string source, bool preview) + string RenderMacros(string source, bool preview) { - // save and set for macro rendering - var inPreviewMode = UmbracoContext.Current.InPreviewMode; - UmbracoContext.Current.InPreviewMode = preview; - - var sb = new StringBuilder(); - - try + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + using (umbracoContext.ForcedPreview(preview)) // force for macro rendering { - var umbracoHelper = new UmbracoHelper(UmbracoContext.Current); + var sb = new StringBuilder(); + + var umbracoHelper = new UmbracoHelper(umbracoContext, _services, _appCache); MacroTagParser.ParseMacros( source, //callback for when text block is found @@ -46,17 +59,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters macroAlias, //needs to be explicitly casted to Dictionary macroAttributes.ConvertTo(x => (string)x, x => x)).ToString())); - } - finally - { - // restore - UmbracoContext.Current.InPreviewMode = inPreviewMode; - } - return sb.ToString(); - } + return sb.ToString(); + } + } - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); @@ -67,4 +75,4 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return sourceString; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index 681aa2ee72..3b165dc9f0 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -1,6 +1,6 @@ -using MarkdownSharp; -using System; +using System; using System.Web; +using HeyRed.MarkdownSharp; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; @@ -8,17 +8,19 @@ using Umbraco.Web.Templates; namespace Umbraco.Web.PropertyEditors.ValueConverters { - [PropertyValueType(typeof(IHtmlString))] - [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Request)] [DefaultPropertyValueConverter] public class MarkdownEditorValueConverter : PropertyValueConverterBase { public override bool IsConverter(PublishedPropertyType propertyType) - { - return Constants.PropertyEditors.MarkdownEditorAlias.Equals(propertyType.PropertyEditorAlias); - } + => Constants.PropertyEditors.Aliases.MarkdownEditor == propertyType.EditorAlias; - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (IHtmlString); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); @@ -30,19 +32,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return sourceString; } - public override object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { - // Convert markup to html for frontend rendering. - Markdown mark = new Markdown(); - + // convert markup to html for frontend rendering. // source should come from ConvertSource and be a string (or null) already - return new HtmlString(source == null ? string.Empty : mark.Transform((string)source)); - } - - public override object ConvertSourceToXPath(PublishedPropertyType propertyType, object source, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return source; + var mark = new Markdown(); + return new HtmlString(inter == null ? string.Empty : mark.Transform((string)inter)); } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerPropertyConverter.cs deleted file mode 100644 index 2e9d0c7d96..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerPropertyConverter.cs +++ /dev/null @@ -1,210 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Umbraco -// -// -// The media picker 2 value converter -// -// -------------------------------------------------------------------------------------------------------------------- - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Extensions; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - /// - /// The media picker property value converter. - /// - [DefaultPropertyValueConverter] - public class MediaPickerPropertyConverter : PropertyValueConverterBase, IPropertyValueConverterMeta - { - private readonly IDataTypeService _dataTypeService; - - //TODO: Remove this ctor in v8 since the other one will use IoC - public MediaPickerPropertyConverter() - : this(ApplicationContext.Current.Services.DataTypeService) - { - } - - public MediaPickerPropertyConverter(IDataTypeService dataTypeService) - { - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - _dataTypeService = dataTypeService; - } - - public Type GetPropertyValueType(PublishedPropertyType propertyType) - { - return IsMultipleDataType(propertyType.DataTypeId, propertyType.PropertyEditorAlias) ? typeof(IEnumerable) : typeof(IPublishedContent); - } - - public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue) - { - PropertyCacheLevel returnLevel; - switch (cacheValue) - { - case PropertyCacheValue.Object: - returnLevel = PropertyCacheLevel.ContentCache; - break; - case PropertyCacheValue.Source: - returnLevel = PropertyCacheLevel.Content; - break; - case PropertyCacheValue.XPath: - returnLevel = PropertyCacheLevel.Content; - break; - default: - returnLevel = PropertyCacheLevel.None; - break; - } - - return returnLevel; - } - - /// - /// The is multiple data type. - /// - /// - /// The data type id. - /// - /// - /// - /// The . - /// - private bool IsMultipleDataType(int dataTypeId, string propertyEditorAlias) - { - // GetPreValuesCollectionByDataTypeId is cached at repository level; - // still, the collection is deep-cloned so this is kinda expensive, - // better to cache here + trigger refresh in DataTypeCacheRefresher - - return Storages.GetOrAdd(dataTypeId, id => - { - var preVals = _dataTypeService.GetPreValuesCollectionByDataTypeId(id).PreValuesAsDictionary; - - if (preVals.ContainsKey("multiPicker")) - { - var preValue = preVals - .FirstOrDefault(x => string.Equals(x.Key, "multiPicker", StringComparison.InvariantCultureIgnoreCase)) - .Value; - - return preValue != null && preValue.Value.TryConvertTo().Result; - } - - //in some odd cases, the pre-values in the db won't exist but their default pre-values contain this key so check there - var propertyEditor = PropertyEditorResolver.Current.GetByAlias(propertyEditorAlias); - if (propertyEditor != null) - { - var preValue = propertyEditor.DefaultPreValues - .FirstOrDefault(x => string.Equals(x.Key, "multiPicker", StringComparison.InvariantCultureIgnoreCase)) - .Value; - - return preValue != null && preValue.TryConvertTo().Result; - } - - return false; - }); - } - - /// - /// Checks if this converter can convert the property editor and registers if it can. - /// - /// - /// The published property type. - /// - /// - /// The . - /// - public override bool IsConverter(PublishedPropertyType propertyType) - { - if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MediaPicker2Alias)) - return true; - - return false; - } - - /// - /// Convert the raw string into a nodeId integer - /// - /// - /// The published property type. - /// - /// - /// The value of the property - /// - /// - /// The preview. - /// - /// - /// The . - /// - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) - { - var nodeIds = source.ToString() - .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) - .Select(Udi.Parse) - .ToArray(); - return nodeIds; - } - - /// - /// Convert the source nodeId into a IPublishedContent (or DynamicPublishedContent) - /// - /// - /// The published property type. - /// - /// - /// The value of the property - /// - /// - /// The preview. - /// - /// - /// The . - /// - public override object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) - { - return null; - } - - var udis = (Udi[])source; - var mediaItems = new List(); - if (UmbracoContext.Current == null) return source; - var helper = new UmbracoHelper(UmbracoContext.Current); - if (udis.Any()) - { - foreach (var udi in udis) - { - var item = helper.TypedMedia(udi); - if (item != null) - mediaItems.Add(item); - } - if (IsMultipleDataType(propertyType.DataTypeId, propertyType.PropertyEditorAlias)) - { - return mediaItems; - } - else - { - return mediaItems.FirstOrDefault(); - } - } - - return source; - } - - private static readonly ConcurrentDictionary Storages = new ConcurrentDictionary(); - - internal static void ClearCaches() - { - Storages.Clear(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs new file mode 100644 index 0000000000..9e9e61cb05 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + /// + /// The media picker property value converter. + /// + [DefaultPropertyValueConverter] + public class MediaPickerValueConverter : PropertyValueConverterBase + { + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + + public MediaPickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); + } + + public override bool IsConverter(PublishedPropertyType propertyType) + { + return propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MediaPicker); + } + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => IsMultipleDataType(propertyType.DataType) + ? typeof (IEnumerable) + : typeof (IPublishedContent); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; + + private bool IsMultipleDataType(PublishedDataType dataType) + { + var config = ConfigurationEditor.ConfigurationAs(dataType.Configuration); + return config.Multiple; + } + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + var nodeIds = source.ToString() + .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) + .Select(Udi.Parse) + .ToArray(); + return nodeIds; + } + + public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + { + if (source == null) + { + return null; + } + + var udis = (Udi[])source; + var mediaItems = new List(); + if (udis.Any()) + { + foreach (var udi in udis) + { + var guidUdi = udi as GuidUdi; + if (guidUdi == null) continue; + var item = _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(guidUdi.Guid); + if (item != null) + mediaItems.Add(item); + } + + if (IsMultipleDataType(propertyType.DataType)) + return mediaItems; + return mediaItems.FirstOrDefault(); + } + + return source; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerPropertyConverter.cs deleted file mode 100644 index e89a5c1a09..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerPropertyConverter.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Extensions; -using Umbraco.Web.Security; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - [DefaultPropertyValueConverter] - [PropertyValueType(typeof(IPublishedContent))] - [PropertyValueCache(PropertyCacheValue.Object, PropertyCacheLevel.ContentCache)] - [PropertyValueCache(PropertyCacheValue.Source, PropertyCacheLevel.Content)] - [PropertyValueCache(PropertyCacheValue.XPath, PropertyCacheLevel.Content)] - public class MemberPickerPropertyConverter : PropertyValueConverterBase - { - public override bool IsConverter(PublishedPropertyType propertyType) - { - if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MemberPicker2Alias)) - return true; - - if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters) - { - return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MemberPickerAlias); - } - return false; - } - - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) - { - var attemptConvertInt = source.TryConvertTo(); - if (attemptConvertInt.Success) - return attemptConvertInt.Result; - var attemptConvertUdi = source.TryConvertTo(); - if (attemptConvertUdi.Success) - return attemptConvertUdi.Result; - return null; - } - - public override object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) - return null; - - if (UmbracoContext.Current == null) return source; - - var umbracoHelper = new UmbracoHelper(UmbracoContext.Current); - IPublishedContent member; - if (source is int) - { - member = umbracoHelper.TypedMember((int)source); - if (member != null) - return member; - } - else - { - var sourceUdi = source as Udi; - member = umbracoHelper.TypedMember(sourceUdi); - if (member != null) - return member; - } - - return source; - } - - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs new file mode 100644 index 0000000000..3abdc0702a --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs @@ -0,0 +1,69 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Security; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + [DefaultPropertyValueConverter] + public class MemberPickerValueConverter : PropertyValueConverterBase + { + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + + public MemberPickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); + } + + public override bool IsConverter(PublishedPropertyType propertyType) + { + return propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.MemberPicker); + } + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (IPublishedContent); + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + var attemptConvertInt = source.TryConvertTo(); + if (attemptConvertInt.Success) + return attemptConvertInt.Result; + var attemptConvertUdi = source.TryConvertTo(); + if (attemptConvertUdi.Success) + return attemptConvertUdi.Result; + return null; + } + + public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + { + if (source == null) + return null; + + if (UmbracoContext.Current != null) + { + IPublishedContent member; + if (source is int id) + { + member = _publishedSnapshotAccessor.PublishedSnapshot.Members.GetById(id); + if (member != null) + return member; + } + else + { + var sourceUdi = source as GuidUdi; + if (sourceUdi == null) return null; + member = _publishedSnapshotAccessor.PublishedSnapshot.Members.GetByProviderKey(sourceUdi.Guid); + if (member != null) + return member; + } + } + + return source; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs deleted file mode 100644 index 249b7e0cac..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs +++ /dev/null @@ -1,216 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Umbraco -// -// -// The multi node tree picker property editor value converter. -// -// -------------------------------------------------------------------------------------------------------------------- - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Web.Extensions; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - - /// - /// The multi node tree picker property editor value converter. - /// - [DefaultPropertyValueConverter(typeof(MustBeStringValueConverter))] - [PropertyValueType(typeof(IEnumerable))] - [PropertyValueCache(PropertyCacheValue.Object, PropertyCacheLevel.ContentCache)] - [PropertyValueCache(PropertyCacheValue.Source, PropertyCacheLevel.Content)] - [PropertyValueCache(PropertyCacheValue.XPath, PropertyCacheLevel.Content)] - public class MultiNodeTreePickerPropertyConverter : PropertyValueConverterBase - { - /// - /// The properties to exclude. - /// - private static readonly List PropertiesToExclude = new List() - { - Constants.Conventions.Content.InternalRedirectId.ToLower(CultureInfo.InvariantCulture), - Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) - }; - - /// - /// Checks if this converter can convert the property editor and registers if it can. - /// - /// - /// The published property type. - /// - /// - /// The . - /// - public override bool IsConverter(PublishedPropertyType propertyType) - { - if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePicker2Alias)) - return true; - - if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters) - { - return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePickerAlias); - } - return false; - } - - /// - /// Convert the raw string into a nodeId integer array - /// - /// - /// The published property type. - /// - /// - /// The value of the property - /// - /// - /// The preview. - /// - /// - /// The . - /// - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) - { - if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePickerAlias)) - { - var nodeIds = source.ToString() - .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) - .Select(int.Parse) - .ToArray(); - return nodeIds; - } - if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePicker2Alias)) - { - var nodeIds = source.ToString() - .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) - .Select(Udi.Parse) - .ToArray(); - return nodeIds; - } - return null; - } - - /// - /// Convert the source nodeId into a IEnumerable of IPublishedContent (or DynamicPublishedContent) - /// - /// - /// The published property type. - /// - /// - /// The value of the property - /// - /// - /// The preview. - /// - /// - /// The . - /// - public override object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) - { - return null; - } - - //TODO: Inject an UmbracoHelper and create a GetUmbracoHelper method based on either injected or singleton - if (UmbracoContext.Current == null) return source; - - var umbHelper = new UmbracoHelper(UmbracoContext.Current); - - if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePickerAlias)) - { - var nodeIds = (int[])source; - - if ((propertyType.PropertyTypeAlias != null && PropertiesToExclude.InvariantContains(propertyType.PropertyTypeAlias)) == false) - { - var multiNodeTreePicker = new List(); - - var objectType = UmbracoObjectTypes.Unknown; - - foreach (var nodeId in nodeIds) - { - var multiNodeTreePickerItem = - GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Document, umbHelper.TypedContent) - ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Media, umbHelper.TypedMedia) - ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Member, umbHelper.TypedMember); - - if (multiNodeTreePickerItem != null) - { - multiNodeTreePicker.Add(multiNodeTreePickerItem); - } - } - - return multiNodeTreePicker; - } - - // return the first nodeId as this is one of the excluded properties that expects a single id - return nodeIds.FirstOrDefault(); - } - - if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePicker2Alias)) - { - var udis = (Udi[])source; - - if ((propertyType.PropertyTypeAlias != null && PropertiesToExclude.InvariantContains(propertyType.PropertyTypeAlias)) == false) - { - var multiNodeTreePicker = new List(); - - var objectType = UmbracoObjectTypes.Unknown; - - foreach (var udi in udis) - { - var multiNodeTreePickerItem = - GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Document, umbHelper.TypedContent) - ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Media, umbHelper.TypedMedia) - ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Member, umbHelper.TypedMember); - if (multiNodeTreePickerItem != null) - { - multiNodeTreePicker.Add(multiNodeTreePickerItem); - } - } - - return multiNodeTreePicker; - } - - // return the first nodeId as this is one of the excluded properties that expects a single id - return udis.FirstOrDefault(); - } - return source; - } - - /// - /// Attempt to get an IPublishedContent instance based on ID and content type - /// - /// The content node ID - /// The type of content being requested - /// The type of content expected/supported by - /// A function to fetch content of type - /// The requested content, or null if either it does not exist or does not match - private IPublishedContent GetPublishedContent(T nodeId, ref UmbracoObjectTypes actualType, UmbracoObjectTypes expectedType, Func contentFetcher) - { - // is the actual type supported by the content fetcher? - if (actualType != UmbracoObjectTypes.Unknown && actualType != expectedType) - { - // no, return null - return null; - } - - // attempt to get the content - var content = contentFetcher(nodeId); - if (content != null) - { - // if we found the content, assign the expected type to the actual type so we don't have to keep looking for other types of content - actualType = expectedType; - } - return content; - } - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs new file mode 100644 index 0000000000..42fc63011b --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Services; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + + /// + /// The multi node tree picker property editor value converter. + /// + [DefaultPropertyValueConverter(typeof(MustBeStringValueConverter))] + public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase + { + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + + private static readonly List PropertiesToExclude = new List + { + Constants.Conventions.Content.InternalRedirectId.ToLower(CultureInfo.InvariantCulture), + Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) + }; + + public MultiNodeTreePickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); + } + + public override bool IsConverter(PublishedPropertyType propertyType) + { + return propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker); + } + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (IEnumerable); + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + if (propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker)) + { + var nodeIds = source.ToString() + .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) + .Select(Udi.Parse) + .ToArray(); + return nodeIds; + } + return null; + } + + public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + { + if (source == null) + { + return null; + } + + //TODO: Inject an UmbracoHelper and create a GetUmbracoHelper method based on either injected or singleton + if (UmbracoContext.Current != null) + { + if (propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker)) + { + var udis = (Udi[])source; + + if ((propertyType.Alias != null && PropertiesToExclude.InvariantContains(propertyType.Alias)) == false) + { + var multiNodeTreePicker = new List(); + + var objectType = UmbracoObjectTypes.Unknown; + + foreach (var udi in udis) + { + var guidUdi = udi as GuidUdi; + if (guidUdi == null) continue; + var multiNodeTreePickerItem = + GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Document, id => _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(guidUdi.Guid)) + ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Media, id => _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(guidUdi.Guid)) + ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Member, id => _publishedSnapshotAccessor.PublishedSnapshot.Members.GetByProviderKey(guidUdi.Guid)); + if (multiNodeTreePickerItem != null) + { + multiNodeTreePicker.Add(multiNodeTreePickerItem); + } + } + + return multiNodeTreePicker; + } + + // return the first nodeId as this is one of the excluded properties that expects a single id + return udis.FirstOrDefault(); + } + } + return source; + } + + /// + /// Attempt to get an IPublishedContent instance based on ID and content type + /// + /// The content node ID + /// The type of content being requested + /// The type of content expected/supported by + /// A function to fetch content of type + /// The requested content, or null if either it does not exist or does not match + private IPublishedContent GetPublishedContent(T nodeId, ref UmbracoObjectTypes actualType, UmbracoObjectTypes expectedType, Func contentFetcher) + { + // is the actual type supported by the content fetcher? + if (actualType != UmbracoObjectTypes.Unknown && actualType != expectedType) + { + // no, return null + return null; + } + + // attempt to get the content + var content = contentFetcher(nodeId); + if (content != null) + { + // if we found the content, assign the expected type to the actual type so we don't have to keep looking for other types of content + actualType = expectedType; + } + return content; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs index c504601d35..7cd631d663 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs @@ -1,41 +1,82 @@ using System; using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core.Logging; -using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.PropertyEditors.ValueConverters { - public class NestedContentManyValueConverter : PropertyValueConverterBase, IPropertyValueConverterMeta + /// + /// + /// Provides an implementation for for nested content. + /// + public class NestedContentManyValueConverter : NestedContentValueConverterBase { + private readonly ProfilingLogger _proflog; + + /// + /// Initializes a new instance of the class. + /// + public NestedContentManyValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory, ProfilingLogger proflog) + : base(publishedSnapshotAccessor, publishedModelFactory) + { + _proflog = proflog; + } + + /// public override bool IsConverter(PublishedPropertyType propertyType) + => IsNestedMany(propertyType); + + /// + public override Type GetPropertyValueType(PublishedPropertyType propertyType) { - return propertyType.IsNestedContentProperty() && !propertyType.IsSingleNestedContentProperty(); + var contentTypes = propertyType.DataType.ConfigurationAs().ContentTypes; + return contentTypes.Length > 1 + ? typeof (IEnumerable) + : typeof (IEnumerable<>).MakeGenericType(ModelType.For(contentTypes[0].Alias)); } - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + /// + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + /// + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { - try + return source?.ToString(); + } + + /// + public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + using (_proflog.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})")) { - return propertyType.ConvertPropertyToNestedContent(source, preview); + var value = (string)inter; + if (string.IsNullOrWhiteSpace(value)) return null; + + var objects = JsonConvert.DeserializeObject>(value); + if (objects.Count == 0) + return Enumerable.Empty(); + + var configuration = propertyType.DataType.ConfigurationAs(); + var contentTypes = configuration.ContentTypes; + var elements = contentTypes.Length > 1 + ? new List() + : PublishedModelFactory.CreateModelList(contentTypes[0].Alias); + + foreach (var sourceObject in objects) + { + var element = ConvertToElement(sourceObject, referenceCacheLevel, preview); + if (element != null) + elements.Add(element); + } + + return elements; } - catch (Exception e) - { - LogHelper.Error("Error converting value", e); - } - - return null; - } - - public virtual Type GetPropertyValueType(PublishedPropertyType propertyType) - { - return typeof(IEnumerable); - } - - public virtual PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue) - { - return PropertyCacheLevel.Content; } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentPublishedPropertyTypeExtensions.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentPublishedPropertyTypeExtensions.cs deleted file mode 100644 index ebfdbd33d3..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentPublishedPropertyTypeExtensions.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Models; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - internal static class NestedContentPublishedPropertyTypeExtensions - { - public static bool IsNestedContentProperty(this PublishedPropertyType publishedProperty) - { - return publishedProperty.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.NestedContentAlias); - } - - public static bool IsSingleNestedContentProperty(this PublishedPropertyType publishedProperty) - { - if (!publishedProperty.IsNestedContentProperty()) - { - return false; - } - - var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(publishedProperty.DataTypeId); - var preValueDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); - - int minItems, maxItems; - return preValueDictionary.ContainsKey("minItems") && - int.TryParse(preValueDictionary["minItems"], out minItems) && minItems == 1 - && preValueDictionary.ContainsKey("maxItems") && - int.TryParse(preValueDictionary["maxItems"], out maxItems) && maxItems == 1; - } - - public static object ConvertPropertyToNestedContent(this PublishedPropertyType propertyType, object source, bool preview) - { - using (DisposableTimer.DebugDuration(string.Format("ConvertPropertyToNestedContent ({0})", propertyType.DataTypeId))) - { - if (source != null && !source.ToString().IsNullOrWhiteSpace()) - { - var rawValue = JsonConvert.DeserializeObject>(source.ToString()); - var processedValue = new List(); - - var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); - var preValueDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); - - for (var i = 0; i < rawValue.Count; i++) - { - var item = (JObject)rawValue[i]; - - // Convert from old style (v.0.1.1) data format if necessary - // - Please note: This call has virtually no impact on rendering performance for new style (>v0.1.1). - // Even so, this should be removed eventually, when it's safe to assume that there is - // no longer any need for conversion. - NestedContentHelper.ConvertItemValueFromV011(item, propertyType.DataTypeId, ref preValueCollection); - - var contentTypeAlias = NestedContentHelper.GetContentTypeAliasFromItem(item); - if (string.IsNullOrEmpty(contentTypeAlias)) - { - continue; - } - - var publishedContentType = PublishedContentType.Get(PublishedItemType.Content, contentTypeAlias); - if (publishedContentType == null) - { - continue; - } - - var propValues = item.ToObject>(); - var properties = new List(); - - foreach (var jProp in propValues) - { - var propType = publishedContentType.GetPropertyType(jProp.Key); - if (propType != null) - { - properties.Add(new DetachedPublishedProperty(propType, jProp.Value, preview)); - } - } - - // Parse out the name manually - object nameObj = null; - if (propValues.TryGetValue("name", out nameObj)) - { - // Do nothing, we just want to parse out the name if we can - } - - object keyObj; - var key = Guid.Empty; - if (propValues.TryGetValue("key", out keyObj)) - { - key = Guid.Parse(keyObj.ToString()); - } - - // Get the current request node we are embedded in - var pcr = UmbracoContext.Current == null ? null : UmbracoContext.Current.PublishedContentRequest; - var containerNode = pcr != null && pcr.HasPublishedContent ? pcr.PublishedContent : null; - - // Create the model based on our implementation of IPublishedContent - IPublishedContent content = new DetachedPublishedContent( - key, - nameObj == null ? null : nameObj.ToString(), - publishedContentType, - properties.ToArray(), - containerNode, - i, - preview); - - if (PublishedContentModelFactoryResolver.HasCurrent && PublishedContentModelFactoryResolver.Current.HasValue) - { - // Let the current model factory create a typed model to wrap our model - content = PublishedContentModelFactoryResolver.Current.Factory.CreateModel(content); - } - - // Add the (typed) model as a result - processedValue.Add(content); - } - - if (propertyType.IsSingleNestedContentProperty()) - { - return processedValue.FirstOrDefault(); - } - - return processedValue; - } - } - - return null; - } - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs index a7af2fd8ae..52d6c7d53a 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs @@ -1,40 +1,70 @@ using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core.Logging; -using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.PropertyEditors.ValueConverters { - public class NestedContentSingleValueConverter : PropertyValueConverterBase, IPropertyValueConverterMeta + /// + /// + /// Provides an implementation for for nested content. + /// + public class NestedContentSingleValueConverter : NestedContentValueConverterBase { + private readonly ProfilingLogger _proflog; + + /// + /// Initializes a new instance of the class. + /// + public NestedContentSingleValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory, ProfilingLogger proflog) + : base(publishedSnapshotAccessor, publishedModelFactory) + { + _proflog = proflog; + } + + /// public override bool IsConverter(PublishedPropertyType propertyType) + => IsNestedSingle(propertyType); + + /// + public override Type GetPropertyValueType(PublishedPropertyType propertyType) { - return propertyType.IsSingleNestedContentProperty(); + var contentTypes = propertyType.DataType.ConfigurationAs().ContentTypes; + return contentTypes.Length > 1 + ? typeof(IPublishedElement) + : ModelType.For(contentTypes[0].Alias); } - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + /// + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + /// + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { - try + return source?.ToString(); + } + + /// + public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + using (_proflog.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})")) { - return propertyType.ConvertPropertyToNestedContent(source, preview); + var value = (string) inter; + if (string.IsNullOrWhiteSpace(value)) return null; + + var objects = JsonConvert.DeserializeObject>(value); + if (objects.Count == 0) + return null; + if (objects.Count > 1) + throw new InvalidOperationException(); + + return ConvertToElement(objects[0], referenceCacheLevel, preview); } - catch (Exception e) - { - LogHelper.Error("Error converting value", e); - } - - return null; - } - - public virtual Type GetPropertyValueType(PublishedPropertyType propertyType) - { - return typeof(IPublishedContent); - } - - public virtual PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue) - { - return PropertyCacheLevel.Content; } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs new file mode 100644 index 0000000000..7e9b1dfe88 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + public abstract class NestedContentValueConverterBase : PropertyValueConverterBase + { + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + + protected NestedContentValueConverterBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + PublishedModelFactory = publishedModelFactory; + } + + protected IPublishedModelFactory PublishedModelFactory { get; } + + public static bool IsNested(PublishedPropertyType publishedProperty) + { + return publishedProperty.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.NestedContent); + } + + public static bool IsNestedSingle(PublishedPropertyType publishedProperty) + { + if (!IsNested(publishedProperty)) + return false; + + var config = publishedProperty.DataType.ConfigurationAs(); + return config.MinItems == 1 && config.MaxItems == 1; + } + + public static bool IsNestedMany(PublishedPropertyType publishedProperty) + { + return IsNested(publishedProperty) && !IsNestedSingle(publishedProperty); + } + + protected IPublishedElement ConvertToElement(JObject sourceObject, PropertyCacheLevel referenceCacheLevel, bool preview) + { + var elementTypeAlias = sourceObject[NestedContentPropertyEditor.ContentTypeAliasPropertyKey]?.ToObject(); + if (string.IsNullOrEmpty(elementTypeAlias)) + return null; + + var publishedContentType = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetContentType(elementTypeAlias); + if (publishedContentType == null) + return null; + + var propertyValues = sourceObject.ToObject>(); + + if (!propertyValues.TryGetValue("key", out var keyo) + || !Guid.TryParse(keyo.ToString(), out var key)) + key = Guid.Empty; + + IPublishedElement element = new PublishedElement(publishedContentType, key, propertyValues, preview, referenceCacheLevel, _publishedSnapshotAccessor); + element = PublishedModelFactory.CreateModel(element); + return element; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs deleted file mode 100644 index 2fce29ae93..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksEditorValueConvertor.cs +++ /dev/null @@ -1,171 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Umbraco -// -// -// Defines the RelatedLinksPropertyConverter type. -// -// -------------------------------------------------------------------------------------------------------------------- - -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Web.Extensions; -using Umbraco.Web.Models; -using Umbraco.Web.Routing; - -namespace Umbraco.Web.PropertyEditors.ValueConverters -{ - /// - /// The related links property value converter. - /// - [DefaultPropertyValueConverter(typeof(LegacyRelatedLinksEditorValueConvertor), typeof(JsonValueConverter))] - [PropertyValueType(typeof(RelatedLinks))] - [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.ContentCache)] - public class RelatedLinksPropertyConverter : PropertyValueConverterBase - { - private readonly UrlProvider _urlProvider; - - public RelatedLinksPropertyConverter() - { - } - - public RelatedLinksPropertyConverter(UrlProvider urlProvider) - { - if (urlProvider == null) throw new ArgumentNullException("urlProvider"); - _urlProvider = urlProvider; - } - - /// - /// Checks if this converter can convert the property editor and registers if it can. - /// - /// - /// The property type. - /// - /// - /// The . - /// - public override bool IsConverter(PublishedPropertyType propertyType) - { - if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.RelatedLinks2Alias)) - return true; - - if (UmbracoConfig.For.UmbracoSettings().Content.EnablePropertyValueConverters) - { - return propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.RelatedLinksAlias); - } - return false; - } - - /// - /// Convert the source nodeId into a RelatedLinks object - /// - /// - /// The published property type. - /// - /// - /// The value of the property - /// - /// - /// The preview. - /// - /// - /// The . - /// - public override object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) - { - if (source == null) - { - return null; - } - - var sourceString = source.ToString(); - - var relatedLinksData = JsonConvert.DeserializeObject>(sourceString); - var relatedLinks = new List(); - - if (UmbracoContext.Current == null) return source; - - var helper = new UmbracoHelper(UmbracoContext.Current); - - foreach (var linkData in relatedLinksData) - { - var relatedLink = new RelatedLink - { - Caption = linkData.Caption, - NewWindow = linkData.NewWindow, - IsInternal = linkData.IsInternal, - Type = linkData.Type, - Link = linkData.Link - }; - - int contentId; - if (int.TryParse(relatedLink.Link, out contentId)) - { - relatedLink.Id = contentId; - relatedLink = CreateLink(relatedLink); - relatedLink.Content = UmbracoContext.Current.ContentCache.GetById(contentId); - } - else - { - var strLinkId = linkData.Link; - var udiAttempt = strLinkId.TryConvertTo(); - if (udiAttempt.Success) - { - var content = helper.TypedContent(udiAttempt.Result); - if (content != null) - { - relatedLink.Id = content.Id; - relatedLink = CreateLink(relatedLink); - relatedLink.Content = content; - } - } - } - - if (relatedLink.IsDeleted == false) - { - relatedLinks.Add(relatedLink); - } - else - { - LogHelper.Warn( - string.Format("Related Links value converter skipped a link as the node has been unpublished/deleted (Internal Link NodeId: {0}, Link Caption: \"{1}\")", relatedLink.Link, relatedLink.Caption)); - } - } - - return new RelatedLinks(relatedLinks, sourceString); - } - - private RelatedLink CreateLink(RelatedLink link) - { - if (link.IsInternal && link.Id != null) - { - if (_urlProvider == null && UmbracoContext.Current == null) - { - return null; - } - - var urlProvider = _urlProvider ?? UmbracoContext.Current.UrlProvider; - - link.Link = urlProvider.GetUrl((int)link.Id); - if (link.Link.Equals("#")) - { - link.IsDeleted = true; - link.Link = link.Id.ToString(); - } - else - { - link.IsDeleted = false; - } - } - - return link; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs new file mode 100644 index 0000000000..ca6f54fcd8 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksLegacyValueConverter.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Services; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + [DefaultPropertyValueConverter(typeof(JsonValueConverter))] //this shadows the JsonValueConverter + public class RelatedLinksLegacyValueConverter : PropertyValueConverterBase + { + private static readonly string[] MatchingEditors = { + Constants.PropertyEditors.Aliases.RelatedLinks + }; + + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILogger _logger; + private readonly ServiceContext _services; + private readonly CacheHelper _appCache; + + public RelatedLinksLegacyValueConverter(IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, CacheHelper appCache, ILogger logger) + { + _umbracoContextAccessor = umbracoContextAccessor; + _services = services; + _appCache = appCache; + _logger = logger; + } + + public override bool IsConverter(PublishedPropertyType propertyType) + => MatchingEditors.Contains(propertyType.EditorAlias); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (JArray); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + if (source == null) return null; + var sourceString = source.ToString(); + + if (sourceString.DetectIsJson()) + { + try + { + var obj = JsonConvert.DeserializeObject(sourceString); + //update the internal links if we have a context + if (UmbracoContext.Current != null) + { + var helper = new UmbracoHelper(_umbracoContextAccessor.UmbracoContext, _services, _appCache); + foreach (var a in obj) + { + var type = a.Value("type"); + if (type.IsNullOrWhiteSpace() == false) + { + if (type == "internal") + { + switch (propertyType.EditorAlias) + { + case Constants.PropertyEditors.Aliases.RelatedLinks: + var strLinkId = a.Value("link"); + var udiAttempt = strLinkId.TryConvertTo(); + if (udiAttempt) + { + var content = helper.PublishedContent(udiAttempt.Result); + if (content == null) break; + a["link"] = helper.NiceUrl(content.Id); + } + break; + } + } + } + } + } + return obj; + } + catch (Exception ex) + { + _logger.Error("Could not parse the string " + sourceString + " to a json object", ex); + } + } + + //it's not json, just return the string + return sourceString; + } + + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object source, bool preview) + { + if (source == null) return null; + var sourceString = source.ToString(); + + if (sourceString.DetectIsJson()) + { + try + { + var obj = JsonConvert.DeserializeObject(sourceString); + + var d = new XmlDocument(); + var e = d.CreateElement("links"); + d.AppendChild(e); + + foreach (dynamic link in obj) + { + var ee = d.CreateElement("link"); + ee.SetAttribute("title", link.title); + ee.SetAttribute("link", link.link); + ee.SetAttribute("type", link.type); + ee.SetAttribute("newwindow", link.newWindow); + + e.AppendChild(ee); + } + + return d.CreateNavigator(); + } + catch (Exception ex) + { + _logger.Error("Could not parse the string " + sourceString + " to a json object", ex); + } + } + + //it's not json, just return the string + return sourceString; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs new file mode 100644 index 0000000000..3a6cc6aa9b --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RelatedLinksValueConverter.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Web.Models; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + /// + /// The related links property value converter. + /// + [DefaultPropertyValueConverter(typeof(RelatedLinksLegacyValueConverter), typeof(JsonValueConverter))] + public class RelatedLinksValueConverter : PropertyValueConverterBase + { + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILogger _logger; + + public RelatedLinksValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IUmbracoContextAccessor umbracoContextAccessor, ILogger logger) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _umbracoContextAccessor = umbracoContextAccessor; + _logger = logger; + } + + /// + /// Checks if this converter can convert the property editor and registers if it can. + /// + /// + /// The property type. + /// + /// + /// The . + /// + public override bool IsConverter(PublishedPropertyType propertyType) + => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.RelatedLinks); + + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (JArray); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + { + if (source == null) return null; + var sourceString = source.ToString(); + + var relatedLinksData = JsonConvert.DeserializeObject>(sourceString); + var relatedLinks = new List(); + + foreach (var linkData in relatedLinksData) + { + var relatedLink = new RelatedLink + { + Caption = linkData.Caption, + NewWindow = linkData.NewWindow, + IsInternal = linkData.IsInternal, + Type = linkData.Type, + Link = linkData.Link + }; + + int contentId; + if (int.TryParse(relatedLink.Link, out contentId)) + { + relatedLink.Id = contentId; + relatedLink = CreateLink(relatedLink); + } + else + { + var strLinkId = linkData.Link; + var udiAttempt = strLinkId.TryConvertTo(); + if (udiAttempt.Success) + { + var content = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(udiAttempt.Result.Guid); + if (content != null) + { + relatedLink.Id = content.Id; + relatedLink = CreateLink(relatedLink); + relatedLink.Content = content; + } + } + } + + if (relatedLink.IsDeleted == false) + { + relatedLinks.Add(relatedLink); + } + else + { + _logger.Warn($"Related Links value converter skipped a link as the node has been unpublished/deleted (Internal Link NodeId: {relatedLink.Link}, Link Caption: \"{relatedLink.Caption}\")"); + } + } + + return new RelatedLinks(relatedLinks, sourceString); + } + + private RelatedLink CreateLink(RelatedLink link) + { + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + + if (link.IsInternal && link.Id != null) + { + if (umbracoContext == null) + return null; + + var urlProvider = umbracoContext.UrlProvider; + + link.Link = urlProvider.GetUrl((int)link.Id); + if (link.Link.Equals("#")) + { + link.IsDeleted = true; + link.Link = link.Id.ToString(); + } + else + { + link.IsDeleted = false; + } + } + + return link; + } + + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + if (inter == null) return null; + var sourceString = inter.ToString(); + + if (sourceString.DetectIsJson()) + { + try + { + var obj = JsonConvert.DeserializeObject(sourceString); + + var d = new XmlDocument(); + var e = d.CreateElement("links"); + d.AppendChild(e); + + foreach (dynamic link in obj) + { + var ee = d.CreateElement("link"); + ee.SetAttribute("title", link.title); + ee.SetAttribute("link", link.link); + ee.SetAttribute("type", link.type); + ee.SetAttribute("newwindow", link.newWindow); + + e.AppendChild(ee); + } + + return d.CreateNavigator(); + } + catch (Exception ex) + { + _logger.Error("Could not parse the string " + sourceString + " to a json object", ex); + } + } + + //it's not json, just return the string + return sourceString; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index a6ea79d283..fcd3d90153 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Web; +using System.Text; using Umbraco.Core; using Umbraco.Core.Macros; using Umbraco.Core.PropertyEditors; @@ -10,59 +7,63 @@ using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Web.Templates; using System.Linq; using HtmlAgilityPack; +using Umbraco.Core.Cache; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors.ValueConverters { - /// - /// A value converter for TinyMCE that will ensure any macro content is rendered properly even when + /// A value converter for TinyMCE that will ensure any macro content is rendered properly even when /// used dynamically. /// - // because that version of RTE converter parses {locallink} and executes macros, when going from - // data to source, its source value has to be cached at the request level, because we have no idea - // what the macros may depend on actually. An so, object and xpath need to follow... request, too. - // note: the TinyMceValueConverter is NOT inherited, so the PropertyValueCache attribute here is not - // actually required (since Request is default) but leave it here to be absolutely explicit. [DefaultPropertyValueConverter] - [PropertyValueType(typeof(IHtmlString))] - [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Request)] public class RteMacroRenderingValueConverter : TinyMceValueConverter - { + { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ServiceContext _services; + private readonly CacheHelper _appCache; + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + { + // because that version of RTE converter parses {locallink} and executes macros, its value has + // to be cached at the published snapshot level, because we have no idea what the macros may depend on actually. + return PropertyCacheLevel.Snapshot; + } + + public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, CacheHelper appCache) + { + _umbracoContextAccessor = umbracoContextAccessor; + _services = services; + _appCache = appCache; + } + // NOT thread-safe over a request because it modifies the // global UmbracoContext.Current.InPreviewMode status. So it // should never execute in // over the same UmbracoContext with // different preview modes. - static string RenderRteMacros(string source, bool preview) + private string RenderRteMacros(string source, bool preview) { - // save and set for macro rendering - var inPreviewMode = UmbracoContext.Current.InPreviewMode; - UmbracoContext.Current.InPreviewMode = preview; + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + using (umbracoContext.ForcedPreview(preview)) // force for macro rendering + { + var sb = new StringBuilder(); - var sb = new StringBuilder(); - - try - { - var umbracoHelper = new UmbracoHelper(UmbracoContext.Current); - MacroTagParser.ParseMacros( - source, - //callback for when text block is found - textBlock => sb.Append(textBlock), - //callback for when macro syntax is found - (macroAlias, macroAttributes) => sb.Append(umbracoHelper.RenderMacro( - macroAlias, - //needs to be explicitly casted to Dictionary - macroAttributes.ConvertTo(x => (string) x, x => x)).ToString())); - } - finally - { - // restore - UmbracoContext.Current.InPreviewMode = inPreviewMode; - } + var umbracoHelper = new UmbracoHelper(umbracoContext, _services, _appCache); + MacroTagParser.ParseMacros( + source, + //callback for when text block is found + textBlock => sb.Append(textBlock), + //callback for when macro syntax is found + (macroAlias, macroAttributes) => sb.Append(umbracoHelper.RenderMacro( + macroAlias, + //needs to be explicitly casted to Dictionary + macroAttributes.ConvertTo(x => (string)x, x => x)).ToString())); - return sb.ToString(); + return sb.ToString(); + } } - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { if (source == null) { @@ -118,4 +119,4 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return sourceString; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs index 8354ca629d..9f9d60c62f 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -1,32 +1,31 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Web.Templates; namespace Umbraco.Web.PropertyEditors.ValueConverters { [DefaultPropertyValueConverter] - [PropertyValueType(typeof(string))] - [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Request)] public class TextStringValueConverter : PropertyValueConverterBase { private static readonly string[] PropertyTypeAliases = { - Constants.PropertyEditors.TextboxAlias, - Constants.PropertyEditors.TextboxMultipleAlias + Constants.PropertyEditors.Aliases.TextBox, + Constants.PropertyEditors.Aliases.TextArea }; public override bool IsConverter(PublishedPropertyType propertyType) - { - return PropertyTypeAliases.Contains(propertyType.PropertyEditorAlias); - } + => PropertyTypeAliases.Contains(propertyType.EditorAlias); - public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + public override Type GetPropertyValueType(PublishedPropertyType propertyType) + => typeof (string); + + public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); @@ -38,16 +37,16 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return sourceString; } - public override object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already - return source ?? string.Empty; + return inter ?? string.Empty; } - public override object ConvertSourceToXPath(PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already - return source; + return inter; } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueListConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/ValueListConfigurationEditor.cs new file mode 100644 index 0000000000..d130fb5952 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueListConfigurationEditor.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Services; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Pre-value editor used to create a list of items + /// + /// + /// This pre-value editor is shared with editors like drop down, checkbox list, etc.... + /// + internal class ValueListConfigurationEditor : ConfigurationEditor + { + public ValueListConfigurationEditor(ILocalizedTextService textService) + { + var items = Fields.First(x => x.Key == "items"); + + // customize the items field + items.Name = textService.Localize("editdatatype/addPrevalue"); + items.Validators.Add(new ValueListUniqueValueValidator()); + } + + // editor... + // + // receives: + // "preValues":[ + // { + // "label":"Add prevalue", + // "description":"Add and remove values for the list", + // "hideLabel":false, + // "view":"multivalues", + // "config":{}, + // "key":"items", + // "value":{"169":{"value":"a","sortOrder":1},"170":{"value":"b","sortOrder":2},"171":{"value":"c","sortOrder":3}} + // }] + // + // posts ('d' being a new value): + // [{key: "items", value: [{value: "a", sortOrder: 1, id: "169"}, {value: "c", sortOrder: 3, id: "171"}, {value: "d"}]}] + // + // values go to DB with alias 0, 1, 2 + their ID + value + // the sort order that comes back makes no sense + + /// + public override Dictionary ToConfigurationEditor(ValueListConfiguration configuration) + { + if (configuration == null) + return new Dictionary + { + { "items", new object() } + }; + + // map to what the (still v7) editor expects + // {"item":{"169":{"value":"a","sortOrder":1},"170":{"value":"b","sortOrder":2},"171":{"value":"c","sortOrder":3}}} + + var i = 1; + return new Dictionary + { + { "items", configuration.Items.ToDictionary(x => x.Id.ToString(), x => new { value = x.Value, sortOrder = i++ }) } + }; + } + + /// + public override ValueListConfiguration FromConfigurationEditor(IDictionary editorValues, ValueListConfiguration configuration) + { + var output = new ValueListConfiguration(); + + if (!editorValues.TryGetValue("items", out var jjj) || !(jjj is JArray jItems)) + return output; // oops + + // auto-assigning our ids, get next id from existing values + var nextId = 1; + if (configuration?.Items != null && configuration.Items.Count > 0) + nextId = configuration.Items.Max(x => x.Id) + 1; + + // create ValueListItem instances - sortOrder is ignored here + foreach (var item in jItems.OfType()) + { + var value = item.Property("value")?.Value?.Value(); + if (string.IsNullOrWhiteSpace(value)) continue; + + var id = item.Property("id")?.Value?.Value() ?? 0; + if (id >= nextId) nextId = id + 1; + + output.Items.Add(new ValueListConfiguration.ValueListItem { Id = id, Value = value }); + } + + // ensure ids + foreach (var item in output.Items) + if (item.Id == 0) + item.Id = nextId++; + + return output; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs deleted file mode 100644 index 42484282e9..0000000000 --- a/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using umbraco; - -namespace Umbraco.Web.PropertyEditors -{ - /// - /// Pre-value editor used to create a list of items - /// - /// - /// This pre-value editor is shared with editors like drop down, checkbox list, etc.... - /// - internal class ValueListPreValueEditor : PreValueEditor - { - - public ValueListPreValueEditor() - { - Fields.AddRange(CreatePreValueFields()); - } - - /// - /// Creates the pre-value fields - /// - /// - protected List CreatePreValueFields() - { - return new List - { - new PreValueField(new EnsureUniqueValuesValidator()) - { - Description = "Add and remove values for the list", - //we're going to call this 'items' because we are going to override the - //serialization of the pre-values to ensure that each one gets saved with it's own key - //(new db row per pre-value, thus to maintain backwards compatibility) - - //It's also important to note that by default the dropdown angular controller is expecting the - // config options to come in with a property called 'items' - Key = "items", - Name = ui.Text("editdatatype", "addPrevalue"), - View = "multivalues" - } - }; - } - - /// - /// The editor is expecting a json array for a field with a key named "items" so we need to format the persisted values - /// to this format to be used in the editor. - /// - /// - /// - /// - public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) - { - var dictionary = persistedPreVals.FormatAsDictionary(); - var arrayOfVals = dictionary.Select(item => item.Value) - //ensure they are sorted - though this isn't too important because in json in may not maintain - // the sorted order and the js has to sort anyways. - .OrderBy(x => x.SortOrder) - .ToList(); - - //the items list will be a dictionary of it's id -> value we need to use the id for persistence for backwards compatibility - return new Dictionary {{"items", arrayOfVals.ToDictionary(x => x.Id, x => PreValueAsDictionary(x))}}; - } - - /// - /// Formats the prevalue as a dictionary (as we need to return not just the value, but also the sort-order, to the client) - /// - /// The prevalue to format - /// Dictionary object containing the prevalue formatted with the field names as keys and the value of those fields as the values - private IDictionary PreValueAsDictionary(PreValue preValue) - { - return new Dictionary() { { "value", preValue.Value }, {"sortOrder", preValue.SortOrder } }; - } - - /// - /// Need to format the delimited posted string to individual values - /// - /// - /// - /// - /// A string/string dictionary since all values that need to be persisted in the database are strings. - /// - /// - /// This is mostly because we want to maintain compatibility with v6 drop down property editors that store their prevalues in different db rows. - /// - public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue) - { - var val = editorValue["items"] as JArray; - var result = new Dictionary(); - - if (val == null) - { - return result; - } - - try - { - var index = 0; - - //get all values in the array that are not empty - foreach (var item in val.OfType() - .Where(jItem => jItem["value"] != null) - .Select(jItem => new - { - idAsString = jItem["id"] == null ? "0" : jItem["id"].ToString(), - valAsString = jItem["value"].ToString() - }) - .Where(x => x.valAsString.IsNullOrWhiteSpace() == false)) - { - var id = 0; - int.TryParse(item.idAsString, out id); - result.Add(index.ToInvariantString(), new PreValue(id, item.valAsString)); - index++; - } - } - catch (Exception ex) - { - LogHelper.Error("Could not deserialize the posted value: " + val, ex); - } - - return result; - } - - /// - /// A custom validator to ensure that all values in the list are unique - /// - internal class EnsureUniqueValuesValidator : IPropertyValidator - { - public IEnumerable Validate(object value, PreValueCollection preValues, PropertyEditor editor) - { - var json = value as JArray; - if (json == null) yield break; - - //get all values in the array that are not empty (we'll remove empty values when persisting anyways) - var groupedValues = json.OfType() - .Where(jItem => jItem["value"] != null) - .Select((jItem, index) => new {value = jItem["value"].ToString(), index = index}) - .Where(asString => asString.value.IsNullOrWhiteSpace() == false) - .GroupBy(x => x.value); - - foreach (var g in groupedValues.Where(g => g.Count() > 1)) - { - yield return new ValidationResult("The value " + g.Last().value + " must be unique", new[] - { - //we'll make the server field the index number of the value so it can be wired up to the view - "item_" + g.Last().index.ToInvariantString() - }); - } - - - } - } - } -} diff --git a/src/Umbraco.Web/PropertyEditors/ValueListUniqueValueValidator.cs b/src/Umbraco.Web/PropertyEditors/ValueListUniqueValueValidator.cs new file mode 100644 index 0000000000..88dd20c516 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueListUniqueValueValidator.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents a validator which ensures that all values in the list are unique. + /// + internal class ValueListUniqueValueValidator : IValueValidator + { + public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) + { + // the value we get should be a JArray + // [ { "value": , "sortOrder": 1 }, { ... }, ... ] + + if (!(value is JArray json)) yield break; + + // we ensure that values are unique + // (those are are not empty - empty values are removed when persisting anyways) + + var groupedValues = json.OfType() + .Where(x => x["value"] != null) + .Select((x, index) => new { value = x["value"].ToString(), index}) + .Where(x => x.value.IsNullOrWhiteSpace() == false) + .GroupBy(x => x.value); + + foreach (var group in groupedValues.Where(x => x.Count() > 1)) + { + yield return new ValidationResult($"The value \"{@group.Last().value}\" must be unique", new[] + { + // use the index number as server field so it can be wired up to the view + "item_" + @group.Last().index.ToInvariantString() + }); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs deleted file mode 100644 index 8decd0100a..0000000000 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs +++ /dev/null @@ -1,243 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml.XPath; -using Umbraco.Core.Models; -using Umbraco.Core.Xml; - -namespace Umbraco.Web.PublishedCache -{ - /// - /// Provides access to cached contents in a specified context. - /// - public abstract class ContextualPublishedCache - { - //TODO: We need to add: - //* GetById(Guid contentId) - //* GetById(UDI contentId) - - protected readonly UmbracoContext UmbracoContext; - - /// - /// Initializes a new instance of the with a context. - /// - /// The context. - protected ContextualPublishedCache(UmbracoContext umbracoContext) - { - UmbracoContext = umbracoContext; - } - - /// - /// Gets a content identified by its unique identifier. - /// - /// The content unique identifier. - /// The content, or null. - /// Considers published or unpublished content depending on context. - public IPublishedContent GetById(int contentId) - { - return GetById(UmbracoContext.InPreviewMode, contentId); - } - - /// - /// Gets a content identified by its unique identifier. - /// - /// The content unique identifier. - /// The content, or null. - /// Considers published or unpublished content depending on context. - public IPublishedContent GetById(Guid contentId) - { - var intId = UmbracoContext.Application.Services.EntityService.GetIdForKey(contentId, UmbracoObjectTypes.Document); - return GetById(intId.Success ? intId.Result : -1); - } - - /// - /// Gets a content identified by its unique identifier. - /// - /// A value indicating whether to consider unpublished content. - /// The content unique identifier. - /// The content, or null. - public abstract IPublishedContent GetById(bool preview, int contentId); - - /// - /// Gets content at root. - /// - /// The contents. - /// Considers published or unpublished content depending on context. - public IEnumerable GetAtRoot() - { - return GetAtRoot(UmbracoContext.InPreviewMode); - } - - /// - /// Gets contents at root. - /// - /// A value indicating whether to consider unpublished content. - /// The contents. - public abstract IEnumerable GetAtRoot(bool preview); - - /// - /// Gets a content resulting from an XPath query. - /// - /// The XPath query. - /// Optional XPath variables. - /// The content, or null. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// Considers published or unpublished content depending on context. - /// - public IPublishedContent GetSingleByXPath(string xpath, params XPathVariable[] vars) - { - return GetSingleByXPath(UmbracoContext.InPreviewMode, xpath, vars); - } - - /// - /// Gets a content resulting from an XPath query. - /// - /// The XPath query. - /// Optional XPath variables. - /// The content, or null. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// Considers published or unpublished content depending on context. - /// - public IPublishedContent GetSingleByXPath(XPathExpression xpath, params XPathVariable[] vars) - { - return GetSingleByXPath(UmbracoContext.InPreviewMode, xpath, vars); - } - - /// - /// Gets a content resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The content, or null. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public abstract IPublishedContent GetSingleByXPath(bool preview, string xpath, params XPathVariable[] vars); - - /// - /// Gets a content resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The content, or null. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public abstract IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); - - /// - /// Gets content resulting from an XPath query. - /// - /// The XPath query. - /// Optional XPath variables. - /// The contents. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// Considers published or unpublished content depending on context. - /// - public IEnumerable GetByXPath(string xpath, params XPathVariable[] vars) - { - return GetByXPath(UmbracoContext.InPreviewMode, xpath, vars); - } - - /// - /// Gets content resulting from an XPath query. - /// - /// The XPath query. - /// Optional XPath variables. - /// The contents. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// Considers published or unpublished content depending on context. - /// - public IEnumerable GetByXPath(XPathExpression xpath, params XPathVariable[] vars) - { - return GetByXPath(UmbracoContext.InPreviewMode, xpath, vars); - } - - /// - /// Gets content resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The contents. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public abstract IEnumerable GetByXPath(bool preview, string xpath, params XPathVariable[] vars); - - /// - /// Gets content resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The contents. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public abstract IEnumerable GetByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); - - /// - /// Gets an XPath navigator that can be used to navigate content. - /// - /// The XPath navigator. - /// Considers published or unpublished content depending on context. - public XPathNavigator GetXPathNavigator() - { - return GetXPathNavigator(UmbracoContext.InPreviewMode); - } - - /// - /// Gets an XPath navigator that can be used to navigate content. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath navigator. - public abstract XPathNavigator GetXPathNavigator(bool preview); - - /// - /// Gets a value indicating whether GetXPathNavigator returns an XPathNavigator - /// and that navigator is a NavigableNavigator. - /// - public abstract bool XPathNavigatorIsNavigable { get; } - - /// - /// Gets a value indicating whether the underlying non-contextual cache contains content. - /// - /// A value indicating whether the underlying non-contextual cache contains content. - /// Considers published or unpublished content depending on context. - public bool HasContent() - { - return HasContent(UmbracoContext.InPreviewMode); - } - - /// - /// Gets a value indicating whether the underlying non-contextual cache contains content. - /// - /// A value indicating whether to consider unpublished content. - /// A value indicating whether the underlying non-contextual cache contains content. - public abstract bool HasContent(bool preview); - } -} diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCacheOfT.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCacheOfT.cs deleted file mode 100644 index 6e897c2c2e..0000000000 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCacheOfT.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml.XPath; -using Umbraco.Core.Models; -using Umbraco.Core.Xml; - -namespace Umbraco.Web.PublishedCache -{ - /// - /// Provides access to cached contents in a specified context. - /// - /// The type of the underlying published cache. - /// The type differenciates between the content cache and the media cache, - /// ie it will be either IPublishedContentCache or IPublishedMediaCache. - public abstract class ContextualPublishedCache : ContextualPublishedCache - where T : IPublishedCache - { - private readonly T _cache; - - /// - /// Initializes a new instance of the with a context and a published cache. - /// - /// The context. - /// The cache. - protected ContextualPublishedCache(UmbracoContext umbracoContext, T cache) - : base(umbracoContext) - { - _cache = cache; - } - - /// - /// Gets the underlying published cache. - /// - public T InnerCache { get { return _cache; } } - - /// - /// Gets a content identified by its unique identifier. - /// - /// A value indicating whether to consider unpublished content. - /// The content unique identifier. - /// The content, or null. - public override IPublishedContent GetById(bool preview, int contentId) - { - return _cache.GetById(UmbracoContext, preview, contentId); - } - - /// - /// Gets content at root. - /// - /// A value indicating whether to consider unpublished content. - /// The contents. - public override IEnumerable GetAtRoot(bool preview) - { - return _cache.GetAtRoot(UmbracoContext, preview); - } - - /// - /// Gets a content resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The content, or null. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public override IPublishedContent GetSingleByXPath(bool preview, string xpath, params XPathVariable[] vars) - { - return _cache.GetSingleByXPath(UmbracoContext, preview, xpath, vars); - } - - /// - /// Gets a content resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The content, or null. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars) - { - return _cache.GetSingleByXPath(UmbracoContext, preview, xpath, vars); - } - - /// - /// Gets content resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The contents. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public override IEnumerable GetByXPath(bool preview, string xpath, params XPathVariable[] vars) - { - return _cache.GetByXPath(UmbracoContext, preview, xpath, vars); - } - - /// - /// Gets content resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The contents. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars) - { - return _cache.GetByXPath(UmbracoContext, preview, xpath, vars); - } - - /// - /// Gets an XPath navigator that can be used to navigate content. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath navigator. - public override XPathNavigator GetXPathNavigator(bool preview) - { - return _cache.GetXPathNavigator(UmbracoContext, preview); - } - - /// - /// Gets a value indicating whether GetXPathNavigator returns an XPathNavigator - /// and that navigator is a NavigableNavigator. - /// - public override bool XPathNavigatorIsNavigable { get { return _cache.XPathNavigatorIsNavigable; } } - - /// - /// Gets a value indicating whether the underlying non-contextual cache contains content. - /// - /// A value indicating whether to consider unpublished content. - /// A value indicating whether the underlying non-contextual cache contains content. - public override bool HasContent(bool preview) - { - return _cache.HasContent(UmbracoContext, preview); - } - } -} diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs deleted file mode 100644 index ebe2743f4b..0000000000 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.Models; - -namespace Umbraco.Web.PublishedCache -{ - /// - /// Provides access to cached documents in a specified context. - /// - public class ContextualPublishedContentCache : ContextualPublishedCache - { - /// - /// Initializes a new instance of the class with a published content cache and a context. - /// - /// A published content cache. - /// A context. - internal ContextualPublishedContentCache(IPublishedContentCache cache, UmbracoContext umbracoContext) - : base(umbracoContext, cache) - { } - - /// - /// Gets content identified by a route. - /// - /// The route - /// A value forcing the HideTopLevelNode setting. - /// The content, or null. - /// - /// A valid route is either a simple path eg /foo/bar/nil or a root node id and a path, eg 123/foo/bar/nil. - /// Considers published or unpublished content depending on context. - /// - public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null) - { - return GetByRoute(UmbracoContext.InPreviewMode, route, hideTopLevelNode); - } - - /// - /// Gets content identified by a route. - /// - /// A value indicating whether to consider unpublished content. - /// The route - /// A value forcing the HideTopLevelNode setting. - /// The content, or null. - /// A valid route is either a simple path eg /foo/bar/nil or a root node id and a path, eg 123/foo/bar/nil. - public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null) - { - return InnerCache.GetByRoute(UmbracoContext, preview, route, hideTopLevelNode); - } - - /// - /// Gets the route for a content identified by its unique identifier. - /// - /// The content unique identifier. - /// The route. - /// Considers published or unpublished content depending on context. - public string GetRouteById(int contentId) - { - return GetRouteById(UmbracoContext.InPreviewMode, contentId); - } - - /// - /// Gets the route for a content identified by its unique identifier. - /// - /// A value indicating whether to consider unpublished content. - /// The content unique identifier. - /// The route. - /// Considers published or unpublished content depending on context. - public string GetRouteById(bool preview, int contentId) - { - return InnerCache.GetRouteById(UmbracoContext, preview, contentId); - } - } -} diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs deleted file mode 100644 index 494a0cd419..0000000000 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.Models; - -namespace Umbraco.Web.PublishedCache -{ - /// - /// Provides access to cached medias in a specified context. - /// - public class ContextualPublishedMediaCache : ContextualPublishedCache - { - /// - /// Initializes a new instance of the class with a published media cache and a context. - /// - /// A published media cache. - /// A context. - internal ContextualPublishedMediaCache(IPublishedMediaCache cache, UmbracoContext umbracoContext) - : base(umbracoContext, cache) - { } - } -} diff --git a/src/Umbraco.Web/PublishedCache/IDomainCache.cs b/src/Umbraco.Web/PublishedCache/IDomainCache.cs new file mode 100644 index 0000000000..113533f500 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/IDomainCache.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.PublishedCache +{ + public interface IDomainCache + { + IEnumerable GetAll(bool includeWildcards); + IEnumerable GetAssigned(int contentId, bool includeWildcards); + } +} diff --git a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs index a900689350..619d39d0ae 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Xml.XPath; -using Umbraco.Core.CodeAnnotations; -using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; namespace Umbraco.Web.PublishedCache @@ -12,103 +9,207 @@ namespace Umbraco.Web.PublishedCache /// /// Provides access to cached contents. /// - [UmbracoExperimentalFeature("http://issues.umbraco.org/issue/U4-1153", - "We need to create something like the IPublishListener interface to have proper published content storage.")] - public interface IPublishedCache + public interface IPublishedCache : IXPathNavigable { /// /// Gets a content identified by its unique identifier. /// - /// The context. /// A value indicating whether to consider unpublished content. /// The content unique identifier. /// The content, or null. - /// The value of overrides the context. - IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int contentId); + /// The value of overrides defaults. + IPublishedContent GetById(bool preview, int contentId); + + /// + /// Gets a content identified by its unique identifier. + /// + /// A value indicating whether to consider unpublished content. + /// The content unique identifier. + /// The content, or null. + /// The value of overrides defaults. + IPublishedContent GetById(bool preview, Guid contentId); + + /// + /// Gets a content identified by its unique identifier. + /// + /// The content unique identifier. + /// The content, or null. + /// Considers published or unpublished content depending on defaults. + IPublishedContent GetById(int contentId); + + /// + /// Gets a content identified by its unique identifier. + /// + /// The content unique identifier. + /// The content, or null. + /// Considers published or unpublished content depending on defaults. + IPublishedContent GetById(Guid contentId); + + /// + /// Gets a value indicating whether the cache contains a specified content. + /// + /// A value indicating whether to consider unpublished content. + /// The content unique identifier. + /// A value indicating whether to the cache contains the specified content. + /// The value of overrides defaults. + bool HasById(bool preview, int contentId); + + /// + /// Gets a value indicating whether the cache contains a specified content. + /// + /// The content unique identifier. + /// A value indicating whether to the cache contains the specified content. + /// Considers published or unpublished content depending on defaults. + bool HasById(int contentId); /// /// Gets contents at root. /// - /// The context. /// A value indicating whether to consider unpublished content. /// The contents. - /// The value of overrides the context. - IEnumerable GetAtRoot(UmbracoContext umbracoContext, bool preview); + /// The value of overrides defaults. + IEnumerable GetAtRoot(bool preview); + + /// + /// Gets contents at root. + /// + /// The contents. + /// Considers published or unpublished content depending on defaults. + IEnumerable GetAtRoot(); /// /// Gets a content resulting from an XPath query. /// - /// The context. /// A value indicating whether to consider unpublished content. /// The XPath query. /// Optional XPath variables. /// The content, or null. - /// The value of overrides the context. - IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, string xpath, XPathVariable[] vars); + /// The value of overrides defaults. + IPublishedContent GetSingleByXPath(bool preview, string xpath, params XPathVariable[] vars); + + /// + /// Gets a content resulting from an XPath query. + /// + /// The XPath query. + /// Optional XPath variables. + /// The content, or null. + /// Considers published or unpublished content depending on defaults. + IPublishedContent GetSingleByXPath(string xpath, params XPathVariable[] vars); /// /// Gets a content resulting from an XPath query. /// - /// The context. /// A value indicating whether to consider unpublished content. /// The XPath query. /// Optional XPath variables. /// The content, or null. - /// The value of overrides the context. - IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, XPathVariable[] vars); + /// The value of overrides defaults. + IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); + + /// + /// Gets a content resulting from an XPath query. + /// + /// The XPath query. + /// Optional XPath variables. + /// The content, or null. + /// Considers published or unpublished content depending on defaults. + IPublishedContent GetSingleByXPath(XPathExpression xpath, params XPathVariable[] vars); /// /// Gets contents resulting from an XPath query. /// - /// The context. /// A value indicating whether to consider unpublished content. /// The XPath query. /// Optional XPath variables. /// The contents. - /// The value of overrides the context. - IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, string xpath, XPathVariable[] vars); + /// The value of overrides defaults. + IEnumerable GetByXPath(bool preview, string xpath, params XPathVariable[] vars); + + /// + /// Gets contents resulting from an XPath query. + /// + /// The XPath query. + /// Optional XPath variables. + /// The contents. + /// Considers published or unpublished content depending on defaults. + IEnumerable GetByXPath(string xpath, params XPathVariable[] vars); /// /// Gets contents resulting from an XPath query. /// - /// The context. /// A value indicating whether to consider unpublished content. /// The XPath query. /// Optional XPath variables. /// The contents. - /// The value of overrides the context. - IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, XPathVariable[] vars); + /// The value of overrides defaults. + IEnumerable GetByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); /// - /// Gets an XPath navigator that can be used to navigate contents. + /// Gets contents resulting from an XPath query. + /// + /// The XPath query. + /// Optional XPath variables. + /// The contents. + /// Considers published or unpublished content depending on defaults. + IEnumerable GetByXPath(XPathExpression xpath, params XPathVariable[] vars); + + /// + /// Creates an XPath navigator that can be used to navigate contents. /// - /// The context. /// A value indicating whether to consider unpublished content. /// The XPath navigator. - /// The value of overrides the context. - XPathNavigator GetXPathNavigator(UmbracoContext umbracoContext, bool preview); + /// + /// The value of overrides the context. + /// The navigator is already a safe clone (no need to clone it again). + /// + XPathNavigator CreateNavigator(bool preview); /// - /// Gets a value indicating whether GetXPathNavigator returns an XPathNavigator - /// and that navigator is a NavigableNavigator. + /// Creates an XPath navigator that can be used to navigate one node. /// - bool XPathNavigatorIsNavigable { get; } + /// The node identifier. + /// A value indicating whether to consider unpublished content. + /// The XPath navigator, or null. + /// + /// The value of overrides the context. + /// The navigator is already a safe clone (no need to clone it again). + /// Navigates over the node - and only the node, ie no children. Exists only for backward + /// compatibility + transition reasons, we should obsolete that one as soon as possible. + /// If the node does not exist, returns null. + /// + XPathNavigator CreateNodeNavigator(int id, bool preview); /// /// Gets a value indicating whether the cache contains published content. /// - /// The context. /// A value indicating whether to consider unpublished content. /// A value indicating whether the cache contains published content. - /// The value of overrides the context. - bool HasContent(UmbracoContext umbracoContext, bool preview); - - //TODO: SD: We should make this happen! This will allow us to natively do a GetByDocumentType query - // on the UmbracoHelper (or an internal DataContext that it uses, etc...) - // One issue is that we need to make media work as fast as we can and need to create a ConvertFromMediaObject - // method in the DefaultPublishedMediaStore, there's already a TODO noting this but in order to do that we'll - // have to also use Examine as much as we can so we don't have to make db calls for looking up things like the - // node type alias, etc... in order to populate the created IPublishedContent object. - //IEnumerable GetDocumentsByType(string docTypeAlias); + /// The value of overrides defaults. + bool HasContent(bool preview); + + /// + /// Gets a value indicating whether the cache contains published content. + /// + /// A value indicating whether the cache contains published content. + /// Considers published or unpublished content depending on defaults. + bool HasContent(); + + /// + /// Gets a content type identified by its unique identifier. + /// + /// The content type unique identifier. + /// The content type, or null. + PublishedContentType GetContentType(int id); + + /// + /// Gets a content type identified by its alias. + /// + /// The content type alias. + /// The content type, or null. + /// The alias is case-insensitive. + PublishedContentType GetContentType(string alias); + + // fixme - can we implement this, now? maybe only with NuCache else will throw NotImplemented... + IEnumerable GetByContentType(PublishedContentType contentType); } } diff --git a/src/Umbraco.Web/PublishedCache/IPublishedCaches.cs b/src/Umbraco.Web/PublishedCache/IPublishedCaches.cs deleted file mode 100644 index 82609b7457..0000000000 --- a/src/Umbraco.Web/PublishedCache/IPublishedCaches.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Web.PublishedCache -{ - /// - /// Provides caches (content and media). - /// - /// Groups caches that _may_ be related. - interface IPublishedCaches - { - /// - /// Creates a contextual content cache for a specified context. - /// - /// The context. - /// A new contextual content cache for the specified context. - ContextualPublishedContentCache CreateContextualContentCache(UmbracoContext context); - - /// - /// Creates a contextual media cache for a specified context. - /// - /// The context. - /// A new contextual media cache for the specified context. - ContextualPublishedMediaCache CreateContextualMediaCache(UmbracoContext context); - } -} diff --git a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs index b73e594727..e033647fdf 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Models; namespace Umbraco.Web.PublishedCache { @@ -12,7 +8,6 @@ namespace Umbraco.Web.PublishedCache /// /// Gets content identified by a route. /// - /// The context. /// A value indicating whether to consider unpublished content. /// The route /// A value forcing the HideTopLevelNode setting. @@ -20,28 +15,38 @@ namespace Umbraco.Web.PublishedCache /// /// A valid route is either a simple path eg /foo/bar/nil or a root node id and a path, eg 123/foo/bar/nil. /// If is null then the settings value is used. - /// The value of overrides the context. + /// The value of overrides defaults. /// - IPublishedContent GetByRoute(UmbracoContext umbracoContext, bool preview, string route, bool? hideTopLevelNode = null); + IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null); + + /// + /// Gets content identified by a route. + /// + /// The route + /// A value forcing the HideTopLevelNode setting. + /// The content, or null. + /// + /// A valid route is either a simple path eg /foo/bar/nil or a root node id and a path, eg 123/foo/bar/nil. + /// If is null then the settings value is used. + /// Considers published or unpublished content depending on defaults. + /// + IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null); /// /// Gets the route for a content identified by its unique identifier. /// - /// The context. /// A value indicating whether to consider unpublished content. /// The content unique identifier. /// The route. - /// The value of overrides the context. - string GetRouteById(UmbracoContext umbracoContext, bool preview, int contentId); + /// The value of overrides defaults. + string GetRouteById(bool preview, int contentId); /// - /// Creates a detached property. + /// Gets the route for a content identified by its unique identifier. /// - /// The published property type. - /// The value. - /// A value indicating whether the property is created within a previewing context. - /// A detached property. - /// Implementations must check that propertyType.IsDetachedOrNested is true. - IPublishedProperty CreateDetachedProperty(PublishedPropertyType propertyType, object value, bool isPreviewing); + /// The content unique identifier. + /// The route. + /// Considers published or unpublished content depending on defaults. + string GetRouteById(int contentId); } } diff --git a/src/Umbraco.Web/PublishedCache/IPublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedMediaCache.cs index 7a3e6500e8..c25867229a 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedMediaCache.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Web.PublishedCache +namespace Umbraco.Web.PublishedCache { public interface IPublishedMediaCache : IPublishedCache - { - } + { } } diff --git a/src/Umbraco.Web/PublishedCache/IPublishedMemberCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedMemberCache.cs new file mode 100644 index 0000000000..53d37a8d31 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/IPublishedMemberCache.cs @@ -0,0 +1,35 @@ +using System.Xml.XPath; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.PublishedCache +{ + public interface IPublishedMemberCache : IXPathNavigable + { + IPublishedContent GetByProviderKey(object key); + IPublishedContent GetById(int memberId); + IPublishedContent GetByUsername(string username); + IPublishedContent GetByEmail(string email); + IPublishedContent GetByMember(IMember member); + + XPathNavigator CreateNavigator(bool preview); + + // if the node does not exist, return null + XPathNavigator CreateNodeNavigator(int id, bool preview); + + /// + /// Gets a content type identified by its unique identifier. + /// + /// The content type unique identifier. + /// The content type, or null. + PublishedContentType GetContentType(int id); + + /// + /// Gets a content type identified by its alias. + /// + /// The content type alias. + /// The content type, or null. + /// The alias is case-insensitive. + PublishedContentType GetContentType(string alias); + } +} diff --git a/src/Umbraco.Web/PublishedCache/IPublishedShapshot.cs b/src/Umbraco.Web/PublishedCache/IPublishedShapshot.cs new file mode 100644 index 0000000000..6e859d0f02 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/IPublishedShapshot.cs @@ -0,0 +1,54 @@ +using System; +using Umbraco.Core.Cache; + +namespace Umbraco.Web.PublishedCache +{ + /// + /// Specifies a published snapshot. + /// + /// A published snapshot is a point-in-time capture of the current state of + /// everything that is "published". + public interface IPublishedShapshot + { + /// + /// Gets the . + /// + IPublishedContentCache Content { get; } + + /// + /// Gets the . + /// + IPublishedMediaCache Media { get; } + + /// + /// Gets the . + /// + IPublishedMemberCache Members { get; } + + /// + /// Gets the . + /// + IDomainCache Domains { get; } + + /// + /// Gets the snapshot-level cache. + /// + ICacheProvider SnapshotCache { get; } + + /// + /// Gets the elements-level cache. + /// + ICacheProvider ElementsCache { get; } + + /// + /// Forces the preview mode. + /// + /// The forced preview mode. + /// A callback to execute when reverting to previous preview. + /// + /// Forcing to false means no preview. Forcing to true means 'full' preview if the snapshot is not already previewing; + /// otherwise the snapshot keeps previewing according to whatever settings it is using already. + /// Stops forcing preview when disposed. + IDisposable ForcedPreview(bool preview, Action callback = null); + } +} diff --git a/src/Umbraco.Web/PublishedCache/IPublishedSnapshotAccessor.cs b/src/Umbraco.Web/PublishedCache/IPublishedSnapshotAccessor.cs new file mode 100644 index 0000000000..9c0bc5a1e7 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/IPublishedSnapshotAccessor.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Web.PublishedCache +{ + /// + /// Provides access to the "current" . + /// + public interface IPublishedSnapshotAccessor + { + IPublishedShapshot PublishedSnapshot { get; set; } + } +} diff --git a/src/Umbraco.Web/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/IPublishedSnapshotService.cs new file mode 100644 index 0000000000..40c3538ed9 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/IPublishedSnapshotService.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Cache; + +namespace Umbraco.Web.PublishedCache +{ + /// + /// Creates and manages instances. + /// + public interface IPublishedSnapshotService : IDisposable + { + #region PublishedSnapshot + + /* Various places (such as Node) want to access the XML content, today as an XmlDocument + * but to migrate to a new cache, they're migrating to an XPathNavigator. Still, they need + * to find out how to get that navigator. + * + * Because a cache such as NuCache is contextual i.e. it has a "snapshot" thing and remains + * consistent over the snapshot, the navigator has to come come from the "current" snapshot. + * + * So although everything should be injected... we also need a notion of "the current published + * snapshot". This is provided by the IPublishedSnapshotAccessor. + * + */ + + /// + /// Creates a published snapshot. + /// + /// A preview token, or null if not previewing. + /// A published snapshot. + /// If is null, the snapshot is not previewing, else it + /// is previewing, and what is or is not visible in preview depends on the content of the token, + /// which is not specified and depends on the actual published snapshot service implementation. + IPublishedShapshot CreatePublishedSnapshot(string previewToken); + + /// + /// Gets the published snapshot accessor. + /// + IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; } + + /// + /// Ensures that the published snapshot has the proper environment to run. + /// + /// The errors, if any. + /// A value indicating whether the published snapshot has the proper environment to run. + bool EnsureEnvironment(out IEnumerable errors); + + #endregion + + #region Preview + + /* Later on we can imagine that EnterPreview would handle a "level" that would be either + * the content only, or the content's branch, or the whole tree + it could be possible + * to register filters against the factory to filter out which nodes should be preview + * vs non preview. + * + * EnterPreview() returns the previewToken. It is up to callers to store that token + * wherever they want, most probably in a cookie. + * + */ + + /// + /// Enters preview for specified user and content. + /// + /// The user. + /// The content identifier. + /// A preview token. + /// + /// Tells the caches that they should prepare any data that they would be keeping + /// in order to provide preview to a give user. In the Xml cache this means creating the Xml + /// file, though other caches may do things differently. + /// Does not handle the preview token storage (cookie, etc) that must be handled separately. + /// + string EnterPreview(IUser user, int contentId); + + /// + /// Refreshes preview for a specified content. + /// + /// The preview token. + /// The content identifier. + /// Tells the caches that they should update any data that they would be keeping + /// in order to provide preview to a given user. In the Xml cache this means updating the Xml + /// file, though other caches may do things differently. + void RefreshPreview(string previewToken, int contentId); + + /// + /// Exits preview for a specified preview token. + /// + /// The preview token. + /// + /// Tells the caches that they can dispose of any data that they would be keeping + /// in order to provide preview to a given user. In the Xml cache this means deleting the Xml file, + /// though other caches may do things differently. + /// Does not handle the preview token storage (cookie, etc) that must be handled separately. + /// + void ExitPreview(string previewToken); + + #endregion + + #region Changes + + /* An IPublishedCachesService implementation can rely on transaction-level events to update + * its internal, database-level data, as these events are purely internal. However, it cannot + * rely on cache refreshers CacheUpdated events to update itself, as these events are external + * and the order-of-execution of the handlers cannot be guaranteed, which means that some + * user code may run before Umbraco is finished updating itself. Instead, the cache refreshers + * explicitely notify the service of changes. + * + */ + + /// + /// Notifies of content cache refresher changes. + /// + /// The changes. + /// A value indicating whether draft contents have been changed in the cache. + /// A value indicating whether published contents have been changed in the cache. + void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged); + + /// + /// Notifies of media cache refresher changes. + /// + /// The changes. + /// A value indicating whether medias have been changed in the cache. + void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged); + + // there is no NotifyChanges for MemberCacheRefresher because we're not caching members. + + /// + /// Notifies of content type refresher changes. + /// + /// The changes. + void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads); + + /// + /// Notifies of data type refresher changes. + /// + /// The changes. + void Notify(DataTypeCacheRefresher.JsonPayload[] payloads); + + /// + /// Notifies of domain refresher changes. + /// + /// The changes. + void Notify(DomainCacheRefresher.JsonPayload[] payloads); + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs b/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs deleted file mode 100644 index 35b3d6ee62..0000000000 --- a/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs +++ /dev/null @@ -1,234 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Web.Security; -using Umbraco.Core; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models; - -namespace Umbraco.Web.PublishedCache -{ - /// - /// Exposes a member object as IPublishedContent - /// - public sealed class MemberPublishedContent : PublishedContentWithKeyBase - { - - private readonly IMember _member; - private readonly IMembershipUser _membershipUser; - private readonly IPublishedProperty[] _properties; - private readonly PublishedContentType _publishedMemberType; - - public MemberPublishedContent(IMember member) - { - if (member == null) throw new ArgumentNullException("member"); - - _member = member; - _membershipUser = member; - _publishedMemberType = PublishedContentType.Get(PublishedItemType.Member, _member.ContentTypeAlias); - if (_publishedMemberType == null) - { - throw new InvalidOperationException("Could not get member type with alias " + _member.ContentTypeAlias); - } - - _properties = PublishedProperty.MapProperties(_publishedMemberType.PropertyTypes, _member.Properties, - (t, v) => new RawValueProperty(t, v ?? string.Empty)) - .ToArray(); - } - - - #region Membership provider member properties - public string Email - { - get { return _membershipUser.Email; } - } - public string UserName - { - get { return _membershipUser.Username; } - } - public string PasswordQuestion - { - get { return _membershipUser.PasswordQuestion; } - } - public string Comments - { - get { return _membershipUser.Comments; } - } - public bool IsApproved - { - get { return _membershipUser.IsApproved; } - } - public bool IsLockedOut - { - get { return _membershipUser.IsLockedOut; } - } - public DateTime LastLockoutDate - { - get { return _membershipUser.LastLockoutDate; } - } - public DateTime CreationDate - { - get { return _membershipUser.CreateDate; } - } - public DateTime LastLoginDate - { - get { return _membershipUser.LastLoginDate; } - } - public DateTime LastActivityDate - { - get { return _membershipUser.LastLoginDate; } - } - public DateTime LastPasswordChangedDate - { - get { return _membershipUser.LastPasswordChangeDate; } - } - #endregion - - #region IPublishedContent - public override PublishedItemType ItemType - { - get { return PublishedItemType.Member; } - } - - public override bool IsDraft - { - get { return false; } - } - - public override IPublishedContent Parent - { - get { return null; } - } - - public override IEnumerable Children - { - get { return Enumerable.Empty(); } - } - - public override ICollection Properties - { - get { return _properties; } - } - - public override IPublishedProperty GetProperty(string alias, bool recurse) - { - if (recurse) - { - throw new NotSupportedException(); - } - return GetProperty(alias); - } - - public override IPublishedProperty GetProperty(string alias) - { - switch (alias) - { - case "Email": - return new PropertyResult("Email", Email, PropertyResultType.CustomProperty); - case "UserName": - return new PropertyResult("UserName", UserName, PropertyResultType.CustomProperty); - } - - return _properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); - } - - public override PublishedContentType ContentType - { - get { return _publishedMemberType; } - } - - public override int Id - { - get { return _member.Id; } - } - - public override Guid Key - { - get { return _member.Key; } - } - - public override int TemplateId - { - get { throw new NotSupportedException(); } - } - - public override int SortOrder - { - get { return 0; } - } - - public override string Name - { - get { return _member.Name; } - } - - public override string UrlName - { - get { throw new NotSupportedException(); } - } - - public override string DocumentTypeAlias - { - get { return _member.ContentTypeAlias; } - } - - public override int DocumentTypeId - { - get { return _member.ContentType.Id; } - } - - public override string WriterName - { - get { return _member.GetCreatorProfile().Name; } - } - - public override string CreatorName - { - get { return _member.GetCreatorProfile().Name; } - } - - public override int WriterId - { - get { return _member.CreatorId; } - } - - public override int CreatorId - { - get { return _member.CreatorId; } - } - - public override string Path - { - get { return _member.Path; } - } - - public override DateTime CreateDate - { - get { return _member.CreateDate; } - } - - public override DateTime UpdateDate - { - get { return _member.UpdateDate; } - } - - public override Guid Version - { - get { return _member.Version; } - } - - public override int Level - { - get { return _member.Level; } - } - #endregion - } -} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/CacheKeys.cs b/src/Umbraco.Web/PublishedCache/NuCache/CacheKeys.cs new file mode 100644 index 0000000000..a3168732e4 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/CacheKeys.cs @@ -0,0 +1,78 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + internal static class CacheKeys + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string DraftOrPub(bool previewing) + { + return previewing ? "D:" : "P:"; + } + + public static string PublishedContentChildren(Guid contentUid, bool previewing) + { + return "NuCache.Content.Children[" + DraftOrPub(previewing) + ":" + contentUid + "]"; + } + + public static string ContentCacheRoots(bool previewing) + { + return "NuCache.ContentCache.Roots[" + DraftOrPub(previewing) + "]"; + } + + public static string MediaCacheRoots(bool previewing) + { + return "NuCache.MediaCache.Roots[" + DraftOrPub(previewing) + "]"; + } + + public static string PublishedContentAsPreviewing(Guid contentUid) + { + return "NuCache.Content.AsPreviewing[" + contentUid + "]"; + } + + public static string ProfileName(int userId) + { + return "NuCache.Profile.Name[" + userId + "]"; + } + + public static string PropertyRecurse(Guid contentUid, string typeAlias, bool previewing) + { + return "NuCache.Property.Recurse[" + DraftOrPub(previewing) + contentUid + ":" + typeAlias + "]"; + } + + public static string PropertyCacheValues(Guid contentUid, string typeAlias, bool previewing) + { + return "NuCache.Property.CacheValues[" + DraftOrPub(previewing) + contentUid + ":" + typeAlias + "]"; + } + + // routes still use int id and not Guid uid, because routable nodes must have + // a valid ID in the database at that point, whereas content and properties + // may be virtual (and not in umbracoNode). + + public static string ContentCacheRouteByContent(int id, bool previewing) + { + return "NuCache.ContentCache.RouteByContent[" + DraftOrPub(previewing) + id + "]"; + } + + public static string ContentCacheContentByRoute(string route, bool previewing) + { + return "NuCache.ContentCache.ContentByRoute[" + DraftOrPub(previewing) + route + "]"; + } + + //public static string ContentCacheRouteByContentStartsWith() + //{ + // return "NuCache.ContentCache.RouteByContent["; + //} + + //public static string ContentCacheContentByRouteStartsWith() + //{ + // return "NuCache.ContentCache.ContentByRoute["; + //} + + public static string MemberCacheMember(string name, bool previewing, object p) + { + return "NuCache.MemberCache." + name + "[" + DraftOrPub(previewing) + p + "]"; + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs new file mode 100644 index 0000000000..3907c4c79c --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -0,0 +1,384 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml.XPath; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; +using Umbraco.Core.Xml; +using Umbraco.Core.Xml.XPath; +using Umbraco.Web.PublishedCache.NuCache.Navigable; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + internal class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigableData, IDisposable + { + private readonly ContentStore.Snapshot _snapshot; + private readonly ICacheProvider _snapshotCache; + private readonly ICacheProvider _elementsCache; + private readonly DomainHelper _domainHelper; + private readonly IGlobalSettings _globalSettings; + + #region Constructor + + public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, ICacheProvider snapshotCache, ICacheProvider elementsCache, DomainHelper domainHelper, IGlobalSettings globalSettings) + : base(previewDefault) + { + _snapshot = snapshot; + _snapshotCache = snapshotCache; + _elementsCache = elementsCache; + _domainHelper = domainHelper; + _globalSettings = globalSettings; + } + + private bool HideTopLevelNodeFromPath => _globalSettings.HideTopLevelNodeFromPath; + + #endregion + + #region Routes + + // routes can be + // "/" + // "123/" + // "/path/to/node" + // "123/path/to/node" + + // at the moment we try our best to be backward compatible, but really, + // should get rid of hideTopLevelNode and other oddities entirely, eventually + + public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null) + { + return GetByRoute(PreviewDefault, route, hideTopLevelNode); + } + + public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null) + { + if (route == null) throw new ArgumentNullException(nameof(route)); + + var cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing ? _elementsCache : _snapshotCache; + var key = CacheKeys.ContentCacheContentByRoute(route, preview); + return cache.GetCacheItem(key, () => GetByRouteInternal(preview, route, hideTopLevelNode)); + } + + private IPublishedContent GetByRouteInternal(bool preview, string route, bool? hideTopLevelNode) + { + hideTopLevelNode = hideTopLevelNode ?? HideTopLevelNodeFromPath; // default = settings + + // the route always needs to be lower case because we only store the urlName attribute in lower case + route = route.ToLowerInvariant(); + + var pos = route.IndexOf('/'); + var path = pos == 0 ? route : route.Substring(pos); + var startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos)); + var parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + IPublishedContent content; + + if (startNodeId > 0) + { + // if in a domain then start with the root node of the domain + // and follow the path + // note: if domain has a path (eg example.com/en) which is not recommended anymore + // then then /en part of the domain is basically ignored here... + content = GetById(preview, startNodeId); + content = FollowRoute(content, parts, 0); + } + else if (parts.Length == 0) + { + // if not in a domain, and path is empty - what is the default page? + // let's say it is the first one in the tree, if any -- order by sortOrder + content = GetAtRoot(preview).FirstOrDefault(); + } + else + { + // if not in a domain... + // hideTopLevelNode = support legacy stuff, look for /*/path/to/node + // else normal, look for /path/to/node + content = hideTopLevelNode.Value + ? GetAtRoot(preview).SelectMany(x => x.Children).FirstOrDefault(x => x.UrlName == parts[0]) + : GetAtRoot(preview).FirstOrDefault(x => x.UrlName == parts[0]); + content = FollowRoute(content, parts, 1); + } + + // if hideTopLevelNodePath is true then for url /foo we looked for /*/foo + // but maybe that was the url of a non-default top-level node, so we also + // have to look for /foo (see note in ApplyHideTopLevelNodeFromPath). + if (content == null && hideTopLevelNode.Value && parts.Length == 1) + { + content = GetAtRoot(preview).FirstOrDefault(x => x.UrlName == parts[0]); + } + + return content; + } + + public string GetRouteById(int contentId) + { + return GetRouteById(PreviewDefault, contentId); + } + + public string GetRouteById(bool preview, int contentId) + { + var cache = (preview == false || PublishedSnapshotService.FullCacheWhenPreviewing) ? _elementsCache : _snapshotCache; + var key = CacheKeys.ContentCacheRouteByContent(contentId, preview); + return cache.GetCacheItem(key, () => GetRouteByIdInternal(preview, contentId, null)); + } + + private string GetRouteByIdInternal(bool preview, int contentId, bool? hideTopLevelNode) + { + var node = GetById(preview, contentId); + if (node == null) + return null; + + hideTopLevelNode = hideTopLevelNode ?? HideTopLevelNodeFromPath; // default = settings + + // walk up from that node until we hit a node with a domain, + // or we reach the content root, collecting urls in the way + var pathParts = new List(); + var n = node; + var hasDomains = _domainHelper.NodeHasDomains(n.Id); + while (hasDomains == false && n != null) // n is null at root + { + // get the url + var urlName = n.UrlName; + pathParts.Add(urlName); + + // move to parent node + n = n.Parent; + hasDomains = n != null && _domainHelper.NodeHasDomains(n.Id); + } + + // no domain, respect HideTopLevelNodeFromPath for legacy purposes + if (hasDomains == false && hideTopLevelNode.Value) + ApplyHideTopLevelNodeFromPath(node, pathParts, preview); + + // assemble the route + pathParts.Reverse(); + var path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc + var route = (n?.Id.ToString(CultureInfo.InvariantCulture) ?? "") + path; + + return route; + } + + private static IPublishedContent FollowRoute(IPublishedContent content, IReadOnlyList parts, int start) + { + var i = start; + while (content != null && i < parts.Count) + { + var part = parts[i++]; + content = content.Children.FirstOrDefault(x => x.UrlName == part); + } + return content; + } + + private void ApplyHideTopLevelNodeFromPath(IPublishedContent content, IList segments, bool preview) + { + // in theory if hideTopLevelNodeFromPath is true, then there should be only one + // top-level node, or else domains should be assigned. but for backward compatibility + // we add this check - we look for the document matching "/" and if it's not us, then + // we do not hide the top level path + // it has to be taken care of in GetByRoute too so if + // "/foo" fails (looking for "/*/foo") we try also "/foo". + // this does not make much sense anyway esp. if both "/foo/" and "/bar/foo" exist, but + // that's the way it works pre-4.10 and we try to be backward compat for the time being + if (content.Parent == null) + { + var rootNode = GetByRoute(preview, "/", true); + if (rootNode == null) + throw new Exception("Failed to get node at /."); + if (rootNode.Id == content.Id) // remove only if we're the default node + segments.RemoveAt(segments.Count - 1); + } + else + { + segments.RemoveAt(segments.Count - 1); + } + } + + #endregion + + #region Get, Has + + public override IPublishedContent GetById(bool preview, int contentId) + { + var n = _snapshot.Get(contentId); + if (n == null) return null; + + // both .Draft and .Published cannot be null at the same time + return preview + ? n.Draft ?? GetPublishedContentAsPreviewing(n.Published) + : n.Published; + } + + public override IPublishedContent GetById(bool preview, Guid contentId) + { + var n = _snapshot.Get(contentId); + if (n == null) return null; + + // both .Draft and .Published cannot be null at the same time + return preview + ? n.Draft ?? GetPublishedContentAsPreviewing(n.Published) + : n.Published; + } + + public override bool HasById(bool preview, int contentId) + { + var n = _snapshot.Get(contentId); + if (n == null) return false; + + return preview || n.Published != null; + } + + public override IEnumerable GetAtRoot(bool preview) + { + if (PublishedSnapshotService.CacheContentCacheRoots == false) + return GetAtRootNoCache(preview); + + var cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing + ? _elementsCache + : _snapshotCache; + + if (cache == null) + return GetAtRootNoCache(preview); + + // note: ToArray is important here, we want to cache the result, not the function! + return (IEnumerable) cache.GetCacheItem( + CacheKeys.ContentCacheRoots(preview), + () => GetAtRootNoCache(preview).ToArray()); + } + + private IEnumerable GetAtRootNoCache(bool preview) + { + var c = _snapshot.GetAtRoot(); + + // both .Draft and .Published cannot be null at the same time + return c.Select(n => preview + ? n.Draft ?? GetPublishedContentAsPreviewing(n.Published) + : n.Published).WhereNotNull().OrderBy(x => x.SortOrder); + } + + // gets a published content as a previewing draft, if preview is true + // this is for published content when previewing + internal static IPublishedContent GetPublishedContentAsPreviewing(IPublishedContent content /*, bool preview*/) + { + if (content == null /*|| preview == false*/) return null; //content; + + // an object in the cache is either an IPublishedContentOrMedia, + // or a model inheriting from PublishedContentExtended - in which + // case we need to unwrap to get to the original IPublishedContentOrMedia. + + var inner = PublishedContent.UnwrapIPublishedContent(content); + return inner.AsPreviewingModel(); + } + + public override bool HasContent(bool preview) + { + return preview + ? _snapshot.IsEmpty == false + : _snapshot.GetAtRoot().Any(x => x.Published != null); + } + + #endregion + + #region XPath + + public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) + { + var navigator = CreateNavigator(preview); + var iterator = navigator.Select(xpath, vars); + return GetSingleByXPath(iterator); + } + + public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) + { + var navigator = CreateNavigator(preview); + var iterator = navigator.Select(xpath, vars); + return GetSingleByXPath(iterator); + } + + private static IPublishedContent GetSingleByXPath(XPathNodeIterator iterator) + { + if (iterator.MoveNext() == false) return null; + + var xnav = iterator.Current as NavigableNavigator; + var xcontent = xnav?.UnderlyingObject as NavigableContent; + return xcontent?.InnerContent; + } + + public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) + { + var navigator = CreateNavigator(preview); + var iterator = navigator.Select(xpath, vars); + return GetByXPath(iterator); + } + + public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) + { + var navigator = CreateNavigator(preview); + var iterator = navigator.Select(xpath, vars); + return GetByXPath(iterator); + } + + private static IEnumerable GetByXPath(XPathNodeIterator iterator) + { + while (iterator.MoveNext()) + { + var xnav = iterator.Current as NavigableNavigator; + var xcontent = xnav?.UnderlyingObject as NavigableContent; + if (xcontent == null) continue; + + yield return xcontent.InnerContent; + } + } + + public override XPathNavigator CreateNavigator(bool preview) + { + var source = new Source(this, preview); + var navigator = new NavigableNavigator(source); + return navigator; + } + + public override XPathNavigator CreateNodeNavigator(int id, bool preview) + { + var source = new Source(this, preview); + var navigator = new NavigableNavigator(source); + return navigator.CloneWithNewRoot(id, 0); + } + + #endregion + + #region Content types + + public override PublishedContentType GetContentType(int id) + { + return _snapshot.GetContentType(id); + } + + public override PublishedContentType GetContentType(string alias) + { + return _snapshot.GetContentType(alias); + } + + public override IEnumerable GetByContentType(PublishedContentType contentType) + { + throw new NotImplementedException(); + } + + #endregion + + #region IDisposable + + public void Dispose() + { + _snapshot.Dispose(); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs new file mode 100644 index 0000000000..a7cc1e950c --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.PublishedCache.NuCache.DataSource; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + // represents a content "node" ie a pair of draft + published versions + // internal, never exposed, to be accessed from ContentStore (only!) + internal class ContentNode + { + // special ctor with no content data - for members + public ContentNode(int id, Guid uid, PublishedContentType contentType, + int level, string path, int sortOrder, + int parentContentId, + DateTime createDate, int creatorId) + { + Id = id; + Uid = uid; + ContentType = contentType; + Level = level; + Path = path; + SortOrder = sortOrder; + ParentContentId = parentContentId; + CreateDate = createDate; + CreatorId = creatorId; + + ChildContentIds = new List(); + } + + public ContentNode(int id, Guid uid, PublishedContentType contentType, + int level, string path, int sortOrder, + int parentContentId, + DateTime createDate, int creatorId, + ContentData draftData, ContentData publishedData, + IPublishedSnapshotAccessor publishedSnapshotAccessor) + : this(id, uid, level, path, sortOrder, parentContentId, createDate, creatorId) + { + SetContentTypeAndData(contentType, draftData, publishedData, publishedSnapshotAccessor); + } + + // 2-phases ctor, phase 1 + public ContentNode(int id, Guid uid, + int level, string path, int sortOrder, + int parentContentId, + DateTime createDate, int creatorId) + { + Id = id; + Uid = uid; + Level = level; + Path = path; + SortOrder = sortOrder; + ParentContentId = parentContentId; + CreateDate = createDate; + CreatorId = creatorId; + + ChildContentIds = new List(); + } + + // two-phase ctor, phase 2 + public void SetContentTypeAndData(PublishedContentType contentType, ContentData draftData, ContentData publishedData, IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + ContentType = contentType; + + if (draftData == null && publishedData == null) + throw new ArgumentException("Both draftData and publishedData cannot be null at the same time."); + + if (draftData != null) + Draft = new PublishedContent(this, draftData, publishedSnapshotAccessor).CreateModel(); + if (publishedData != null) + Published = new PublishedContent(this, publishedData, publishedSnapshotAccessor).CreateModel(); + } + + // clone parent + private ContentNode(ContentNode origin) + { + // everything is the same, except for the child items + // list which is a clone of the original list + + Id = origin.Id; + Uid = origin.Uid; + ContentType = origin.ContentType; + Level = origin.Level; + Path = origin.Path; + SortOrder = origin.SortOrder; + ParentContentId = origin.ParentContentId; + CreateDate = origin.CreateDate; + CreatorId = origin.CreatorId; + + var originDraft = origin.Draft == null ? null : PublishedContent.UnwrapIPublishedContent(origin.Draft); + var originPublished = origin.Published == null ? null : PublishedContent.UnwrapIPublishedContent(origin.Published); + + Draft = originDraft == null ? null : new PublishedContent(this, originDraft).CreateModel(); + Published = originPublished == null ? null : new PublishedContent(this, originPublished).CreateModel(); + + ChildContentIds = new List(origin.ChildContentIds); // needs to be *another* list + } + + // clone with new content type + public ContentNode(ContentNode origin, PublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + Id = origin.Id; + Uid = origin.Uid; + ContentType = contentType; // change! + Level = origin.Level; + Path = origin.Path; + SortOrder = origin.SortOrder; + ParentContentId = origin.ParentContentId; + CreateDate = origin.CreateDate; + CreatorId = origin.CreatorId; + + var originDraft = origin.Draft == null ? null : PublishedContent.UnwrapIPublishedContent(origin.Draft); + var originPublished = origin.Published == null ? null : PublishedContent.UnwrapIPublishedContent(origin.Published); + + Draft = originDraft == null ? null : new PublishedContent(this, originDraft._contentData, publishedSnapshotAccessor).CreateModel(); + Published = originPublished == null ? null : new PublishedContent(this, originPublished._contentData, publishedSnapshotAccessor).CreateModel(); + + ChildContentIds = origin.ChildContentIds; // can be the *same* list FIXME oh really? + } + + // everything that is common to both draft and published versions + // keep this as small as possible + public readonly int Id; + public readonly Guid Uid; + public PublishedContentType ContentType; + public readonly int Level; + public readonly string Path; + public readonly int SortOrder; + public readonly int ParentContentId; + public List ChildContentIds; + public readonly DateTime CreateDate; + public readonly int CreatorId; + + // draft and published version (either can be null, but not both) + // are models not direct PublishedContent instances + public IPublishedContent Draft; + public IPublishedContent Published; + + public ContentNode CloneParent(IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + return new ContentNode(this); + } + + public ContentNodeKit ToKit() + { + var draft = Draft is PublishedContentModel draftModel + ? (PublishedContent) draftModel.Unwrap() + : (PublishedContent) Draft; + + var published = Published is PublishedContentModel publishedModel + ? (PublishedContent) publishedModel.Unwrap() + : (PublishedContent) Published; + + return new ContentNodeKit + { + Node = this, + ContentTypeId = ContentType.Id, + + DraftData = draft?._contentData, + PublishedData = published?._contentData + }; + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs new file mode 100644 index 0000000000..82bfc8766a --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs @@ -0,0 +1,25 @@ +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.PublishedCache.NuCache.DataSource; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + // what's needed to actually build a content node + internal struct ContentNodeKit + { + public ContentNode Node; + public int ContentTypeId; + public ContentData DraftData; + public ContentData PublishedData; + + public bool IsEmpty => Node == null; + + public bool IsNull => ContentTypeId < 0; + + public static ContentNodeKit Null { get; } = new ContentNodeKit { ContentTypeId = -1 }; + + public void Build(PublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + Node.SetContentTypeAndData(contentType, DraftData, PublishedData, publishedSnapshotAccessor); + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs new file mode 100644 index 0000000000..74613509ba --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -0,0 +1,1228 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CSharpTest.Net.Collections; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Scoping; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + // stores content + internal class ContentStore + { + // this class is an extended version of SnapDictionary + // most of the snapshots management code, etc is an exact copy + // SnapDictionary has unit tests to ensure it all works correctly + + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly ConcurrentDictionary> _contentNodes; + private readonly ConcurrentDictionary> _contentRootNodes; + private readonly ConcurrentDictionary> _contentTypesById; + private readonly ConcurrentDictionary> _contentTypesByAlias; + private readonly ConcurrentDictionary _xmap; + + private readonly ILogger _logger; + private BPlusTree _localDb; + private readonly ConcurrentQueue _genRefRefs; + private GenRefRef _genRefRef; + private readonly object _wlocko = new object(); + private readonly object _rlocko = new object(); + private long _liveGen, _floorGen; + private bool _nextGen, _collectAuto; + private Task _collectTask; + private volatile int _wlocked; + private List> _wchanges; + + // fixme - collection trigger (ok for now) + // see SnapDictionary notes + private const long CollectMinGenDelta = 8; + + #region Ctor + + public ContentStore(IPublishedSnapshotAccessor publishedSnapshotAccessor, ILogger logger, BPlusTree localDb = null) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _logger = logger; + _localDb = localDb; + + _contentNodes = new ConcurrentDictionary>(); + _contentRootNodes = new ConcurrentDictionary>(); + _contentTypesById = new ConcurrentDictionary>(); + _contentTypesByAlias = new ConcurrentDictionary>(StringComparer.InvariantCultureIgnoreCase); + _xmap = new ConcurrentDictionary(); + + _genRefRefs = new ConcurrentQueue(); + _genRefRef = null; // no initial gen exists + _liveGen = _floorGen = 0; + _nextGen = false; // first time, must create a snapshot + _collectAuto = true; // collect automatically by default + } + + #endregion + + #region Locking + + // see notes on SnapDictionary + + private readonly string _instanceId = Guid.NewGuid().ToString("N"); + + private class ReadLockInfo + { + public bool Taken; + } + + private class WriteLockInfo + { + public bool Taken; + public bool Count; + } + + // a scope contextual that represents a locked writer to the dictionary + private class ContentStoreWriter : ScopeContextualBase + { + private readonly WriteLockInfo _lockinfo = new WriteLockInfo(); + private ContentStore _store; + + public ContentStoreWriter(ContentStore store, bool scoped) + { + _store = store; + store.Lock(_lockinfo, scoped); + } + + public override void Release(bool completed) + { + if (_store== null) return; + _store.Release(_lockinfo, completed); + _store = null; + } + } + + // gets a scope contextual representing a locked writer to the dictionary + // fixme GetScopedWriter? should the dict have a ref onto the scope provider? + public IDisposable GetWriter(IScopeProvider scopeProvider) + { + return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ContentStoreWriter(this, scoped)); + } + + private void Lock(WriteLockInfo lockInfo, bool forceGen = false) + { + Monitor.Enter(_wlocko, ref lockInfo.Taken); + + var rtaken = false; + try + { + Monitor.Enter(_rlocko, ref rtaken); + + // see SnapDictionary + try { } finally + { + _wlocked++; + lockInfo.Count = true; + if (_nextGen == false || (forceGen && _wlocked == 1)) // if true already... ok to have "holes" in generation objects + { + // because we are changing things, a new generation + // is created, which will trigger a new snapshot + _nextGen = true; + _liveGen += 1; + } + } + } + finally + { + if (rtaken) Monitor.Exit(_rlocko); + } + } + + private void Lock(ReadLockInfo lockInfo) + { + Monitor.Enter(_rlocko, ref lockInfo.Taken); + } + + private void Release(WriteLockInfo lockInfo, bool commit = true) + { + if (commit == false) + { + var rtaken = false; + try + { + Monitor.Enter(_rlocko, ref rtaken); + try { } + finally + { + _nextGen = false; + _liveGen -= 1; + } + } + finally + { + if (rtaken) Monitor.Exit(_rlocko); + } + + Rollback(_contentNodes); + Rollback(_contentRootNodes); + Rollback(_contentTypesById); + Rollback(_contentTypesByAlias); + } + else if (_localDb != null && _wchanges != null) + { + foreach (var change in _wchanges) + { + if (change.Value.IsNull) + _localDb.TryRemove(change.Key, out ContentNodeKit unused); + else + _localDb[change.Key] = change.Value; + } + _wchanges = null; + _localDb.Commit(); + } + + if (lockInfo.Count) _wlocked--; + if (lockInfo.Taken) Monitor.Exit(_wlocko); + } + + private void Release(ReadLockInfo lockInfo) + { + if (lockInfo.Taken) Monitor.Exit(_rlocko); + } + + private void Rollback(ConcurrentDictionary> dictionary) + where TValue : class + { + foreach (var item in dictionary) + { + var link = item.Value; + if (link.Gen <= _liveGen) continue; + + var key = item.Key; + if (link.Next == null) + dictionary.TryRemove(key, out link); + else + dictionary.TryUpdate(key, link.Next, link); + } + + } + + #endregion + + #region LocalDb + + public void ReleaseLocalDb() + { + var lockInfo = new WriteLockInfo(); + try + { + Lock(lockInfo); + + if (_localDb == null) return; + _localDb.Dispose(); + _localDb = null; + } + finally + { + Release(lockInfo); + } + } + + private void RegisterChange(int id, ContentNodeKit kit) + { + if (_wchanges == null) _wchanges = new List>(); + _wchanges.Add(new KeyValuePair(id, kit)); + } + + #endregion + + #region Content types + + public void NewContentTypes(IEnumerable types) + { + var lockInfo = new WriteLockInfo(); + try + { + Lock(lockInfo); + + foreach (var type in types) + { + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); + } + } + finally + { + Release(lockInfo); + } + } + + public void UpdateContentTypes(IEnumerable types) + { + var lockInfo = new WriteLockInfo(); + try + { + Lock(lockInfo); + + var index = types.ToDictionary(x => x.Id, x => x); + + foreach (var type in index.Values) + { + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); + } + + foreach (var link in _contentNodes.Values) + { + var node = link.Value; + if (node == null) continue; + var contentTypeId = node.ContentType.Id; + if (index.TryGetValue(contentTypeId, out PublishedContentType contentType) == false) continue; + SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType, _publishedSnapshotAccessor)); + } + } + finally + { + Release(lockInfo); + } + } + + public void UpdateContentTypes(IEnumerable removedIds, IEnumerable refreshedTypes, IEnumerable kits) + { + var removedIdsA = removedIds?.ToArray() ?? Array.Empty(); + var refreshedTypesA = refreshedTypes?.ToArray() ?? Array.Empty(); + var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToArray(); + kits = kits ?? Array.Empty(); + + var lockInfo = new WriteLockInfo(); + try + { + Lock(lockInfo); + + var removedContentTypeNodes = new List(); + var refreshedContentTypeNodes = new List(); + + foreach (var link in _contentNodes.Values) + { + var node = link.Value; + if (node == null) continue; + var contentTypeId = node.ContentType.Id; + if (removedIdsA.Contains(contentTypeId)) removedContentTypeNodes.Add(node.Id); + if (refreshedIdsA.Contains(contentTypeId)) refreshedContentTypeNodes.Add(node.Id); + } + + // all content should have been deleted - but + foreach (var node in removedContentTypeNodes) + ClearBranchLocked(node); + + foreach (var id in removedIdsA) + { + if (_contentTypesById.TryGetValue(id, out LinkedNode link) == false || link.Value == null) + continue; + SetValueLocked(_contentTypesById, id, null); + SetValueLocked(_contentTypesByAlias, link.Value.Alias, null); + } + + foreach (var type in refreshedTypesA) + { + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); + } + + // skip missing type + // skip missing parents & unbuildable kits - what else could we do? + var visited = new List(); + foreach (var kit in kits.Where(x => + refreshedIdsA.Contains(x.ContentTypeId) && + ParentExistsLocked(x) && + BuildKit(x))) + { + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + visited.Add(kit.Node.Id); + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + } + + // all content should have been refreshed - but... + + var orphans = refreshedContentTypeNodes.Except(visited); + foreach (var id in orphans) + ClearBranchLocked(id); + } + finally + { + Release(lockInfo); + } + } + + public void UpdateDataTypes(IEnumerable dataTypeIds, Func getContentType) + { + var lockInfo = new WriteLockInfo(); + try + { + Lock(lockInfo); + + var contentTypes = _contentTypesById + .Where(kvp => + kvp.Value.Value != null && + kvp.Value.Value.PropertyTypes.Any(p => dataTypeIds.Contains(p.DataType.Id))) + .Select(kvp => kvp.Value.Value) + .Select(x => getContentType(x.Id)) + .Where(x => x != null) // poof, gone, very unlikely and probably an anomaly + .ToArray(); + + var contentTypeIdsA = contentTypes.Select(x => x.Id).ToArray(); + var contentTypeNodes = new Dictionary>(); + foreach (var id in contentTypeIdsA) + contentTypeNodes[id] = new List(); + foreach (var link in _contentNodes.Values) + { + var node = link.Value; + if (node != null && contentTypeIdsA.Contains(node.ContentType.Id)) + contentTypeNodes[node.ContentType.Id].Add(node.Id); + } + + foreach (var contentType in contentTypes) + { + // again, weird situation + if (contentTypeNodes.ContainsKey(contentType.Id) == false) + continue; + + foreach (var id in contentTypeNodes[contentType.Id]) + { + _contentNodes.TryGetValue(id, out LinkedNode link); + if (link?.Value == null) + continue; + var node = new ContentNode(link.Value, contentType, _publishedSnapshotAccessor); + SetValueLocked(_contentNodes, id, node); + if (_localDb != null) RegisterChange(id, node.ToKit()); + } + } + } + finally + { + Release(lockInfo); + } + } + + private bool BuildKit(ContentNodeKit kit) + { + // make sure the kit is valid + if (kit.DraftData == null && kit.PublishedData == null) + return false; + + // unknown = bad + if (_contentTypesById.TryGetValue(kit.ContentTypeId, out LinkedNode link) == false || link.Value == null) + return false; + + // and use + kit.Build(link.Value, _publishedSnapshotAccessor); + + return true; + } + + #endregion + + #region Set, Clear, Get + + public int Count => _contentNodes.Count; + + private static LinkedNode GetHead(ConcurrentDictionary> dict, TKey key) + where TValue : class + { + dict.TryGetValue(key, out LinkedNode link); // else null + return link; + } + + public void Set(ContentNodeKit kit) + { + // ReSharper disable LocalizableElement + if (kit.IsEmpty) + throw new ArgumentException("Kit is empty.", nameof(kit)); + if (kit.Node.ChildContentIds.Count > 0) + throw new ArgumentException("Kit content cannot have children.", nameof(kit)); + // ReSharper restore LocalizableElement + + _logger.Debug("Set content ID:" + kit.Node.Id); + + var lockInfo = new WriteLockInfo(); + try + { + Lock(lockInfo); + + // get existing + _contentNodes.TryGetValue(kit.Node.Id, out LinkedNode link); + var existing = link?.Value; + + // else ignore, what else could we do? + if (ParentExistsLocked(kit) == false || BuildKit(kit) == false) + return; + + // moving? + var moving = existing != null && existing.ParentContentId != kit.Node.ParentContentId; + + // manage children + if (existing != null) + kit.Node.ChildContentIds = existing.ChildContentIds; + + // set + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + + // manage the tree + if (existing == null) + { + // new, add to parent + AddToParentLocked(kit.Node); + } + else if (moving) + { + // moved, remove existing from its parent, add content to its parent + RemoveFromParentLocked(existing); + AddToParentLocked(kit.Node); + } + + _xmap[kit.Node.Uid] = kit.Node.Id; + } + finally + { + Release(lockInfo); + } + } + + public void SetAll(IEnumerable kits) + { + var lockInfo = new WriteLockInfo(); + try + { + Lock(lockInfo); + + ClearLocked(_contentNodes); + ClearLocked(_contentRootNodes); + + // do NOT clear types else they are gone! + //ClearLocked(_contentTypesById); + //ClearLocked(_contentTypesByAlias); + + // skip missing parents & unbuildable kits - what else could we do? + foreach (var kit in kits.Where(x => ParentExistsLocked(x) && BuildKit(x))) + { + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + AddToParentLocked(kit.Node); + + _xmap[kit.Node.Uid] = kit.Node.Id; + } + } + finally + { + Release(lockInfo); + } + } + + public void SetBranch(int rootContentId, IEnumerable kits) + { + var lockInfo = new WriteLockInfo(); + try + { + Lock(lockInfo); + + // get existing + _contentNodes.TryGetValue(rootContentId, out LinkedNode link); + var existing = link?.Value; + + // clear + if (existing != null) + { + ClearBranchLocked(existing); + RemoveFromParentLocked(existing); + } + + // now add them all back + // skip missing parents & unbuildable kits - what else could we do? + foreach (var kit in kits.Where(x => ParentExistsLocked(x) && BuildKit(x))) + { + SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + if (_localDb != null) RegisterChange(kit.Node.Id, kit); + AddToParentLocked(kit.Node); + + _xmap[kit.Node.Uid] = kit.Node.Id; + } + } + finally + { + Release(lockInfo); + } + } + + public bool Clear(int id) + { + var lockInfo = new WriteLockInfo(); + try + { + Lock(lockInfo); + + // try to find the content + // if it is not there, nothing to do + _contentNodes.TryGetValue(id, out LinkedNode link); // else null + if (link?.Value == null) return false; + + var content = link.Value; + _logger.Debug("Clear content ID:" + content.Id); + + // clear the entire branch + ClearBranchLocked(content); + + // manage the tree + RemoveFromParentLocked(content); + + return true; + } + finally + { + Release(lockInfo); + } + } + + private void ClearBranchLocked(int id) + { + _contentNodes.TryGetValue(id, out var link); + if (link?.Value == null) + return; + ClearBranchLocked(link.Value); + } + + private void ClearBranchLocked(ContentNode content) + { + SetValueLocked(_contentNodes, content.Id, null); + if (_localDb != null) RegisterChange(content.Id, ContentNodeKit.Null); + + _xmap.TryRemove(content.Uid, out _); + + foreach (var childId in content.ChildContentIds) + { + if (_contentNodes.TryGetValue(childId, out LinkedNode link) == false || link.Value == null) continue; + ClearBranchLocked(link.Value); + } + } + + private LinkedNode GetParentLink(ContentNode content) + { + _contentNodes.TryGetValue(content.ParentContentId, out var link); // else null + //if (link == null || link.Value == null) + // throw new Exception("Panic: parent not found."); + return link; + } + + private void RemoveFromParentLocked(ContentNode content) + { + // remove from root content index, + // or parent's children index + if (content.ParentContentId < 0) + { + SetValueLocked(_contentRootNodes, content.Id, null); + } + else + { + // obviously parent has to exist + var link = GetParentLink(content); + var parent = link.Value; + if (link.Gen < _liveGen) + parent = parent.CloneParent(_publishedSnapshotAccessor); + parent.ChildContentIds.Remove(content.Id); + if (link.Gen < _liveGen) + SetValueLocked(_contentNodes, parent.Id, parent); + } + } + + private bool ParentExistsLocked(ContentNodeKit kit) + { + if (kit.Node.ParentContentId < 0) + return true; + var link = GetParentLink(kit.Node); + return link?.Value != null; + } + + private void AddToParentLocked(ContentNode content) + { + // add to root content index, + // or parent's children index + if (content.ParentContentId < 0) + { + // need an object reference... just use this... + SetValueLocked(_contentRootNodes, content.Id, this); + } + else + { + // assume parent has been validated and exists + var link = GetParentLink(content); + var parent = link.Value; + if (link.Gen < _liveGen) + parent = parent.CloneParent(_publishedSnapshotAccessor); + parent.ChildContentIds.Add(content.Id); + if (link.Gen < _liveGen) + SetValueLocked(_contentNodes, parent.Id, parent); + } + } + + private void SetValueLocked(ConcurrentDictionary> dict, TKey key, TValue value) + where TValue : class + { + // this is safe only because we're write-locked + var link = GetHead(dict, key); + if (link != null) + { + // already in the dict + if (link.Gen != _liveGen) + { + // for an older gen - if value is different then insert a new + // link for the new gen, with the new value + if (link.Value != value) + dict.TryUpdate(key, new LinkedNode(value, _liveGen, link), link); + } + else + { + // for the live gen - we can fix the live gen - and remove it + // if value is null and there's no next gen + if (value == null && link.Next == null) + dict.TryRemove(key, out link); + else + link.Value = value; + } + } + else + { + dict.TryAdd(key, new LinkedNode(value, _liveGen)); + } + } + + private void ClearLocked(ConcurrentDictionary> dict) + where TValue : class + { + // this is safe only because we're write-locked + foreach (var kvp in dict.Where(x => x.Value != null)) + { + if (kvp.Value.Gen < _liveGen) + { + var link = new LinkedNode(null, _liveGen, kvp.Value); + dict.TryUpdate(kvp.Key, link, kvp.Value); + } + else + { + kvp.Value.Value = null; + } + } + } + + public ContentNode Get(int id, long gen) + { + return GetValue(_contentNodes, id, gen); + } + + public ContentNode Get(Guid uid, long gen) + { + return _xmap.TryGetValue(uid, out var id) + ? GetValue(_contentNodes, id, gen) + : null; + } + + public IEnumerable GetAtRoot(long gen) + { + // look ma, no lock! + foreach (var kvp in _contentRootNodes) + { + var link = kvp.Value; + while (link != null) + { + if (link.Gen <= gen) + break; + link = link.Next; + } + if (link?.Value != null) + yield return Get(kvp.Key, gen); + } + } + + private TValue GetValue(ConcurrentDictionary> dict, TKey key, long gen) + where TValue : class + { + // look ma, no lock! + var link = GetHead(dict, key); + while (link != null) + { + if (link.Gen <= gen) + return link.Value; // may be null + link = link.Next; + } + return null; + } + + public IEnumerable GetAll(long gen) + { + // enumerating on .Values locks the concurrent dictionary, + // so better get a shallow clone in an array and release + var links = _contentNodes.Values.ToArray(); + foreach (var l in links) + { + var link = l; + while (link != null) + { + if (link.Gen <= gen) + { + if (link.Value != null) + yield return link.Value; + break; + } + link = link.Next; + } + } + } + + public bool IsEmpty(long gen) + { + var has = _contentNodes.Any(x => + { + var link = x.Value; + while (link != null) + { + if (link.Gen <= gen && link.Value != null) + return true; + link = link.Next; + } + return false; + }); + return has == false; + } + + public PublishedContentType GetContentType(int id, long gen) + { + return GetValue(_contentTypesById, id, gen); + } + + public PublishedContentType GetContentType(string alias, long gen) + { + return GetValue(_contentTypesByAlias, alias, gen); + } + + #endregion + + #region Snapshots + + public Snapshot CreateSnapshot() + { + var lockInfo = new ReadLockInfo(); + try + { + Lock(lockInfo); + + // if no next generation is required, and we already have one, + // use it and create a new snapshot + if (_nextGen == false && _genRefRef != null) + return new Snapshot(this, _genRefRef.GetGenRef() +#if DEBUG + , _logger +#endif + ); + + // else we need to try to create a new gen ref + // whether we are wlocked or not, noone can rlock while we do, + // so _liveGen and _nextGen are safe + if (_wlocked > 0) // volatile, cannot ++ but could -- + { + // write-locked, cannot use latest gen (at least 1) so use previous + var snapGen = _nextGen ? _liveGen - 1 : _liveGen; + + // create a new gen ref unless we already have it + if (_genRefRef == null) + _genRefRefs.Enqueue(_genRefRef = new GenRefRef(snapGen)); + else if (_genRefRef.Gen != snapGen) + throw new Exception("panic"); + } + else + { + // not write-locked, can use latest gen, create a new gen ref + _genRefRefs.Enqueue(_genRefRef = new GenRefRef(_liveGen)); + _nextGen = false; // this is the ONLY thing that triggers a _liveGen++ + } + + // so... + // the genRefRef has a weak ref to the genRef, and is queued + // the snapshot has a ref to the genRef, which has a ref to the genRefRef + // when the snapshot is disposed, it decreases genRefRef counter + // so after a while, one of these conditions is going to be true: + // - the genRefRef counter is zero because all snapshots have properly been disposed + // - the genRefRef weak ref is dead because all snapshots have been collected + // in both cases, we will dequeue and collect + + var snapshot = new Snapshot(this, _genRefRef.GetGenRef() +#if DEBUG + , _logger +#endif + ); + + // reading _floorGen is safe if _collectTask is null + if (_collectTask == null && _collectAuto && _liveGen - _floorGen > CollectMinGenDelta) + CollectAsyncLocked(); + + return snapshot; + } + finally + { + Release(lockInfo); + } + } + + public Snapshot LiveSnapshot => new Snapshot(this, _liveGen +#if DEBUG + , _logger +#endif + ); + + public Task CollectAsync() + { + lock (_rlocko) + { + return CollectAsyncLocked(); + } + } + + private Task CollectAsyncLocked() + { + if (_collectTask != null) + return _collectTask; + + // ReSharper disable InconsistentlySynchronizedField + var task = _collectTask = Task.Run(() => Collect()); + _collectTask.ContinueWith(_ => + { + lock (_rlocko) + { + _collectTask = null; + } + }, TaskContinuationOptions.ExecuteSynchronously); + // ReSharper restore InconsistentlySynchronizedField + + return task; + } + + private void Collect() + { + // see notes in CreateSnapshot +#if DEBUG + _logger.Debug("Collect."); +#endif + while (_genRefRefs.TryPeek(out GenRefRef genRefRef) && (genRefRef.Count == 0 || genRefRef.WGenRef.IsAlive == false)) + { + _genRefRefs.TryDequeue(out genRefRef); // cannot fail since TryPeek has succeeded + _floorGen = genRefRef.Gen; +#if DEBUG + //_logger.Debug("_floorGen=" + _floorGen + ", _liveGen=" + _liveGen); +#endif + } + + Collect(_contentNodes); + Collect(_contentRootNodes); + Collect(_contentTypesById); + Collect(_contentTypesByAlias); + } + + private void Collect(ConcurrentDictionary> dict) + where TValue : class + { + // it is OK to enumerate a concurrent dictionary and it does not lock + // it - and here it's not an issue if we skip some items, they will be + // processed next time we collect + + long liveGen; + lock (_rlocko) // r is good + { + liveGen = _liveGen; + if (_nextGen == false) + liveGen += 1; + } + + foreach (var kvp in dict) + { + var link = kvp.Value; + +#if DEBUG + //_logger.Debug("Collect id:" + kvp.Key + ", gen:" + link.Gen + + // ", nxt:" + (link.Next == null ? "null" : "link") + + // ", val:" + (link.Value == null ? "null" : "value")); +#endif + + // reasons to collect the head: + // gen must be < liveGen (we never collect live gen) + // next == null && value == null (we have no data at all) + // next != null && value == null BUT gen > floor (noone wants us) + // not live means .Next and .Value are safe + if (link.Gen < liveGen && link.Value == null + && (link.Next == null || link.Gen <= _floorGen)) + { + // not live, null value, no next link = remove that one -- but only if + // the dict has not been updated, have to do it via ICollection<> (thanks + // Mr Toub) -- and if the dict has been updated there is nothing to collect + var idict = dict as ICollection>>; + idict.Remove(kvp); + continue; + } + + // in any other case we're not collecting the head, we need to go to Next + // and if there is no Next, skip + if (link.Next == null) + continue; + + // else go to Next and loop while above floor, and kill everything below + while (link.Next != null && link.Next.Gen > _floorGen) + link = link.Next; + link.Next = null; + } + } + + public async Task WaitForPendingCollect() + { + Task task; + lock (_rlocko) + { + task = _collectTask; + } + if (task != null) + await task; + } + + public long GenCount => _genRefRefs.Count; + + public long SnapCount => _genRefRefs.Sum(x => x.Count); + + #endregion + + #region Unit testing + + private TestHelper _unitTesting; + + // note: nothing here is thread-safe + internal class TestHelper + { + private readonly ContentStore _store; + + public TestHelper(ContentStore store) + { + _store = store; + } + + public long LiveGen => _store._liveGen; + public long FloorGen => _store._floorGen; + public bool NextGen => _store._nextGen; + public bool CollectAuto + { + get => _store._collectAuto; + set => _store._collectAuto = value; + } + + public Tuple[] GetValues(int id) + { + _store._contentNodes.TryGetValue(id, out LinkedNode link); // else null + + if (link == null) + return new Tuple[0]; + + var tuples = new List>(); + do + { + tuples.Add(Tuple.Create(link.Gen, link.Value)); + link = link.Next; + } while (link != null); + return tuples.ToArray(); + } + } + + internal TestHelper Test => _unitTesting ?? (_unitTesting = new TestHelper(this)); + + #endregion + + #region Classes + + private class LinkedNode + where TValue: class + { + public LinkedNode(TValue value, long gen, LinkedNode next = null) + { + Value = value; + Gen = gen; + Next = next; + } + + internal readonly long Gen; + + // reading & writing references is thread-safe on all .NET platforms + // mark as volatile to ensure we always read the correct value + internal volatile TValue Value; + internal volatile LinkedNode Next; + } + + public class Snapshot : IDisposable + { + private readonly ContentStore _store; + private readonly GenRef _genRef; + private long _gen; +#if DEBUG + private readonly ILogger _logger; +#endif + + //private static int _count; + //private readonly int _thisCount; + + internal Snapshot(ContentStore store, GenRef genRef +#if DEBUG + , ILogger logger +#endif + ) + { + _store = store; + _genRef = genRef; + _gen = genRef.Gen; + Interlocked.Increment(ref genRef.GenRefRef.Count); + //_thisCount = _count++; + +#if DEBUG + _logger = logger; + _logger.Debug("Creating snapshot."); +#endif + } + + internal Snapshot(ContentStore store, long gen +#if DEBUG + , ILogger logger +#endif + ) + { + _store = store; + _gen = gen; + +#if DEBUG + _logger = logger; + _logger.Debug("Creating live."); +#endif + } + + public ContentNode Get(int id) + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.Get(id, _gen); + } + + public ContentNode Get(Guid id) + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.Get(id, _gen); + } + + public IEnumerable GetAtRoot() + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.GetAtRoot(_gen); + } + + public IEnumerable GetAll() + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.GetAll(_gen); + } + + public PublishedContentType GetContentType(int id) + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.GetContentType(id, _gen); + } + + public PublishedContentType GetContentType(string alias) + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.GetContentType(alias, _gen); + } + + // this code is here just so you don't try to implement it + // the only way we can iterate over "all" without locking the entire cache forever + // is by shallow cloning the cache, which is quite expensive, so we should probably not do it, + // and implement cache-level indexes + //public IEnumerable GetAll() + //{ + // if (_gen < 0) + // throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + // return _store.GetAll(_gen); + //} + + public bool IsEmpty + { + get + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.IsEmpty(_gen); + } + } + + public long Gen + { + get + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _gen; + } + } + + public void Dispose() + { + if (_gen < 0) return; +#if DEBUG + _logger.Debug("Dispose snapshot (" + (_genRef?.GenRefRef.Count.ToString() ?? "live") + ")."); +#endif + _gen = -1; + if (_genRef != null) + Interlocked.Decrement(ref _genRef.GenRefRef.Count); + GC.SuppressFinalize(this); + } + } + + internal class GenRefRef + { + public GenRefRef(long gen) + { + Gen = gen; + WGenRef = new WeakReference(null); + } + + public GenRef GetGenRef() + { + // not thread-safe but always invoked from within a lock + var genRef = (GenRef) WGenRef.Target; + if (genRef == null) + WGenRef.Target = genRef = new GenRef(this, Gen); + return genRef; + } + + public readonly long Gen; + public readonly WeakReference WGenRef; + public int Count; + } + + internal class GenRef + { + public GenRef(GenRefRef genRefRef, long gen) + { + GenRefRef = genRefRef; + Gen = gen; + } + + public readonly GenRefRef GenRefRef; + public readonly long Gen; + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.cs new file mode 100644 index 0000000000..6fd731cbee --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.IO; +using CSharpTest.Net.Collections; +using CSharpTest.Net.Serialization; + +namespace Umbraco.Web.PublishedCache.NuCache.DataSource +{ + internal class BTree + { + public static BPlusTree GetTree(string filepath, bool exists) + { + var keySerializer = new PrimitiveSerializer(); + var valueSerializer = new ContentNodeKitSerializer(); + var options = new BPlusTree.OptionsV2(keySerializer, valueSerializer) + { + CreateFile = exists ? CreatePolicy.IfNeeded : CreatePolicy.Always, + FileName = filepath, + + // other options? + }; + + var tree = new BPlusTree(options); + + // anything? + //btree. + + return tree; + } + + private class ContentNodeKitSerializer : ISerializer + { + static readonly ContentDataSerializer DataSerializer = new ContentDataSerializer(); + //static readonly ListOfIntSerializer ChildContentIdsSerializer = new ListOfIntSerializer(); + + public ContentNodeKit ReadFrom(Stream stream) + { + var kit = new ContentNodeKit + { + Node = new ContentNode( + PrimitiveSerializer.Int32.ReadFrom(stream), // id + PrimitiveSerializer.Guid.ReadFrom(stream), // uid + PrimitiveSerializer.Int32.ReadFrom(stream), // level + PrimitiveSerializer.String.ReadFrom(stream), // path + PrimitiveSerializer.Int32.ReadFrom(stream), // sort order + PrimitiveSerializer.Int32.ReadFrom(stream), // parent id + PrimitiveSerializer.DateTime.ReadFrom(stream), // date created + PrimitiveSerializer.Int32.ReadFrom(stream) // creator id + ), + ContentTypeId = PrimitiveSerializer.Int32.ReadFrom(stream) + }; + var hasDraft = PrimitiveSerializer.Boolean.ReadFrom(stream); + if (hasDraft) + kit.DraftData = DataSerializer.ReadFrom(stream); + var hasPublished = PrimitiveSerializer.Boolean.ReadFrom(stream); + if (hasPublished) + kit.PublishedData = DataSerializer.ReadFrom(stream); + return kit; + } + + public void WriteTo(ContentNodeKit value, Stream stream) + { + PrimitiveSerializer.Int32.WriteTo(value.Node.Id, stream); + PrimitiveSerializer.Guid.WriteTo(value.Node.Uid, stream); + PrimitiveSerializer.Int32.WriteTo(value.Node.Level, stream); + PrimitiveSerializer.String.WriteTo(value.Node.Path, stream); + PrimitiveSerializer.Int32.WriteTo(value.Node.SortOrder, stream); + PrimitiveSerializer.Int32.WriteTo(value.Node.ParentContentId, stream); + PrimitiveSerializer.DateTime.WriteTo(value.Node.CreateDate, stream); + PrimitiveSerializer.Int32.WriteTo(value.Node.CreatorId, stream); + PrimitiveSerializer.Int32.WriteTo(value.ContentTypeId, stream); + + PrimitiveSerializer.Boolean.WriteTo(value.DraftData != null, stream); + if (value.DraftData != null) + DataSerializer.WriteTo(value.DraftData, stream); + + PrimitiveSerializer.Boolean.WriteTo(value.PublishedData != null, stream); + if (value.PublishedData != null) + DataSerializer.WriteTo(value.PublishedData, stream); + } + } + + class ContentDataSerializer : ISerializer + { + private static readonly DictionaryOfPropertyDataSerializer PropertiesSerializer = new DictionaryOfPropertyDataSerializer(); + + public ContentData ReadFrom(Stream stream) + { + return new ContentData + { + Published = PrimitiveSerializer.Boolean.ReadFrom(stream), + Name = PrimitiveSerializer.String.ReadFrom(stream), + VersionId = PrimitiveSerializer.Int32.ReadFrom(stream), + VersionDate = PrimitiveSerializer.DateTime.ReadFrom(stream), + WriterId = PrimitiveSerializer.Int32.ReadFrom(stream), + TemplateId = PrimitiveSerializer.Int32.ReadFrom(stream), + Properties = PropertiesSerializer.ReadFrom(stream) + }; + } + + public void WriteTo(ContentData value, Stream stream) + { + PrimitiveSerializer.Boolean.WriteTo(value.Published, stream); + PrimitiveSerializer.String.WriteTo(value.Name, stream); + PrimitiveSerializer.Int32.WriteTo(value.VersionId, stream); + PrimitiveSerializer.DateTime.WriteTo(value.VersionDate, stream); + PrimitiveSerializer.Int32.WriteTo(value.WriterId, stream); + PrimitiveSerializer.Int32.WriteTo(value.TemplateId, stream); + PropertiesSerializer.WriteTo(value.Properties, stream); + } + } + + /* + class ListOfIntSerializer : ISerializer> + { + public List ReadFrom(Stream stream) + { + var list = new List(); + var count = PrimitiveSerializer.Int32.ReadFrom(stream); + for (var i = 0; i < count; i++) + list.Add(PrimitiveSerializer.Int32.ReadFrom(stream)); + return list; + } + + public void WriteTo(List value, Stream stream) + { + PrimitiveSerializer.Int32.WriteTo(value.Count, stream); + foreach (var item in value) + PrimitiveSerializer.Int32.WriteTo(item, stream); + } + } + */ + + private class DictionaryOfPropertyDataSerializer : ISerializer> + { + public IDictionary ReadFrom(Stream stream) + { + var dict = new Dictionary(); + + // read properties count + var pcount = PrimitiveSerializer.Int32.ReadFrom(stream); + + // read each property + for (var i = 0; i < pcount; i++) + { + // read property alias + var key = PrimitiveSerializer.String.ReadFrom(stream); + + // read values count + var vcount = PrimitiveSerializer.Int32.ReadFrom(stream); + + // create pdata and add to the dictionary + var pdatas = new List(); + + // for each value, read and add to pdata + for (var j = 0; j < vcount; j++) + { + var pdata = new PropertyData(); + pdatas.Add(pdata); + + var type = PrimitiveSerializer.Char.ReadFrom(stream); + pdata.LanguageId = (int?) ReadObject(type, stream); + type = PrimitiveSerializer.Char.ReadFrom(stream); + pdata.Segment = (string) ReadObject(type, stream); + type = PrimitiveSerializer.Char.ReadFrom(stream); + pdata.Value = ReadObject(type, stream); + } + + dict[key] = pdatas.ToArray(); + } + return dict; + } + + private object ReadObject(char type, Stream stream) + { + switch (type) + { + case 'N': + return null; + case 'S': + return PrimitiveSerializer.String.ReadFrom(stream); + case 'I': + return PrimitiveSerializer.Int32.ReadFrom(stream); + case 'L': + return PrimitiveSerializer.Int64.ReadFrom(stream); + case 'F': + return PrimitiveSerializer.Float.ReadFrom(stream); + case 'B': + return PrimitiveSerializer.Double.ReadFrom(stream); + case 'D': + return PrimitiveSerializer.DateTime.ReadFrom(stream); + default: + throw new NotSupportedException("Cannot deserialize '" + type + "' value."); + } + } + + public void WriteTo(IDictionary value, Stream stream) + { + // write properties count + PrimitiveSerializer.Int32.WriteTo(value.Count, stream); + + // write each property + foreach (var kvp in value) + { + // write alias + PrimitiveSerializer.String.WriteTo(kvp.Key, stream); + + // write values count + PrimitiveSerializer.Int32.WriteTo(kvp.Value.Length, stream); + + // write each value + foreach (var pdata in kvp.Value) + { + WriteObject(pdata.LanguageId, stream); + WriteObject(pdata.Segment, stream); + WriteObject(pdata.Value, stream); + } + } + } + + private void WriteObject(object value, Stream stream) + { + if (value == null) + { + PrimitiveSerializer.Char.WriteTo('N', stream); + } + else if (value is string stringValue) + { + PrimitiveSerializer.Char.WriteTo('S', stream); + PrimitiveSerializer.String.WriteTo(stringValue, stream); + } + else if (value is int intValue) + { + PrimitiveSerializer.Char.WriteTo('I', stream); + PrimitiveSerializer.Int32.WriteTo(intValue, stream); + } + else if (value is long longValue) + { + PrimitiveSerializer.Char.WriteTo('L', stream); + PrimitiveSerializer.Int64.WriteTo(longValue, stream); + } + else if (value is float floatValue) + { + PrimitiveSerializer.Char.WriteTo('F', stream); + PrimitiveSerializer.Float.WriteTo(floatValue, stream); + } + else if (value is double doubleValue) + { + PrimitiveSerializer.Char.WriteTo('B', stream); + PrimitiveSerializer.Double.WriteTo(doubleValue, stream); + } + else if (value is DateTime dateValue) + { + PrimitiveSerializer.Char.WriteTo('D', stream); + PrimitiveSerializer.DateTime.WriteTo(dateValue, stream); + } + else + throw new NotSupportedException("Value type " + value.GetType().FullName + " cannot be serialized."); + } + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentData.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentData.cs new file mode 100644 index 0000000000..2b8fd108b2 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentData.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Web.PublishedCache.NuCache.DataSource +{ + // represents everything that is specific to edited or published version + internal class ContentData + { + public bool Published { get; set; } + + public string Name { get; set; } + public int VersionId { get; set; } + public DateTime VersionDate { get; set; } + public int WriterId { get; set; } + public int TemplateId { get; set; } + + public IDictionary Properties { get; set; } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentSourceDto.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentSourceDto.cs new file mode 100644 index 0000000000..37fb9df8bb --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentSourceDto.cs @@ -0,0 +1,39 @@ +using System; + +namespace Umbraco.Web.PublishedCache.NuCache.DataSource +{ + // read-only dto + internal class ContentSourceDto + { + public int Id { get; set; } + public Guid Uid { get; set; } + public int ContentTypeId { get; set; } + + public int Level { get; set; } + public string Path { get; set; } + public int SortOrder { get; set; } + public int ParentId { get; set; } + + public bool Published { get; set; } + public bool Edited { get; set; } + + public DateTime CreateDate { get; set; } + public int CreatorId { get; set; } + + // edited data + public int VersionId { get; set; } + public string EditName { get; set; } + public DateTime EditVersionDate { get; set; } + public int EditWriterId { get; set; } + public int EditTemplateId { get; set; } + public string EditData { get; set; } + + // published data + public int PublishedVersionId { get; set; } + public string PubName { get; set; } + public DateTime PubVersionDate { get; set; } + public int PubWriterId { get; set; } + public int PubTemplateId { get; set; } + public string PubData { get; set; } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/Database.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/Database.cs new file mode 100644 index 0000000000..b4b93e799a --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/Database.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Newtonsoft.Json; +using NPoco; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Scoping; +using Umbraco.Core.Serialization; +using Umbraco.Web.Composing; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; + +namespace Umbraco.Web.PublishedCache.NuCache.DataSource +{ + // fixme - use SqlTemplate for these queries else it's going to be horribly slow! + + // provides efficient database access for NuCache + internal class Database + { + // we want arrays, we want them all loaded, not an enumerable + + private Sql ContentSourcesSelect(IScope scope, Func, Sql> joins = null) + { + var sql = scope.SqlContext.Sql() + + .Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"), + x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"), + x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId")) + .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId")) + .AndSelect(x => Alias(x.Published, "Published"), x => Alias(x.Edited, "Edited")) + + .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId")) + .AndSelect(x => Alias(x.TemplateId, "EditTemplateId")) + + .AndSelect("pcver", x => Alias(x.Id, "PublishedVersionId"), x => Alias(x.Text, "PubName"), x => Alias(x.VersionDate, "PubVersionDate"), x => Alias(x.UserId, "PubWriterId")) + .AndSelect("pdver", x => Alias(x.TemplateId, "PubTemplateId")) + + .AndSelect("nuEdit", x => Alias(x.Data, "EditData")) + .AndSelect("nuPub", x => Alias(x.Data, "PubData")) + + .From(); + + if (joins != null) + sql = joins(sql); + + sql = sql + .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + + .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) + .InnerJoin().On((left, right) => left.Id == right.Id) + + .LeftJoin(j => + j.InnerJoin("pdver").On((left, right) => left.Id == right.Id && right.Published, "pcver", "pdver"), "pcver") + .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcver") + + .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit") + .LeftJoin("nuPub").On((left, right) => left.NodeId == right.NodeId && right.Published, aliasRight: "nuPub"); + + return sql; + } + + public ContentNodeKit GetContentSource(IScope scope, int id) + { + var sql = ContentSourcesSelect(scope) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && x.NodeId == id) + .OrderBy(x => x.Level, x => x.SortOrder); + + var dto = scope.Database.Fetch(sql).FirstOrDefault(); + return dto == null ? new ContentNodeKit() : CreateContentNodeKit(dto); + } + + public IEnumerable GetAllContentSources(IScope scope) + { + var sql = ContentSourcesSelect(scope) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document) + .OrderBy(x => x.Level, x => x.SortOrder); + + return scope.Database.Query(sql).Select(CreateContentNodeKit); + } + + public IEnumerable GetBranchContentSources(IScope scope, int id) + { + var syntax = scope.SqlContext.SqlSyntax; + var sql = ContentSourcesSelect(scope, s => s + + .InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) + + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document) + .Where(x => x.NodeId == id, "x") + .OrderBy(x => x.Level, x => x.SortOrder); + + return scope.Database.Query(sql).Select(CreateContentNodeKit); + } + + public IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids) + { + var sql = ContentSourcesSelect(scope) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document) + .WhereIn(x => x.ContentTypeId, ids) + .OrderBy(x => x.Level, x => x.SortOrder); + + return scope.Database.Query(sql).Select(CreateContentNodeKit); + } + + private Sql MediaSourcesSelect(IScope scope, Func, Sql> joins = null) + { + var sql = scope.SqlContext.Sql() + + .Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"), + x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"), + x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId")) + .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId")) + + .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId")) + + .AndSelect("nuEdit", x => Alias(x.Data, "EditData")) + + .From(); + + if (joins != null) + sql = joins(sql); + + sql = sql + .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + + .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) + + .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit"); + + return sql; + } + + public ContentNodeKit GetMediaSource(IScope scope, int id) + { + var sql = MediaSourcesSelect(scope) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && x.NodeId == id) + .OrderBy(x => x.Level, x => x.SortOrder); + + var dto = scope.Database.Fetch(sql).FirstOrDefault(); + return dto == null ? new ContentNodeKit() : CreateContentNodeKit(dto); + } + + public IEnumerable GetAllMediaSources(IScope scope) + { + var sql = MediaSourcesSelect(scope) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media) + .OrderBy(x => x.Level, x => x.SortOrder); + + return scope.Database.Query(sql).Select(CreateMediaNodeKit); + } + + public IEnumerable GetBranchMediaSources(IScope scope, int id) + { + var syntax = scope.SqlContext.SqlSyntax; + var sql = MediaSourcesSelect(scope, s => s + + .InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) + + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media) + .Where(x => x.NodeId == id, "x") + .OrderBy(x => x.Level, x => x.SortOrder); + + return scope.Database.Query(sql).Select(CreateMediaNodeKit); + } + + public IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids) + { + var sql = MediaSourcesSelect(scope) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media) + .WhereIn(x => x.ContentTypeId, ids) + .OrderBy(x => x.Level, x => x.SortOrder); + + return scope.Database.Query(sql).Select(CreateMediaNodeKit); + } + + private static ContentNodeKit CreateContentNodeKit(ContentSourceDto dto) + { + ContentData d = null; + ContentData p = null; + + if (dto.EditData == null) + { + if (Debugger.IsAttached) + throw new Exception("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding."); + Current.Logger.Warn("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding."); + } + else + { + d = new ContentData + { + Name = dto.EditName, + Published = false, + TemplateId = dto.EditTemplateId, + VersionId = dto.VersionId, + VersionDate = dto.EditVersionDate, + WriterId = dto.EditWriterId, + Properties = DeserializeData(dto.EditData) + }; + } + + if (dto.Published) + { + if (dto.PubData == null) + { + if (Debugger.IsAttached) + throw new Exception("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding."); + Current.Logger.Warn("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding."); + } + else + { + p = new ContentData + { + Name = dto.PubName, + Published = true, + TemplateId = dto.PubTemplateId, + VersionId = dto.VersionId, + VersionDate = dto.PubVersionDate, + WriterId = dto.PubWriterId, + Properties = DeserializeData(dto.PubData) + }; + } + } + + var n = new ContentNode(dto.Id, dto.Uid, + dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId); + + var s = new ContentNodeKit + { + Node = n, + ContentTypeId = dto.ContentTypeId, + DraftData = d, + PublishedData = p + }; + + return s; + } + + private static ContentNodeKit CreateMediaNodeKit(ContentSourceDto dto) + { + if (dto.EditData == null) + throw new Exception("No data for media " + dto.Id); + + var p = new ContentData + { + Name = dto.EditName, + Published = true, + TemplateId = -1, + VersionId = dto.VersionId, + VersionDate = dto.EditVersionDate, + WriterId = dto.CreatorId, // what-else? + Properties = DeserializeData(dto.EditData) + }; + + var n = new ContentNode(dto.Id, dto.Uid, + dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId); + + var s = new ContentNodeKit + { + Node = n, + ContentTypeId = dto.ContentTypeId, + PublishedData = p + }; + + return s; + } + + private static Dictionary DeserializeData(string data) + { + // by default JsonConvert will deserialize our numeric values as Int64 + // which is bad, because they were Int32 in the database - take care + + var settings = new JsonSerializerSettings + { + Converters = new List { new ForceInt32Converter() } + }; + + return JsonConvert.DeserializeObject>(data, settings); + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs new file mode 100644 index 0000000000..ab9d3cdcac --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Umbraco.Web.PublishedCache.NuCache.DataSource +{ + internal class PropertyData + { + [JsonProperty("lang")] + public int? LanguageId { get; set; } + + [JsonProperty("seg")] + public string Segment { get; set; } + + [JsonProperty("val")] + public object Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DomainCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/DomainCache.cs new file mode 100644 index 0000000000..3b2165c00a --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/DomainCache.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + internal class DomainCache : IDomainCache + { + private readonly SnapDictionary.Snapshot _snapshot; + + public DomainCache(SnapDictionary.Snapshot snapshot) + { + _snapshot = snapshot; + } + + public IEnumerable GetAll(bool includeWildcards) + { + var list = _snapshot.GetAll(); + if (includeWildcards == false) list = list.Where(x => x.IsWildcard == false); + return list; + } + + public IEnumerable GetAssigned(int contentId, bool includeWildcards) + { + // probably this could be optimized with an index + // but then we'd need a custom DomainStore of some sort + + var list = _snapshot.GetAll(); + list = list.Where(x => x.ContentId == contentId); + if (includeWildcards == false) list = list.Where(x => x.IsWildcard == false); + return list; + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs new file mode 100644 index 0000000000..760a8e88db --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.XPath; +using Umbraco.Core.Cache; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Xml; +using Umbraco.Core.Xml.XPath; +using Umbraco.Web.PublishedCache.NuCache.Navigable; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + internal class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableData, IDisposable + { + private readonly ContentStore.Snapshot _snapshot; + private readonly ICacheProvider _snapshotCache; + private readonly ICacheProvider _elementsCache; + + #region Constructors + + public MediaCache(bool previewDefault, ContentStore.Snapshot snapshot, ICacheProvider snapshotCache, ICacheProvider elementsCache) + : base(previewDefault) + { + _snapshot = snapshot; + _snapshotCache = snapshotCache; + _elementsCache = elementsCache; + } + + #endregion + + #region Get, Has + + public override IPublishedContent GetById(bool preview, int contentId) + { + // ignore preview, there's only draft for media + var n = _snapshot.Get(contentId); + return n?.Draft; + } + + public override IPublishedContent GetById(bool preview, Guid contentId) + { + // ignore preview, there's only draft for media + var n = _snapshot.Get(contentId); + return n?.Draft; + } + + public override bool HasById(bool preview, int contentId) + { + var n = _snapshot.Get(contentId); + return n != null; + } + + public override IEnumerable GetAtRoot(bool preview) + { + if (PublishedSnapshotService.CacheContentCacheRoots == false) + return GetAtRootNoCache(); + + var cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing + ? _elementsCache + : _snapshotCache; + + if (cache == null) + return GetAtRootNoCache(); + + // note: ToArray is important here, we want to cache the result, not the function! + return (IEnumerable)cache.GetCacheItem( + CacheKeys.MediaCacheRoots(false), // ignore preview, only 1 key! + () => GetAtRootNoCache().ToArray()); + } + + private IEnumerable GetAtRootNoCache() + { + var c = _snapshot.GetAtRoot(); + + // ignore preview, there's only draft for media + return c.Select(n => n.Draft); + } + + public override bool HasContent(bool preview) + { + return _snapshot.IsEmpty == false; + } + + #endregion + + #region XPath + + public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) + { + var navigator = CreateNavigator(preview); + var iterator = navigator.Select(xpath, vars); + return GetSingleByXPath(iterator); + } + + public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) + { + var navigator = CreateNavigator(preview); + var iterator = navigator.Select(xpath, vars); + return GetSingleByXPath(iterator); + } + + private static IPublishedContent GetSingleByXPath(XPathNodeIterator iterator) + { + if (iterator.MoveNext() == false) return null; + + var xnav = iterator.Current as NavigableNavigator; + if (xnav == null) return null; + + var xcontent = xnav.UnderlyingObject as NavigableContent; + return xcontent == null ? null : xcontent.InnerContent; + } + + public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) + { + var navigator = CreateNavigator(preview); + var iterator = navigator.Select(xpath, vars); + return GetByXPath(iterator); + } + + public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) + { + var navigator = CreateNavigator(preview); + var iterator = navigator.Select(xpath, vars); + return GetByXPath(iterator); + } + + private static IEnumerable GetByXPath(XPathNodeIterator iterator) + { + while (iterator.MoveNext()) + { + var xnav = iterator.Current as NavigableNavigator; + if (xnav == null) continue; + + var xcontent = xnav.UnderlyingObject as NavigableContent; + if (xcontent == null) continue; + + yield return xcontent.InnerContent; + } + } + + public override XPathNavigator CreateNavigator(bool preview) + { + var source = new Source(this, preview); + var navigator = new NavigableNavigator(source); + return navigator; + } + + public override XPathNavigator CreateNodeNavigator(int id, bool preview) + { + var source = new Source(this, preview); + var navigator = new NavigableNavigator(source); + return navigator.CloneWithNewRoot(id, 0); + } + + #endregion + + #region Content types + + public override PublishedContentType GetContentType(int id) + { + return _snapshot.GetContentType(id); + } + + public override PublishedContentType GetContentType(string alias) + { + return _snapshot.GetContentType(alias); + } + + public override IEnumerable GetByContentType(PublishedContentType contentType) + { + throw new NotImplementedException(); + } + + #endregion + + #region IDisposable + + public void Dispose() + { + _snapshot.Dispose(); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs new file mode 100644 index 0000000000..a8cf4a97c0 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.XPath; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Core.Xml.XPath; +using Umbraco.Web.PublishedCache.NuCache.Navigable; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + class MemberCache : IPublishedMemberCache, INavigableData + { + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly ICacheProvider _snapshotCache; + private readonly IMemberService _memberService; + private readonly IDataTypeService _dataTypeService; + private readonly ILocalizationService _localizationService; + private readonly PublishedContentTypeCache _contentTypeCache; + private readonly bool _previewDefault; + + public MemberCache(bool previewDefault, ICacheProvider snapshotCache, IMemberService memberService, IDataTypeService dataTypeService, ILocalizationService localizationService, PublishedContentTypeCache contentTypeCache, IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + _snapshotCache = snapshotCache; + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _memberService = memberService; + _dataTypeService = dataTypeService; + _localizationService = localizationService; + _previewDefault = previewDefault; + _contentTypeCache = contentTypeCache; + } + + private T GetCacheItem(string cacheKey, Func getCacheItem) + where T : class + { + var cache = _snapshotCache; + return cache == null + ? getCacheItem() + : cache.GetCacheItem(cacheKey, getCacheItem); + } + + private static void EnsureProvider() + { + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + if (provider.IsUmbracoMembershipProvider() == false) + throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); + } + + public IPublishedContent GetById(bool preview, int memberId) + { + return GetById(memberId); + } + + public IPublishedContent /*IPublishedMember*/ GetById(int memberId) + { + return GetCacheItem(CacheKeys.MemberCacheMember("ById", _previewDefault, memberId), () => + { + EnsureProvider(); + var member = _memberService.GetById(memberId); + return member == null + ? null + : PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor); + }); + } + + private IPublishedContent /*IPublishedMember*/ GetById(IMember member, bool previewing) + { + return GetCacheItem(CacheKeys.MemberCacheMember("ById", _previewDefault, member.Id), () => + PublishedMember.Create(member, GetContentType(member.ContentTypeId), previewing, _publishedSnapshotAccessor)); + } + + public IPublishedContent /*IPublishedMember*/ GetByProviderKey(object key) + { + return GetCacheItem(CacheKeys.MemberCacheMember("ByProviderKey", _previewDefault, key), () => + { + EnsureProvider(); + var member = _memberService.GetByProviderKey(key); + return member == null ? null : GetById(member, _previewDefault); + }); + } + + public IPublishedContent /*IPublishedMember*/ GetByUsername(string username) + { + return GetCacheItem(CacheKeys.MemberCacheMember("ByUsername", _previewDefault, username), () => + { + EnsureProvider(); + var member = _memberService.GetByUsername(username); + return member == null ? null : GetById(member, _previewDefault); + }); + } + + public IPublishedContent /*IPublishedMember*/ GetByEmail(string email) + { + return GetCacheItem(CacheKeys.MemberCacheMember("ByEmail", _previewDefault, email), () => + { + EnsureProvider(); + var member = _memberService.GetByEmail(email); + return member == null ? null : GetById(member, _previewDefault); + }); + } + + public IPublishedContent /*IPublishedMember*/ GetByMember(IMember member) + { + return PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor); + } + + public IEnumerable GetAtRoot(bool preview) + { + // because members are flat (not a tree) everything is at root + // because we're loading everything... let's just not cache? + var members = _memberService.GetAllMembers(); + return members.Select(m => PublishedMember.Create(m, GetContentType(m.ContentTypeId), preview, _publishedSnapshotAccessor)); + } + + public XPathNavigator CreateNavigator() + { + var source = new Source(this, false); + var navigator = new NavigableNavigator(source); + return navigator; + } + + public XPathNavigator CreateNavigator(bool preview) + { + return CreateNavigator(); + } + + public XPathNavigator CreateNodeNavigator(int id, bool preview) + { + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + if (provider.IsUmbracoMembershipProvider() == false) + { + throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); + } + + var result = _memberService.GetById(id); + if (result == null) return null; + + var s = EntityXmlSerializer.Serialize(_dataTypeService, _localizationService, result); + var n = s.GetXmlNode(); + return n.CreateNavigator(); + } + + #region Content types + + public PublishedContentType GetContentType(int id) + { + return _contentTypeCache.Get(PublishedItemType.Member, id); + } + + public PublishedContentType GetContentType(string alias) + { + return _contentTypeCache.Get(PublishedItemType.Member, alias); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/INavigableData.cs b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/INavigableData.cs new file mode 100644 index 0000000000..6651f9dcee --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/INavigableData.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.PublishedCache.NuCache.Navigable +{ + internal interface INavigableData + { + IPublishedContent GetById(bool preview, int contentId); + IEnumerable GetAtRoot(bool preview); + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs new file mode 100644 index 0000000000..2357d17273 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Xml.XPath; + +namespace Umbraco.Web.PublishedCache.NuCache.Navigable +{ + internal class NavigableContent : INavigableContent + { + private readonly PublishedContent _content; + private readonly string[] _builtInValues; + + public NavigableContent(IPublishedContent content) + { + InnerContent = content; + _content = PublishedContent.UnwrapIPublishedContent(InnerContent); + + var i = 0; + _builtInValues = new [] + { + XmlString(i++, _content.Name), + XmlString(i++, _content.ParentId), + XmlString(i++, _content.CreateDate), + XmlString(i++, _content.UpdateDate), + XmlString(i++, true), // isDoc + XmlString(i++, _content.SortOrder), + XmlString(i++, _content.Level), + XmlString(i++, _content.TemplateId), + XmlString(i++, _content.WriterId), + XmlString(i++, _content.CreatorId), + XmlString(i++, _content.UrlName), + XmlString(i, _content.IsDraft) + }; + } + + private string XmlString(int index, object value) + { + var field = Type.FieldTypes[index]; + return field.XmlStringConverter == null ? value.ToString() : field.XmlStringConverter(value); + } + + #region INavigableContent + + public IPublishedContent InnerContent { get; } + + public int Id => _content.Id; + + public int ParentId => _content.ParentId; + + public INavigableContentType Type => NavigableContentType.GetContentType(_content.ContentType); + + // returns all child ids, will be filtered by the source + public IList ChildIds => _content.ChildIds; + + public object Value(int index) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (index < NavigableContentType.BuiltinProperties.Length) + { + // built-in field, ie attribute + //return XmlString(index, _builtInValues1[index]); + return _builtInValues[index]; + } + + index -= NavigableContentType.BuiltinProperties.Length; + var properties = _content.PropertiesArray; + if (index >= properties.Length) + throw new ArgumentOutOfRangeException(nameof(index)); + + // custom property, ie element + return properties[index].GetXPathValue(); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContentType.cs b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContentType.cs new file mode 100644 index 0000000000..068a1866ee --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContentType.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Xml; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Xml.XPath; + +namespace Umbraco.Web.PublishedCache.NuCache.Navigable +{ + internal class NavigableContentType : INavigableContentType + { + public static readonly INavigableFieldType[] BuiltinProperties; + private readonly object _locko = new object(); + + // called by the conditional weak table -- must be public +// ReSharper disable EmptyConstructor + public NavigableContentType() +// ReSharper restore EmptyConstructor + { } + + // note - PublishedContentType are immutable ie they do not _change_ when the actual IContentTypeComposition + // changes, but they are replaced by a new instance, so our map here will clean itself automatically and + // we don't have to manage cache - ConditionalWeakTable does not prevent keys from beeing GCed + + private static readonly ConditionalWeakTable TypesMap + = new ConditionalWeakTable(); + + public static NavigableContentType GetContentType(PublishedContentType contentType) + { + return TypesMap.GetOrCreateValue(contentType).EnsureInitialized(contentType); + } + + static NavigableContentType() + { + BuiltinProperties = new INavigableFieldType[] + { + new NavigablePropertyType("nodeName"), + new NavigablePropertyType("parentId"), + new NavigablePropertyType("createDate", v => XmlConvert.ToString((DateTime)v, "yyyy-MM-ddTHH:mm:ss")), + new NavigablePropertyType("updateDate", v => XmlConvert.ToString((DateTime)v, "yyyy-MM-ddTHH:mm:ss")), + new NavigablePropertyType("isDoc", v => XmlConvert.ToString((bool)v)), + new NavigablePropertyType("sortOrder"), + new NavigablePropertyType("level"), + new NavigablePropertyType("templateId"), + new NavigablePropertyType("writerId"), + new NavigablePropertyType("creatorId"), + new NavigablePropertyType("urlName"), + new NavigablePropertyType("isDraft", v => XmlConvert.ToString((bool)v)) + }; + } + + private NavigableContentType EnsureInitialized(PublishedContentType contentType) + { + lock (_locko) + { + if (Name == null) + { + Name = contentType.Alias; + FieldTypes = BuiltinProperties + .Union(contentType.PropertyTypes.Select(propertyType => new NavigablePropertyType(propertyType.Alias))) + .ToArray(); + } + } + return this; + } + + public string Name { get; private set; } + public INavigableFieldType[] FieldTypes { get; private set; } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigablePropertyType.cs b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigablePropertyType.cs new file mode 100644 index 0000000000..082ebd1fde --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigablePropertyType.cs @@ -0,0 +1,17 @@ +using System; +using Umbraco.Core.Xml.XPath; + +namespace Umbraco.Web.PublishedCache.NuCache.Navigable +{ + internal class NavigablePropertyType : INavigableFieldType + { + public NavigablePropertyType(string name, Func xmlStringConverter = null) + { + Name = name; + XmlStringConverter = xmlStringConverter; + } + + public string Name { get; } + public Func XmlStringConverter { get; } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/RootContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/RootContent.cs new file mode 100644 index 0000000000..1e6da0a23f --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/RootContent.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Xml.XPath; + +namespace Umbraco.Web.PublishedCache.NuCache.Navigable +{ + internal class RootContent : INavigableContent + { + private static readonly RootContentType ContentType = new RootContentType(); + private readonly int[] _childIds; + + public RootContent(IEnumerable childIds) + { + _childIds = childIds.ToArray(); + } + + public int Id => -1; + + public int ParentId => -1; + + public INavigableContentType Type => ContentType; + + public IList ChildIds => _childIds; + + public object Value(int index) + { + // only id has a value + return index == 0 ? "-1" : null; + } + + private class RootContentType : INavigableContentType + { + public string Name => "root"; + + public INavigableFieldType[] FieldTypes => NavigableContentType.BuiltinProperties; + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/Source.cs b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/Source.cs new file mode 100644 index 0000000000..436a89c803 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/Source.cs @@ -0,0 +1,33 @@ +using System.Linq; +using Umbraco.Core.Xml.XPath; + +namespace Umbraco.Web.PublishedCache.NuCache.Navigable +{ + internal class Source : INavigableSource + { + private readonly INavigableData _data; + private readonly bool _preview; + private readonly RootContent _root; + + public Source(INavigableData data, bool preview) + { + _data = data; + _preview = preview; + + var contentAtRoot = data.GetAtRoot(preview); + _root = new RootContent(contentAtRoot.Select(x => x.Id)); + } + + public INavigableContent Get(int id) + { + // wrap in a navigable content + + var content = _data.GetById(_preview, id); + return content == null ? null : new NavigableContent(content); + } + + public int LastAttributeIndex => NavigableContentType.BuiltinProperties.Length - 1; + + public INavigableContent Root => _root; + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs new file mode 100644 index 0000000000..3482a6cf2c --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs @@ -0,0 +1,33 @@ +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using LightInject; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence.Repositories; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + public class NuCacheComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public override void Compose(Composition composition) + { + base.Compose(composition); + + // register the NuCache published snapshot service + // must register default options, required in the service ctor + composition.Container.Register(factory => new PublishedSnapshotService.Options()); + composition.SetPublishedSnapshotService(); + + // add the NuCache health check (hidden from type finder) + // todo - no NuCache health check yet + //composition.HealthChecks().Add(); + } + + public void Initialize(IPublishedSnapshotService service) + { + // nothing - this just ensures that the service is created at boot time + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs new file mode 100644 index 0000000000..9a74b66897 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Serialization; +using Umbraco.Core.Cache; +using Umbraco.Core.Collections; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache.NuCache.DataSource; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + [Serializable] + [XmlType(Namespace = "http://umbraco.org/webservices/")] + internal class Property : PublishedPropertyBase + { + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly Guid _contentUid; + private readonly bool _isPreviewing; + private readonly bool _isMember; + private readonly IPublishedContent _content; + + private readonly object _locko = new object(); + + // the invariant-neutral source and inter values + private readonly object _sourceValue; + private bool _interInitialized; + private object _interValue; + + // the variant source and inter values + private Dictionary _sourceValues; + + // the variant and non-variant object values + private CacheValues _cacheValues; + + private string _valuesCacheKey; + private string _recurseCacheKey; + + // initializes a published content property with no value + public Property(PublishedPropertyType propertyType, PublishedContent content, IPublishedSnapshotAccessor publishedSnapshotAccessor, PropertyCacheLevel referenceCacheLevel = PropertyCacheLevel.Element) + : this(propertyType, content, null, publishedSnapshotAccessor, referenceCacheLevel) + { } + + // initializes a published content property with a value + public Property(PublishedPropertyType propertyType, PublishedContent content, PropertyData[] sourceValues, IPublishedSnapshotAccessor publishedSnapshotAccessor, PropertyCacheLevel referenceCacheLevel = PropertyCacheLevel.Element) + : base(propertyType, referenceCacheLevel) + { + if (sourceValues != null) + { + foreach (var sourceValue in sourceValues) + { + if (sourceValue.LanguageId == null && sourceValue.Segment == null) + { + _sourceValue = sourceValue.Value; + } + else + { + if (_sourceValues == null) + _sourceValues = new Dictionary(); + _sourceValues[new CompositeIntStringKey(sourceValue.LanguageId, sourceValue.Segment)] + = new SourceInterValue { LanguageId = sourceValue.LanguageId, Segment = sourceValue.Segment, SourceValue = sourceValue.Value }; + } + } + } + + _contentUid = content.Key; + _content = content; + _isPreviewing = content.IsPreviewing; + _isMember = content.ContentType.ItemType == PublishedItemType.Member; + _publishedSnapshotAccessor = publishedSnapshotAccessor; + } + + // clone for previewing as draft a published content that is published and has no draft + public Property(Property origin, IPublishedContent content) + : base(origin.PropertyType, origin.ReferenceCacheLevel) + { + _sourceValue = origin._sourceValue; + _sourceValues = origin._sourceValues; + + _contentUid = origin._contentUid; + _content = content; + _isPreviewing = true; + _isMember = origin._isMember; + _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; + } + + public override bool HasValue(int? languageId = null, string segment = null) => _sourceValue != null + && (!(_sourceValue is string) || string.IsNullOrWhiteSpace((string) _sourceValue) == false); + + // used to cache the recursive *property* for this property + internal string RecurseCacheKey => _recurseCacheKey + ?? (_recurseCacheKey = CacheKeys.PropertyRecurse(_contentUid, Alias, _isPreviewing)); + + // used to cache the CacheValues of this property + internal string ValuesCacheKey => _valuesCacheKey + ?? (_valuesCacheKey = CacheKeys.PropertyCacheValues(_contentUid, Alias, _isPreviewing)); + + private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel) + { + CacheValues cacheValues; + PublishedShapshot publishedSnapshot; + ICacheProvider cache; + switch (cacheLevel) + { + case PropertyCacheLevel.None: + // never cache anything + cacheValues = new CacheValues(); + break; + case PropertyCacheLevel.Element: + // cache within the property object itself, ie within the content object + cacheValues = _cacheValues ?? (_cacheValues = new CacheValues()); + break; + case PropertyCacheLevel.Elements: + // cache within the elements cache, unless previewing, then use the snapshot or + // elements cache (if we don't want to pollute the elements cache with short-lived + // data) depending on settings + // for members, always cache in the snapshot cache - never pollute elements cache + publishedSnapshot = (PublishedShapshot) _publishedSnapshotAccessor.PublishedSnapshot; + cache = publishedSnapshot == null + ? null + : ((_isPreviewing == false || PublishedSnapshotService.FullCacheWhenPreviewing) && (_isMember == false) + ? publishedSnapshot.ElementsCache + : publishedSnapshot.SnapshotCache); + cacheValues = GetCacheValues(cache); + break; + case PropertyCacheLevel.Snapshot: + // cache within the snapshot cache + publishedSnapshot = (PublishedShapshot) _publishedSnapshotAccessor.PublishedSnapshot; + cache = publishedSnapshot?.SnapshotCache; + cacheValues = GetCacheValues(cache); + break; + default: + throw new InvalidOperationException("Invalid cache level."); + } + return cacheValues; + } + + private CacheValues GetCacheValues(ICacheProvider cache) + { + if (cache == null) // no cache, don't cache + return new CacheValues(); + return (CacheValues) cache.GetCacheItem(ValuesCacheKey, () => new CacheValues()); + } + + // this is always invoked from within a lock, so does not require its own lock + private object GetInterValue(int? languageId, string segment) + { + if (languageId == null && segment == null) + { + if (_interInitialized) return _interValue; + _interValue = PropertyType.ConvertSourceToInter(_content, _sourceValue, _isPreviewing); + _interInitialized = true; + return _interValue; + } + + if (_sourceValues == null) + _sourceValues = new Dictionary(); + + var k = new CompositeIntStringKey(languageId, segment); + if (!_sourceValues.TryGetValue(k, out var vvalue)) + _sourceValues[k] = vvalue = new SourceInterValue { LanguageId = languageId, Segment = segment }; + + if (vvalue.InterInitialized) return vvalue.InterValue; + vvalue.InterValue = PropertyType.ConvertSourceToInter(_content, vvalue.SourceValue, _isPreviewing); + vvalue.InterInitialized = true; + return vvalue.InterValue; + } + + public override object GetSourceValue(int? languageId = null, string segment = null) + { + if (languageId == null && segment == null) + return _sourceValue; + + lock (_locko) + { + if (_sourceValues == null) return null; + return _sourceValues.TryGetValue(new CompositeIntStringKey(languageId, segment), out var sourceValue) ? sourceValue.SourceValue : null; + } + } + + public override object GetValue(int? languageId = null, string segment = null) + { + lock (_locko) + { + var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(languageId, segment); + + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + + if (cacheValues.ObjectInitialized) return cacheValues.ObjectValue; + cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(languageId, segment), _isPreviewing); + cacheValues.ObjectInitialized = true; + return cacheValues.ObjectValue; + } + } + + public override object GetXPathValue(int? languageId = null, string segment = null) + { + lock (_locko) + { + var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(languageId, segment); + + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + + if (cacheValues.XPathInitialized) return cacheValues.XPathValue; + cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(languageId, segment), _isPreviewing); + cacheValues.XPathInitialized = true; + return cacheValues.XPathValue; + } + } + + #region Classes + + private class CacheValue + { + public bool ObjectInitialized { get; set; } + public object ObjectValue { get; set; } + public bool XPathInitialized { get; set; } + public object XPathValue { get; set; } + } + + private class CacheValues : CacheValue + { + private Dictionary _values; + + // this is always invoked from within a lock, so does not require its own lock + public CacheValue For(int? languageId, string segment) + { + if (languageId == null && segment == null) + return this; + + if (_values == null) + _values = new Dictionary(); + + var k = new CompositeIntStringKey(languageId, segment); + if (!_values.TryGetValue(k, out var value)) + _values[k] = value = new CacheValue(); + + return value; + } + } + + private class SourceInterValue + { + private string _segment; + + public int? LanguageId { get; set; } + public string Segment + { + get => _segment; + internal set => _segment = value?.ToLowerInvariant(); + } + public object SourceValue { get; set; } + public bool InterInitialized { get; set; } + public object InterValue { get; set; } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs new file mode 100644 index 0000000000..558580d7c0 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -0,0 +1,331 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; +using Umbraco.Web.Models; +using Umbraco.Web.PublishedCache.NuCache.DataSource; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + internal class PublishedContent : PublishedContentBase + { + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly ContentNode _contentNode; + // ReSharper disable once InconsistentNaming + internal readonly ContentData _contentData; // internal for ContentNode cloning + + private readonly string _urlName; + + #region Constructors + + public PublishedContent(ContentNode contentNode, ContentData contentData, IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + _contentNode = contentNode; + _contentData = contentData; + _publishedSnapshotAccessor = publishedSnapshotAccessor; + + _urlName = _contentData.Name.ToUrlSegment(); + IsPreviewing = _contentData.Published == false; + + var properties = new List(); + foreach (var propertyType in _contentNode.ContentType.PropertyTypes) + { + if (contentData.Properties.TryGetValue(propertyType.Alias, out var pdatas)) + properties.Add(new Property(propertyType, this, pdatas, _publishedSnapshotAccessor)); + } + PropertiesArray = properties.ToArray(); + } + + private string GetProfileNameById(int id) + { + var cache = GetCurrentSnapshotCache(); + return cache == null + ? GetProfileNameByIdNoCache(id) + : (string) cache.GetCacheItem(CacheKeys.ProfileName(id), () => GetProfileNameByIdNoCache(id)); + } + + private static string GetProfileNameByIdNoCache(int id) + { +#if DEBUG + var userService = Current.Services?.UserService; + if (userService == null) return "[null]"; // for tests +#else + // we don't want each published content to hold a reference to the service + // so where should they get the service from really? from the locator... + var userService = Current.Services.UserService; +#endif + var user = userService.GetProfileById(id); + return user?.Name; + } + + // (see ContentNode.CloneParent) + public PublishedContent(ContentNode contentNode, PublishedContent origin) + { + _contentNode = contentNode; + _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; + _contentData = origin._contentData; + + _urlName = origin._urlName; + IsPreviewing = origin.IsPreviewing; + + // here is the main benefit: we do not re-create properties so if anything + // is cached locally, we share the cache - which is fine - if anything depends + // on the tree structure, it should not be cached locally to begin with + PropertiesArray = origin.PropertiesArray; + } + + // clone for previewing as draft a published content that is published and has no draft + private PublishedContent(PublishedContent origin) + { + _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; + _contentNode = origin._contentNode; + _contentData = origin._contentData; + + _urlName = origin._urlName; + IsPreviewing = true; + + // clone properties so _isPreviewing is true + PropertiesArray = origin.PropertiesArray.Select(x => (IPublishedProperty) new Property((Property) x, this)).ToArray(); + } + + #endregion + + #region Get Content/Media for Parent/Children + + // this is for tests purposes + // args are: current published snapshot (may be null), previewing, content id - returns: content + + internal static Func GetContentByIdFunc { get; set; } + = (publishedShapshot, previewing, id) => publishedShapshot.Content.GetById(previewing, id); + + internal static Func GetMediaByIdFunc { get; set; } + = (publishedShapshot, previewing, id) => publishedShapshot.Media.GetById(previewing, id); + + private IPublishedContent GetContentById(bool previewing, int id) + { + return GetContentByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id); + } + + private IEnumerable GetContentByIds(bool previewing, IEnumerable ids) + { + var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; + + // beware! the loop below CANNOT be converted to query such as: + //return ids.Select(x => _getContentByIdFunc(publishedSnapshot, previewing, x)).Where(x => x != null); + // because it would capture the published snapshot and cause all sorts of issues + // + // we WANT to get the actual current published snapshot each time we run + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var id in ids) + { + var content = GetContentByIdFunc(publishedSnapshot, previewing, id); + if (content != null) yield return content; + } + } + + private IPublishedContent GetMediaById(bool previewing, int id) + { + return GetMediaByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id); + } + + private IEnumerable GetMediaByIds(bool previewing, IEnumerable ids) + { + var publishedShapshot = _publishedSnapshotAccessor.PublishedSnapshot; + + // see note above for content + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var id in ids) + { + var content = GetMediaByIdFunc(publishedShapshot, previewing, id); + if (content != null) yield return content; + } + } + + #endregion + + #region IPublishedContent + + public override int Id => _contentNode.Id; + public override Guid Key => _contentNode.Uid; + public override int DocumentTypeId => _contentNode.ContentType.Id; + public override string DocumentTypeAlias => _contentNode.ContentType.Alias; + public override PublishedItemType ItemType => _contentNode.ContentType.ItemType; + + public override string Name => _contentData.Name; + public override int Level => _contentNode.Level; + public override string Path => _contentNode.Path; + public override int SortOrder => _contentNode.SortOrder; + public override int TemplateId => _contentData.TemplateId; + + public override string UrlName => _urlName; + + public override DateTime CreateDate => _contentNode.CreateDate; + public override DateTime UpdateDate => _contentData.VersionDate; + + public override int CreatorId => _contentNode.CreatorId; + public override string CreatorName => GetProfileNameById(_contentNode.CreatorId); + public override int WriterId => _contentData.WriterId; + public override string WriterName => GetProfileNameById(_contentData.WriterId); + + public override bool IsDraft => _contentData.Published == false; + + public override IPublishedContent Parent + { + get + { + // have to use the "current" cache because a PublishedContent can be shared + // amongst many snapshots and other content depend on the snapshots + switch (_contentNode.ContentType.ItemType) + { + case PublishedItemType.Content: + return GetContentById(IsPreviewing, _contentNode.ParentContentId); + case PublishedItemType.Media: + return GetMediaById(IsPreviewing, _contentNode.ParentContentId); + default: + throw new Exception($"Panic: unsupported item type \"{_contentNode.ContentType.ItemType}\"."); + } + } + } + + private string _childrenCacheKey; + + private string ChildrenCacheKey => _childrenCacheKey ?? (_childrenCacheKey = CacheKeys.PublishedContentChildren(Key, IsPreviewing)); + + public override IEnumerable Children + { + get + { + var cache = GetAppropriateCache(); + if (cache == null || PublishedSnapshotService.CachePublishedContentChildren == false) + return GetChildren(); + + // note: ToArray is important here, we want to cache the result, not the function! + return (IEnumerable) cache.GetCacheItem(ChildrenCacheKey, () => GetChildren().ToArray()); + } + } + + private IEnumerable GetChildren() + { + IEnumerable c; + switch (_contentNode.ContentType.ItemType) + { + case PublishedItemType.Content: + c = GetContentByIds(IsPreviewing, _contentNode.ChildContentIds); + break; + case PublishedItemType.Media: + c = GetMediaByIds(IsPreviewing, _contentNode.ChildContentIds); + break; + default: + throw new Exception("oops"); + } + + return c.OrderBy(x => x.SortOrder); + + // notes: + // _contentNode.ChildContentIds is an unordered int[] + // need needs to fetch & sort - do it only once, lazyily, though + // Q: perfs-wise, is it better than having the store managed an ordered list + } + + public override IEnumerable Properties => PropertiesArray; + + public override IPublishedProperty GetProperty(string alias) + { + var index = _contentNode.ContentType.GetPropertyIndex(alias); + var property = index < 0 ? null : PropertiesArray[index]; + return property; + } + + public override IPublishedProperty GetProperty(string alias, bool recurse) + { + var property = GetProperty(alias); + if (recurse == false) return property; + + var cache = GetAppropriateCache(); + if (cache == null) + return base.GetProperty(alias, true); + + var key = ((Property) property).RecurseCacheKey; + return (Property) cache.GetCacheItem(key, () => base.GetProperty(alias, true)); + } + + public override PublishedContentType ContentType => _contentNode.ContentType; + + #endregion + + #region Caching + + // beware what you use that one for - you don't want to cache its result + private ICacheProvider GetAppropriateCache() + { + var publishedSnapshot = (PublishedShapshot) _publishedSnapshotAccessor.PublishedSnapshot; + var cache = publishedSnapshot == null + ? null + : ((IsPreviewing == false || PublishedSnapshotService.FullCacheWhenPreviewing) && (ItemType != PublishedItemType.Member) + ? publishedSnapshot.ElementsCache + : publishedSnapshot.SnapshotCache); + return cache; + } + + private ICacheProvider GetCurrentSnapshotCache() + { + var publishedSnapshot = (PublishedShapshot) _publishedSnapshotAccessor.PublishedSnapshot; + return publishedSnapshot?.SnapshotCache; + } + + #endregion + + #region Internal + + // used by navigable content + internal IPublishedProperty[] PropertiesArray { get; } + + // used by navigable content + internal int ParentId => _contentNode.ParentContentId; + + // used by navigable content + // includes all children, published or unpublished + // NavigableNavigator takes care of selecting those it wants + internal IList ChildIds => _contentNode.ChildContentIds; + + // used by Property + // gets a value indicating whether the content or media exists in + // a previewing context or not, ie whether its Parent, Children, and + // properties should refer to published, or draft content + internal bool IsPreviewing { get; } + + private string _asPreviewingCacheKey; + + private string AsPreviewingCacheKey => _asPreviewingCacheKey ?? (_asPreviewingCacheKey = CacheKeys.PublishedContentAsPreviewing(Key)); + + // used by ContentCache + internal IPublishedContent AsPreviewingModel() + { + if (IsPreviewing) + return this; + + var cache = GetAppropriateCache(); + if (cache == null) return new PublishedContent(this).CreateModel(); + return (IPublishedContent) cache.GetCacheItem(AsPreviewingCacheKey, () => new PublishedContent(this).CreateModel()); + } + + // used by Navigable.Source,... + internal static PublishedContent UnwrapIPublishedContent(IPublishedContent content) + { + PublishedContentWrapped wrapped; + while ((wrapped = content as PublishedContentWrapped) != null) + content = wrapped.Unwrap(); + var inner = content as PublishedContent; + if (inner == null) + throw new InvalidOperationException("Innermost content is not PublishedContent."); + return inner; + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs new file mode 100644 index 0000000000..8b3d01f6e9 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.PublishedCache.NuCache.DataSource; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + // note + // the whole PublishedMember thing should be refactored because as soon as a member + // is wrapped on in a model, the inner IMember and all associated properties are lost + + internal class PublishedMember : PublishedContent //, IPublishedMember + { + private readonly IMember _member; + + private PublishedMember(IMember member, ContentNode contentNode, ContentData contentData, IPublishedSnapshotAccessor publishedSnapshotAccessor) + : base(contentNode, contentData, publishedSnapshotAccessor) + { + _member = member; + } + + public static IPublishedContent Create(IMember member, PublishedContentType contentType, bool previewing, IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + var d = new ContentData + { + Name = member.Name, + Published = previewing, + TemplateId = -1, + VersionDate = member.UpdateDate, + WriterId = member.CreatorId, // what else? + Properties = GetPropertyValues(contentType, member) + }; + var n = new ContentNode(member.Id, member.Key, + contentType, + member.Level, member.Path, member.SortOrder, + member.ParentId, + member.CreateDate, member.CreatorId); + return new PublishedMember(member, n, d, publishedSnapshotAccessor).CreateModel(); + } + + private static Dictionary GetPropertyValues(PublishedContentType contentType, IMember member) + { + // see node in PublishedSnapshotService + // we do not (want to) support ConvertDbToXml/String + + //var propertyEditorResolver = PropertyEditorResolver.Current; + + // see note in MemberType.Variations + // we don't want to support variations on members + + var properties = member + .Properties + //.Select(property => + //{ + // var e = propertyEditorResolver.GetByAlias(property.PropertyType.PropertyEditorAlias); + // var v = e == null + // ? property.Value + // : e.ValueEditor.ConvertDbToString(property, property.PropertyType, ApplicationContext.Current.Services.DataTypeService); + // return new KeyValuePair(property.Alias, v); + //}) + //.ToDictionary(x => x.Key, x => x.Value); + .ToDictionary(x => x.Alias, x => new[] { new PropertyData { Value = x.GetValue() } }, StringComparer.OrdinalIgnoreCase); + + // see also PublishedContentType + AddIf(contentType, properties, "Email", member.Email); + AddIf(contentType, properties, "Username", member.Username); + AddIf(contentType, properties, "PasswordQuestion", member.PasswordQuestion); + AddIf(contentType, properties, "Comments", member.Comments); + AddIf(contentType, properties, "IsApproved", member.IsApproved); + AddIf(contentType, properties, "IsLockedOut", member.IsLockedOut); + AddIf(contentType, properties, "LastLockoutDate", member.LastLockoutDate); + AddIf(contentType, properties, "CreateDate", member.CreateDate); + AddIf(contentType, properties, "LastLoginDate", member.LastLoginDate); + AddIf(contentType, properties, "LastPasswordChangeDate", member.LastPasswordChangeDate); + + return properties; + } + + private static void AddIf(PublishedContentType contentType, IDictionary properties, string alias, object value) + { + var propertyType = contentType.GetPropertyType(alias); + if (propertyType == null || propertyType.IsUserProperty) return; + properties[alias] = new[] { new PropertyData { Value = value } }; + } + + #region IPublishedMember + + public IMember Member => _member; + + public string Email => _member.Email; + + public string UserName => _member.Username; + + public string PasswordQuestion => _member.PasswordQuestion; + + public string Comments => _member.Comments; + + public bool IsApproved => _member.IsApproved; + + public bool IsLockedOut => _member.IsLockedOut; + + public DateTime LastLockoutDate => _member.LastLockoutDate; + + public DateTime CreationDate => _member.CreateDate; + + public DateTime LastLoginDate => _member.LastLoginDate; + + public DateTime LastActivityDate => _member.LastLoginDate; + + public DateTime LastPasswordChangedDate => _member.LastPasswordChangeDate; + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedShapshot.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedShapshot.cs new file mode 100644 index 0000000000..b21834f7b4 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedShapshot.cs @@ -0,0 +1,112 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Cache; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + // implements published snapshot + internal class PublishedShapshot : IPublishedShapshot, IDisposable + { + private readonly PublishedSnapshotService _service; + private bool _defaultPreview; + private PublishedSnapshotElements _elements; + + #region Constructors + + public PublishedShapshot(PublishedSnapshotService service, bool defaultPreview) + { + _service = service; + _defaultPreview = defaultPreview; + SnapshotCache = new ObjectCacheRuntimeCacheProvider(); + } + + public class PublishedSnapshotElements : IDisposable + { + public ContentCache ContentCache; + public MediaCache MediaCache; + public MemberCache MemberCache; + public DomainCache DomainCache; + public ICacheProvider SnapshotCache; + public ICacheProvider ElementsCache; + + public void Dispose() + { + ContentCache.Dispose(); + MediaCache.Dispose(); + } + } + + private PublishedSnapshotElements Elements => _elements ?? (_elements = _service.GetElements(_defaultPreview)); + + public void Resync() + { + // no lock - published snapshots are single-thread + _elements?.Dispose(); + _elements = null; + } + + #endregion + + #region Caches + + public ICacheProvider SnapshotCache { get; } + + public ICacheProvider ElementsCache => Elements.ElementsCache; + + #endregion + + #region IPublishedSnapshot + + public IPublishedContentCache Content => Elements.ContentCache; + + public IPublishedMediaCache Media => Elements.MediaCache; + + public IPublishedMemberCache Members => Elements.MemberCache; + + public IDomainCache Domains => Elements.DomainCache; + + public IDisposable ForcedPreview(bool preview, Action callback = null) + { + return new ForcedPreviewObject(this, preview, callback); + } + + private class ForcedPreviewObject : DisposableObject + { + private readonly PublishedShapshot _publishedShapshot; + private readonly bool _origPreview; + private readonly Action _callback; + + public ForcedPreviewObject(PublishedShapshot publishedShapshot, bool preview, Action callback) + { + _publishedShapshot = publishedShapshot; + _callback = callback; + + // save and force + _origPreview = publishedShapshot._defaultPreview; + publishedShapshot._defaultPreview = preview; + } + + protected override void DisposeResources() + { + // restore + _publishedShapshot._defaultPreview = _origPreview; + _callback?.Invoke(_origPreview); + } + } + + #endregion + + #region IDisposable + + private bool _disposed; + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + _elements?.Dispose(); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs new file mode 100644 index 0000000000..992ac62733 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -0,0 +1,1541 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Web.Hosting; +using CSharpTest.Net.Collections; +using Newtonsoft.Json; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; +using Umbraco.Core.Services.Implement; +using Umbraco.Web.Cache; +using Umbraco.Web.Install; +using Umbraco.Web.PublishedCache.NuCache.DataSource; +using Umbraco.Web.PublishedCache.XmlPublishedCache; +using Umbraco.Web.Routing; +using Database = Umbraco.Web.PublishedCache.NuCache.DataSource.Database; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + class PublishedSnapshotService : PublishedSnapshotServiceBase + { + private readonly ServiceContext _serviceContext; + private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; + private readonly IScopeProvider _scopeProvider; + private readonly Database _dataSource; + private readonly ILogger _logger; + private readonly IDocumentRepository _documentRepository; + private readonly IMediaRepository _mediaRepository; + private readonly IMemberRepository _memberRepository; + private readonly IGlobalSettings _globalSettings; + + // volatile because we read it with no lock + private volatile bool _isReady; + + private readonly ContentStore _contentStore; + private readonly ContentStore _mediaStore; + private readonly SnapDictionary _domainStore; + private readonly object _storesLock = new object(); + + private BPlusTree _localContentDb; + private BPlusTree _localMediaDb; + private readonly bool _localDbExists; + + // define constant - determines whether to use cache when previewing + // to store eg routes, property converted values, anything - caching + // means faster execution, but uses memory - not sure if we want it + // so making it configureable. + public static readonly bool FullCacheWhenPreviewing = true; + + // define constant - determines whether to cache the published content + // objects (in the elements cache, or snapshot cache, depending on preview) + // or to refetch them all the time. caching is faster but uses more + // memory. not sure what we want. + public static readonly bool CachePublishedContentChildren = true; + + // define constant - determines whether to cache the content cache root + // objects (in the elements cache, or snapshot cache, depending on preview) + // or to refecth them all the time. caching is faster but uses more + // memory - not sure what we want. + public static readonly bool CacheContentCacheRoots = true; + + #region Constructors + + //private static int _singletonCheck; + + public PublishedSnapshotService(Options options, MainDom mainDom, IRuntimeState runtime, + ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IdkMap idkMap, + IPublishedSnapshotAccessor publishedSnapshotAccessor, ILogger logger, IScopeProvider scopeProvider, + IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IGlobalSettings globalSettings) + : base(publishedSnapshotAccessor) + { + //if (Interlocked.Increment(ref _singletonCheck) > 1) + // throw new Exception("Singleton must be instancianted only once!"); + + _serviceContext = serviceContext; + _publishedContentTypeFactory = publishedContentTypeFactory; + _dataSource = new Database(); + _logger = logger; + _scopeProvider = scopeProvider; + _documentRepository = documentRepository; + _mediaRepository = mediaRepository; + _memberRepository = memberRepository; + _globalSettings = globalSettings; + + // we always want to handle repository events, configured or not + // assuming no repository event will trigger before the whole db is ready + // (ideally we'd have Upgrading.App vs Upgrading.Data application states...) + InitializeRepositoryEvents(); + + // however, the cache is NOT available until we are configured, because loading + // content (and content types) from database cannot be consistent (see notes in "Handle + // Notifications" region), so + // - notifications will be ignored + // - trying to obtain a published snapshot from the service will throw + if (runtime.Level != RuntimeLevel.Run) + return; + + if (options.IgnoreLocalDb == false) + { + var registered = mainDom.Register( + null, + () => + { + lock (_storesLock) + { + _contentStore.ReleaseLocalDb(); + _localContentDb = null; + _mediaStore.ReleaseLocalDb(); + _localMediaDb = null; + } + }); + + if (registered) + { + var localContentDbPath = HostingEnvironment.MapPath("~/App_Data/NuCache.Content.db"); + var localMediaDbPath = HostingEnvironment.MapPath("~/App_Data/NuCache.Media.db"); + _localDbExists = System.IO.File.Exists(localContentDbPath) && System.IO.File.Exists(localMediaDbPath); + + // if both local dbs exist then GetTree will open them, else new dbs will be created + _localContentDb = BTree.GetTree(localContentDbPath, _localDbExists); + _localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists); + } + + // stores are created with a db so they can write to it, but they do not read from it, + // stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to + // figure out whether it can read the dbs or it should populate them from sql + _contentStore = new ContentStore(publishedSnapshotAccessor, logger, _localContentDb); + _mediaStore = new ContentStore(publishedSnapshotAccessor, logger, _localMediaDb); + } + else + { + _contentStore = new ContentStore(publishedSnapshotAccessor, logger); + _mediaStore = new ContentStore(publishedSnapshotAccessor, logger); + } + + _domainStore = new SnapDictionary(); + + LoadCaches(); + + if (idkMap != null) + { + idkMap.SetMapper(UmbracoObjectTypes.Document, id => _contentStore.LiveSnapshot.Get(id).Uid, uid => _contentStore.LiveSnapshot.Get(uid).Id); + idkMap.SetMapper(UmbracoObjectTypes.Media, id => _mediaStore.LiveSnapshot.Get(id).Uid, uid => _mediaStore.LiveSnapshot.Get(uid).Id); + } + } + + private void LoadCaches() + { + lock (_storesLock) + { + // populate the stores + + try + { + if (_localDbExists) // fixme? + { + LockAndLoadContent(LoadContentFromLocalDbLocked); + LockAndLoadMedia(LoadMediaFromLocalDbLocked); + } + else + { + LockAndLoadContent(LoadContentFromDatabaseLocked); + LockAndLoadMedia(LoadMediaFromDatabaseLocked); + } + + LockAndLoadDomains(); + } + catch (Exception e) + { + _logger.Error("Panic, exception while loading cache data.", e); + } + + // finaly, cache is ready! + _isReady = true; + } + } + + private void InitializeRepositoryEvents() + { + // plug repository event handlers + // these trigger within the transaction to ensure consistency + // and are used to maintain the central, database-level XML cache + DocumentRepository.ScopeEntityRemove += OnContentRemovingEntity; + //ContentRepository.RemovedVersion += OnContentRemovedVersion; + DocumentRepository.ScopedEntityRefresh += OnContentRefreshedEntity; + MediaRepository.ScopeEntityRemove += OnMediaRemovingEntity; + //MediaRepository.RemovedVersion += OnMediaRemovedVersion; + MediaRepository.ScopedEntityRefresh += OnMediaRefreshedEntity; + MemberRepository.ScopeEntityRemove += OnMemberRemovingEntity; + //MemberRepository.RemovedVersion += OnMemberRemovedVersion; + MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity; + + // plug + ContentTypeService.UowRefreshedEntity += OnContentTypeRefreshedEntity; + MediaTypeService.UowRefreshedEntity+= OnMediaTypeRefreshedEntity; + MemberTypeService.UowRefreshedEntity += OnMemberTypeRefreshedEntity; + } + + private void TearDownRepositoryEvents() + { + DocumentRepository.ScopeEntityRemove -= OnContentRemovingEntity; + //ContentRepository.RemovedVersion -= OnContentRemovedVersion; + DocumentRepository.ScopedEntityRefresh -= OnContentRefreshedEntity; + MediaRepository.ScopeEntityRemove -= OnMediaRemovingEntity; + //MediaRepository.RemovedVersion -= OnMediaRemovedVersion; + MediaRepository.ScopedEntityRefresh -= OnMediaRefreshedEntity; + MemberRepository.ScopeEntityRemove -= OnMemberRemovingEntity; + //MemberRepository.RemovedVersion -= OnMemberRemovedVersion; + MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity; + + ContentTypeService.UowRefreshedEntity -= OnContentTypeRefreshedEntity; + MediaTypeService.UowRefreshedEntity -= OnMediaTypeRefreshedEntity; + MemberTypeService.UowRefreshedEntity -= OnMemberTypeRefreshedEntity; + } + + public override void Dispose() + { + TearDownRepositoryEvents(); + base.Dispose(); + } + + public class Options + { + // disabled: prevents the published snapshot from updating and exposing changes + // or even creating a new published snapshot to see changes, uses old cache = bad + // + //// indicates that the snapshot cache should reuse the application request cache + //// otherwise a new cache object would be created for the snapshot specifically, + //// which is the default - web boot manager uses this to optimze facades + //public bool PublishedSnapshotCacheIsApplicationRequestCache; + + public bool IgnoreLocalDb; + } + + #endregion + + #region Environment + + public override bool EnsureEnvironment(out IEnumerable errors) + { + // must have app_data and be able to write files into it + var ok = FilePermissionHelper.TryCreateDirectory(SystemDirectories.Data); + errors = ok ? Enumerable.Empty() : new[] { "NuCache local DB files." }; + return ok; + } + + #endregion + + #region Populate Stores + + // sudden panic... but in RepeatableRead can a content that I haven't already read, be removed + // before I read it? NO! because the WHOLE content tree is read-locked using WithReadLocked. + // don't panic. + + private void LockAndLoadContent(Action action) + { + using (_contentStore.GetWriter(_scopeProvider)) + { + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTree); + action(scope); + scope.Complete(); + } + } + } + + private void LoadContentFromDatabaseLocked(IScope scope) + { + // locks: + // contentStore is wlocked (1 thread) + // content (and types) are read-locked + + var contentTypes = _serviceContext.ContentTypeService.GetAll() + .Select(x => _publishedContentTypeFactory.CreateContentType(x)); + _contentStore.UpdateContentTypes(null, contentTypes, null); + + _localContentDb?.Clear(); + + _logger.Debug("Loading content from database..."); + var sw = Stopwatch.StartNew(); + var kits = _dataSource.GetAllContentSources(scope); + _contentStore.SetAll(kits); + sw.Stop(); + _logger.Debug("Loaded content from database (" + sw.ElapsedMilliseconds + "ms)."); + } + + private void LoadContentFromLocalDbLocked(IScope scope) + { + var contentTypes = _serviceContext.ContentTypeService.GetAll() + .Select(x => _publishedContentTypeFactory.CreateContentType(x)); + _contentStore.UpdateContentTypes(null, contentTypes, null); + + _logger.Debug("Loading content from local db..."); + var sw = Stopwatch.StartNew(); + var kits = _localContentDb.Select(x => x.Value).OrderBy(x => x.Node.Level); + _contentStore.SetAll(kits); + sw.Stop(); + _logger.Debug("Loaded content from local db (" + sw.ElapsedMilliseconds + "ms)."); + } + + // keep these around - might be useful + + //private void LoadContentBranch(IContent content) + //{ + // LoadContent(content); + + // foreach (var child in content.Children()) + // LoadContentBranch(child); + //} + + //private void LoadContent(IContent content) + //{ + // var contentService = _serviceContext.ContentService as ContentService; + // if (contentService == null) throw new Exception("oops"); + // var newest = content; + // var published = newest.Published + // ? newest + // : (newest.HasPublishedVersion ? contentService.GetByVersion(newest.PublishedVersionGuid) : null); + + // var contentNode = CreateContentNode(newest, published); + // _contentStore.Set(contentNode); + //} + + private void LockAndLoadMedia(Action action) + { + using (_mediaStore.GetWriter(_scopeProvider)) + { + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.MediaTree); + action(scope); + scope.Complete(); + } + } + } + + private void LoadMediaFromDatabaseLocked(IScope scope) + { + // locks & notes: see content + + var mediaTypes = _serviceContext.MediaTypeService.GetAll() + .Select(x => _publishedContentTypeFactory.CreateContentType(x)); + _mediaStore.UpdateContentTypes(null, mediaTypes, null); + + _localMediaDb?.Clear(); + + _logger.Debug("Loading media from database..."); + var sw = Stopwatch.StartNew(); + var kits = _dataSource.GetAllMediaSources(scope); + _mediaStore.SetAll(kits); + sw.Stop(); + _logger.Debug("Loaded media from database (" + sw.ElapsedMilliseconds + "ms)."); + } + + private void LoadMediaFromLocalDbLocked(IScope scope) + { + var mediaTypes = _serviceContext.MediaTypeService.GetAll() + .Select(x => _publishedContentTypeFactory.CreateContentType(x)); + _mediaStore.UpdateContentTypes(null, mediaTypes, null); + + _logger.Debug("Loading media from local db..."); + var sw = Stopwatch.StartNew(); + var kits = _localMediaDb.Select(x => x.Value); + _mediaStore.SetAll(kits); + sw.Stop(); + _logger.Debug("Loaded media from local db (" + sw.ElapsedMilliseconds + "ms)."); + } + + // keep these around - might be useful + + //private void LoadMediaBranch(IMedia media) + //{ + // LoadMedia(media); + + // foreach (var child in media.Children()) + // LoadMediaBranch(child); + //} + + //private void LoadMedia(IMedia media) + //{ + // var mediaType = _contentTypeCache.Get(PublishedItemType.Media, media.ContentTypeId); + + // var mediaData = new ContentData + // { + // Name = media.Name, + // Published = true, + // Version = media.Version, + // VersionDate = media.UpdateDate, + // WriterId = media.CreatorId, // what else? + // TemplateId = -1, // have none + // Properties = GetPropertyValues(media) + // }; + + // var mediaNode = new ContentNode(media.Id, mediaType, + // media.Level, media.Path, media.SortOrder, + // media.ParentId, media.CreateDate, media.CreatorId, + // null, mediaData); + + // _mediaStore.Set(mediaNode); + //} + + //private Dictionary GetPropertyValues(IContentBase content) + //{ + // var propertyEditorResolver = PropertyEditorResolver.Current; // should inject + + // return content + // .Properties + // .Select(property => + // { + // var e = propertyEditorResolver.GetByAlias(property.PropertyType.PropertyEditorAlias); + // var v = e == null + // ? property.Value + // : e.ValueEditor.ConvertDbToString(property, property.PropertyType, _serviceContext.DataTypeService); + // return new KeyValuePair(property.Alias, v); + // }) + // .ToDictionary(x => x.Key, x => x.Value); + //} + + //private ContentData CreateContentData(IContent content) + //{ + // return new ContentData + // { + // Name = content.Name, + // Published = content.Published, + // Version = content.Version, + // VersionDate = content.UpdateDate, + // WriterId = content.WriterId, + // TemplateId = content.Template == null ? -1 : content.Template.Id, + // Properties = GetPropertyValues(content) + // }; + //} + + //private ContentNode CreateContentNode(IContent newest, IContent published) + //{ + // var contentType = _contentTypeCache.Get(PublishedItemType.Content, newest.ContentTypeId); + + // var draftData = newest.Published + // ? null + // : CreateContentData(newest); + + // var publishedData = newest.Published + // ? CreateContentData(newest) + // : (published == null ? null : CreateContentData(published)); + + // var contentNode = new ContentNode(newest.Id, contentType, + // newest.Level, newest.Path, newest.SortOrder, + // newest.ParentId, newest.CreateDate, newest.CreatorId, + // draftData, publishedData); + + // return contentNode; + //} + + private void LockAndLoadDomains() + { + using (_domainStore.GetWriter(_scopeProvider)) + { + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.Domains); + LoadDomainsLocked(); + scope.Complete(); + } + } + } + + private void LoadDomainsLocked() + { + var domains = _serviceContext.DomainService.GetAll(true); + foreach (var domain in domains + .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) + .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard))) + { + _domainStore.Set(domain.Id, domain); + } + } + + #endregion + + #region Handle Notifications + + // note: if the service is not ready, ie _isReady is false, then notifications are ignored + + // SetUmbracoVersionStep issues a DistributedCache.Instance.RefreshAll...() call which should cause + // the entire content, media etc caches to reload from database -- and then the app restarts -- however, + // at the time SetUmbracoVersionStep runs, Umbraco is not fully initialized and therefore some property + // value converters, etc are not registered, and rebuilding the NuCache may not work properly. + // + // More details: ApplicationContext.IsConfigured being false, ApplicationEventHandler.ExecuteWhen... is + // called and in most cases events are skipped, so property value converters are not registered or + // removed, so PublishedPropertyType either initializes with the wrong converter, or throws because it + // detects more than one converter for a property type. + // + // It's not an issue for XmlStore - the app restart takes place *after* the install has refreshed the + // cache, and XmlStore just writes a new umbraco.config file upon RefreshAll, so that's OK. + // + // But for NuCache... we cannot rebuild the cache now. So it will NOT work and we are not fixing it, + // because now we should ALWAYS run with the database server messenger, and then the RefreshAll will + // be processed as soon as we are configured and the messenger processes instructions. + + public override void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) + { + // no cache, nothing we can do + if (_isReady == false) + { + draftChanged = publishedChanged = false; + return; + } + + using (_contentStore.GetWriter(_scopeProvider)) + { + NotifyLocked(payloads, out bool draftChanged2, out bool publishedChanged2); + draftChanged = draftChanged2; + publishedChanged = publishedChanged2; + } + + if (draftChanged || publishedChanged) + ((PublishedShapshot)CurrentPublishedShapshot).Resync(); + } + + private void NotifyLocked(IEnumerable payloads, out bool draftChanged, out bool publishedChanged) + { + publishedChanged = false; + draftChanged = false; + + if (!(_serviceContext.ContentService is ContentService)) + throw new Exception("oops"); + + // locks: + // content (and content types) are read-locked while reading content + // contentStore is wlocked (so readable, only no new views) + // and it can be wlocked by 1 thread only at a time + // contentStore is write-locked during changes + + foreach (var payload in payloads) + { + _logger.Debug($"Notified {payload.ChangeTypes} for content {payload.Id}"); + + if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) + { + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTree); + LoadContentFromDatabaseLocked(scope); + scope.Complete(); + } + draftChanged = publishedChanged = true; + continue; + } + + if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) + { + if (_contentStore.Clear(payload.Id)) + draftChanged = publishedChanged = true; + continue; + } + + if (payload.ChangeTypes.HasTypesNone(TreeChangeTypes.RefreshNode | TreeChangeTypes.RefreshBranch)) + { + // ?! + continue; + } + + // fixme - should we do some RV check here? (later) + + var capture = payload; + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTree); + + if (capture.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) + { + // ?? should we do some RV check here? + var kits = _dataSource.GetBranchContentSources(scope, capture.Id); + _contentStore.SetBranch(capture.Id, kits); + } + else + { + // ?? should we do some RV check here? + var kit = _dataSource.GetContentSource(scope, capture.Id); + if (kit.IsEmpty) + { + _contentStore.Clear(capture.Id); + } + else + { + _contentStore.Set(kit); + } + } + + scope.Complete(); + } + + // ?? cannot tell really because we're not doing RV checks + draftChanged = publishedChanged = true; + } + } + + public override void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) + { + // no cache, nothing we can do + if (_isReady == false) + { + anythingChanged = false; + return; + } + + using (_mediaStore.GetWriter(_scopeProvider)) + { + NotifyLocked(payloads, out bool anythingChanged2); + anythingChanged = anythingChanged2; + } + + if (anythingChanged) + ((PublishedShapshot)CurrentPublishedShapshot).Resync(); + } + + private void NotifyLocked(IEnumerable payloads, out bool anythingChanged) + { + anythingChanged = false; + + if (!(_serviceContext.MediaService is MediaService)) + throw new Exception("oops"); + + // locks: + // see notes for content cache refresher + + foreach (var payload in payloads) + { + _logger.Debug($"Notified {payload.ChangeTypes} for media {payload.Id}"); + + if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) + { + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.MediaTree); + LoadMediaFromDatabaseLocked(scope); + scope.Complete(); + } + anythingChanged = true; + continue; + } + + if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) + { + if (_mediaStore.Clear(payload.Id)) + anythingChanged = true; + continue; + } + + if (payload.ChangeTypes.HasTypesNone(TreeChangeTypes.RefreshNode | TreeChangeTypes.RefreshBranch)) + { + // ?! + continue; + } + + // fixme - should we do some RV checks here? (later) + + var capture = payload; + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.MediaTree); + + if (capture.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) + { + // ?? should we do some RV check here? + var kits = _dataSource.GetBranchMediaSources(scope, capture.Id); + _mediaStore.SetBranch(capture.Id, kits); + } + else + { + // ?? should we do some RV check here? + var kit = _dataSource.GetMediaSource(scope, capture.Id); + if (kit.IsEmpty) + { + _mediaStore.Clear(capture.Id); + } + else + { + _mediaStore.Set(kit); + } + } + + scope.Complete(); + } + + // ?? cannot tell really because we're not doing RV checks + anythingChanged = true; + } + } + + public override void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) + { + // no cache, nothing we can do + if (_isReady == false) + return; + + foreach (var payload in payloads) + _logger.Debug($"Notified {payload.ChangeTypes} for {payload.ItemType} {payload.Id}"); + + Notify(_contentStore, payloads, RefreshContentTypesLocked); + Notify(_mediaStore, payloads, RefreshMediaTypesLocked); + + ((PublishedShapshot)CurrentPublishedShapshot).Resync(); + } + + private void Notify(ContentStore store, ContentTypeCacheRefresher.JsonPayload[] payloads, Action, IEnumerable, IEnumerable, IEnumerable> action) + { + var nameOfT = typeof (T).Name; + + var removedIds = new List(); + var refreshedIds = new List(); + var otherIds = new List(); + var newIds = new List(); + + foreach (var payload in payloads) + { + if (payload.ItemType != nameOfT) continue; + + if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Remove)) + removedIds.Add(payload.Id); + else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshMain)) + refreshedIds.Add(payload.Id); + else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshOther)) + otherIds.Add(payload.Id); + else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Create)) + newIds.Add(payload.Id); + } + + if (removedIds.Count == 0 && refreshedIds.Count == 0 && otherIds.Count == 0 && newIds.Count == 0) return; + + using (store.GetWriter(_scopeProvider)) + { + // ReSharper disable AccessToModifiedClosure + action(removedIds, refreshedIds, otherIds, newIds); + // ReSharper restore AccessToModifiedClosure + } + } + + public override void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) + { + // no cache, nothing we can do + if (_isReady == false) + return; + + var idsA = payloads.Select(x => x.Id).ToArray(); + + foreach (var payload in payloads) + _logger.Debug($"Notified {(payload.Removed ? "Removed" : "Refreshed")} for data type {payload.Id}"); + + using (_contentStore.GetWriter(_scopeProvider)) + using (_mediaStore.GetWriter(_scopeProvider)) + { + // fixme - need to add a datatype lock + // this is triggering datatypes reload in the factory, and right after we create some + // content types by loading them ... there's a race condition here, which would require + // some locking on datatypes + _publishedContentTypeFactory.NotifyDataTypeChanges(idsA); + + if (!(_serviceContext.ContentService is ContentService)) + throw new Exception("oops"); + + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTree); + _contentStore.UpdateDataTypes(idsA, id => CreateContentType(PublishedItemType.Content, id)); + scope.Complete(); + } + + if (!(_serviceContext.MediaService is MediaService)) + throw new Exception("oops"); + + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.MediaTree); + _mediaStore.UpdateDataTypes(idsA, id => CreateContentType(PublishedItemType.Media, id)); + scope.Complete(); + } + } + + ((PublishedShapshot) CurrentPublishedShapshot).Resync(); + } + + public override void Notify(DomainCacheRefresher.JsonPayload[] payloads) + { + // no cache, nothing we can do + if (_isReady == false) + return; + + using (_domainStore.GetWriter(_scopeProvider)) + { + foreach (var payload in payloads) + { + switch (payload.ChangeType) + { + case DomainChangeTypes.RefreshAll: + if (!(_serviceContext.DomainService is DomainService)) + throw new Exception("oops"); + + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.Domains); + LoadDomainsLocked(); + scope.Complete(); + } + break; + case DomainChangeTypes.Remove: + _domainStore.Clear(payload.Id); + break; + case DomainChangeTypes.Refresh: + var domain = _serviceContext.DomainService.GetById(payload.Id); + if (domain == null) continue; + if (domain.RootContentId.HasValue == false) continue; // anomaly + if (domain.LanguageIsoCode.IsNullOrWhiteSpace()) continue; // anomaly + var culture = CultureInfo.GetCultureInfo(domain.LanguageIsoCode); + _domainStore.Set(domain.Id, new Domain(domain.Id, domain.DomainName, domain.RootContentId.Value, culture, domain.IsWildcard)); + break; + } + } + } + } + + #endregion + + #region Content Types + + private IEnumerable CreateContentTypes(PublishedItemType itemType, int[] ids) + { + // XxxTypeService.GetAll(empty) returns everything! + if (ids.Length == 0) + return Enumerable.Empty(); + + IEnumerable contentTypes; + switch (itemType) + { + case PublishedItemType.Content: + contentTypes = _serviceContext.ContentTypeService.GetAll(ids); + break; + case PublishedItemType.Media: + contentTypes = _serviceContext.MediaTypeService.GetAll(ids); + break; + case PublishedItemType.Member: + contentTypes = _serviceContext.MemberTypeService.GetAll(ids); + break; + default: + throw new ArgumentOutOfRangeException(nameof(itemType)); + } + + // some may be missing - not checking here + + return contentTypes.Select(x => _publishedContentTypeFactory.CreateContentType(x)); + } + + private PublishedContentType CreateContentType(PublishedItemType itemType, int id) + { + IContentTypeComposition contentType; + switch (itemType) + { + case PublishedItemType.Content: + contentType = _serviceContext.ContentTypeService.Get(id); + break; + case PublishedItemType.Media: + contentType = _serviceContext.MediaTypeService.Get(id); + break; + case PublishedItemType.Member: + contentType = _serviceContext.MemberTypeService.Get(id); + break; + default: + throw new ArgumentOutOfRangeException(nameof(itemType)); + } + + return contentType == null ? null : _publishedContentTypeFactory.CreateContentType(contentType); + } + + private void RefreshContentTypesLocked(IEnumerable removedIds, IEnumerable refreshedIds, IEnumerable otherIds, IEnumerable newIds) + { + // locks: + // content (and content types) are read-locked while reading content + // contentStore is wlocked (so readable, only no new views) + // and it can be wlocked by 1 thread only at a time + + if (!(_serviceContext.ContentService is ContentService)) + throw new Exception("oops"); + + var refreshedIdsA = refreshedIds.ToArray(); + + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTypes); + var typesA = CreateContentTypes(PublishedItemType.Content, refreshedIdsA).ToArray(); + var kits = _dataSource.GetTypeContentSources(scope, refreshedIdsA); + _contentStore.UpdateContentTypes(removedIds, typesA, kits); + _contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray()).ToArray()); + _contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray()).ToArray()); + scope.Complete(); + } + } + + private void RefreshMediaTypesLocked(IEnumerable removedIds, IEnumerable refreshedIds, IEnumerable otherIds, IEnumerable newIds) + { + // locks: + // media (and content types) are read-locked while reading media + // mediaStore is wlocked (so readable, only no new views) + // and it can be wlocked by 1 thread only at a time + + if (!(_serviceContext.MediaService is MediaService)) + throw new Exception("oops"); + + var refreshedIdsA = refreshedIds.ToArray(); + + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.MediaTypes); + var typesA = CreateContentTypes(PublishedItemType.Media, refreshedIdsA).ToArray(); + var kits = _dataSource.GetTypeMediaSources(scope, refreshedIdsA); + _mediaStore.UpdateContentTypes(removedIds, typesA, kits); + _mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); + _mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray()); + scope.Complete(); + } + } + + #endregion + + #region Create, Get Published Snapshot + + private long _contentGen, _mediaGen, _domainGen; + private ICacheProvider _elementsCache; + + public override IPublishedShapshot CreatePublishedSnapshot(string previewToken) + { + // no cache, no joy + if (_isReady == false) + throw new InvalidOperationException("The published snapshot service has not properly initialized."); + + var preview = previewToken.IsNullOrWhiteSpace() == false; + return new PublishedShapshot(this, preview); + } + + public PublishedShapshot.PublishedSnapshotElements GetElements(bool previewDefault) + { + // note: using ObjectCacheRuntimeCacheProvider for elements and snapshot caches + // is not recommended because it creates an inner MemoryCache which is a heavy + // thing - better use a StaticCacheProvider which "just" creates a concurrent + // dictionary + + // for snapshot cache, StaticCacheProvider MAY be OK but it is not thread-safe, + // nothing like that... + // for elements cache, StaticCacheProvider is a No-No, use something better. + + ContentStore.Snapshot contentSnap, mediaSnap; + SnapDictionary.Snapshot domainSnap; + ICacheProvider elementsCache; + lock (_storesLock) + { + var scopeContext = _scopeProvider.Context; + + if (scopeContext == null) + { + contentSnap = _contentStore.CreateSnapshot(); + mediaSnap = _mediaStore.CreateSnapshot(); + domainSnap = _domainStore.CreateSnapshot(); + elementsCache = _elementsCache; + } + else + { + contentSnap = _contentStore.LiveSnapshot; + mediaSnap = _mediaStore.LiveSnapshot; + domainSnap = _domainStore.Test.LiveSnapshot; + elementsCache = _elementsCache; + + // this is tricky + // we are returning elements composed from live snapshots, which we need to replace + // with actual snapshots when the context is gone - but when the action runs, there + // still is a context - so we cannot get elements - just resync = nulls the current + // elements + // just need to make sure nothing gets elements in another enlisted action... so using + // a MaxValue to make sure this one runs last, and it should be ok + scopeContext.Enlist("Umbraco.Web.PublishedCache.NuCache.PublishedSnapshotService.Resync", () => this, (completed, svc) => + { + ((PublishedShapshot) svc.CurrentPublishedShapshot).Resync(); + }, int.MaxValue); + } + + // create a new snapshot cache if snapshots are different gens + if (contentSnap.Gen != _contentGen || mediaSnap.Gen != _mediaGen || domainSnap.Gen != _domainGen || _elementsCache == null) + { + _contentGen = contentSnap.Gen; + _mediaGen = mediaSnap.Gen; + _domainGen = domainSnap.Gen; + elementsCache = _elementsCache = new DictionaryCacheProvider(); + } + } + + var snapshotCache = new StaticCacheProvider(); + + var memberTypeCache = new PublishedContentTypeCache(null, null, _serviceContext.MemberTypeService, _publishedContentTypeFactory, _logger); + + var domainCache = new DomainCache(domainSnap); + + return new PublishedShapshot.PublishedSnapshotElements + { + ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, new DomainHelper(domainCache), _globalSettings), + MediaCache = new MediaCache(previewDefault, mediaSnap, snapshotCache, elementsCache), + MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, _serviceContext.DataTypeService, _serviceContext.LocalizationService, memberTypeCache, PublishedSnapshotAccessor), + DomainCache = domainCache, + SnapshotCache = snapshotCache, + ElementsCache = elementsCache + }; + } + + #endregion + + #region Preview + + public override string EnterPreview(IUser user, int contentId) + { + return "preview"; // anything + } + + public override void RefreshPreview(string previewToken, int contentId) + { + // nothing + } + + public override void ExitPreview(string previewToken) + { + // nothing + } + + #endregion + + #region Handle Repository Events For Database PreCache + + // note: if the service is not ready, ie _isReady is false, then we still handle repository events, + // because we can, we do not need a working published snapshot to do it - the only reason why it could cause an + // issue is if the database table is not ready, but that should be prevented by migrations. + + // we need them to be "repository" events ie to trigger from within the repository transaction, + // because they need to be consistent with the content that is being refreshed/removed - and that + // should be guaranteed by a DB transaction + + private void OnContentRemovingEntity(DocumentRepository sender, DocumentRepository.ScopedEntityEventArgs args) + { + OnRemovedEntity(args.Scope.Database, args.Entity); + } + + private void OnMediaRemovingEntity(MediaRepository sender, MediaRepository.ScopedEntityEventArgs args) + { + OnRemovedEntity(args.Scope.Database, args.Entity); + } + + private void OnMemberRemovingEntity(MemberRepository sender, MemberRepository.ScopedEntityEventArgs args) + { + OnRemovedEntity(args.Scope.Database, args.Entity); + } + + private void OnRemovedEntity(IUmbracoDatabase db, IContentBase item) + { + db.Execute("DELETE FROM cmsContentNu WHERE nodeId=@id", new { id = item.Id }); + } + + private static readonly string[] PropertiesImpactingAllVersions = { "SortOrder", "ParentId", "Level", "Path", "Trashed" }; + + private static bool HasChangesImpactingAllVersions(IContent icontent) + { + var content = (Content) icontent; + + // UpdateDate will be dirty + // Published may be dirty if saving a Published entity + // so cannot do this (would always be true): + //return content.IsEntityDirty(); + + // have to be more precise & specify properties + return PropertiesImpactingAllVersions.Any(content.IsPropertyDirty); + } + + private void OnContentRefreshedEntity(DocumentRepository sender, DocumentRepository.ScopedEntityEventArgs args) + { + var db = args.Scope.Database; + var content = (Content) args.Entity; + + // always refresh the edited data + OnRepositoryRefreshed(db, content, false); + + // if unpublishing, remove published data from table + if (content.PublishedState == PublishedState.Unpublishing) + db.Execute("DELETE FROM cmsContentNu WHERE nodeId=@id AND published=1", new { id = content.Id }); + + // if publishing, refresh the published data + else if (content.PublishedState == PublishedState.Publishing) + OnRepositoryRefreshed(db, content, true); + } + + private void OnMediaRefreshedEntity(MediaRepository sender, MediaRepository.ScopedEntityEventArgs args) + { + var db = args.Scope.Database; + var media = args.Entity; + + // refresh the edited data + OnRepositoryRefreshed(db, media, false); + } + + private void OnMemberRefreshedEntity(MemberRepository sender, MemberRepository.ScopedEntityEventArgs args) + { + var db = args.Scope.Database; + var member = args.Entity; + + // refresh the edited data + OnRepositoryRefreshed(db, member, true); + } + + private void OnRepositoryRefreshed(IUmbracoDatabase db, IContentBase content, bool published) + { + // use a custom SQL to update row version on each update + //db.InsertOrUpdate(dto); + + var dto = GetDto(content, published); + db.InsertOrUpdate(dto, + "SET data=@data, rv=rv+1 WHERE nodeId=@id AND published=@published", + new + { + data = dto.Data, + id = dto.NodeId, + published = dto.Published + }); + } + + private void OnContentTypeRefreshedEntity(IContentTypeService sender, ContentTypeChange.EventArgs args) + { + const ContentTypeChangeTypes types // only for those that have been refreshed + = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther; + var contentTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); + if (contentTypeIds.Any()) + RebuildContentDbCache(contentTypeIds: contentTypeIds); + } + + private void OnMediaTypeRefreshedEntity(IMediaTypeService sender, ContentTypeChange.EventArgs args) + { + const ContentTypeChangeTypes types // only for those that have been refreshed + = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther; + var mediaTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); + if (mediaTypeIds.Any()) + RebuildMediaDbCache(contentTypeIds: mediaTypeIds); + } + + private void OnMemberTypeRefreshedEntity(IMemberTypeService sender, ContentTypeChange.EventArgs args) + { + const ContentTypeChangeTypes types // only for those that have been refreshed + = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther; + var memberTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); + if (memberTypeIds.Any()) + RebuildMemberDbCache(contentTypeIds: memberTypeIds); + } + + private static ContentNuDto GetDto(IContentBase content, bool published) + { + // should inject these in ctor + // BUT for the time being we decide not to support ConvertDbToXml/String + //var propertyEditorResolver = PropertyEditorResolver.Current; + //var dataTypeService = ApplicationContext.Current.Services.DataTypeService; + + var data = new Dictionary(); + foreach (var prop in content.Properties) + { + var pdatas = new List(); + foreach (var pvalue in prop.Values) + { + var value = published ? pvalue.PublishedValue : pvalue.EditedValue; + if (value != null) + pdatas.Add(new PropertyData { LanguageId = pvalue.LanguageId, Segment = pvalue.Segment, Value = value }); + + //Core.Composing.Current.Logger.Debug($"{content.Id} {prop.Alias} [{pvalue.LanguageId},{pvalue.Segment}] {value} {(published?"pub":"edit")}"); + + //if (value != null) + //{ + // var e = propertyEditorResolver.GetByAlias(prop.PropertyType.PropertyEditorAlias); + + // // We are converting to string, even for database values which are integer or + // // DateTime, which is not optimum. Doing differently would require that we have a way to tell + // // whether the conversion to XML string changes something or not... which we don't, and we + // // don't want to implement it as PropertyValueEditor.ConvertDbToXml/String should die anyway. + + // // Don't think about improving the situation here: this is a corner case and the real + // // thing to do is to get rig of PropertyValueEditor.ConvertDbToXml/String. + + // // Use ConvertDbToString to keep it simple, although everywhere we use ConvertDbToXml and + // // nothing ensures that the two methods are consistent. + + // if (e != null) + // value = e.ValueEditor.ConvertDbToString(prop, prop.PropertyType, dataTypeService); + //} + } + data[prop.Alias] = pdatas.ToArray(); + } + + var dto = new ContentNuDto + { + NodeId = content.Id, + Published = published, + + // note that numeric values (which are Int32) are serialized without their + // type (eg "value":1234) and JsonConvert by default deserializes them as Int64 + + Data = JsonConvert.SerializeObject(data) + }; + + //Core.Composing.Current.Logger.Debug(dto.Data); + + return dto; + } + + #endregion + + #region Rebuild Database PreCache + + public void RebuildContentDbCache(int groupSize = 5000, IEnumerable contentTypeIds = null) + { + using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) + { + scope.ReadLock(Constants.Locks.ContentTree); + RebuildContentDbCacheLocked(scope, groupSize, contentTypeIds); + scope.Complete(); + } + } + + // assumes content tree lock + private void RebuildContentDbCacheLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) + { + var contentTypeIdsA = contentTypeIds?.ToArray(); + var contentObjectType = Constants.ObjectTypes.Document; + var db = scope.Database; + + // remove all - if anything fails the transaction will rollback + if (contentTypeIds == null || contentTypeIdsA.Length == 0) + { + // must support SQL-CE + db.Execute(@"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType +)", + new { objType = contentObjectType }); + } + else + { + // assume number of ctypes won't blow IN(...) + // must support SQL-CE + db.Execute($@"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode + JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id + WHERE umbracoNode.nodeObjectType=@objType + AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) +)", + new { objType = contentObjectType, ctypes = contentTypeIdsA }); + } + + // insert back - if anything fails the transaction will rollback + var query = scope.SqlContext.Query(); + if (contentTypeIds != null && contentTypeIdsA.Length > 0) + query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) + + long pageIndex = 0; + long processed = 0; + long total; + do + { + var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var items = new List(); + foreach (var c in descendants) + { + // always the edited version + items.Add(GetDto(c, false)); + // and also the published version if it makes any sense + if (c.Published) + items.Add(GetDto(c, true)); + } + + db.BulkInsertRecords(items); + processed += items.Count; + } while (processed < total); + } + + public void RebuildMediaDbCache(int groupSize = 5000, IEnumerable contentTypeIds = null) + { + using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) + { + scope.ReadLock(Constants.Locks.MediaTree); + RebuildMediaDbCacheLocked(scope, groupSize, contentTypeIds); + scope.Complete(); + } + } + + // assumes media tree lock + public void RebuildMediaDbCacheLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) + { + var contentTypeIdsA = contentTypeIds?.ToArray(); + var mediaObjectType = Constants.ObjectTypes.Media; + var db = scope.Database; + + // remove all - if anything fails the transaction will rollback + if (contentTypeIds == null || contentTypeIdsA.Length == 0) + { + // must support SQL-CE + db.Execute(@"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType +)", + new { objType = mediaObjectType }); + } + else + { + // assume number of ctypes won't blow IN(...) + // must support SQL-CE + db.Execute($@"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode + JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id + WHERE umbracoNode.nodeObjectType=@objType + AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) +)", + new { objType = mediaObjectType, ctypes = contentTypeIdsA }); + } + + // insert back - if anything fails the transaction will rollback + var query = scope.SqlContext.Query(); + if (contentTypeIds != null && contentTypeIdsA.Length > 0) + query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) + + long pageIndex = 0; + long processed = 0; + long total; + do + { + var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var items = descendants.Select(m => GetDto(m, false)).ToArray(); + db.BulkInsertRecords(items); + processed += items.Length; + } while (processed < total); + } + + public void RebuildMemberDbCache(int groupSize = 5000, IEnumerable contentTypeIds = null) + { + using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) + { + scope.ReadLock(Constants.Locks.MemberTree); + RebuildMemberDbCacheLocked(scope, groupSize, contentTypeIds); + scope.Complete(); + } + } + + // assumes member tree lock + public void RebuildMemberDbCacheLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) + { + var contentTypeIdsA = contentTypeIds?.ToArray(); + var memberObjectType = Constants.ObjectTypes.Member; + var db = scope.Database; + + // remove all - if anything fails the transaction will rollback + if (contentTypeIds == null || contentTypeIdsA.Length == 0) + { + // must support SQL-CE + db.Execute(@"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType +)", + new { objType = memberObjectType }); + } + else + { + // assume number of ctypes won't blow IN(...) + // must support SQL-CE + db.Execute($@"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode + JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id + WHERE umbracoNode.nodeObjectType=@objType + AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) +)", + new { objType = memberObjectType, ctypes = contentTypeIdsA }); + } + + // insert back - if anything fails the transaction will rollback + var query = scope.SqlContext.Query(); + if (contentTypeIds != null && contentTypeIdsA.Length > 0) + query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) + + long pageIndex = 0; + long processed = 0; + long total; + do + { + var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var items = descendants.Select(m => GetDto(m, false)).ToArray(); + db.BulkInsertRecords(items); + processed += items.Length; + } while (processed < total); + } + + public bool VerifyContentDbCache() + { + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTree); + var ok = VerifyContentDbCacheLocked(scope); + scope.Complete(); + return ok; + } + } + + // assumes content tree lock + private bool VerifyContentDbCacheLocked(IScope scope) + { + // every document should have a corresponding row for edited properties + // and if published, may have a corresponding row for published properties + + var contentObjectType = Constants.ObjectTypes.Document; + var db = scope.Database; + + var count = db.ExecuteScalar($@"SELECT COUNT(*) +FROM umbracoNode +JOIN {Constants.DatabaseSchema.Tables.Document} ON umbracoNode.id={Constants.DatabaseSchema.Tables.Document}.nodeId +LEFT JOIN cmsContentNu nuEdited ON (umbracoNode.id=nuEdited.nodeId AND nuEdited.published=0) +LEFT JOIN cmsContentNu nuPublished ON (umbracoNode.id=nuPublished.nodeId AND nuPublished.published=1) +WHERE umbracoNode.nodeObjectType=@objType +AND nuEdited.nodeId IS NULL OR ({Constants.DatabaseSchema.Tables.Document}.published=1 AND nuPublished.nodeId IS NULL);" + , new { objType = contentObjectType }); + + return count == 0; + } + + public bool VerifyMediaDbCache() + { + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.MediaTree); + var ok = VerifyMediaDbCacheLocked(scope); + scope.Complete(); + return ok; + } + } + + // assumes media tree lock + public bool VerifyMediaDbCacheLocked(IScope scope) + { + // every media item should have a corresponding row for edited properties + + var mediaObjectType = Constants.ObjectTypes.Media; + var db = scope.Database; + + var count = db.ExecuteScalar(@"SELECT COUNT(*) +FROM umbracoNode +LEFT JOIN cmsContentNu ON (umbracoNode.id=cmsContentNu.nodeId AND cmsContentNu.published=0) +WHERE umbracoNode.nodeObjectType=@objType +AND cmsContentNu.nodeId IS NULL +", new { objType = mediaObjectType }); + + return count == 0; + } + + public bool VerifyMemberDbCache() + { + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.MemberTree); + var ok = VerifyMemberDbCacheLocked(scope); + scope.Complete(); + return ok; + } + } + + // assumes member tree lock + public bool VerifyMemberDbCacheLocked(IScope scope) + { + // every member item should have a corresponding row for edited properties + + var memberObjectType = Constants.ObjectTypes.Member; + var db = scope.Database; + + var count = db.ExecuteScalar(@"SELECT COUNT(*) +FROM umbracoNode +LEFT JOIN cmsContentNu ON (umbracoNode.id=cmsContentNu.nodeId AND cmsContentNu.published=0) +WHERE umbracoNode.nodeObjectType=@objType +AND cmsContentNu.nodeId IS NULL +", new { objType = memberObjectType }); + + return count == 0; + } + + #endregion + + #region Instrument + + public string GetStatus() + { + var dbCacheIsOk = VerifyContentDbCache() + && VerifyMediaDbCache() + && VerifyMemberDbCache(); + + var cg = _contentStore.GenCount; + var mg = _mediaStore.GenCount; + var cs = _contentStore.SnapCount; + var ms = _mediaStore.SnapCount; + var ce = _contentStore.Count; + var me = _mediaStore.Count; + + return "I'm feeling good, really." + + " Database cache is " + (dbCacheIsOk ? "ok" : "NOT ok (rebuild?)") + "." + + " ContentStore has " + cg + " generation" + (cg > 1 ? "s" : "") + + ", " + cs + " snapshot" + (cs > 1 ? "s" : "") + + " and " + ce + " entr" + (ce > 1 ? "ies" : "y") + "." + + " MediaStore has " + mg + " generation" + (mg > 1 ? "s" : "") + + ", " + ms + " snapshot" + (ms > 1 ? "s" : "") + + " and " + me + " entr" + (me > 1 ? "ies" : "y") + "."; + } + + public void Collect() + { + var contentCollect = _contentStore.CollectAsync(); + var mediaCollect = _mediaStore.CollectAsync(); + System.Threading.Tasks.Task.WaitAll(contentCollect, mediaCollect); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs new file mode 100644 index 0000000000..67fe72d31b --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs @@ -0,0 +1,687 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Core.Scoping; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + internal class SnapDictionary + where TValue : class + { + // read + // http://www.codeproject.com/Articles/548406/Dictionary-plus-Locking-versus-ConcurrentDictionar + // http://arbel.net/2013/02/03/best-practices-for-using-concurrentdictionary/ + // http://blogs.msdn.com/b/pfxteam/archive/2011/04/02/10149222.aspx + + // Set, Clear and GetSnapshot have to be protected by a lock + // This class is optimized for many readers, few writers + // Readers are lock-free + + private readonly ConcurrentDictionary _items; + private readonly ConcurrentQueue _generationObjects; + private GenerationObject _generationObject; + private readonly object _wlocko = new object(); + private readonly object _rlocko = new object(); + private long _liveGen, _floorGen; + private bool _nextGen, _collectAuto; + private Task _collectTask; + private volatile int _wlocked; + + // fixme - collection trigger (ok for now) + // minGenDelta to be adjusted + // we may want to throttle collects even if delta is reached + // we may want to force collect if delta is not reached but very old + // we may want to adjust delta depending on the number of changes + private const long CollectMinGenDelta = 4; + + #region Ctor + + public SnapDictionary() + { + _items = new ConcurrentDictionary(); + _generationObjects = new ConcurrentQueue(); + _generationObject = null; // no initial gen exists + _liveGen = _floorGen = 0; + _nextGen = false; // first time, must create a snapshot + _collectAuto = true; // collect automatically by default + } + + #endregion + + #region Locking + + // read and write locks are not exclusive + // it is not possible to write-lock while someone is read-locked + // it is possible to read-lock while someone is write-locked + // + // so when getting a read-lock, + // either we are write-locked or not, but if not, we won't be write-locked + // otoh the write-lock may be released in the meantime + + // Lock has a 'forceGen' parameter: + // used to start a set of changes that may not commit, to isolate the set from any pending + // changes that would not have been snapshotted yet, so they cannot be rolled back by accident + // + // Release has a 'commit' parameter: + // if false, the live gen is scrapped and changes that have been applied as part of the lock + // are all ignored - Release is private and meant to be invoked with 'commit' being false only + // only on the outermost lock (by SnapDictionaryWriter) + + // using (...) {} for locking is prone to nasty leaks in case of weird exceptions + // such as thread-abort or out-of-memory, but let's not worry about it now + + private readonly string _instanceId = Guid.NewGuid().ToString("N"); + + private class ReadLockInfo + { + public bool Taken; + } + + private class WriteLockInfo + { + public bool Taken; + public bool Count; + } + + // a scope contextual that represents a locked writer to the dictionary + private class SnapDictionaryWriter : ScopeContextualBase + { + private readonly WriteLockInfo _lockinfo = new WriteLockInfo(); + private SnapDictionary _dictionary; + + public SnapDictionaryWriter(SnapDictionary dictionary, bool scoped) + { + _dictionary = dictionary; + dictionary.Lock(_lockinfo, scoped); + } + + public override void Release(bool completed) + { + if (_dictionary == null) return; + _dictionary.Release(_lockinfo, completed); + _dictionary = null; + } + } + + // gets a scope contextual representing a locked writer to the dictionary + // fixme GetScopedWriter? should the dict have a ref onto the scope provider? + public IDisposable GetWriter(IScopeProvider scopeProvider) + { + return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new SnapDictionaryWriter(this, scoped)); + } + + private void Lock(WriteLockInfo lockInfo, bool forceGen = false) + { + Monitor.Enter(_wlocko, ref lockInfo.Taken); + + var rtaken = false; + try + { + Monitor.Enter(_rlocko, ref rtaken); + + // assume everything in finally runs atomically + // http://stackoverflow.com/questions/18501678/can-this-unexpected-behavior-of-prepareconstrainedregions-and-thread-abort-be-ex + // http://joeduffyblog.com/2005/03/18/atomicity-and-asynchronous-exception-failures/ + // http://joeduffyblog.com/2007/02/07/introducing-the-new-readerwriterlockslim-in-orcas/ + // http://chabster.blogspot.fr/2013/12/readerwriterlockslim-fails-on-dual.html + //RuntimeHelpers.PrepareConstrainedRegions(); + try { } finally + { + _wlocked++; + lockInfo.Count = true; + if (_nextGen == false || (forceGen && _wlocked == 1)) // if true already... ok to have "holes" in generation objects + { + // because we are changing things, a new generation + // is created, which will trigger a new snapshot + _nextGen = true; + _liveGen += 1; + } + } + } + finally + { + if (rtaken) Monitor.Exit(_rlocko); + } + } + + private void Lock(ReadLockInfo lockInfo) + { + Monitor.Enter(_rlocko, ref lockInfo.Taken); + } + + private void Release(WriteLockInfo lockInfo, bool commit = true) + { + if (commit == false) + { + var rtaken = false; + try + { + Monitor.Enter(_rlocko, ref rtaken); + try { } finally + { + _nextGen = false; + _liveGen -= 1; + } + } + finally + { + if (rtaken) Monitor.Exit(_rlocko); + } + + foreach (var item in _items) + { + var link = item.Value; + if (link.Gen <= _liveGen) continue; + + var key = item.Key; + if (link.Next == null) + _items.TryRemove(key, out link); + else + _items.TryUpdate(key, link.Next, link); + } + } + + if (lockInfo.Count) _wlocked--; + if (lockInfo.Taken) Monitor.Exit(_wlocko); + } + + private void Release(ReadLockInfo lockInfo) + { + if (lockInfo.Taken) Monitor.Exit(_rlocko); + } + + #endregion + + #region Set, Clear, Get, Has + + public int Count => _items.Count; + + private LinkedNode GetHead(TKey key) + { + _items.TryGetValue(key, out LinkedNode link); // else null + return link; + } + + public void Set(TKey key, TValue value) + { + var lockInfo = new WriteLockInfo(); + try + { + Lock(lockInfo); + + // this is safe only because we're write-locked + var link = GetHead(key); + if (link != null) + { + // already in the dict + if (link.Gen != _liveGen) + { + // for an older gen - if value is different then insert a new + // link for the new gen, with the new value + if (link.Value != value) + _items.TryUpdate(key, new LinkedNode(value, _liveGen, link), link); + } + else + { + // for the live gen - we can fix the live gen - and remove it + // if value is null and there's no next gen + if (value == null && link.Next == null) + _items.TryRemove(key, out link); + else + link.Value = value; + } + } + else + { + _items.TryAdd(key, new LinkedNode(value, _liveGen)); + } + } + finally + { + Release(lockInfo); + } + } + + public void Clear(TKey key) + { + Set(key, null); + } + + public void Clear() + { + var lockInfo = new WriteLockInfo(); + try + { + Lock(lockInfo); + + // this is safe only because we're write-locked + foreach (var kvp in _items.Where(x => x.Value != null)) + { + if (kvp.Value.Gen < _liveGen) + { + var link = new LinkedNode(null, _liveGen, kvp.Value); + _items.TryUpdate(kvp.Key, link, kvp.Value); + } + else + { + kvp.Value.Value = null; + } + } + } + finally + { + Release(lockInfo); + } + } + + public TValue Get(TKey key, long gen) + { + // look ma, no lock! + var link = GetHead(key); + while (link != null) + { + if (link.Gen <= gen) + return link.Value; // may be null + link = link.Next; + } + return null; + } + + public IEnumerable GetAll(long gen) + { + // enumerating on .Values locks the concurrent dictionary, + // so better get a shallow clone in an array and release + var links = _items.Values.ToArray(); + foreach (var l in links) + { + var link = l; + while (link != null) + { + if (link.Gen <= gen) + { + if (link.Value != null) + yield return link.Value; + break; + } + link = link.Next; + } + } + } + + public bool IsEmpty(long gen) + { + var has = _items.Any(x => + { + var link = x.Value; + while (link != null) + { + if (link.Gen <= gen && link.Value != null) + return true; + link = link.Next; + } + return false; + }); + return has == false; + } + + #endregion + + #region Snapshots + + public Snapshot CreateSnapshot() + { + var lockInfo = new ReadLockInfo(); + try + { + Lock(lockInfo); + + // if no next generation is required, and we already have one, + // use it and create a new snapshot + if (_nextGen == false && _generationObject != null) + return new Snapshot(this, _generationObject.GetReference()); + + // else we need to try to create a new gen ref + // whether we are wlocked or not, noone can rlock while we do, + // so _liveGen and _nextGen are safe + if (_wlocked > 0) // volatile, cannot ++ but could -- + { + // write-locked, cannot use latest gen (at least 1) so use previous + var snapGen = _nextGen ? _liveGen - 1 : _liveGen; + + // create a new gen ref unless we already have it + if (_generationObject == null) + _generationObjects.Enqueue(_generationObject = new GenerationObject(snapGen)); + else if (_generationObject.Gen != snapGen) + throw new Exception("panic"); + } + else + { + // not write-locked, can use latest gen, create a new gen ref + _generationObjects.Enqueue(_generationObject = new GenerationObject(_liveGen)); + _nextGen = false; // this is the ONLY thing that triggers a _liveGen++ + } + + // so... + // the genRefRef has a weak ref to the genRef, and is queued + // the snapshot has a ref to the genRef, which has a ref to the genRefRef + // when the snapshot is disposed, it decreases genRefRef counter + // so after a while, one of these conditions is going to be true: + // - the genRefRef counter is zero because all snapshots have properly been disposed + // - the genRefRef weak ref is dead because all snapshots have been collected + // in both cases, we will dequeue and collect + + var snapshot = new Snapshot(this, _generationObject.GetReference()); + + // reading _floorGen is safe if _collectTask is null + if (_collectTask == null && _collectAuto && _liveGen - _floorGen > CollectMinGenDelta) + CollectAsyncLocked(); + + return snapshot; + } + finally + { + Release(lockInfo); + } + } + + public Task CollectAsync() + { + lock (_rlocko) + { + return CollectAsyncLocked(); + } + } + + private Task CollectAsyncLocked() + { + if (_collectTask != null) + return _collectTask; + + // ReSharper disable InconsistentlySynchronizedField + var task = _collectTask = Task.Run(() => Collect()); + _collectTask.ContinueWith(_ => + { + lock (_rlocko) + { + _collectTask = null; + } + }, TaskContinuationOptions.ExecuteSynchronously); + // ReSharper restore InconsistentlySynchronizedField + + return task; + } + + private void Collect() + { + // see notes in CreateSnapshot + while (_generationObjects.TryPeek(out GenerationObject generationObject) && (generationObject.Count == 0 || generationObject.WeakReference.IsAlive == false)) + { + _generationObjects.TryDequeue(out generationObject); // cannot fail since TryPeek has succeeded + _floorGen = generationObject.Gen; + } + + Collect(_items); + } + + private void Collect(ConcurrentDictionary dict) + { + // it is OK to enumerate a concurrent dictionary and it does not lock + // it - and here it's not an issue if we skip some items, they will be + // processed next time we collect + + long liveGen; + lock (_rlocko) // r is good + { + liveGen = _liveGen; + if (_nextGen == false) + liveGen += 1; + } + + //Console.WriteLine("Collect live=" + liveGen + " floor=" + _floorGen); + + foreach (var kvp in dict) + { + var link = kvp.Value; + + //Console.WriteLine("Collect id=" + kvp.Key + " gen=" + link.Gen + // + " nxt=" + (link.Next == null ? null : "next") + // + " val=" + link.Value); + + // reasons to collect the head: + // gen must be < liveGen (we never collect live gen) + // next == null && value == null (we have no data at all) + // next != null && value == null BUT gen > floor (noone wants us) + // not live means .Next and .Value are safe + if (link.Gen < liveGen && link.Value == null + && (link.Next == null || link.Gen <= _floorGen)) + { + // not live, null value, no next link = remove that one -- but only if + // the dict has not been updated, have to do it via ICollection<> (thanks + // Mr Toub) -- and if the dict has been updated there is nothing to collect + var idict = dict as ICollection>; + /*var removed =*/ idict.Remove(kvp); + //Console.WriteLine("remove (" + (removed ? "true" : "false") + ")"); + continue; + } + + // in any other case we're not collecting the head, we need to go to Next + // and if there is no Next, skip + if (link.Next == null) + continue; + + // else go to Next and loop while above floor, and kill everything below + while (link.Next != null && link.Next.Gen > _floorGen) + link = link.Next; + link.Next = null; + } + } + + public /*async*/ Task PendingCollect() + { + Task task; + lock (_rlocko) + { + task = _collectTask; + } + return task ?? Task.FromResult(0); + //if (task != null) + // await task; + } + + public long GenCount => _generationObjects.Count; + + public long SnapCount => _generationObjects.Sum(x => x.Count); + + #endregion + + #region Unit testing + + private TestHelper _unitTesting; + + // note: nothing here is thread-safe + internal class TestHelper + { + private readonly SnapDictionary _dict; + + public TestHelper(SnapDictionary dict) + { + _dict = dict; + } + + public long LiveGen => _dict._liveGen; + public long FloorGen => _dict._floorGen; + public bool NextGen => _dict._nextGen; + + public bool CollectAuto + { + get => _dict._collectAuto; + set => _dict._collectAuto = value; + } + + public ConcurrentQueue GenerationObjects => _dict._generationObjects; + + public Snapshot LiveSnapshot => new Snapshot(_dict, _dict._liveGen); + + public GenVal[] GetValues(TKey key) + { + _dict._items.TryGetValue(key, out LinkedNode link); // else null + + if (link == null) + return new GenVal[0]; + + var genVals = new List(); + do + { + genVals.Add(new GenVal(link.Gen, link.Value)); + link = link.Next; + } while (link != null); + return genVals.ToArray(); + } + + public class GenVal + { + public GenVal(long gen, TValue value) + { + Gen = gen; + Value = value; + } + + public long Gen { get; } + public TValue Value { get; } + } + } + + internal TestHelper Test => _unitTesting ?? (_unitTesting = new TestHelper(this)); + + #endregion + + #region Classes + + private class LinkedNode + { + public LinkedNode(TValue value, long gen, LinkedNode next = null) + { + Value = value; + Gen = gen; + Next = next; + } + + internal readonly long Gen; + + // reading & writing references is thread-safe on all .NET platforms + // mark as volatile to ensure we always read the correct value + internal volatile TValue Value; + internal volatile LinkedNode Next; + } + + public class Snapshot : IDisposable + { + private readonly SnapDictionary _store; + private readonly GenerationReference _generationReference; + private long _gen; // copied for perfs + + internal Snapshot(SnapDictionary store, GenerationReference generationReference) + { + _store = store; + _generationReference = generationReference; + _gen = generationReference.GenerationObject.Gen; + _generationReference.GenerationObject.Reference(); + } + + internal Snapshot(SnapDictionary store, long gen) + { + _store = store; + _gen = gen; + } + + public TValue Get(TKey key) + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.Get(key, _gen); + } + + public IEnumerable GetAll() + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.GetAll(_gen); + } + + public bool IsEmpty + { + get + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.IsEmpty(_gen); + } + } + + public long Gen + { + get + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _gen; + } + } + + public void Dispose() + { + if (_gen < 0) return; + _gen = -1; + _generationReference?.GenerationObject.Release(); + GC.SuppressFinalize(this); + } + } + + internal class GenerationObject + { + public GenerationObject(long gen) + { + Gen = gen; + WeakReference = new WeakReference(null); + } + + public GenerationReference GetReference() + { + // not thread-safe but always invoked from within a lock + var generationReference = (GenerationReference) WeakReference.Target; + if (generationReference == null) + WeakReference.Target = generationReference = new GenerationReference(this); + return generationReference; + } + + public readonly long Gen; + public readonly WeakReference WeakReference; + public int Count; + + public void Reference() + { + Interlocked.Increment(ref Count); + } + + public void Release() + { + Interlocked.Decrement(ref Count); + } + } + + internal class GenerationReference + { + public GenerationReference(GenerationObject generationObject) + { + GenerationObject = generationObject; + } + + public readonly GenerationObject GenerationObject; + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/notes.txt b/src/Umbraco.Web/PublishedCache/NuCache/notes.txt new file mode 100644 index 0000000000..ff2d8dd48b --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/notes.txt @@ -0,0 +1,124 @@ +NuCache Documentation +====================== + +NOTE - RENAMING +Facade = PublishedSnapshot +and everything needs to be renamed accordingly + +HOW IT WORKS +------------- + +NuCache uses a ContentStore to keep content - basically, a dictionary of int => content, +and some logic to maintain it up-to-date. In order to provide immutable content to +pages rendering, a ContentStore can create ContentViews. A ContentView basically is +another int => content dictionary, containing entries only for things that have changed +in the ContentStore - so the ContentStore changes, but it updates the views so that +they + +Views are chained, ie each new view is the parent of previously existing views. A view +knows its parent but not the other way round, so views just disappear when they are GC. + +When reading the cache, we read views up the chain until we find a value (which may be +null) for the given id, and finally we read the store itself. + + +The FacadeService manages a ContentStore for content, and another for media. +When a Facade is created, the FacadeService gets ContentView objects from the stores. +Views provide an immutable snapshot of the content and media. + +When the FacadeService is notified of changes, it notifies the stores. +Then it resync the current Facade, so that it requires new views, etc. + +Whenever a content, media or member is modified or removed, the cmsContentNu table +is updated with a json dictionary of alias => property value, so that a content, +media or member can be loaded with one database row - this is what is used to populate +the in-memory cache. + + +A ContentStore actually stores ContentNode instances, which contain what's common to +both the published and draft version of content + the actual published and/or draft +content. + + +LOCKS +------ + +Each ContentStore is protected by a reader/writer lock 'Locker' that is used both by +the store and its views to ensure that the store remains consistent. + +Each ContentStore has a _freezeLock object used to protect the 'Frozen' +state of the store. It's a disposable object that releases the lock when disposed, +so usage would be: using (store.Frozen) { ... }. + +The FacadeService has a _storesLock object used to guarantee atomic access to the +set of content, media stores. + + +CACHE +------ + +For each set of views, the FacadeService creates a SnapshotCache. So a SnapshotCache +is valid until anything changes in the content or media trees. In other words, things +that go in the SnapshotCache stay until a content or media is modified. + +For each Facade, the FacadeService creates a FacadeCache. So a FacadeCache is valid +for the duration of the Facade (usually, the request). In other words, things that go +in the FacadeCache stay (and are visible to) for the duration of the request only. + +The FacadeService defines a static constant FullCacheWhenPreviewing, that defines +how caches operate when previewing: +- when true, the caches in preview mode work normally. +- when false, everything that would go to the SnapshotCache goes to the FacadeCache. +At the moment it is true in the code, which means that eg converted values for +previewed content will go in the SnapshotCache. Makes for faster preview, but uses +more memory on the long term... would need some benchmarking to figure out what is +best. + +Members only live for the duration of the Facade. So, for members SnapshotCache is +never used, and everything goes to the FacadeCache. + +All cache keys are computed in the CacheKeys static class. + + +TESTS +----- + +For testing purposes the following mechanisms exist: + +The Facade type has a static Current property that is used to obtain the 'current' +facade in many places, going through the PublishedCachesServiceResolver to get the +current service, and asking the current service for the current facade, which by +default relies on UmbracoContext. For test purposes, it is possible to override the +entire mechanism by defining Facade.GetCurrentFacadeFunc which should return a facade. + +A PublishedContent keeps only id-references to its parent and children, and needs a +way to retrieve the actual objects from the cache - which depends on the current +facade. It is possible to override the entire mechanism by defining PublishedContent. +GetContentByIdFunc or .GetMediaByIdFunc. + +Setting these functions must be done before Resolution is frozen. + + +STATUS +------ + +"Detached" contents & properties, which need to be refactored anyway, are not supported +by NuCache - throwing NotImplemented in ContentCache. + +All the cached elements rely on guids for the cache key, and not ints, so it could be +possible to support detached contents & properties, even those that do not have an actual +int id, but again this should be refactored entirely anyway. + +Not doing any row-version checks (see XmlStore) when reloading from database, though it +is maintained in the database. Two FIXME in FacadeService. Should we do it? + +There is no on-disk cache at all so everything is reloaded from the cmsContentNu table +when the site restarts. This is pretty fast, but we should experiment with solutions to +store things locally (and deal with the sync issues, see XmlStore...). + +Doing our best with PublishedMember but the whole thing should be refactored, because +PublishedMember exposes properties that IPublishedContent does not, and that are going +to be lost soon as the member is wrapped (content set, model...) - so we probably need +some sort of IPublishedMember. + +/ \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs b/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs new file mode 100644 index 0000000000..9806e58554 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Xml.XPath; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Xml; +using Umbraco.Core.Models; + +namespace Umbraco.Web.PublishedCache +{ + abstract class PublishedCacheBase : IPublishedCache + { + public bool PreviewDefault { get; } + + protected PublishedCacheBase(bool previewDefault) + { + PreviewDefault = previewDefault; + } + + public abstract IPublishedContent GetById(bool preview, int contentId); + + public IPublishedContent GetById(int contentId) + { + return GetById(PreviewDefault, contentId); + } + + public abstract IPublishedContent GetById(bool preview, Guid contentId); + + public IPublishedContent GetById(Guid contentId) + { + return GetById(PreviewDefault, contentId); + } + + public abstract bool HasById(bool preview, int contentId); + + public bool HasById(int contentId) + { + return HasById(PreviewDefault, contentId); + } + + public abstract IEnumerable GetAtRoot(bool preview); + + public IEnumerable GetAtRoot() + { + return GetAtRoot(PreviewDefault); + } + + public abstract IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars); + + public IPublishedContent GetSingleByXPath(string xpath, XPathVariable[] vars) + { + return GetSingleByXPath(PreviewDefault, xpath, vars); + } + + public abstract IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars); + + public IPublishedContent GetSingleByXPath(XPathExpression xpath, XPathVariable[] vars) + { + return GetSingleByXPath(PreviewDefault, xpath, vars); + } + + public abstract IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars); + + public IEnumerable GetByXPath(string xpath, XPathVariable[] vars) + { + return GetByXPath(PreviewDefault, xpath, vars); + } + + public abstract IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars); + + public IEnumerable GetByXPath(XPathExpression xpath, XPathVariable[] vars) + { + return GetByXPath(PreviewDefault, xpath, vars); + } + + public abstract XPathNavigator CreateNavigator(bool preview); + + public XPathNavigator CreateNavigator() + { + return CreateNavigator(PreviewDefault); + } + + public abstract XPathNavigator CreateNodeNavigator(int id, bool preview); + + public abstract bool HasContent(bool preview); + + public bool HasContent() + { + return HasContent(PreviewDefault); + } + + public abstract PublishedContentType GetContentType(int id); + + public abstract PublishedContentType GetContentType(string alias); + + public abstract IEnumerable GetByContentType(PublishedContentType contentType); + } +} diff --git a/src/Umbraco.Web/PublishedCache/PublishedCaches.cs b/src/Umbraco.Web/PublishedCache/PublishedCaches.cs deleted file mode 100644 index 287cd740e6..0000000000 --- a/src/Umbraco.Web/PublishedCache/PublishedCaches.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Umbraco.Web.PublishedCache -{ - /// - /// Provides caches (content and media). - /// - /// Default implementation for unrelated caches. - class PublishedCaches : IPublishedCaches - { - private readonly IPublishedContentCache _contentCache; - private readonly IPublishedMediaCache _mediaCache; - - /// - /// Initializes a new instance of the class with a content cache - /// and a media cache. - /// - public PublishedCaches(IPublishedContentCache contentCache, IPublishedMediaCache mediaCache) - { - _contentCache = contentCache; - _mediaCache = mediaCache; - } - - /// - /// Creates a contextual content cache for a specified context. - /// - /// The context. - /// A new contextual content cache for the specified context. - public ContextualPublishedContentCache CreateContextualContentCache(UmbracoContext context) - { - return new ContextualPublishedContentCache(_contentCache, context); - } - - /// - /// Creates a contextual media cache for a specified context. - /// - /// The context. - /// A new contextual media cache for the specified context. - public ContextualPublishedMediaCache CreateContextualMediaCache(UmbracoContext context) - { - return new ContextualPublishedMediaCache(_mediaCache, context); - } - } -} diff --git a/src/Umbraco.Web/PublishedCache/PublishedCachesResolver.cs b/src/Umbraco.Web/PublishedCache/PublishedCachesResolver.cs deleted file mode 100644 index d060ac554b..0000000000 --- a/src/Umbraco.Web/PublishedCache/PublishedCachesResolver.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.PublishedCache -{ - /// - /// Resolves the IPublishedCaches object. - /// - internal sealed class PublishedCachesResolver : SingleObjectResolverBase - { - /// - /// Initializes a new instance of the class with caches. - /// - /// The caches. - /// The resolver is created by the WebBootManager and thus the constructor remains internal. - internal PublishedCachesResolver(IPublishedCaches caches) - : base(caches) - { } - - /// - /// Sets the caches. - /// - /// The caches. - /// For developers, at application startup. - public void SetCaches(IPublishedCaches caches) - { - Value = caches; - } - - /// - /// Gets the caches. - /// - public IPublishedCaches Caches - { - get { return Value; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs b/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs new file mode 100644 index 0000000000..c846e0e863 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs @@ -0,0 +1,334 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; + +namespace Umbraco.Web.PublishedCache +{ + /// + /// Represents a content type cache. + /// + /// This cache is not snapshotted, so it refreshes any time things change. + public class PublishedContentTypeCache + { + private readonly Dictionary _typesByAlias = new Dictionary(); + private readonly Dictionary _typesById = new Dictionary(); + private readonly IContentTypeService _contentTypeService; + private readonly IMediaTypeService _mediaTypeService; + private readonly IMemberTypeService _memberTypeService; + private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; + private readonly ILogger _logger; + private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + + // default ctor + internal PublishedContentTypeCache(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, IPublishedContentTypeFactory publishedContentTypeFactory, ILogger logger) + { + _contentTypeService = contentTypeService; + _mediaTypeService = mediaTypeService; + _memberTypeService = memberTypeService; + _logger = logger; + _publishedContentTypeFactory = publishedContentTypeFactory; + } + + // for unit tests ONLY + internal PublishedContentTypeCache(ILogger logger, IPublishedContentTypeFactory publishedContentTypeFactory) + { + _logger = logger; + _publishedContentTypeFactory = publishedContentTypeFactory; + } + + // note: cache clearing is performed by XmlStore + + /// + /// Clears all cached content types. + /// + public void ClearAll() + { + _logger.Debug("Clear all."); + + try + { + _lock.EnterWriteLock(); + + _typesByAlias.Clear(); + _typesById.Clear(); + } + finally + { + if (_lock.IsWriteLockHeld) + _lock.ExitWriteLock(); + } + } + + /// + /// Clears a cached content type. + /// + /// An identifier. + public void ClearContentType(int id) + { + _logger.Debug(() => $"Clear content type w/id {id}."); + + try + { + _lock.EnterUpgradeableReadLock(); + + if (_typesById.TryGetValue(id, out var type) == false) + return; + + try + { + _lock.EnterWriteLock(); + + _typesByAlias.Remove(GetAliasKey(type)); + _typesById.Remove(id); + } + finally + { + if (_lock.IsWriteLockHeld) + _lock.ExitWriteLock(); + } + } + finally + { + if (_lock.IsUpgradeableReadLockHeld) + _lock.ExitUpgradeableReadLock(); + } + } + + /// + /// Clears all cached content types referencing a data type. + /// + /// A data type identifier. + public void ClearDataType(int id) + { + _logger.Debug(() => $"Clear data type w/id {id}."); + + // there is no recursion to handle here because a PublishedContentType contains *all* its + // properties ie both its own properties and those that were inherited (it's based upon an + // IContentTypeComposition) and so every PublishedContentType having a property based upon + // the cleared data type, be it local or inherited, will be cleared. + + try + { + _lock.EnterWriteLock(); + + var toRemove = _typesById.Values.Where(x => x.PropertyTypes.Any(xx => xx.DataType.Id == id)).ToArray(); + foreach (var type in toRemove) + { + _typesByAlias.Remove(GetAliasKey(type)); + _typesById.Remove(type.Id); + } + } + finally + { + if (_lock.IsWriteLockHeld) + _lock.ExitWriteLock(); + } + } + + /// + /// Gets a published content type. + /// + /// An item type. + /// An alias. + /// The published content type corresponding to the item type and alias. + public PublishedContentType Get(PublishedItemType itemType, string alias) + { + var aliasKey = GetAliasKey(itemType, alias); + + try + { + _lock.EnterUpgradeableReadLock(); + + if (_typesByAlias.TryGetValue(aliasKey, out var type)) + return type; + + type = CreatePublishedContentType(itemType, alias); + + try + { + _lock.EnterWriteLock(); + + return _typesByAlias[aliasKey] = _typesById[type.Id] = type; + } + finally + { + if (_lock.IsWriteLockHeld) + _lock.ExitWriteLock(); + } + } + finally + { + if (_lock.IsUpgradeableReadLockHeld) + _lock.ExitUpgradeableReadLock(); + } + } + + /// + /// Gets a published content type. + /// + /// An item type. + /// An identifier. + /// The published content type corresponding to the item type and identifier. + public PublishedContentType Get(PublishedItemType itemType, int id) + { + try + { + _lock.EnterUpgradeableReadLock(); + + if (_typesById.TryGetValue(id, out var type)) + return type; + + type = CreatePublishedContentType(itemType, id); + + try + { + _lock.EnterWriteLock(); + + return _typesByAlias[GetAliasKey(type)] = _typesById[type.Id] = type; + } + finally + { + if (_lock.IsWriteLockHeld) + _lock.ExitWriteLock(); + } + } + finally + { + if (_lock.IsUpgradeableReadLockHeld) + _lock.ExitUpgradeableReadLock(); + } + } + + private PublishedContentType CreatePublishedContentType(PublishedItemType itemType, string alias) + { + if (GetPublishedContentTypeByAlias != null) + return GetPublishedContentTypeByAlias(alias); + + IContentTypeComposition contentType; + switch (itemType) + { + case PublishedItemType.Content: + contentType = _contentTypeService.Get(alias); + break; + case PublishedItemType.Media: + contentType = _mediaTypeService.Get(alias); + break; + case PublishedItemType.Member: + contentType = _memberTypeService.Get(alias); + break; + default: + throw new ArgumentOutOfRangeException(nameof(itemType)); + } + + if (contentType == null) + throw new Exception($"ContentTypeService failed to find a {itemType.ToString().ToLower()} type with alias \"{alias}\"."); + + return _publishedContentTypeFactory.CreateContentType(contentType); + } + + private PublishedContentType CreatePublishedContentType(PublishedItemType itemType, int id) + { + if (GetPublishedContentTypeById != null) + return GetPublishedContentTypeById(id); + + IContentTypeComposition contentType; + switch (itemType) + { + case PublishedItemType.Content: + contentType = _contentTypeService.Get(id); + break; + case PublishedItemType.Media: + contentType = _mediaTypeService.Get(id); + break; + case PublishedItemType.Member: + contentType = _memberTypeService.Get(id); + break; + default: + throw new ArgumentOutOfRangeException(nameof(itemType)); + } + + if (contentType == null) + throw new Exception($"ContentTypeService failed to find a {itemType.ToString().ToLower()} type with id {id}."); + + return _publishedContentTypeFactory.CreateContentType(contentType); + } + + // for unit tests - changing the callback must reset the cache obviously + private Func _getPublishedContentTypeByAlias; + internal Func GetPublishedContentTypeByAlias + { + get => _getPublishedContentTypeByAlias; + set + { + try + { + _lock.EnterWriteLock(); + + _typesByAlias.Clear(); + _typesById.Clear(); + _getPublishedContentTypeByAlias = value; + } + finally + { + if (_lock.IsWriteLockHeld) + _lock.ExitWriteLock(); + } + } + } + + // for unit tests - changing the callback must reset the cache obviously + private Func _getPublishedContentTypeById; + internal Func GetPublishedContentTypeById + { + get => _getPublishedContentTypeById; + set + { + try + { + _lock.EnterWriteLock(); + + _typesByAlias.Clear(); + _typesById.Clear(); + _getPublishedContentTypeById = value; + } + finally + { + if (_lock.IsWriteLockHeld) + _lock.ExitWriteLock(); + } + } + } + + private static string GetAliasKey(PublishedItemType itemType, string alias) + { + string k; + + switch (itemType) + { + case PublishedItemType.Content: + k = "c"; + break; + case PublishedItemType.Media: + k = "m"; + break; + case PublishedItemType.Member: + k = "m"; + break; + default: + throw new ArgumentOutOfRangeException(nameof(itemType)); + } + + return k + ":" + alias; + } + + private static string GetAliasKey(PublishedContentType contentType) + { + return GetAliasKey(contentType.ItemType, contentType.Alias); + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/PublishedElement.cs b/src/Umbraco.Web/PublishedCache/PublishedElement.cs new file mode 100644 index 0000000000..6c90a28e0d --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/PublishedElement.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PublishedCache +{ + // notes: + // a published element does NOT manage any tree-like elements, neither the + // original NestedContent (from Lee) nor the DetachedPublishedContent POC did. + // + // at the moment we do NOT support models for sets - that would require + // an entirely new models factory + not even sure it makes sense at all since + // sets are created manually fixme yes it does! + // + internal class PublishedElement : IPublishedElement + { + // initializes a new instance of the PublishedElement class + // within the context of a published snapshot service (eg a published content property value) + public PublishedElement(PublishedContentType contentType, Guid key, Dictionary values, bool previewing, + PropertyCacheLevel referenceCacheLevel, IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + if (key == Guid.Empty) throw new ArgumentException("Empty guid."); + if (values == null) throw new ArgumentNullException(nameof(values)); + if (referenceCacheLevel != PropertyCacheLevel.None && publishedSnapshotAccessor == null) + throw new ArgumentNullException("A published snapshot accessor is required when referenceCacheLevel != None.", nameof(publishedSnapshotAccessor)); + + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + Key = key; + + values = GetCaseInsensitiveValueDictionary(values); + + _propertiesArray = contentType + .PropertyTypes + .Select(propertyType => + { + values.TryGetValue(propertyType.Alias, out var value); + return (IPublishedProperty) new PublishedElementPropertyBase(propertyType, this, previewing, referenceCacheLevel, value, publishedSnapshotAccessor); + }) + .ToArray(); + } + + // initializes a new instance of the PublishedElement class + // without any context, so it's purely 'standalone' and should NOT interfere with the published snapshot service + // + using an initial reference cache level of .None ensures that everything will be + // cached at .Content level - and that reference cache level will propagate to all + // properties + public PublishedElement(PublishedContentType contentType, Guid key, Dictionary values, bool previewing) + : this(contentType, key, values, previewing, PropertyCacheLevel.None, null) + { } + + private static Dictionary GetCaseInsensitiveValueDictionary(Dictionary values) + { + // ensure we ignore case for property aliases + var comparer = values.Comparer; + var ignoreCase = Equals(comparer, StringComparer.OrdinalIgnoreCase) || Equals(comparer, StringComparer.InvariantCultureIgnoreCase) || Equals(comparer, StringComparer.CurrentCultureIgnoreCase); + return ignoreCase ? values : new Dictionary(values, StringComparer.OrdinalIgnoreCase); + } + + #region ContentType + + public PublishedContentType ContentType { get; } + + #endregion + + #region PublishedElement + + public Guid Key { get; } + + #endregion + + #region Properties + + private readonly IPublishedProperty[] _propertiesArray; + + public IEnumerable Properties => _propertiesArray; + + public IPublishedProperty GetProperty(string alias) + { + var index = ContentType.GetPropertyIndex(alias); + var property = index < 0 ? null : _propertiesArray[index]; + return property; + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs new file mode 100644 index 0000000000..2c5b93f219 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs @@ -0,0 +1,169 @@ +using System; +using Umbraco.Core.Cache; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PublishedCache +{ + internal class PublishedElementPropertyBase : PublishedPropertyBase + { + private readonly object _locko = new object(); + private readonly object _sourceValue; + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + + protected readonly IPublishedElement Element; + protected readonly bool IsPreviewing; + protected readonly bool IsMember; + + private bool _interInitialized; + private object _interValue; + private CacheValues _cacheValues; + private string _valuesCacheKey; + + // define constant - determines whether to use cache when previewing + // to store eg routes, property converted values, anything - caching + // means faster execution, but uses memory - not sure if we want it + // so making it configureable. + private const bool FullCacheWhenPreviewing = true; + + public PublishedElementPropertyBase(PublishedPropertyType propertyType, IPublishedElement element, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null, IPublishedSnapshotAccessor publishedSnapshotAccessor = null) + : base(propertyType, referenceCacheLevel) + { + _sourceValue = sourceValue; + _publishedSnapshotAccessor = publishedSnapshotAccessor; + Element = element; + IsPreviewing = previewing; + IsMember = propertyType.ContentType.ItemType == PublishedItemType.Member; + } + + public override bool HasValue(int? languageId = null, string segment = null) + => _sourceValue != null && (!(_sourceValue is string s) || !string.IsNullOrWhiteSpace(s)); + + // used to cache the CacheValues of this property + // ReSharper disable InconsistentlySynchronizedField + internal string ValuesCacheKey => _valuesCacheKey + ?? (_valuesCacheKey = PropertyCacheValues(Element.Key, Alias, IsPreviewing)); + // ReSharper restore InconsistentlySynchronizedField + + protected class CacheValues + { + public bool ObjectInitialized; + public object ObjectValue; + public bool XPathInitialized; + public object XPathValue; + } + + public static string PropertyCacheValues(Guid contentUid, string typeAlias, bool previewing) + { + return "PublishedSnapshot.Property.CacheValues[" + (previewing ? "D:" : "P:") + contentUid + ":" + typeAlias + "]"; + } + + private void GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel) + { + // based upon the current reference cache level (ReferenceCacheLevel) and this property + // cache level (PropertyType.CacheLevel), determines both the actual cache level for the + // property, and the new reference cache level. + + // if the property cache level is 'shorter-termed' that the reference + // then use it and it becomes the new reference, else use Content and + // don't change the reference. + // + // examples: + // currently (reference) caching at published snapshot, property specifies + // elements, ok to use element. OTOH, currently caching at elements, + // property specifies snapshot, need to use snapshot. + // + if (PropertyType.CacheLevel > ReferenceCacheLevel || PropertyType.CacheLevel == PropertyCacheLevel.None) + { + cacheLevel = PropertyType.CacheLevel; + referenceCacheLevel = cacheLevel; + } + else + { + cacheLevel = PropertyCacheLevel.Element; + referenceCacheLevel = ReferenceCacheLevel; + } + } + + private ICacheProvider GetSnapshotCache() + { + // cache within the snapshot cache, unless previewing, then use the snapshot or + // elements cache (if we don't want to pollute the elements cache with short-lived + // data) depending on settings + // for members, always cache in the snapshot cache - never pollute elements cache + var publishedSnapshot = _publishedSnapshotAccessor?.PublishedSnapshot; + if (publishedSnapshot == null) return null; + return (IsPreviewing == false || FullCacheWhenPreviewing) && IsMember == false + ? publishedSnapshot.ElementsCache + : publishedSnapshot.SnapshotCache; + } + + private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel) + { + CacheValues cacheValues; + switch (cacheLevel) + { + case PropertyCacheLevel.None: + // never cache anything + cacheValues = new CacheValues(); + break; + case PropertyCacheLevel.Element: + // cache within the property object itself, ie within the content object + cacheValues = _cacheValues ?? (_cacheValues = new CacheValues()); + break; + case PropertyCacheLevel.Elements: + // cache within the elements cache, depending... + var snapshotCache = GetSnapshotCache(); + cacheValues = (CacheValues) snapshotCache?.GetCacheItem(ValuesCacheKey, () => new CacheValues()) ?? new CacheValues(); + break; + case PropertyCacheLevel.Snapshot: + // cache within the snapshot cache + var facadeCache = _publishedSnapshotAccessor?.PublishedSnapshot?.SnapshotCache; + cacheValues = (CacheValues) facadeCache?.GetCacheItem(ValuesCacheKey, () => new CacheValues()) ?? new CacheValues(); + break; + default: + throw new InvalidOperationException("Invalid cache level."); + } + return cacheValues; + } + + private object GetInterValue() + { + if (_interInitialized) return _interValue; + + _interValue = PropertyType.ConvertSourceToInter(Element, _sourceValue, IsPreviewing); + _interInitialized = true; + return _interValue; + } + + public override object GetSourceValue(int? languageId = null, string segment = null) => _sourceValue; + + public override object GetValue(int? languageId = null, string segment = null) + { + GetCacheLevels(out var cacheLevel, out var referenceCacheLevel); + + lock (_locko) + { + var cacheValues = GetCacheValues(cacheLevel); + if (cacheValues.ObjectInitialized) return cacheValues.ObjectValue; + cacheValues.ObjectValue = PropertyType.ConvertInterToObject(Element, referenceCacheLevel, GetInterValue(), IsPreviewing); + cacheValues.ObjectInitialized = true; + return cacheValues.ObjectValue; + } + } + + public override object GetXPathValue(int? languageId = null, string segment = null) + { + GetCacheLevels(out var cacheLevel, out var referenceCacheLevel); + + lock (_locko) + { + var cacheValues = GetCacheValues(cacheLevel); + if (cacheValues.XPathInitialized) return cacheValues.XPathValue; + cacheValues.XPathValue = PropertyType.ConvertInterToXPath(Element, referenceCacheLevel, GetInterValue(), IsPreviewing); + cacheValues.XPathInitialized = true; + return cacheValues.XPathValue; + } + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs new file mode 100644 index 0000000000..0d5e61a683 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Models; + +namespace Umbraco.Web.PublishedCache +{ + /// + /// Exposes a member object as IPublishedContent + /// + public sealed class PublishedMember : PublishedContentBase + { + private readonly IMember _member; + private readonly IMembershipUser _membershipUser; + private readonly IPublishedProperty[] _properties; + private readonly PublishedContentType _publishedMemberType; + + public PublishedMember(IMember member, PublishedContentType publishedMemberType) + { + _member = member ?? throw new ArgumentNullException(nameof(member)); + _membershipUser = member; + _publishedMemberType = publishedMemberType ?? throw new ArgumentNullException(nameof(publishedMemberType)); + + // fixme + // RawValueProperty is used for two things here + // - for the 'map properties' thing that we should really get rid of + // - for populating properties that every member should always have, and that we force-create + // if they are not part of the member type properties - in which case they are created as + // simple raw properties - which are completely invariant + + var _properties = PublishedProperty.MapProperties(_publishedMemberType.PropertyTypes, _member.Properties, + (t, v) => new RawValueProperty(t, this, v ?? string.Empty)); + + var properties = new List(); + foreach (var propertyType in _publishedMemberType.PropertyTypes) + { + var property = _member.Properties[propertyType.Alias]; + if (property == null) continue; + + //properties.Add(new FooProperty(propertyType, this, property.Values)); + } + EnsureMemberProperties(properties); + _properties = properties.ToArray(); + } + + #region Membership provider member properties + + // fixme why this? + + public string Email => _membershipUser.Email; + + public string UserName => _membershipUser.Username; + + public string PasswordQuestion => _membershipUser.PasswordQuestion; + + public string Comments => _membershipUser.Comments; + + public bool IsApproved => _membershipUser.IsApproved; + + public bool IsLockedOut => _membershipUser.IsLockedOut; + + public DateTime LastLockoutDate => _membershipUser.LastLockoutDate; + + public DateTime CreationDate => _membershipUser.CreateDate; + + public DateTime LastLoginDate => _membershipUser.LastLoginDate; + + public DateTime LastActivityDate => _membershipUser.LastLoginDate; + + public DateTime LastPasswordChangeDate => _membershipUser.LastPasswordChangeDate; + + #endregion + + #region IPublishedContent + + public override PublishedItemType ItemType => PublishedItemType.Member; + + public override bool IsDraft => false; + + public override IPublishedContent Parent => null; + + public override IEnumerable Children => Enumerable.Empty(); + + public override IEnumerable Properties => _properties; + + public override IPublishedProperty GetProperty(string alias, bool recurse) + { + if (recurse) + { + throw new NotSupportedException(); + } + return GetProperty(alias); + } + + public override IPublishedProperty GetProperty(string alias) + { + return _properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } + + private void EnsureMemberProperties(List properties) + { + var aliases = properties.Select(x => x.Alias).ToList(); + + EnsureMemberProperty(properties, aliases, "Email", Email); + EnsureMemberProperty(properties, aliases, "UserName", UserName); + EnsureMemberProperty(properties, aliases, "PasswordQuestion", PasswordQuestion); + EnsureMemberProperty(properties, aliases, "Comments", Comments); + EnsureMemberProperty(properties, aliases, "IsApproved", IsApproved); + EnsureMemberProperty(properties, aliases, "IsLockedOut", IsLockedOut); + EnsureMemberProperty(properties, aliases, "LastLockoutDate", LastLockoutDate); + EnsureMemberProperty(properties, aliases, "CreateDate", CreateDate); + EnsureMemberProperty(properties, aliases, "LastLoginDate", LastLoginDate); + EnsureMemberProperty(properties, aliases, "LastPasswordChangeDate", LastPasswordChangeDate); + } + + private void EnsureMemberProperty(List properties, List aliases, string alias, object value) + { + // if the property already has a value, nothing to do + if (aliases.Contains(alias)) return; + + // if not a property type, ignore + var propertyType = ContentType.GetPropertyType(alias); + if (propertyType == null) return; + + // create a raw-value property + // note: throws if propertyType variations is not InvariantNeutral + var property = new RawValueProperty(propertyType, this, value); + properties.Add(property); + } + + public override PublishedContentType ContentType => _publishedMemberType; + + public override int Id => _member.Id; + + public override Guid Key => _member.Key; + + public override int TemplateId => throw new NotSupportedException(); + + public override int SortOrder => 0; + + public override string Name => _member.Name; + + public override string UrlName => throw new NotSupportedException(); + + public override string DocumentTypeAlias => _member.ContentTypeAlias; + + public override int DocumentTypeId => _member.ContentType.Id; + + //TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current + public override string WriterName => _member.GetCreatorProfile().Name; + + //TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current + public override string CreatorName => _member.GetCreatorProfile().Name; + + public override int WriterId => _member.CreatorId; + + public override int CreatorId => _member.CreatorId; + + public override string Path => _member.Path; + + public override DateTime CreateDate => _member.CreateDate; + + public override DateTime UpdateDate => _member.UpdateDate; + + public override int Level => _member.Level; + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/PublishedSnapshotServiceBase.cs b/src/Umbraco.Web/PublishedCache/PublishedSnapshotServiceBase.cs new file mode 100644 index 0000000000..08a754153c --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/PublishedSnapshotServiceBase.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Cache; + +namespace Umbraco.Web.PublishedCache +{ + abstract class PublishedSnapshotServiceBase : IPublishedSnapshotService + { + protected PublishedSnapshotServiceBase(IPublishedSnapshotAccessor publishedSnapshotAccessor) + { + PublishedSnapshotAccessor = publishedSnapshotAccessor; + } + + public IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; } + + // note: NOT setting _publishedSnapshotAccessor.PublishedSnapshot here because it is the + // responsibility of the caller to manage what the 'current' facade is + public abstract IPublishedShapshot CreatePublishedSnapshot(string previewToken); + + protected IPublishedShapshot CurrentPublishedShapshot => PublishedSnapshotAccessor.PublishedSnapshot; + + public abstract bool EnsureEnvironment(out IEnumerable errors); + + public abstract string EnterPreview(IUser user, int contentId); + public abstract void RefreshPreview(string previewToken, int contentId); + public abstract void ExitPreview(string previewToken); + public abstract void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged); + public abstract void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged); + public abstract void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads); + public abstract void Notify(DataTypeCacheRefresher.JsonPayload[] payloads); + public abstract void Notify(DomainCacheRefresher.JsonPayload[] payloads); + + public virtual void Dispose() + { } + } +} diff --git a/src/Umbraco.Web/PublishedCache/RawValueProperty.cs b/src/Umbraco.Web/PublishedCache/RawValueProperty.cs deleted file mode 100644 index 5fa95f127b..0000000000 --- a/src/Umbraco.Web/PublishedCache/RawValueProperty.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Web.PublishedCache -{ - /// - /// A published property base that uses a raw object value - /// - internal class RawValueProperty : PublishedPropertyBase - { - private readonly object _dbVal; //the value in the db - private readonly Lazy _sourceValue; - private readonly Lazy _objectValue; - private readonly Lazy _xpathValue; - - /// - /// Gets the raw value of the property. - /// - public override object DataValue { get { return _dbVal; } } - - public override bool HasValue - { - get { return _dbVal != null && _dbVal.ToString().Trim().Length > 0; } - } - - public override object Value { get { return _objectValue.Value; } } - public override object XPathValue { get { return _xpathValue.Value; } } - - public RawValueProperty(PublishedPropertyType propertyType, object propertyData) - : this(propertyType) - { - if (propertyData == null) - throw new ArgumentNullException("propertyData"); - _dbVal = propertyData; - } - - public RawValueProperty(PublishedPropertyType propertyType) - : base(propertyType) - { - _dbVal = null; - _sourceValue = new Lazy(() => PropertyType.ConvertDataToSource(_dbVal, false)); - _objectValue = new Lazy(() => PropertyType.ConvertSourceToObject(_sourceValue.Value, false)); - _xpathValue = new Lazy(() => PropertyType.ConvertSourceToXPath(_sourceValue.Value, false)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs b/src/Umbraco.Web/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs new file mode 100644 index 0000000000..cc4b09f657 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs @@ -0,0 +1,29 @@ +using System; + +namespace Umbraco.Web.PublishedCache +{ + public class UmbracoContextPublishedSnapshotAccessor : IPublishedSnapshotAccessor + { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public UmbracoContextPublishedSnapshotAccessor(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor; + } + + public IPublishedShapshot PublishedSnapshot + { + get + { + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + if (umbracoContext == null) throw new Exception("The IUmbracoContextAccessor could not provide an UmbracoContext."); + return umbracoContext.PublishedShapshot; + } + + set + { + throw new NotSupportedException(); // not ok to set + } + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DomainCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DomainCache.cs new file mode 100644 index 0000000000..83651a9986 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DomainCache.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Web.Routing; +using Umbraco.Core; +using Umbraco.Core.Services; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + class DomainCache : IDomainCache + { + private readonly IDomainService _domainService; + + public DomainCache(IDomainService domainService) + { + _domainService = domainService; + } + + public IEnumerable GetAll(bool includeWildcards) + { + return _domainService.GetAll(includeWildcards) + .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) + .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard)); + } + + public IEnumerable GetAssigned(int contentId, bool includeWildcards) + { + return _domainService.GetAssignedDomains(contentId, includeWildcards) + .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) + .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard)); + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PreviewContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PreviewContent.cs new file mode 100644 index 0000000000..36b38b11ac --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PreviewContent.cs @@ -0,0 +1,163 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Web.Composing; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + class PreviewContent + { + private readonly int _userId; + private readonly Guid _previewSet; + private string _previewSetPath; + private XmlDocument _previewXml; + private readonly XmlStore _xmlStore; + + /// + /// Gets the XML document. + /// + /// May return null if the preview content set is invalid. + public XmlDocument XmlContent + { + get + { + // null if invalid preview content + if (_previewSetPath == null) return null; + + // load if not loaded yet + if (_previewXml != null) + return _previewXml; + + _previewXml = new XmlDocument(); + + try + { + _previewXml.Load(_previewSetPath); + } + catch (Exception ex) + { + Current.Logger.Error($"Could not load preview set {_previewSet} for user {_userId}.", ex); + + ClearPreviewSet(); + + _previewXml = null; + _previewSetPath = null; // do not try again + } + + return _previewXml; + } + } + + /// + /// Gets the preview token. + /// + /// To be stored in a cookie or wherever appropriate. + public string Token => _userId + ":" + _previewSet; + + /// + /// Initializes a new instance of the class for a user. + /// + /// The underlying Xml store. + /// The user identifier. + public PreviewContent(XmlStore xmlStore, int userId) + { + if (xmlStore == null) + throw new ArgumentNullException(nameof(xmlStore)); + _xmlStore = xmlStore; + + _userId = userId; + _previewSet = Guid.NewGuid(); + _previewSetPath = GetPreviewSetPath(_userId, _previewSet); + } + + /// + /// Initializes a new instance of the with a preview token. + /// + /// The underlying Xml store. + /// The preview token. + public PreviewContent(XmlStore xmlStore, string token) + { + if (xmlStore == null) + throw new ArgumentNullException(nameof(xmlStore)); + _xmlStore = xmlStore; + + if (token.IsNullOrWhiteSpace()) + throw new ArgumentException("Null or empty token.", nameof(token)); + var parts = token.Split(':'); + if (parts.Length != 2) + throw new ArgumentException("Invalid token.", nameof(token)); + + if (int.TryParse(parts[0], out _userId) == false) + throw new ArgumentException("Invalid token.", nameof(token)); + if (Guid.TryParse(parts[1], out _previewSet) == false) + throw new ArgumentException("Invalid token.", nameof(token)); + + _previewSetPath = GetPreviewSetPath(_userId, _previewSet); + } + + // creates and saves a new preview set + // used in 2 places and each time includeSubs is true + // have to use the Document class at the moment because IContent does not do ToXml... + public void CreatePreviewSet(int contentId, bool includeSubs) + { + // note: always include subs + _previewXml = _xmlStore.GetPreviewXml(contentId, includeSubs); + + // make sure the preview folder exists + var dir = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Preview)); + if (dir.Exists == false) + dir.Create(); + + // clean old preview sets + ClearPreviewDirectory(_userId, dir); + + // save + _previewXml.Save(_previewSetPath); + } + + // get the full path to the preview set + private static string GetPreviewSetPath(int userId, Guid previewSet) + { + return IOHelper.MapPath(Path.Combine(SystemDirectories.Preview, userId + "_" + previewSet + ".config")); + } + + // deletes files for the user, and files accessed more than one hour ago + private static void ClearPreviewDirectory(int userId, DirectoryInfo dir) + { + var now = DateTime.Now; + var prefix = userId + "_"; + foreach (var file in dir.GetFiles("*.config") + .Where(x => x.Name.StartsWith(prefix) || (now - x.LastAccessTime).TotalMinutes > 1)) + { + DeletePreviewSetFile(userId, file); + } + } + + // delete one preview set file in a safe way + private static void DeletePreviewSetFile(int userId, FileSystemInfo file) + { + try + { + file.Delete(); + } + catch (Exception ex) + { + Current.Logger.Error($"Couldn't delete preview set {file.Name} for user {userId}", ex); + } + } + + /// + /// Deletes the preview set in a safe way. + /// + public void ClearPreviewSet() + { + if (_previewSetPath == null) return; + var previewSetFile = new FileInfo(_previewSetPath); + DeletePreviewSetFile(_userId, previewSetFile); + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index ae612afe09..e44f6864a0 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -1,72 +1,97 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; -using System.Runtime.CompilerServices; using System.Xml; using System.Xml.XPath; using Umbraco.Core.Configuration; using Umbraco.Core; -using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; using Umbraco.Web.Routing; -using umbraco; using System.Linq; -using umbraco.BusinessLogic; -using umbraco.presentation.preview; -using Umbraco.Core.Services; -using GlobalSettings = umbraco.GlobalSettings; +using Umbraco.Core.Cache; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { - internal class PublishedContentCache : IPublishedContentCache + internal class PublishedContentCache : PublishedCacheBase, IPublishedContentCache { - #region Routes cache + private readonly ICacheProvider _cacheProvider; + private readonly IGlobalSettings _globalSettings; + private readonly RoutesCache _routesCache; + private readonly IDomainCache _domainCache; + private readonly DomainHelper _domainHelper; + private readonly PublishedContentTypeCache _contentTypeCache; - private readonly RoutesCache _routesCache = new RoutesCache(!UnitTesting); - - private DomainHelper _domainHelper; - - private DomainHelper GetDomainHelper(IDomainService domainService) + // initialize a PublishedContentCache instance with + // an XmlStore containing the master xml + // an ICacheProvider that should be at request-level + // a RoutesCache - need to cleanup that one + // a preview token string (or null if not previewing) + public PublishedContentCache( + XmlStore xmlStore, // an XmlStore containing the master xml + IDomainCache domainCache, // an IDomainCache implementation + ICacheProvider cacheProvider, // an ICacheProvider that should be at request-level + IGlobalSettings globalSettings, + PublishedContentTypeCache contentTypeCache, // a PublishedContentType cache + RoutesCache routesCache, // a RoutesCache + string previewToken) // a preview token string (or null if not previewing) + : base(previewToken.IsNullOrWhiteSpace() == false) { - return _domainHelper ?? (_domainHelper = new DomainHelper(domainService)); + _cacheProvider = cacheProvider; + _globalSettings = globalSettings; + _routesCache = routesCache; // may be null for unit-testing + _contentTypeCache = contentTypeCache; + _domainCache = domainCache; + _domainHelper = new DomainHelper(_domainCache); + + _xmlStore = xmlStore; + _xml = _xmlStore.Xml; // capture - because the cache has to remain consistent + + if (previewToken.IsNullOrWhiteSpace() == false) + _previewContent = new PreviewContent(_xmlStore, previewToken); } - // for INTERNAL, UNIT TESTS use ONLY - internal RoutesCache RoutesCache { get { return _routesCache; } } + #region Unit Tests // for INTERNAL, UNIT TESTS use ONLY - internal static bool UnitTesting = false; + internal RoutesCache RoutesCache => _routesCache; - public virtual IPublishedContent GetByRoute(UmbracoContext umbracoContext, bool preview, string route, bool? hideTopLevelNode = null) + // for INTERNAL, UNIT TESTS use ONLY + internal XmlStore XmlStore => _xmlStore; + + #endregion + + #region Routes + + public virtual IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null) { - if (route == null) throw new ArgumentNullException("route"); + if (route == null) throw new ArgumentNullException(nameof(route)); // try to get from cache if not previewing - var contentId = preview ? 0 : _routesCache.GetNodeId(route); + var contentId = preview || _routesCache == null ? 0 : _routesCache.GetNodeId(route); // if found id in cache then get corresponding content // and clear cache if not found - for whatever reason IPublishedContent content = null; if (contentId > 0) { - content = GetById(umbracoContext, preview, contentId); + content = GetById(preview, contentId); if (content == null) - _routesCache.ClearNode(contentId); + _routesCache?.ClearNode(contentId); } // still have nothing? actually determine the id - hideTopLevelNode = hideTopLevelNode ?? GlobalSettings.HideTopLevelNodeFromPath; // default = settings - content = content ?? DetermineIdByRoute(umbracoContext, preview, route, hideTopLevelNode.Value); + hideTopLevelNode = hideTopLevelNode ?? _globalSettings.HideTopLevelNodeFromPath; // default = settings + content = content ?? DetermineIdByRoute(preview, route, hideTopLevelNode.Value); // cache if we have a content and not previewing - if (content != null && preview == false) - AddToCacheIfDeepestRoute(umbracoContext, content, route); + if (content != null && preview == false && _routesCache != null) + AddToCacheIfDeepestRoute(content, route); return content; } - private void AddToCacheIfDeepestRoute(UmbracoContext umbracoContext, IPublishedContent content, string route) + private void AddToCacheIfDeepestRoute(IPublishedContent content, string route) { var domainRootNodeId = route.StartsWith("/") ? -1 : int.Parse(route.Substring(0, route.IndexOf('/'))); @@ -76,24 +101,28 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // that would be returned - the "deepest" route - and that is the route we want to cache, *not* the // longer one - so make sure we don't cache the wrong route - var deepest = UnitTesting == false - && DomainHelper.ExistsDomainInPath(umbracoContext.Application.Services.DomainService.GetAll(false), content.Path, domainRootNodeId) == false; + var deepest = DomainHelper.ExistsDomainInPath(_domainCache.GetAll(false), content.Path, domainRootNodeId) == false; if (deepest) _routesCache.Store(content.Id, route, true); // trusted route } - public virtual string GetRouteById(UmbracoContext umbracoContext, bool preview, int contentId) + public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null) + { + return GetByRoute(PreviewDefault, route, hideTopLevelNode); + } + + public virtual string GetRouteById(bool preview, int contentId) { // try to get from cache if not previewing - var route = preview ? null : _routesCache.GetRoute(contentId); + var route = preview || _routesCache == null ? null : _routesCache.GetRoute(contentId); // if found in cache then return if (route != null) return route; // else actually determine the route - route = DetermineRouteById(umbracoContext, preview, contentId); + route = DetermineRouteById(preview, contentId); // node not found if (route == null) @@ -108,20 +137,23 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return route; } - IPublishedContent DetermineIdByRoute(UmbracoContext umbracoContext, bool preview, string route, bool hideTopLevelNode) + public string GetRouteById(int contentId) { - if (route == null) throw new ArgumentNullException("route"); + return GetRouteById(PreviewDefault, contentId); + } + IPublishedContent DetermineIdByRoute(bool preview, string route, bool hideTopLevelNode) + { //the route always needs to be lower case because we only store the urlName attribute in lower case - route = route.ToLowerInvariant(); + route = route?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(route)); var pos = route.IndexOf('/'); var path = pos == 0 ? route : route.Substring(pos); var startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos)); //check if we can find the node in our xml cache - var id = NavigateRoute(umbracoContext, preview, startNodeId, path, hideTopLevelNode); - return id > 0 ? GetById(umbracoContext, preview, id) : null; + var id = NavigateRoute(preview, startNodeId, path, hideTopLevelNode); + return id > 0 ? GetById(preview, id) : null; } private static XmlElement GetXmlElementChildWithLowestSortOrder(XmlNode element) @@ -142,9 +174,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return elt; } - private int NavigateRoute(UmbracoContext umbracoContext, bool preview, int startNodeId, string path, bool hideTopLevelNode) + private int NavigateRoute(bool preview, int startNodeId, string path, bool hideTopLevelNode) { - var xml = GetXml(umbracoContext, preview); + var xml = GetXml(preview); XmlElement elt; // empty path @@ -188,11 +220,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return NavigateElementRoute(elt, urlParts); } - private static bool UseLegacySchema - { - get { return UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema; } - } - private static int NavigateElementRoute(XmlElement elt, string[] urlParts) { var found = true; @@ -208,9 +235,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var child = o as XmlElement; if (child == null) continue; - var noNode = UseLegacySchema - ? child.Name != "node" - : child.GetAttributeNode("isDoc") == null; + var noNode = child.GetAttributeNode("isDoc") == null; if (noNode) continue; if (child.GetAttribute("urlName") != urlParts[i]) continue; @@ -227,41 +252,36 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return found ? int.Parse(elt.GetAttribute("id")) : -1; } - string DetermineRouteById(UmbracoContext umbracoContext, bool preview, int contentId) + string DetermineRouteById(bool preview, int contentId) { - var xml = GetXml(umbracoContext, preview); - var elt = xml.GetElementById(contentId.ToString(CultureInfo.InvariantCulture)); - if (elt == null) return null; - - var domainHelper = GetDomainHelper(umbracoContext.Application.Services.DomainService); + var node = GetById(preview, contentId); + if (node == null) return null; // walk up from that node until we hit a node with a domain, // or we reach the content root, collecting urls in the way var pathParts = new List(); - var eltId = int.Parse(elt.GetAttribute("id")); - var eltParentId = int.Parse(((XmlElement) elt.ParentNode).GetAttribute("id")); - var e = elt; - var id = eltId; - var hasDomains = domainHelper.NodeHasDomains(id); - while (hasDomains == false && id != -1) + var n = node; + var hasDomains = _domainHelper.NodeHasDomains(n.Id); + while (hasDomains == false && n != null) // n is null at root { // get the url - var urlName = e.GetAttribute("urlName"); + var urlName = n.UrlName; pathParts.Add(urlName); // move to parent node - e = (XmlElement) e.ParentNode; - id = int.Parse(e.GetAttribute("id"), CultureInfo.InvariantCulture); - hasDomains = id != -1 && domainHelper.NodeHasDomains(id); + n = n.Parent; + hasDomains = n != null && _domainHelper.NodeHasDomains(n.Id); } // no domain, respect HideTopLevelNodeFromPath for legacy purposes - if (hasDomains == false && GlobalSettings.HideTopLevelNodeFromPath) + if (hasDomains == false && _globalSettings.HideTopLevelNodeFromPath) { - if (eltParentId == -1) + if (node.Parent == null) { - var rootNode = GetXmlElementChildWithLowestSortOrder(xml.DocumentElement); - if (rootNode != null && rootNode == elt) + var rootNode = GetByRoute(preview, "/", true); + if (rootNode == null) + throw new Exception("Failed to get node at /."); + if (rootNode.Id == node.Id) // remove only if we're the default node pathParts.RemoveAt(pathParts.Count - 1); } else @@ -273,7 +293,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // assemble the route pathParts.Reverse(); var path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc - var route = (id == -1 ? "" : id.ToString(CultureInfo.InvariantCulture)) + path; + var route = (n?.Id.ToString(CultureInfo.InvariantCulture) ?? "") + path; return route; } @@ -282,212 +302,249 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache #region XPath Strings - class XPathStringsDefinition - { - public int Version { get; private set; } + static class XPathStrings + { + public const string Root = "/root"; + public const string RootDocuments = "/root/* [@isDoc]"; + } - public string RootDocuments { get; private set; } - - public XPathStringsDefinition(int version) - { - Version = version; - - switch (version) - { - // legacy XML schema - case 0: - RootDocuments = "/root/node"; - break; - - // default XML schema as of 4.10 - case 1: - RootDocuments = "/root/* [@isDoc]"; - break; - - default: - throw new Exception(string.Format("Unsupported Xml schema version '{0}').", version)); - } - } - } - - static XPathStringsDefinition _xPathStringsValue; - static XPathStringsDefinition XPathStrings - { - get - { - // in theory XPathStrings should be a static variable that - // we should initialize in a static ctor - but then test cases - // that switch schemas fail - so cache and refresh when needed, - // ie never when running the actual site - - var version = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? 0 : 1; - if (_xPathStringsValue == null || _xPathStringsValue.Version != version) - _xPathStringsValue = new XPathStringsDefinition(version); - return _xPathStringsValue; - } - } - - #endregion + #endregion #region Converters - private static IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing) - { - return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing); - } + private IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing) + { + return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing, _cacheProvider, _contentTypeCache); + } - private static IEnumerable ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing) + private IEnumerable ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing) { return xmlNodes.Cast() - .Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing)); + .Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing, _cacheProvider, _contentTypeCache)); } #endregion #region Getters - public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int nodeId) - { - return ConvertToDocument(GetXml(umbracoContext, preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)), preview); - } - - public virtual IEnumerable GetAtRoot(UmbracoContext umbracoContext, bool preview) + public override IPublishedContent GetById(bool preview, int nodeId) { - return ConvertToDocuments(GetXml(umbracoContext, preview).SelectNodes(XPathStrings.RootDocuments), preview); - } + return ConvertToDocument(GetXml(preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)), preview); + } - public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, string xpath, params XPathVariable[] vars) + public override IPublishedContent GetById(bool preview, Guid nodeId) { - if (xpath == null) throw new ArgumentNullException("xpath"); + // implement this, but in a more efficient way + //const string xpath = "//* [@isDoc and @key=$guid]"; + //return GetSingleByXPath(preview, xpath, new[] { new XPathVariable("guid", nodeId.ToString()) }); + + var keyMatch = nodeId.ToString(); + + var nav = GetXml(preview).CreateNavigator(); + if (nav.MoveToFirstChild() == false) return null; // from / to /root + if (nav.MoveToFirstChild() == false) return null; // from /root to /root/* + + while (true) + { + var isDoc = false; + string key = null; + + if (nav.HasAttributes) + { + nav.MoveToFirstAttribute(); + do + { + if (nav.Name == "isDoc") isDoc = true; + if (nav.Name == "key") key = nav.Value; + if (isDoc && key != null) break; + } while (nav.MoveToNextAttribute()); + nav.MoveToParent(); + } + + if (isDoc == false || key != keyMatch) + { + if (isDoc && nav.MoveToFirstChild()) + continue; + while (nav.MoveToNext(XPathNodeType.Element) == false) + if (nav.MoveToParent() == false || nav.NodeType == XPathNodeType.Root) return null; + continue; + } + + var elt = nav.UnderlyingObject as XmlNode; + return ConvertToDocument(elt, preview); + } + } + + public override bool HasById(bool preview, int contentId) + { + return GetXml(preview).CreateNavigator().MoveToId(contentId.ToString(CultureInfo.InvariantCulture)); + } + + public override IEnumerable GetAtRoot(bool preview) + { + return ConvertToDocuments(GetXml(preview).SelectNodes(XPathStrings.RootDocuments), preview); + } + + public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) + { + if (xpath == null) throw new ArgumentNullException(nameof(xpath)); if (string.IsNullOrWhiteSpace(xpath)) return null; - var xml = GetXml(umbracoContext, preview); + var xml = GetXml(preview); var node = vars == null ? xml.SelectSingleNode(xpath) : xml.SelectSingleNode(xpath, vars); return ConvertToDocument(node, preview); } - public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, params XPathVariable[] vars) + public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) { - if (xpath == null) throw new ArgumentNullException("xpath"); + if (xpath == null) throw new ArgumentNullException(nameof(xpath)); - var xml = GetXml(umbracoContext, preview); + var xml = GetXml(preview); var node = vars == null ? xml.SelectSingleNode(xpath) : xml.SelectSingleNode(xpath, vars); return ConvertToDocument(node, preview); } - public virtual IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, string xpath, params XPathVariable[] vars) + public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) { - if (xpath == null) throw new ArgumentNullException("xpath"); + if (xpath == null) throw new ArgumentNullException(nameof(xpath)); if (string.IsNullOrWhiteSpace(xpath)) return Enumerable.Empty(); - var xml = GetXml(umbracoContext, preview); + var xml = GetXml(preview); var nodes = vars == null ? xml.SelectNodes(xpath) : xml.SelectNodes(xpath, vars); return ConvertToDocuments(nodes, preview); } - public virtual IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, params XPathVariable[] vars) + public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) { - if (xpath == null) throw new ArgumentNullException("xpath"); + if (xpath == null) throw new ArgumentNullException(nameof(xpath)); - var xml = GetXml(umbracoContext, preview); + var xml = GetXml(preview); var nodes = vars == null ? xml.SelectNodes(xpath) : xml.SelectNodes(xpath, vars); return ConvertToDocuments(nodes, preview); } - public virtual bool HasContent(UmbracoContext umbracoContext, bool preview) + public override bool HasContent(bool preview) { - var xml = GetXml(umbracoContext, preview); - if (xml == null) - return false; - var node = xml.SelectSingleNode(XPathStrings.RootDocuments); - return node != null; + var xml = GetXml(preview); + var node = xml?.SelectSingleNode(XPathStrings.RootDocuments); + return node != null; } - public virtual XPathNavigator GetXPathNavigator(UmbracoContext umbracoContext, bool preview) + public override XPathNavigator CreateNavigator(bool preview) { - var xml = GetXml(umbracoContext, preview); + var xml = GetXml(preview); return xml.CreateNavigator(); } - public virtual bool XPathNavigatorIsNavigable { get { return false; } } + public override XPathNavigator CreateNodeNavigator(int id, bool preview) + { + // hackish - backward compatibility ;-( + + XPathNavigator navigator = null; + + if (preview) + { + var node = _xmlStore.GetPreviewXmlNode(id); + if (node != null) + { + navigator = node.CreateNavigator(); + } + } + else + { + var node = GetXml(false).GetElementById(id.ToInvariantString()); + if (node != null) + { + var doc = new XmlDocument(); + var clone = doc.ImportNode(node, false); + var props = node.SelectNodes("./* [not(@id)]"); + if (props == null) throw new Exception("oops"); + foreach (var n in props.Cast()) + clone.AppendChild(doc.ImportNode(n, true)); + navigator = node.CreateNavigator(); + } + } + + return navigator; + } #endregion #region Legacy Xml - static readonly ConditionalWeakTable PreviewContentCache - = new ConditionalWeakTable(); + private readonly XmlStore _xmlStore; + private XmlDocument _xml; + private readonly PreviewContent _previewContent; - private Func _xmlDelegate; - - /// - /// Gets/sets the delegate used to retrieve the Xml content, generally the setter is only used for unit tests - /// and by default if it is not set will use the standard delegate which ONLY works when in the context an Http Request - /// - /// - /// If not defined, we will use the standard delegate which ONLY works when in the context an Http Request - /// mostly because the 'content' object heavily relies on HttpContext, SQL connections and a bunch of other stuff - /// that when run inside of a unit test fails. - /// - internal Func GetXmlDelegate + internal XmlDocument GetXml(bool preview) { - get + // not trying to be thread-safe here, that's not the point + + if (preview == false) { - return _xmlDelegate ?? (_xmlDelegate = (context, preview) => - { - if (preview) - { - var previewContent = PreviewContentCache.GetOrCreateValue(context); // will use the ctor with no parameters - previewContent.EnsureInitialized(context.UmbracoUser, StateHelper.Cookies.Preview.GetValue(), true, () => - { - if (previewContent.ValidPreviewSet) - previewContent.LoadPreviewset(); - }); - if (previewContent.ValidPreviewSet) - return previewContent.XmlContent; - } - return content.Instance.XmlContent; - }); - } - set - { - _xmlDelegate = value; + // if there's a current enlisted reader/writer, use its xml + var tempXml = _xmlStore.TempXml; + if (tempXml != null) return tempXml; + return _xml; } + + // Xml cache does not support retrieving preview content when not previewing + if (_previewContent == null) + throw new InvalidOperationException("Cannot retrieve preview content when not previewing."); + + // PreviewContent tries to load the Xml once and if it fails, + // it invalidates itself and always return null for XmlContent. + var previewXml = _previewContent.XmlContent; + return previewXml ?? _xml; } - internal XmlDocument GetXml(UmbracoContext umbracoContext, bool preview) + internal void Resync(XmlDocument xml) { - var xml = GetXmlDelegate(umbracoContext, preview); - if (xml == null) - throw new Exception("The Xml cache is corrupt. Use the Health Check data integrity dashboard to fix it."); - return xml; + _xml = xml; // re-capture + + // note: we're not resyncing "preview" because that would mean re-building the whole + // preview set which is costly, so basically when previewing, there will be no resync. + + // clear recursive properties cached by XmlPublishedContent.GetProperty + // assume that nothing else is going to cache IPublishedProperty items (else would need to do ByKeySearch) + // NOTE also clears all the media cache properties, which is OK (see media cache) + _cacheProvider.ClearCacheObjectTypes(); + //_cacheProvider.ClearCacheByKeySearch("XmlPublishedCache.PublishedContentCache:RecursiveProperty-"); } #endregion #region XPathQuery - static readonly char[] SlashChar = new[] { '/' }; + static readonly char[] SlashChar = { '/' }; #endregion - #region Detached + #region Content types - public IPublishedProperty CreateDetachedProperty(PublishedPropertyType propertyType, object value, bool isPreviewing) + public override PublishedContentType GetContentType(int id) { - if (propertyType.IsDetachedOrNested == false) - throw new ArgumentException("Property type is neither detached nor nested.", "propertyType"); - return new XmlPublishedProperty(propertyType, isPreviewing, value.ToString()); + return _contentTypeCache.Get(PublishedItemType.Content, id); + } + + public override PublishedContentType GetContentType(string alias) + { + return _contentTypeCache.Get(PublishedItemType.Content, alias); + } + + public override IEnumerable GetByContentType(PublishedContentType contentType) + { + throw new NotImplementedException(); } #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index fb40905a03..fc8afe4939 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Configuration; @@ -7,23 +7,21 @@ using System.Linq; using System.Threading; using System.Xml.XPath; using Examine; +using Examine.LuceneEngine.Providers; using Examine.LuceneEngine.SearchCriteria; using Examine.Providers; -using Lucene.Net.Documents; using Lucene.Net.Store; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Dynamics; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; using Umbraco.Web.Models; -using UmbracoExamine; +using Umbraco.Examine; using umbraco; using Umbraco.Core.Cache; -using Umbraco.Core.Sync; -using Umbraco.Web.Cache; +using Umbraco.Core.Services; +using Umbraco.Web.Composing; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { @@ -33,29 +31,52 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// /// NOTE: In the future if we want to properly cache all media this class can be extended or replaced when these classes/interfaces are exposed publicly. /// - internal class PublishedMediaCache : IPublishedMediaCache + internal class PublishedMediaCache : PublishedCacheBase, IPublishedMediaCache { - public PublishedMediaCache(ApplicationContext applicationContext) + private readonly IMediaService _mediaService; + private readonly IUserService _userService; + + // by default these are null unless specified by the ctor dedicated to tests + // when they are null the cache derives them from the ExamineManager, see + // method GetExamineManagerSafe(). + // + private readonly ISearcher _searchProvider; + private readonly IIndexer _indexProvider; + private readonly XmlStore _xmlStore; + private readonly PublishedContentTypeCache _contentTypeCache; + + // must be specified by the ctor + private readonly ICacheProvider _cacheProvider; + + public PublishedMediaCache(XmlStore xmlStore, IMediaService mediaService, IUserService userService, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache) + : base(false) { - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); - _applicationContext = applicationContext; + _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); + + _cacheProvider = cacheProvider; + _xmlStore = xmlStore; + _contentTypeCache = contentTypeCache; } /// /// Generally used for unit testing to use an explicit examine searcher /// - /// + /// + /// /// /// - internal PublishedMediaCache(ApplicationContext applicationContext, BaseSearchProvider searchProvider, BaseIndexProvider indexProvider) + /// + /// + internal PublishedMediaCache(IMediaService mediaService, IUserService userService, ISearcher searchProvider, BaseIndexProvider indexProvider, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache) + : base(false) { - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); - if (searchProvider == null) throw new ArgumentNullException("searchProvider"); - if (indexProvider == null) throw new ArgumentNullException("indexProvider"); - - _applicationContext = applicationContext; - _searchProvider = searchProvider; - _indexProvider = indexProvider; + _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); + _searchProvider = searchProvider ?? throw new ArgumentNullException(nameof(searchProvider)); + _indexProvider = indexProvider ?? throw new ArgumentNullException(nameof(indexProvider)); + _cacheProvider = cacheProvider; + _contentTypeCache = contentTypeCache; } static PublishedMediaCache() @@ -63,16 +84,22 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache InitializeCacheConfig(); } - private readonly ApplicationContext _applicationContext; - private readonly BaseSearchProvider _searchProvider; - private readonly BaseIndexProvider _indexProvider; - - public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int nodeId) + public override IPublishedContent GetById(bool preview, int nodeId) { return GetUmbracoMedia(nodeId); } - public virtual IEnumerable GetAtRoot(UmbracoContext umbracoContext, bool preview) + public override IPublishedContent GetById(bool preview, Guid nodeId) + { + throw new NotImplementedException(); + } + + public override bool HasById(bool preview, int contentId) + { + return GetUmbracoMedia(contentId) != null; + } + + public override IEnumerable GetAtRoot(bool preview) { var searchProvider = GetSearchProviderSafe(); @@ -83,8 +110,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // first check in Examine for the cache values // +(+parentID:-1) +__IndexType:media - var criteria = searchProvider.CreateSearchCriteria("media"); - var filter = criteria.ParentId(-1).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); + var criteria = searchProvider.CreateCriteria("media"); + var filter = criteria.ParentId(-1).Not().Field(UmbracoExamineIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); var result = searchProvider.Search(filter.Compile()); if (result != null) @@ -98,54 +125,103 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache //See this thread: http://examine.cdodeplex.com/discussions/264341 //Catch the exception here for the time being, and just fallback to GetMedia //TODO: Need to fix examine in LB scenarios! - LogHelper.Error("Could not load data from Examine index for media", ex); + Current.Logger.Error("Could not load data from Examine index for media", ex); } else if (ex is AlreadyClosedException) { //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. - LogHelper.Error("Could not load data from Examine index for media, the app domain is most likely in a shutdown state", ex); + Current.Logger.Error("Could not load data from Examine index for media, the app domain is most likely in a shutdown state", ex); } else throw; } - } - - //something went wrong, fetch from the db + } - var rootMedia = _applicationContext.Services.MediaService.GetRootMedia(); - return rootMedia.Select(m => CreateFromCacheValues(ConvertFromIMedia(m))); - } + //something went wrong, fetch from the db - public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, string xpath, XPathVariable[] vars) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); + var rootMedia = _mediaService.GetRootMedia(); + return rootMedia.Select(m => GetUmbracoMedia(m.Id)); } - public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, XPathVariable[] vars) + public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) { throw new NotImplementedException("PublishedMediaCache does not support XPath."); + //var navigator = CreateNavigator(preview); + //var iterator = navigator.Select(xpath, vars); + //return GetSingleByXPath(iterator); } - public virtual IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, string xpath, XPathVariable[] vars) + public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) { throw new NotImplementedException("PublishedMediaCache does not support XPath."); + //var navigator = CreateNavigator(preview); + //var iterator = navigator.Select(xpath, vars); + //return GetSingleByXPath(iterator); } - public virtual IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, XPathVariable[] vars) + private IPublishedContent GetSingleByXPath(XPathNodeIterator iterator) { throw new NotImplementedException("PublishedMediaCache does not support XPath."); + //if (iterator.MoveNext() == false) return null; + //var idAttr = iterator.Current.GetAttribute("id", ""); + //int id; + //return int.TryParse(idAttr, out id) ? GetUmbracoMedia(id) : null; } - public virtual XPathNavigator GetXPathNavigator(UmbracoContext umbracoContext, bool preview) + public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) { throw new NotImplementedException("PublishedMediaCache does not support XPath."); + //var navigator = CreateNavigator(preview); + //var iterator = navigator.Select(xpath, vars); + //return GetByXPath(iterator); } - public bool XPathNavigatorIsNavigable { get { return false; } } + public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) + { + throw new NotImplementedException("PublishedMediaCache does not support XPath."); + //var navigator = CreateNavigator(preview); + //var iterator = navigator.Select(xpath, vars); + //return GetByXPath(iterator); + } - public virtual bool HasContent(UmbracoContext context, bool preview) { throw new NotImplementedException(); } + private IEnumerable GetByXPath(XPathNodeIterator iterator) + { + while (iterator.MoveNext()) + { + var idAttr = iterator.Current.GetAttribute("id", ""); + int id; + if (int.TryParse(idAttr, out id)) + yield return GetUmbracoMedia(id); + } + } - private ExamineManager GetExamineManagerSafe() + public override XPathNavigator CreateNavigator(bool preview) + { + throw new NotImplementedException("PublishedMediaCache does not support XPath."); + //var doc = _xmlStore.GetMediaXml(); + //return doc.CreateNavigator(); + } + + public override XPathNavigator CreateNodeNavigator(int id, bool preview) + { + // preview is ignored for media cache + + // this code is mostly used when replacing old media.ToXml() code, and that code + // stored the XML attached to the media itself - so for some time in memory - so + // unless we implement some sort of cache here, we're probably degrading perfs. + + XPathNavigator navigator = null; + var node = _xmlStore.GetMediaXmlNode(id); + if (node != null) + { + navigator = node.CreateNavigator(); + } + return navigator; + } + + public override bool HasContent(bool preview) { throw new NotImplementedException(); } + + private static IExamineManager GetExamineManagerSafe() { try { @@ -157,64 +233,36 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } - private BaseIndexProvider GetIndexProviderSafe() - { - if (_indexProvider != null) - return _indexProvider; - - var eMgr = GetExamineManagerSafe(); - if (eMgr != null) - { - try - { - //by default use the InternalSearcher - var indexer = eMgr.IndexProviderCollection[Constants.Examine.InternalIndexer]; - if (indexer.IndexerData.IncludeNodeTypes.Any() || indexer.IndexerData.ExcludeNodeTypes.Any()) - { - LogHelper.Warn("The InternalIndexer for examine is configured incorrectly, it should not list any include/exclude node types or field names, it should simply be configured as: " + ""); - } - return indexer; - } - catch (Exception ex) - { - LogHelper.Error("Could not retrieve the InternalIndexer", ex); - //something didn't work, continue returning null. - } - } - return null; - } - - private BaseSearchProvider GetSearchProviderSafe() + private ISearcher GetSearchProviderSafe() { if (_searchProvider != null) return _searchProvider; var eMgr = GetExamineManagerSafe(); - if (eMgr != null) + if (eMgr == null) return null; + + try { - try - { - //by default use the InternalSearcher - return eMgr.SearchProviderCollection[Constants.Examine.InternalSearcher]; - } - catch (FileNotFoundException) - { - //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco - //See this thread: http://examine.cdodeplex.com/discussions/264341 - //Catch the exception here for the time being, and just fallback to GetMedia - //TODO: Need to fix examine in LB scenarios! - } - catch (NullReferenceException) - { - //This will occur when the search provider cannot be initialized. In newer examine versions the initialization is lazy and therefore - // the manager will return the singleton without throwing initialization errors, however if examine isn't configured correctly a null - // reference error will occur because the examine settings are null. - } - catch (AlreadyClosedException) - { - //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot - //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. - } + //by default use the internal index + return eMgr.GetSearcher(Constants.Examine.InternalIndexer); + } + catch (FileNotFoundException) + { + //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco + //See this thread: http://examine.cdodeplex.com/discussions/264341 + //Catch the exception here for the time being, and just fallback to GetMedia + //TODO: Need to fix examine in LB scenarios! + } + catch (NullReferenceException) + { + //This will occur when the search provider cannot be initialized. In newer examine versions the initialization is lazy and therefore + // the manager will return the singleton without throwing initialization errors, however if examine isn't configured correctly a null + // reference error will occur because the examine settings are null. + } + catch (AlreadyClosedException) + { + //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot + //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. } return null; } @@ -247,8 +295,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // // note that since the use of the wildcard, it automatically escapes it in Lucene. - var criteria = searchProvider.CreateSearchCriteria("media"); - var filter = criteria.Id(id).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); + var criteria = searchProvider.CreateCriteria("media"); + var filter = criteria.Id(id.ToInvariantString()).Not().Field(UmbracoExamineIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); var result = searchProvider.Search(filter.Compile()).FirstOrDefault(); if (result != null) return ConvertFromSearchResult(result); @@ -261,13 +309,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache //See this thread: http://examine.cdodeplex.com/discussions/264341 //Catch the exception here for the time being, and just fallback to GetMedia //TODO: Need to fix examine in LB scenarios! - LogHelper.Error("Could not load data from Examine index for media", ex); + Current.Logger.Error("Could not load data from Examine index for media", ex); } else if (ex is AlreadyClosedException) { //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. - LogHelper.Error("Could not load data from Examine index for media, the app domain is most likely in a shutdown state", ex); + Current.Logger.Error("Could not load data from Examine index for media, the app domain is most likely in a shutdown state", ex); } else throw; } @@ -276,8 +324,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // don't log a warning here, as it can flood the log in case of eg a media picker referencing a media // that has been deleted, hence is not in the Examine index anymore (for a good reason). try to get // the media from the service, first - - var media = ApplicationContext.Current.Services.MediaService.GetById(id); + var media = _mediaService.GetById(id); if (media == null || media.Trashed) return null; // not found, ok // so, the media was not found in Examine's index *yet* it exists, which probably indicates that @@ -286,9 +333,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var miss = Interlocked.CompareExchange(ref _examineIndexMiss, 0, 0); // volatile read if (miss < ExamineIndexMissMax && Interlocked.Increment(ref _examineIndexMiss) == ExamineIndexMissMax) - LogHelper.Warn("Failed ({0} times) to retrieve medias from Examine index and had to load" - + " them from DB. This may indicate that the Examine index is corrupted.", - () => ExamineIndexMissMax); + Current.Logger.Warn(() => $"Failed ({ExamineIndexMissMax} times) to retrieve medias from Examine index and had to load" + + " them from DB. This may indicate that the Examine index is corrupted."); return ConvertFromIMedia(media); } @@ -298,80 +344,36 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache internal CacheValues ConvertFromXPathNodeIterator(XPathNodeIterator media, int id) { - if (media != null && media.Current != null) + if (media?.Current != null) { return media.Current.Name.InvariantEquals("error") ? null : ConvertFromXPathNavigator(media.Current); } - LogHelper.Warn( - "Could not retrieve media {0} from Examine index or from legacy library.GetMedia method", - () => id); + Current.Logger.Warn(() => + $"Could not retrieve media {id} from Examine index or from legacy library.GetMedia method"); return null; } internal CacheValues ConvertFromSearchResult(SearchResult searchResult) { - //NOTE: Some fields will not be included if the config section for the internal index has been - //mucked around with. It should index everything and so the index definition should simply be: - // - - - var values = new Dictionary(searchResult.Fields); - //we need to ensure some fields exist, because of the above issue - if (!new[] { "template", "templateId" }.Any(values.ContainsKey)) - values.Add("template", 0.ToString()); - if (!new[] { "sortOrder" }.Any(values.ContainsKey)) - values.Add("sortOrder", 0.ToString()); - if (!new[] { "urlName" }.Any(values.ContainsKey)) - values.Add("urlName", ""); - if (!new[] { "nodeType" }.Any(values.ContainsKey)) - values.Add("nodeType", 0.ToString()); - if (!new[] { "creatorName" }.Any(values.ContainsKey)) - values.Add("creatorName", ""); - if (!new[] { "writerID" }.Any(values.ContainsKey)) - values.Add("writerID", 0.ToString()); - if (!new[] { "creatorID" }.Any(values.ContainsKey)) - values.Add("creatorID", 0.ToString()); - if (!new[] { "createDate" }.Any(values.ContainsKey)) - values.Add("createDate", default(DateTime).ToString("yyyy-MM-dd HH:mm:ss")); - if (!new[] { "level" }.Any(values.ContainsKey)) - { - values.Add("level", values["__Path"].Split(',').Length.ToString()); - } - - // because, migration - if (values.ContainsKey("key") == false) - values["key"] = Guid.Empty.ToString(); + // note: fixing fields in 7.x, removed by Shan for 8.0 return new CacheValues { - Values = values, + Values = searchResult.Fields, FromExamine = true }; - - //var content = new DictionaryPublishedContent(values, - // d => d.ParentId != -1 //parent should be null if -1 - // ? GetUmbracoMedia(d.ParentId) - // : null, - // //callback to return the children of the current node - // d => GetChildrenMedia(d.Id), - // GetProperty, - // true); - //return content.CreateModel(); } internal CacheValues ConvertFromXPathNavigator(XPathNavigator xpath, bool forceNav = false) { - if (xpath == null) throw new ArgumentNullException("xpath"); + if (xpath == null) throw new ArgumentNullException(nameof(xpath)); var values = new Dictionary { { "nodeName", xpath.GetAttribute("nodeName", "") } }; - if (!UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema) - { - values["nodeTypeAlias"] = xpath.Name; - } + values["nodeTypeAlias"] = xpath.Name; var result = xpath.SelectChildren(XPathNodeType.Element); //add the attributes e.g. id, parentId etc @@ -380,13 +382,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (result.Current.MoveToFirstAttribute()) { //checking for duplicate keys because of the 'nodeTypeAlias' might already be added above. - if (!values.ContainsKey(result.Current.Name)) + if (values.ContainsKey(result.Current.Name) == false) { values[result.Current.Name] = result.Current.Value; } while (result.Current.MoveToNextAttribute()) { - if (!values.ContainsKey(result.Current.Name)) + if (values.ContainsKey(result.Current.Name) == false) { values[result.Current.Name] = result.Current.Value; } @@ -400,9 +402,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache //add the user props while (result.MoveNext()) { - if (result.Current != null && !result.Current.HasAttributes) + if (result.Current != null && result.Current.HasAttributes == false) { - string value = result.Current.Value; + var value = result.Current.Value; if (string.IsNullOrEmpty(value)) { if (result.Current.HasAttributes || result.Current.SelectChildren(XPathNodeType.Element).Count > 0) @@ -419,23 +421,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache Values = values, XPath = forceNav ? xpath : null // outside of tests we do NOT want to cache the navigator! }; - - //var content = new DictionaryPublishedContent(values, - // d => d.ParentId != -1 //parent should be null if -1 - // ? GetUmbracoMedia(d.ParentId) - // : null, - // //callback to return the children of the current node based on the xml structure already found - // d => GetChildrenMedia(d.Id, xpath), - // GetProperty, - // false); - //return content.CreateModel(); } internal CacheValues ConvertFromIMedia(IMedia media) { var values = new Dictionary(); - var creator = _applicationContext.Services.UserService.GetProfileById(media.CreatorId); + var creator = _userService.GetProfileById(media.CreatorId); var creatorName = creator == null ? "" : creator.Name; values["id"] = media.Id.ToString(); @@ -458,7 +450,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // add the user props foreach (var prop in media.Properties) - values[prop.Alias] = prop.Value == null ? null : prop.Value.ToString(); + values[prop.Alias] = prop.GetValue()?.ToString(); return new CacheValues { @@ -478,7 +470,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache //lets check if the alias does not exist on the document. //NOTE: Examine will not index empty values and we do not output empty XML Elements to the cache - either of these situations // would mean that the property is missing from the collection whether we are getting the value from Examine or from the library media cache. - if (dd.Properties.All(x => x.PropertyTypeAlias.InvariantEquals(alias) == false)) + if (dd.Properties.All(x => x.Alias.InvariantEquals(alias) == false)) { return null; } @@ -487,13 +479,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { //We are going to check for a special field however, that is because in some cases we store a 'Raw' //value in the index such as for xml/html. - var rawValue = dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(UmbracoContentIndexer.RawFieldPrefix + alias)); + var rawValue = dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(UmbracoExamineIndexer.RawFieldPrefix + alias)); return rawValue - ?? dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + ?? dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } //if its not loaded from examine, then just return the property - return dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + return dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } /// @@ -515,28 +507,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache try { //first check in Examine as this is WAY faster - var criteria = searchProvider.CreateSearchCriteria("media"); + var criteria = searchProvider.CreateCriteria("media"); - var filter = criteria.ParentId(parentId).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); + var filter = criteria.ParentId(parentId).Not().Field(UmbracoExamineIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); //the above filter will create a query like this, NOTE: That since the use of the wildcard, it automatically escapes it in Lucene. //+(+parentId:3113 -__Path:-1,-21,*) +__IndexType:media - ISearchResults results; - - //we want to check if the indexer for this searcher has "sortOrder" flagged as sortable. - //if so, we'll use Lucene to do the sorting, if not we'll have to manually sort it (slower). - var indexer = GetIndexProviderSafe(); - var useLuceneSort = indexer != null && indexer.IndexerData.StandardFields.Any(x => x.Name.InvariantEquals("sortOrder") && x.EnableSorting); - if (useLuceneSort) - { - //we have a sortOrder field declared to be sorted, so we'll use Examine - results = searchProvider.Search( - filter.And().OrderBy(new SortableField("sortOrder", SortType.Int)).Compile()); - } - else - { - results = searchProvider.Search(filter.Compile()); - } + // sort with the Sort field (updated for 8.0) + var results = searchProvider.Search( + filter.And().OrderBy(new SortableField("sortOrder", SortType.Int)).Compile()); if (results.Any()) { @@ -550,16 +529,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return CreateFromCacheValues(cacheValues); }); - return useLuceneSort ? medias : medias.OrderBy(x => x.SortOrder); - } - else - { - //if there's no result then return null. Previously we defaulted back to library.GetMedia below - //but this will always get called for when we are getting descendents since many items won't have - //children and then we are hitting the database again! - //So instead we're going to rely on Examine to have the correct results like it should. - return Enumerable.Empty(); + return medias; } + + //if there's no result then return null. Previously we defaulted back to library.GetMedia below + //but this will always get called for when we are getting descendents since many items won't have + //children and then we are hitting the database again! + //So instead we're going to rely on Examine to have the correct results like it should. + return Enumerable.Empty(); } catch (FileNotFoundException) { @@ -572,7 +549,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache //falling back to get media var media = library.GetMedia(parentId, true); - if (media != null && media.Current != null) + if (media?.Current != null) { xpath = media.Current; } @@ -637,7 +614,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// This is a helper class and definitely not intended for public use, it expects that all of the values required /// to create an IPublishedContent exist in the dictionary by specific aliases. /// - internal class DictionaryPublishedContent : PublishedContentWithKeyBase + internal class DictionaryPublishedContent : PublishedContentBase { // note: I'm not sure this class fully complies with IPublishedContent rules especially // I'm not sure that _properties contains all properties including those without a value, @@ -648,35 +625,37 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private static readonly string[] IgnoredKeys = { "version", "isDoc" }; public DictionaryPublishedContent( - IDictionary valueDictionary, + IReadOnlyDictionary valueDictionary, Func getParent, Func> getChildren, Func getProperty, + ICacheProvider cacheProvider, + PublishedContentTypeCache contentTypeCache, XPathNavigator nav, bool fromExamine) { - if (valueDictionary == null) throw new ArgumentNullException("valueDictionary"); - if (getParent == null) throw new ArgumentNullException("getParent"); - if (getProperty == null) throw new ArgumentNullException("getProperty"); + if (valueDictionary == null) throw new ArgumentNullException(nameof(valueDictionary)); + if (getParent == null) throw new ArgumentNullException(nameof(getParent)); + if (getProperty == null) throw new ArgumentNullException(nameof(getProperty)); _getParent = new Lazy(() => getParent(ParentId)); _getChildren = new Lazy>(() => getChildren(Id, nav)); _getProperty = getProperty; + _cacheProvider = cacheProvider; LoadedFromExamine = fromExamine; ValidateAndSetProperty(valueDictionary, val => _id = int.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int! ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key"); - // wtf are we dealing with templates for medias?! - ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); + //ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); ValidateAndSetProperty(valueDictionary, val => _sortOrder = int.Parse(val), "sortOrder"); ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName", "__nodeName"); ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName"); - ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", UmbracoContentIndexer.NodeTypeAliasFieldName); + ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", LuceneIndexer.ItemTypeFieldName); ValidateAndSetProperty(valueDictionary, val => _documentTypeId = int.Parse(val), "nodeType"); - ValidateAndSetProperty(valueDictionary, val => _writerName = val, "writerName"); + //ValidateAndSetProperty(valueDictionary, val => _writerName = val, "writerName"); ValidateAndSetProperty(valueDictionary, val => _creatorName = val, "creatorName", "writerName"); //this is a bit of a hack fix for: U4-1132 - ValidateAndSetProperty(valueDictionary, val => _writerId = int.Parse(val), "writerID"); + //ValidateAndSetProperty(valueDictionary, val => _writerId = int.Parse(val), "writerID"); ValidateAndSetProperty(valueDictionary, val => _creatorId = int.Parse(val), "creatorID", "writerID"); //this is a bit of a hack fix for: U4-1132 ValidateAndSetProperty(valueDictionary, val => _path = val, "path", "__Path"); ValidateAndSetProperty(valueDictionary, val => _createDate = ParseDateTimeValue(val), "createDate"); @@ -692,20 +671,20 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } }, "parentID"); - _contentType = PublishedContentType.Get(PublishedItemType.Media, _documentTypeAlias); + _contentType = contentTypeCache.Get(PublishedItemType.Media, _documentTypeAlias); _properties = new Collection(); //handle content type properties //make sure we create them even if there's no value foreach (var propertyType in _contentType.PropertyTypes) { - var alias = propertyType.PropertyTypeAlias; + var alias = propertyType.Alias; _keysAdded.Add(alias); string value; const bool isPreviewing = false; // false :: never preview a media var property = valueDictionary.TryGetValue(alias, out value) == false || value == null - ? new XmlPublishedProperty(propertyType, isPreviewing) - : new XmlPublishedProperty(propertyType, isPreviewing, value); + ? new XmlPublishedProperty(propertyType, this, isPreviewing) + : new XmlPublishedProperty(propertyType, this, isPreviewing, value); _properties.Add(property); } @@ -716,171 +695,93 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { if (i.Key.InvariantStartsWith("__")) { - // no type for that one, dunno how to convert - IPublishedProperty property = new PropertyResult(i.Key, i.Value, PropertyResultType.CustomProperty); - _properties.Add(property); + // no type for that one, dunno how to convert, drop it + //IPublishedProperty property = new PropertyResult(i.Key, i.Value, PropertyResultType.CustomProperty); + //_properties.Add(property); } else { // this is a property that does not correspond to anything, ignore and log - LogHelper.Warn("Dropping property \"" + i.Key + "\" because it does not belong to the content type."); + Current.Logger.Warn("Dropping property \"" + i.Key + "\" because it does not belong to the content type."); } } } private DateTime ParseDateTimeValue(string val) { - if (LoadedFromExamine) - { - try - { - //we might need to parse the date time using Lucene converters - return DateTools.StringToDate(val); - } - catch (FormatException) - { - //swallow exception, its not formatted correctly so revert to just trying to parse - } - } + if (LoadedFromExamine == false) + return DateTime.Parse(val); - return DateTime.Parse(val); + //we need to parse the date time using Lucene converters + var ticks = long.Parse(val); + return new DateTime(ticks); } /// /// Flag to get/set if this was laoded from examine cache /// - internal bool LoadedFromExamine { get; private set; } + internal bool LoadedFromExamine { get; } //private readonly Func _getParent; private readonly Lazy _getParent; //private readonly Func> _getChildren; private readonly Lazy> _getChildren; private readonly Func _getProperty; + private readonly ICacheProvider _cacheProvider; /// /// Returns 'Media' as the item type /// - public override PublishedItemType ItemType - { - get { return PublishedItemType.Media; } - } + public override PublishedItemType ItemType => PublishedItemType.Media; - public override IPublishedContent Parent - { - get { return _getParent.Value; } - } + public override IPublishedContent Parent => _getParent.Value; public int ParentId { get; private set; } - public override int Id - { - get { return _id; } - } - public override Guid Key { get { return _key; } } + public override int Id => _id; - public override int TemplateId - { - get - { - //TODO: should probably throw a not supported exception since media doesn't actually support this. - return _templateId; - } - } + public override Guid Key => _key; - public override int SortOrder - { - get { return _sortOrder; } - } + public override int TemplateId => 0; - public override string Name - { - get { return _name; } - } + public override int SortOrder => _sortOrder; - public override string UrlName - { - get { return _urlName; } - } + public override string Name => _name; - public override string DocumentTypeAlias - { - get { return _documentTypeAlias; } - } + public override string UrlName => _urlName; - public override int DocumentTypeId - { - get { return _documentTypeId; } - } + public override string DocumentTypeAlias => _documentTypeAlias; - public override string WriterName - { - get { return _writerName; } - } + public override int DocumentTypeId => _documentTypeId; - public override string CreatorName - { - get { return _creatorName; } - } + public override string WriterName => _creatorName; - public override int WriterId - { - get { return _writerId; } - } + public override string CreatorName => _creatorName; - public override int CreatorId - { - get { return _creatorId; } - } + public override int WriterId => _creatorId; - public override string Path - { - get { return _path; } - } + public override int CreatorId => _creatorId; - public override DateTime CreateDate - { - get { return _createDate; } - } + public override string Path => _path; - public override DateTime UpdateDate - { - get { return _updateDate; } - } + public override DateTime CreateDate => _createDate; - public override Guid Version - { - get { return _version; } - } + public override DateTime UpdateDate => _updateDate; - public override int Level - { - get { return _level; } - } + public override int Level => _level; - public override bool IsDraft - { - get { return false; } - } + public override bool IsDraft => false; - public override ICollection Properties - { - get { return _properties; } - } + public override IEnumerable Properties => _properties; - public override IEnumerable Children - { - get { return _getChildren.Value; } - } + public override IEnumerable Children => _getChildren.Value; public override IPublishedProperty GetProperty(string alias) { return _getProperty(this, alias); } - public override PublishedContentType ContentType - { - get { return _contentType; } - } + public override PublishedContentType ContentType => _contentType; // override to implement cache // cache at context level, ie once for the whole request @@ -889,54 +790,33 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { if (recurse == false) return GetProperty(alias); - IPublishedProperty property; - string key = null; - var cache = UmbracoContextCache.Current; - - if (cache != null) - { - key = string.Format("RECURSIVE_PROPERTY::{0}::{1}", Id, alias.ToLowerInvariant()); - object o; - if (cache.TryGetValue(key, out o)) - { - property = o as IPublishedProperty; - if (property == null) - throw new InvalidOperationException("Corrupted cache."); - return property; - } - } - - // else get it for real, no cache - property = base.GetProperty(alias, true); - - if (cache != null) - cache[key] = property; - - return property; + var key = $"XmlPublishedCache.PublishedMediaCache:RecursiveProperty-{Id}-{alias.ToLowerInvariant()}"; + var cacheProvider = _cacheProvider; + return cacheProvider.GetCacheItem(key, () => base.GetProperty(alias, true)); } private readonly List _keysAdded = new List(); private int _id; private Guid _key; - private int _templateId; + //private int _templateId; private int _sortOrder; private string _name; private string _urlName; private string _documentTypeAlias; private int _documentTypeId; - private string _writerName; + //private string _writerName; private string _creatorName; - private int _writerId; + //private int _writerId; private int _creatorId; private string _path; private DateTime _createDate; private DateTime _updateDate; - private Guid _version; + //private Guid _version; private int _level; private readonly ICollection _properties; private readonly PublishedContentType _contentType; - private void ValidateAndSetProperty(IDictionary valueDictionary, Action setProperty, params string[] potentialKeys) + private void ValidateAndSetProperty(IReadOnlyDictionary valueDictionary, Action setProperty, params string[] potentialKeys) { var key = potentialKeys.FirstOrDefault(x => valueDictionary.ContainsKey(x) && valueDictionary[x] != null); if (key == null) @@ -949,6 +829,34 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } + internal void Resync() + { + // clear recursive properties cached by XmlPublishedContent.GetProperty + // assume that nothing else is going to cache IPublishedProperty items (else would need to do ByKeySearch) + // NOTE all properties cleared when clearing the content cache (see content cache) + //_cacheProvider.ClearCacheObjectTypes(); + //_cacheProvider.ClearCacheByKeySearch("XmlPublishedCache.PublishedMediaCache:RecursiveProperty-"); + } + + #region Content types + + public override PublishedContentType GetContentType(int id) + { + return _contentTypeCache.Get(PublishedItemType.Media, id); + } + + public override PublishedContentType GetContentType(string alias) + { + return _contentTypeCache.Get(PublishedItemType.Media, alias); + } + + public override IEnumerable GetByContentType(PublishedContentType contentType) + { + throw new NotImplementedException(); + } + + #endregion + // REFACTORING // caching the basic atomic values - and the parent id @@ -957,7 +865,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache internal class CacheValues { - public IDictionary Values { get; set; } + public IReadOnlyDictionary Values { get; set; } public XPathNavigator XPath { get; set; } public bool FromExamine { get; set; } } @@ -991,6 +899,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache parentId => parentId < 0 ? null : GetUmbracoMedia(parentId), GetChildrenMedia, GetProperty, + _cacheProvider, + _contentTypeCache, cacheValues.XPath, // though, outside of tests, that should be null cacheValues.FromExamine ); @@ -1002,14 +912,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (_publishedMediaCacheEnabled == false) return func(id); - var cache = ApplicationContext.Current.ApplicationCache.RuntimeCache; + var cache = Current.ApplicationCache.RuntimeCache; var key = PublishedMediaCacheKey + id; return (CacheValues)cache.GetCacheItem(key, () => func(id), _publishedMediaCacheTimespan); } internal static void ClearCache(int id) { - var cache = ApplicationContext.Current.ApplicationCache.RuntimeCache; + var cache = Current.ApplicationCache.RuntimeCache; var sid = id.ToString(); var key = PublishedMediaCacheKey + sid; @@ -1030,7 +940,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache GetValuesValue(v.Values, "path", "__Path").Contains(fid)); } - private static string GetValuesValue(IDictionary d, params string[] keys) + private static string GetValuesValue(IReadOnlyDictionary d, params string[] keys) { string value = null; var ignored = keys.Any(x => d.TryGetValue(x, out value)); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMemberCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMemberCache.cs new file mode 100644 index 0000000000..2328a1cefd --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMemberCache.cs @@ -0,0 +1,148 @@ +using System; +using System.Text; +using System.Xml.XPath; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Web.Security; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + class PublishedMemberCache : IPublishedMemberCache + { + private readonly IMemberService _memberService; + private readonly ICacheProvider _requestCache; + private readonly XmlStore _xmlStore; + private readonly PublishedContentTypeCache _contentTypeCache; + + public PublishedMemberCache(XmlStore xmlStore, ICacheProvider requestCacheProvider, IMemberService memberService, PublishedContentTypeCache contentTypeCache) + { + _requestCache = requestCacheProvider; + _memberService = memberService; + _xmlStore = xmlStore; + _contentTypeCache = contentTypeCache; + } + + public IPublishedContent GetByProviderKey(object key) + { + return _requestCache.GetCacheItem( + GetCacheKey("GetByProviderKey", key), () => + { + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + if (provider.IsUmbracoMembershipProvider() == false) + { + throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); + } + + var result = _memberService.GetByProviderKey(key); + if (result == null) return null; + var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); + return new PublishedMember(result, type).CreateModel(); + }); + } + + public IPublishedContent GetById(int memberId) + { + return _requestCache.GetCacheItem( + GetCacheKey("GetById", memberId), () => + { + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + if (provider.IsUmbracoMembershipProvider() == false) + { + throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); + } + + var result = _memberService.GetById(memberId); + if (result == null) return null; + var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); + return new PublishedMember(result, type).CreateModel(); + }); + } + + public IPublishedContent GetByUsername(string username) + { + return _requestCache.GetCacheItem( + GetCacheKey("GetByUsername", username), () => + { + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + if (provider.IsUmbracoMembershipProvider() == false) + { + throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); + } + + var result = _memberService.GetByUsername(username); + if (result == null) return null; + var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); + return new PublishedMember(result, type).CreateModel(); + }); + } + + public IPublishedContent GetByEmail(string email) + { + return _requestCache.GetCacheItem( + GetCacheKey("GetByEmail", email), () => + { + var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + if (provider.IsUmbracoMembershipProvider() == false) + { + throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); + } + + var result = _memberService.GetByEmail(email); + if (result == null) return null; + var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); + return new PublishedMember(result, type).CreateModel(); + }); + } + + public IPublishedContent GetByMember(IMember member) + { + var type = _contentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId); + return new PublishedMember(member, type).CreateModel(); + } + + public XPathNavigator CreateNavigator() + { + var doc = _xmlStore.GetMemberXml(); + return doc.CreateNavigator(); + } + + public XPathNavigator CreateNavigator(bool preview) + { + return CreateNavigator(); + } + + public XPathNavigator CreateNodeNavigator(int id, bool preview) + { + var n = _xmlStore.GetMemberXmlNode(id); + return n?.CreateNavigator(); + } + + private static string GetCacheKey(string key, params object[] additional) + { + var sb = new StringBuilder($"{typeof (MembershipHelper).Name}-{key}"); + foreach (var s in additional) + { + sb.Append("-"); + sb.Append(s); + } + return sb.ToString(); + } + + #region Content types + + public PublishedContentType GetContentType(int id) + { + return _contentTypeCache.Get(PublishedItemType.Member, id); + } + + public PublishedContentType GetContentType(string alias) + { + return _contentTypeCache.Get(PublishedItemType.Member, alias); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedShapshot.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedShapshot.cs new file mode 100644 index 0000000000..e0417bdebf --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedShapshot.cs @@ -0,0 +1,59 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Cache; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + /// + /// Implements a published snapshot. + /// + class PublishedShapshot : IPublishedShapshot + { + /// + /// Initializes a new instance of the class with a content cache + /// and a media cache. + /// + public PublishedShapshot( + PublishedContentCache contentCache, + PublishedMediaCache mediaCache, + PublishedMemberCache memberCache, + DomainCache domainCache) + { + Content = contentCache; + Media = mediaCache; + Members = memberCache; + Domains = domainCache; + } + + /// + public IPublishedContentCache Content { get; } + + /// + public IPublishedMediaCache Media { get; } + + /// + public IPublishedMemberCache Members { get; } + + /// + public IDomainCache Domains { get; } + + /// + public ICacheProvider SnapshotCache => null; + + /// + public ICacheProvider ElementsCache => null; + + /// + public IDisposable ForcedPreview(bool preview, Action callback = null) + { + // the XML cache does not support forcing preview, really, so, just pretend... + return new ForcedPreviewObject(); + } + + private class ForcedPreviewObject : DisposableObject + { + protected override void DisposeResources() + { } + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedSnapshotService.cs new file mode 100644 index 0000000000..fedff54c29 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedSnapshotService.cs @@ -0,0 +1,257 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Web.Cache; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + /// + /// Implements a published snapshot service. + /// + internal class PublishedSnapshotService : PublishedSnapshotServiceBase + { + private readonly XmlStore _xmlStore; + private readonly RoutesCache _routesCache; + private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; + private readonly PublishedContentTypeCache _contentTypeCache; + private readonly IDomainService _domainService; + private readonly IMemberService _memberService; + private readonly IMediaService _mediaService; + private readonly IUserService _userService; + private readonly ICacheProvider _requestCache; + private readonly IGlobalSettings _globalSettings; + + #region Constructors + + // used in WebBootManager + tests + public PublishedSnapshotService(ServiceContext serviceContext, + IPublishedContentTypeFactory publishedContentTypeFactory, + IScopeProvider scopeProvider, + ICacheProvider requestCache, + IEnumerable segmentProviders, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, + ILogger logger, + IGlobalSettings globalSettings, + MainDom mainDom, + bool testing = false, bool enableRepositoryEvents = true) + : this(serviceContext, publishedContentTypeFactory, scopeProvider, requestCache, segmentProviders, publishedSnapshotAccessor, + documentRepository, mediaRepository, memberRepository, + logger, globalSettings, null, mainDom, testing, enableRepositoryEvents) + { } + + // used in some tests + internal PublishedSnapshotService(ServiceContext serviceContext, + IPublishedContentTypeFactory publishedContentTypeFactory, + IScopeProvider scopeProvider, + ICacheProvider requestCache, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, + ILogger logger, + IGlobalSettings globalSettings, + PublishedContentTypeCache contentTypeCache, + MainDom mainDom, + bool testing, bool enableRepositoryEvents) + : this(serviceContext, publishedContentTypeFactory, scopeProvider, requestCache, Enumerable.Empty(), publishedSnapshotAccessor, + documentRepository, mediaRepository, memberRepository, + logger, globalSettings, contentTypeCache, mainDom, testing, enableRepositoryEvents) + { } + + private PublishedSnapshotService(ServiceContext serviceContext, + IPublishedContentTypeFactory publishedContentTypeFactory, + IScopeProvider scopeProvider, + ICacheProvider requestCache, + IEnumerable segmentProviders, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, + ILogger logger, + IGlobalSettings globalSettings, + PublishedContentTypeCache contentTypeCache, + MainDom mainDom, + bool testing, bool enableRepositoryEvents) + : base(publishedSnapshotAccessor) + { + _routesCache = new RoutesCache(); + _publishedContentTypeFactory = publishedContentTypeFactory; + _contentTypeCache = contentTypeCache + ?? new PublishedContentTypeCache(serviceContext.ContentTypeService, serviceContext.MediaTypeService, serviceContext.MemberTypeService, publishedContentTypeFactory, logger); + + _xmlStore = new XmlStore(serviceContext, scopeProvider, _routesCache, + _contentTypeCache, segmentProviders, publishedSnapshotAccessor, mainDom, testing, enableRepositoryEvents, + documentRepository, mediaRepository, memberRepository, globalSettings); + + _domainService = serviceContext.DomainService; + _memberService = serviceContext.MemberService; + _mediaService = serviceContext.MediaService; + _userService = serviceContext.UserService; + + _requestCache = requestCache; + _globalSettings = globalSettings; + } + + public override void Dispose() + { + _xmlStore.Dispose(); + } + + #endregion + + #region Environment + + public override bool EnsureEnvironment(out IEnumerable errors) + { + // Test creating/saving/deleting a file in the same location as the content xml file + // NOTE: We cannot modify the xml file directly because a background thread is responsible for + // that and we might get lock issues. + try + { + XmlStore.EnsureFilePermission(); + errors = Enumerable.Empty(); + return true; + } + catch + { + errors = new[] { SystemFiles.GetContentCacheXml(_globalSettings) }; + return false; + } + } + + #endregion + + #region Caches + + public override IPublishedShapshot CreatePublishedSnapshot(string previewToken) + { + // use _requestCache to store recursive properties lookup, etc. both in content + // and media cache. Life span should be the current request. Or, ideally + // the current caches, but that would mean creating an extra cache (StaticCache + // probably) so better use RequestCache. + + var domainCache = new DomainCache(_domainService); + + return new PublishedShapshot( + new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _contentTypeCache, _routesCache, previewToken), + new PublishedMediaCache(_xmlStore, _mediaService, _userService, _requestCache, _contentTypeCache), + new PublishedMemberCache(_xmlStore, _requestCache, _memberService, _contentTypeCache), + domainCache); + } + + #endregion + + #region Preview + + public override string EnterPreview(IUser user, int contentId) + { + var previewContent = new PreviewContent(_xmlStore, user.Id); + previewContent.CreatePreviewSet(contentId, true); // preview branch below that content + return previewContent.Token; + //previewContent.ActivatePreviewCookie(); + } + + public override void RefreshPreview(string previewToken, int contentId) + { + if (previewToken.IsNullOrWhiteSpace()) return; + var previewContent = new PreviewContent(_xmlStore, previewToken); + previewContent.CreatePreviewSet(contentId, true); // preview branch below that content + } + + public override void ExitPreview(string previewToken) + { + if (previewToken.IsNullOrWhiteSpace()) return; + var previewContent = new PreviewContent(_xmlStore, previewToken); + previewContent.ClearPreviewSet(); + } + + #endregion + + #region Xml specific + + /// + /// Gets the underlying XML store. + /// + public XmlStore XmlStore => _xmlStore; + + /// + /// Gets the underlying RoutesCache. + /// + public RoutesCache RoutesCache => _routesCache; + + public bool VerifyContentAndPreviewXml() + { + return XmlStore.VerifyContentAndPreviewXml(); + } + + public void RebuildContentAndPreviewXml() + { + XmlStore.RebuildContentAndPreviewXml(); + } + + public bool VerifyMediaXml() + { + return XmlStore.VerifyMediaXml(); + } + + public void RebuildMediaXml() + { + XmlStore.RebuildMediaXml(); + } + + public bool VerifyMemberXml() + { + return XmlStore.VerifyMemberXml(); + } + + public void RebuildMemberXml() + { + XmlStore.RebuildMemberXml(); + } + + #endregion + + #region Change management + + public override void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) + { + _xmlStore.Notify(payloads, out draftChanged, out publishedChanged); + } + + public override void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) + { + foreach (var payload in payloads) + PublishedMediaCache.ClearCache(payload.Id); + + anythingChanged = true; + } + + public override void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) + { + _xmlStore.Notify(payloads); + if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) + _routesCache.Clear(); + } + + public override void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) + { + _publishedContentTypeFactory.NotifyDataTypeChanges(payloads.Select(x => x.Id).ToArray()); + _xmlStore.Notify(payloads); + } + + public override void Notify(DomainCacheRefresher.JsonPayload[] payloads) + { + _routesCache.Clear(); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs index 4e775b3253..f614a410c4 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs @@ -3,59 +3,31 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; -using Umbraco.Core.ObjectResolution; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { + // Note: RoutesCache closely follows the caching strategy dating from v4, which + // is obviously broken in many ways (eg it's a global cache but relying to some + // extend to the content cache, which itself is local to each request...). + // Not going to fix it anyway. + class RoutesCache { private ConcurrentDictionary _routes; private ConcurrentDictionary _nodeIds; + // NOTE + // RoutesCache is cleared by + // - ContentTypeCacheRefresher, whenever anything happens to any content type + // - DomainCacheRefresher, whenever anything happens to any domain + // - XmlStore, whenever anything happens to the XML cache + /// /// Initializes a new instance of the class. /// public RoutesCache() - : this(true) - { } - - /// - /// Initializes a new instance of the class. - /// - internal RoutesCache(bool bindToEvents) - { - Clear(); - - if (bindToEvents) - { - Resolution.Frozen += ResolutionFrozen; - } - } - - /// - /// Once resolution is frozen, then we can bind to the events that we require - /// - /// - /// - private void ResolutionFrozen(object s, EventArgs args) { - // content - whenever the entire XML cache is rebuilt (from disk cache or from database) - // we must clear the cache entirely - global::umbraco.content.AfterRefreshContent += (sender, e) => Clear(); - - // document - whenever a document is updated in, or removed from, the XML cache - // we must clear the cache - at the moment, we clear the entire cache - global::umbraco.content.AfterUpdateDocumentCache += (sender, e) => Clear(); - global::umbraco.content.AfterClearDocumentCache += (sender, e) => Clear(); - - // fixme - should refactor once content events are refactored - // the content class needs to be refactored - at the moment - // content.XmlContentInternal setter does not trigger any event - // content.UpdateDocumentCache(List Documents) does not trigger any event - // content.RefreshContentFromDatabaseAsync triggers AfterRefresh _while_ refreshing - // etc... - // in addition some events do not make sense... we trigger Publish when moving - // a node, which we should not (the node is moved, not published...) etc. + Clear(); } /// @@ -137,7 +109,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _routes = new ConcurrentDictionary(); _nodeIds = new ConcurrentDictionary(); } - + #endregion } } diff --git a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/SafeXmlReaderWriter.cs similarity index 76% rename from src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs rename to src/Umbraco.Web/PublishedCache/XmlPublishedCache/SafeXmlReaderWriter.cs index b42ae55706..ade6cbf30b 100644 --- a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/SafeXmlReaderWriter.cs @@ -1,19 +1,17 @@ -using System; +using System; using System.Xml; using Umbraco.Core; using Umbraco.Core.Scoping; -// ReSharper disable once CheckNamespace -namespace umbraco +namespace Umbraco.Web.PublishedCache.XmlPublishedCache { - // provides safe access to the Xml cache + // fixme should be a ScopeContextualBase internal class SafeXmlReaderWriter : IDisposable { private readonly bool _scoped; private readonly Action _refresh; private readonly Action _apply; private IDisposable _releaser; - private bool _isWriter; private bool _applyChanges; private XmlDocument _xml, _origXml; private bool _using; @@ -22,25 +20,26 @@ namespace umbraco // the default enlist priority is 100 // enlist with a lower priority to ensure that anything "default" has a clean xml private const int EnlistPriority = 60; + private const string EnlistKey = "safeXmlReaderWriter"; private SafeXmlReaderWriter(IDisposable releaser, XmlDocument xml, Action refresh, Action apply, bool isWriter, bool scoped) { _releaser = releaser; _refresh = refresh; _apply = apply; - _isWriter = isWriter; _scoped = scoped; - _xml = _isWriter ? Clone(xml) : xml; + IsWriter = isWriter; + + _xml = IsWriter ? Clone(xml) : xml; } - public static SafeXmlReaderWriter Get(IScopeProviderInternal scopeProvider) + public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider) { - var scopeContext = scopeProvider.Context; - return scopeContext == null ? null : scopeContext.GetEnlisted("safeXmlReaderWriter"); + return scopeProvider?.Context?.GetEnlisted(EnlistKey); } - public static SafeXmlReaderWriter Get(IScopeProviderInternal scopeProvider, AsyncLock xmlLock, XmlDocument xml, Action refresh, Action apply, bool writer) + public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider, AsyncLock xmlLock, XmlDocument xml, Action refresh, Action apply, bool writer) { var scopeContext = scopeProvider.Context; @@ -53,7 +52,7 @@ namespace umbraco } // get or create an enlisted reader/writer - var rw = scopeContext.Enlist("safeXmlReaderWriter", + var rw = scopeContext.Enlist(EnlistKey, () => // creator { // obtain exclusive access to xml and create reader/writer @@ -67,40 +66,41 @@ namespace umbraco // ensure it's not already in-use - should never happen, just being super safe if (rw._using) - throw new InvalidOperationException(); + throw new InvalidOperationException("panic: used."); rw._using = true; return rw; } - public bool IsWriter { get { return _isWriter; } } + public bool IsWriter { get; private set; } - public void UpgradeToWriter() + public void UpgradeToWriter(bool auto) { - if (_isWriter) + if (IsWriter) throw new InvalidOperationException("Already a writer."); - _isWriter = true; + IsWriter = true; _xml = Clone(_xml); } + // for tests internal static Action Cloning { get; set; } private XmlDocument Clone(XmlDocument xml) { - if (Cloning != null) Cloning(); + Cloning?.Invoke(); if (_origXml != null) throw new Exception("panic."); _origXml = xml; - return xml == null ? null : (XmlDocument) xml.CloneNode(true); + return (XmlDocument) xml?.CloneNode(true); } public XmlDocument Xml { - get { return _xml; } + get => _xml; set { - if (_isWriter == false) + if (IsWriter == false) throw new InvalidOperationException("Not a writer."); _xml = value; } @@ -111,7 +111,7 @@ namespace umbraco // to do if we're committing Xml precisely after we've read from disk! public void AcceptChanges(bool registerXmlChange = true) { - if (_isWriter == false) + if (IsWriter == false) throw new InvalidOperationException("Not a writer."); _applyChanges = true; @@ -120,7 +120,7 @@ namespace umbraco private void DisposeForReal(bool completed) { - if (_isWriter) + if (IsWriter) { // apply changes, or restore the original xml for the current request if (_applyChanges && completed) diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs new file mode 100644 index 0000000000..00bb705c1d --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheComponent.cs @@ -0,0 +1,47 @@ +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; +using Umbraco.Web.HealthCheck.Checks.DataIntegrity; +using LightInject; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence.Repositories; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + [DisableComponent] // is not enabled by default + public class XmlCacheComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public override void Compose(Composition composition) + { + base.Compose(composition); + + // register the XML facade service + composition.SetPublishedSnapshotService(factory => new PublishedSnapshotService( + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance().RequestCache, + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance())); + + // add the Xml cache health check (hidden from type finder) + composition.HealthChecks().Add(); + } + + public void Initialize(IPublishedSnapshotService service) + { + // nothing - this just ensures that the service is created at boot time + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs deleted file mode 100644 index 3b9f184d1c..0000000000 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using umbraco; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Web.Scheduling; - -namespace Umbraco.Web.PublishedCache.XmlPublishedCache -{ - /// - /// This is the background task runner that persists the xml file to the file system - /// - /// - /// This is used so that all file saving is done on a web aware worker background thread and all logic is performed async so this - /// process will not interfere with any web requests threads. This is also done as to not require any global locks and to ensure that - /// if multiple threads are performing publishing tasks that the file will be persisted in accordance with the final resulting - /// xml structure since the file writes are queued. - /// - internal class XmlCacheFilePersister : LatchedBackgroundTaskBase - { - private readonly IBackgroundTaskRunner _runner; - private readonly content _content; - private readonly ProfilingLogger _logger; - private readonly object _locko = new object(); - private bool _released; - private Timer _timer; - private DateTime _initialTouch; - private readonly AsyncLock _runLock = new AsyncLock(); // ensure we run once at a time - - // note: - // as long as the runner controls the runs, we know that we run once at a time, but - // when the AppDomain goes down and the runner has completed and yet the persister is - // asked to save, then we need to run immediately - but the runner may be running, so - // we need to make sure there's no collision - hence _runLock - - private const int WaitMilliseconds = 4000; // save the cache 4s after the last change (ie every 4s min) - private const int MaxWaitMilliseconds = 30000; // save the cache after some time (ie no more than 30s of changes) - - // save the cache when the app goes down - public override bool RunsOnShutdown { get { return _timer != null; } } - - // initialize the first instance, which is inactive (not touched yet) - public XmlCacheFilePersister(IBackgroundTaskRunner runner, content content, ProfilingLogger logger) - : this(runner, content, logger, false) - { } - - private XmlCacheFilePersister(IBackgroundTaskRunner runner, content content, ProfilingLogger logger, bool touched) - { - _runner = runner; - _content = content; - _logger = logger; - - if (runner.TryAdd(this) == false) - { - _runner = null; // runner's down - _released = true; // don't mess with timer - return; - } - - // runner could decide to run it anytime now - - if (touched == false) return; - - _logger.Logger.Debug("Created, save in {0}ms.", () => WaitMilliseconds); - _initialTouch = DateTime.Now; - _timer = new Timer(_ => TimerRelease()); - _timer.Change(WaitMilliseconds, 0); - } - - public XmlCacheFilePersister Touch() - { - // if _released is false then we're going to setup a timer - // then the runner wants to shutdown & run immediately - // this sets _released to true & the timer will trigger eventualy & who cares? - // if _released is true, either it's a normal release, or - // a runner shutdown, in which case we won't be able to - // add a new task, and so we'll run immediately - - var ret = this; - var runNow = false; - - lock (_locko) - { - if (_released) // our timer has triggered OR the runner is shutting down - { - _logger.Logger.Debug("Touched, was released..."); - - // release: has run or is running, too late, return a new task (adds itself to runner) - if (_runner == null) - { - _logger.Logger.Debug("Runner is down, run now."); - runNow = true; - } - else - { - _logger.Logger.Debug("Create new..."); - ret = new XmlCacheFilePersister(_runner, _content, _logger, true); - if (ret._runner == null) - { - // could not enlist with the runner, runner is completed, must run now - _logger.Logger.Debug("Runner is down, run now."); - runNow = true; - } - } - } - - else if (_timer == null) // we don't have a timer yet - { - _logger.Logger.Debug("Touched, was idle, start and save in {0}ms.", () => WaitMilliseconds); - _initialTouch = DateTime.Now; - _timer = new Timer(_ => TimerRelease()); - _timer.Change(WaitMilliseconds, 0); - } - - else // we have a timer - { - // change the timer to trigger in WaitMilliseconds unless we've been touched first more - // than MaxWaitMilliseconds ago and then leave the time unchanged - - if (DateTime.Now - _initialTouch < TimeSpan.FromMilliseconds(MaxWaitMilliseconds)) - { - _logger.Logger.Debug("Touched, was waiting, can delay, save in {0}ms.", () => WaitMilliseconds); - _timer.Change(WaitMilliseconds, 0); - } - else - { - _logger.Logger.Debug("Touched, was waiting, cannot delay."); - } - } - } - - if (runNow) - //Run(); - LogHelper.Warn("Cannot write now because we are going down, changes may be lost."); - - return ret; // this, by default, unless we created a new one - } - - private void TimerRelease() - { - lock (_locko) - { - _logger.Logger.Debug("Timer: release."); - _released = true; - - Release(); - } - } - - public override bool IsAsync - { - get { return false; } - } - - public override void Run() - { - lock (_locko) - { - _logger.Logger.Debug("Run now (sync)."); - // not really needed but safer (it's only us invoking Run, but the method is public...) - _released = true; - } - - using (_runLock.Lock()) - { - _content.SaveXmlToFile(); - } - } - - protected override void DisposeResources() - { - base.DisposeResources(); - - // stop the timer - if (_timer == null) return; - _timer.Change(Timeout.Infinite, Timeout.Infinite); - _timer.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs index 1b0a428610..0ab219d368 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -1,85 +1,83 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Xml; using System.Xml.Serialization; using System.Xml.XPath; -using umbraco; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; +using Umbraco.Core.Cache; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; using Umbraco.Web.Models; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { - /// - /// Represents an IPublishedContent which is created based on an Xml structure. - /// - [Serializable] - [XmlType(Namespace = "http://umbraco.org/webservices/")] - internal class XmlPublishedContent : PublishedContentWithKeyBase - { - /// - /// Initializes a new instance of the XmlPublishedContent class with an Xml node. - /// - /// The Xml node. - /// A value indicating whether the published content is being previewed. - private XmlPublishedContent(XmlNode xmlNode, bool isPreviewing) - { - _xmlNode = xmlNode; - _isPreviewing = isPreviewing; - } + /// + /// Represents an IPublishedContent which is created based on an Xml structure. + /// + [Serializable] + [XmlType(Namespace = "http://umbraco.org/webservices/")] + internal class XmlPublishedContent : PublishedContentBase + { + private XmlPublishedContent(XmlNode xmlNode, bool isPreviewing, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache) + { + _xmlNode = xmlNode; + _isPreviewing = isPreviewing; + + _cacheProvider = cacheProvider; + _contentTypeCache = contentTypeCache; + } private readonly XmlNode _xmlNode; private readonly bool _isPreviewing; + private readonly ICacheProvider _cacheProvider; // at snapshot/request level (see PublishedContentCache) + private readonly PublishedContentTypeCache _contentTypeCache; private bool _nodeInitialized; - private bool _parentInitialized; - private bool _childrenInitialized; + private bool _parentInitialized; + private bool _childrenInitialized; - private IEnumerable _children = Enumerable.Empty(); - private IPublishedContent _parent; + private IEnumerable _children = Enumerable.Empty(); + private IPublishedContent _parent; private PublishedContentType _contentType; private Dictionary _properties; private int _id; - private Guid _key; - private int _template; - private string _name; - private string _docTypeAlias; - private int _docTypeId; - private string _writerName; - private string _creatorName; - private int _writerId; - private int _creatorId; - private string _urlName; - private string _path; - private DateTime _createDate; - private DateTime _updateDate; - private Guid _version; - private int _sortOrder; - private int _level; - private bool _isDraft; + private Guid _key; + private int _template; + private string _name; + private string _docTypeAlias; + private int _docTypeId; + private string _writerName; + private string _creatorName; + private int _writerId; + private int _creatorId; + private string _urlName; + private string _path; + private DateTime _createDate; + private DateTime _updateDate; + private int _sortOrder; + private int _level; + private bool _isDraft; - public override IEnumerable Children - { - get - { - if (_nodeInitialized == false) InitializeNode(); + public override IEnumerable Children + { + get + { + if (_nodeInitialized == false) InitializeNode(); if (_childrenInitialized == false) InitializeChildren(); - return _children; - } - } + return _children; + } + } - public override IPublishedProperty GetProperty(string alias) - { + public override IPublishedProperty GetProperty(string alias) + { if (_nodeInitialized == false) InitializeNode(); IPublishedProperty property; - return _properties.TryGetValue(alias, out property) ? property : null; - } + return _properties.TryGetValue(alias, out property) ? property : null; + } // override to implement cache // cache at context level, ie once for the whole request @@ -88,208 +86,186 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { if (recurse == false) return GetProperty(alias); - var cache = UmbracoContextCache.Current; + var key = $"XmlPublishedCache.PublishedContentCache:RecursiveProperty-{Id}-{alias.ToLowerInvariant()}"; + var cacheProvider = _cacheProvider; + return cacheProvider.GetCacheItem(key, () => base.GetProperty(alias, true)); - if (cache == null) - return base.GetProperty(alias, true); - - var key = string.Format("RECURSIVE_PROPERTY::{0}::{1}", Id, alias.ToLowerInvariant()); - var value = cache.GetOrAdd(key, k => base.GetProperty(alias, true)); - if (value == null) - return null; - - var property = value as IPublishedProperty; - if (property == null) - throw new InvalidOperationException("Corrupted cache."); - - return property; + // note: cleared by PublishedContentCache.Resync - any change here must be applied there } - public override PublishedItemType ItemType - { - get { return PublishedItemType.Content; } - } + public override PublishedItemType ItemType => PublishedItemType.Content; - public override IPublishedContent Parent - { - get - { - if (_nodeInitialized == false) InitializeNode(); + public override IPublishedContent Parent + { + get + { + if (_nodeInitialized == false) InitializeNode(); if (_parentInitialized == false) InitializeParent(); - return _parent; - } - } + return _parent; + } + } - public override int Id - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _id; - } - } + public override int Id + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _id; + } + } - public override Guid Key - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _key; - } - } + public override Guid Key + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _key; + } + } - public override int TemplateId - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _template; - } - } + public override int TemplateId + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _template; + } + } - public override int SortOrder - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _sortOrder; - } - } + public override int SortOrder + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _sortOrder; + } + } - public override string Name - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _name; - } - } + public override string Name + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _name; + } + } - public override string DocumentTypeAlias - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _docTypeAlias; - } - } + public override string DocumentTypeAlias + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _docTypeAlias; + } + } - public override int DocumentTypeId - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _docTypeId; - } - } + public override int DocumentTypeId + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _docTypeId; + } + } - public override string WriterName - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _writerName; - } - } + public override string WriterName + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _writerName; + } + } - public override string CreatorName - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _creatorName; - } - } + public override string CreatorName + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _creatorName; + } + } - public override int WriterId - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _writerId; - } - } + public override int WriterId + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _writerId; + } + } - public override int CreatorId - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _creatorId; - } - } + public override int CreatorId + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _creatorId; + } + } - public override string Path - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _path; - } - } + public override string Path + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _path; + } + } - public override DateTime CreateDate - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _createDate; - } - } + public override DateTime CreateDate + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _createDate; + } + } - public override DateTime UpdateDate - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _updateDate; - } - } + public override DateTime UpdateDate + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _updateDate; + } + } - public override Guid Version - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _version; - } - } + public override string UrlName + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _urlName; + } + } - public override string UrlName - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _urlName; - } - } + public override int Level + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _level; + } + } - public override int Level - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _level; - } - } + public override bool IsDraft + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _isDraft; + } + } - public override bool IsDraft - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _isDraft; - } - } - - public override ICollection Properties - { - get - { - if (_nodeInitialized == false) InitializeNode(); - return _properties.Values; - } - } + public override IEnumerable Properties + { + get + { + if (_nodeInitialized == false) InitializeNode(); + return _properties.Values; + } + } public override PublishedContentType ContentType { @@ -300,36 +276,34 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } - private void InitializeParent() - { - if (_xmlNode == null) return; - - var parent = _xmlNode.ParentNode; + private void InitializeParent() + { + var parent = _xmlNode?.ParentNode; if (parent == null) return; - if (parent.Name == "node" || (parent.Attributes != null && parent.Attributes.GetNamedItem("isDoc") != null)) - _parent = Get(parent, _isPreviewing); + if (parent.Attributes?.GetNamedItem("isDoc") != null) + _parent = Get(parent, _isPreviewing, _cacheProvider, _contentTypeCache); // warn: this is not thread-safe... _parentInitialized = true; - } + } - private void InitializeNode() - { - InitializeNode(_xmlNode, UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema, _isPreviewing, + private void InitializeNode() + { + InitializeNode(this, _xmlNode, _isPreviewing, out _id, out _key, out _template, out _sortOrder, out _name, out _writerName, out _urlName, out _creatorName, out _creatorId, out _writerId, out _docTypeAlias, out _docTypeId, out _path, - out _version, out _createDate, out _updateDate, out _level, out _isDraft, out _contentType, out _properties, - PublishedContentType.Get); + out _createDate, out _updateDate, out _level, out _isDraft, out _contentType, out _properties, + _contentTypeCache.Get); // warn: this is not thread-safe... _nodeInitialized = true; } - internal static void InitializeNode(XmlNode xmlNode, bool legacy, bool isPreviewing, + internal static void InitializeNode(XmlPublishedContent node, XmlNode xmlNode, bool isPreviewing, out int id, out Guid key, out int template, out int sortOrder, out string name, out string writerName, out string urlName, out string creatorName, out int creatorId, out int writerId, out string docTypeAlias, out int docTypeId, out string path, - out Guid version, out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft, + out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft, out PublishedContentType contentType, out Dictionary properties, Func getPublishedContentType) { @@ -337,100 +311,68 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache writerName = null; docTypeAlias = null; id = template = sortOrder = template = creatorId = writerId = docTypeId = level = default(int); - key = version = default(Guid); + key = default(Guid); name = writerName = urlName = creatorName = docTypeAlias = path = null; createDate = updateDate = default(DateTime); isDraft = false; contentType = null; properties = null; - //return if this is null - if (xmlNode == null) + if (xmlNode == null) return; + + if (xmlNode.Attributes != null) { - return; - } - - if (xmlNode.Attributes != null) - { - id = int.Parse(xmlNode.Attributes.GetNamedItem("id").Value); + id = int.Parse(xmlNode.Attributes.GetNamedItem("id").Value); if (xmlNode.Attributes.GetNamedItem("key") != null) // because, migration - key = Guid.Parse(xmlNode.Attributes.GetNamedItem("key").Value); - if (xmlNode.Attributes.GetNamedItem("template") != null) - template = int.Parse(xmlNode.Attributes.GetNamedItem("template").Value); - if (xmlNode.Attributes.GetNamedItem("sortOrder") != null) - sortOrder = int.Parse(xmlNode.Attributes.GetNamedItem("sortOrder").Value); - if (xmlNode.Attributes.GetNamedItem("nodeName") != null) - name = xmlNode.Attributes.GetNamedItem("nodeName").Value; - if (xmlNode.Attributes.GetNamedItem("writerName") != null) - writerName = xmlNode.Attributes.GetNamedItem("writerName").Value; - if (xmlNode.Attributes.GetNamedItem("urlName") != null) - urlName = xmlNode.Attributes.GetNamedItem("urlName").Value; - // Creatorname is new in 2.1, so published xml might not have it! - try - { - creatorName = xmlNode.Attributes.GetNamedItem("creatorName").Value; - } - catch - { - creatorName = writerName; - } + key = Guid.Parse(xmlNode.Attributes.GetNamedItem("key").Value); + if (xmlNode.Attributes.GetNamedItem("template") != null) + template = int.Parse(xmlNode.Attributes.GetNamedItem("template").Value); + if (xmlNode.Attributes.GetNamedItem("sortOrder") != null) + sortOrder = int.Parse(xmlNode.Attributes.GetNamedItem("sortOrder").Value); + if (xmlNode.Attributes.GetNamedItem("nodeName") != null) + name = xmlNode.Attributes.GetNamedItem("nodeName").Value; + if (xmlNode.Attributes.GetNamedItem("writerName") != null) + writerName = xmlNode.Attributes.GetNamedItem("writerName").Value; + if (xmlNode.Attributes.GetNamedItem("urlName") != null) + urlName = xmlNode.Attributes.GetNamedItem("urlName").Value; + if (xmlNode.Attributes.GetNamedItem("creatorName") != null) + creatorName = xmlNode.Attributes.GetNamedItem("creatorName").Value; - //Added the actual userID, as a user cannot be looked up via full name only... - if (xmlNode.Attributes.GetNamedItem("creatorID") != null) - creatorId = int.Parse(xmlNode.Attributes.GetNamedItem("creatorID").Value); - if (xmlNode.Attributes.GetNamedItem("writerID") != null) - writerId = int.Parse(xmlNode.Attributes.GetNamedItem("writerID").Value); + //Added the actual userID, as a user cannot be looked up via full name only... + if (xmlNode.Attributes.GetNamedItem("creatorID") != null) + creatorId = int.Parse(xmlNode.Attributes.GetNamedItem("creatorID").Value); + if (xmlNode.Attributes.GetNamedItem("writerID") != null) + writerId = int.Parse(xmlNode.Attributes.GetNamedItem("writerID").Value); - if (legacy) - { - if (xmlNode.Attributes.GetNamedItem("nodeTypeAlias") != null) - docTypeAlias = xmlNode.Attributes.GetNamedItem("nodeTypeAlias").Value; - } - else - { - docTypeAlias = xmlNode.Name; - } + docTypeAlias = xmlNode.Name; - if (xmlNode.Attributes.GetNamedItem("nodeType") != null) - docTypeId = int.Parse(xmlNode.Attributes.GetNamedItem("nodeType").Value); - if (xmlNode.Attributes.GetNamedItem("path") != null) - path = xmlNode.Attributes.GetNamedItem("path").Value; - if (xmlNode.Attributes.GetNamedItem("version") != null) - version = new Guid(xmlNode.Attributes.GetNamedItem("version").Value); - if (xmlNode.Attributes.GetNamedItem("createDate") != null) - createDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("createDate").Value); - if (xmlNode.Attributes.GetNamedItem("updateDate") != null) - updateDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("updateDate").Value); - if (xmlNode.Attributes.GetNamedItem("level") != null) - level = int.Parse(xmlNode.Attributes.GetNamedItem("level").Value); + if (xmlNode.Attributes.GetNamedItem("nodeType") != null) + docTypeId = int.Parse(xmlNode.Attributes.GetNamedItem("nodeType").Value); + if (xmlNode.Attributes.GetNamedItem("path") != null) + path = xmlNode.Attributes.GetNamedItem("path").Value; + if (xmlNode.Attributes.GetNamedItem("createDate") != null) + createDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("createDate").Value); + if (xmlNode.Attributes.GetNamedItem("updateDate") != null) + updateDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("updateDate").Value); + if (xmlNode.Attributes.GetNamedItem("level") != null) + level = int.Parse(xmlNode.Attributes.GetNamedItem("level").Value); - isDraft = (xmlNode.Attributes.GetNamedItem("isDraft") != null); + isDraft = xmlNode.Attributes.GetNamedItem("isDraft") != null; } - //dictionary to store the property node data + //dictionary to store the property node data var propertyNodes = new Dictionary(); foreach (XmlNode n in xmlNode.ChildNodes) { var e = n as XmlElement; if (e == null) continue; - if (legacy) - { - if (n.Name == "data") - { - PopulatePropertyNodes(propertyNodes, e, true); - } - else break; //we are not longer on property elements + if (e.HasAttribute("isDoc") == false) + { + PopulatePropertyNodes(propertyNodes, e, false); } - else - { - if (e.HasAttribute("isDoc") == false) - { - PopulatePropertyNodes(propertyNodes, e, false); - } - else break; //we are not longer on property elements - } - } + else break; //we are not longer on property elements + } //lookup the content type and create the properties collection try @@ -440,33 +382,27 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } catch (InvalidOperationException e) { - content.Instance.RefreshContentFromDatabase(); - - - throw new InvalidOperationException( - string.Format("{0}. This usually indicates that the content cache is corrupt; the content cache has been rebuilt in an attempt to self-fix the issue.", - //keep the original message but don't use this as an inner exception because we want the above message to be displayed, if we use the inner exception - //we can keep the stack trace but the header message will be the original message and the one we really want to display will be hidden below the fold. - e.Message)); + // fixme - enable! + //content.Instance.RefreshContentFromDatabase(); + throw new InvalidOperationException($"{e.Message}. This usually indicates that the content cache is corrupt; the content cache has been rebuilt in an attempt to self-fix the issue."); } - properties = new Dictionary(StringComparer.OrdinalIgnoreCase); //fill in the property collection - foreach (var propertyType in contentType.PropertyTypes) - { - XmlNode n; - var val = propertyNodes.TryGetValue(propertyType.PropertyTypeAlias.ToLowerInvariant(), out n) - ? new XmlPublishedProperty(propertyType, isPreviewing, n) - : new XmlPublishedProperty(propertyType, isPreviewing); + properties = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var propertyType in contentType.PropertyTypes) + { + var val = propertyNodes.TryGetValue(propertyType.Alias.ToLowerInvariant(), out XmlNode n) + ? new XmlPublishedProperty(propertyType, node, isPreviewing, n) + : new XmlPublishedProperty(propertyType, node, isPreviewing); - properties[propertyType.PropertyTypeAlias] = val; - } - } + properties[propertyType.Alias] = val; + } + } private static void PopulatePropertyNodes(IDictionary propertyNodes, XmlNode n, bool legacy) { var attrs = n.Attributes; - if (attrs == null) return; + if (attrs == null) return; var alias = legacy ? attrs.GetNamedItem("alias").Value @@ -474,52 +410,53 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache propertyNodes[alias.ToLowerInvariant()] = n; } - private void InitializeChildren() - { - if (_xmlNode == null) return; + private void InitializeChildren() + { + if (_xmlNode == null) return; // load children - var childXPath = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "node" : "* [@isDoc]"; + const string childXPath = "* [@isDoc]"; var nav = _xmlNode.CreateNavigator(); var expr = nav.Compile(childXPath); //expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number); var iterator = nav.Select(expr); _children = iterator.Cast() - .Select(n => Get(((IHasXmlNode) n).GetNode(), _isPreviewing)) + .Select(n => Get(((IHasXmlNode) n).GetNode(), _isPreviewing, _cacheProvider, _contentTypeCache)) .OrderBy(x => x.SortOrder) .ToList(); // warn: this is not thread-safe _childrenInitialized = true; - } + } /// /// Gets an IPublishedContent corresponding to an Xml cache node. /// /// The Xml node. /// A value indicating whether we are previewing or not. + /// A cache provider. + /// A content type cache. /// The IPublishedContent corresponding to the Xml cache node. /// Maintains a per-request cache of IPublishedContent items in order to make /// sure that we create only one instance of each for the duration of a request. The /// returned IPublishedContent is a model, if models are enabled. - public static IPublishedContent Get(XmlNode node, bool isPreviewing) + public static IPublishedContent Get(XmlNode node, bool isPreviewing, ICacheProvider cacheProvider, PublishedContentTypeCache contentTypeCache) { // only 1 per request var attrs = node.Attributes; - var id = attrs == null ? null : attrs.GetNamedItem("id").Value; + var id = attrs?.GetNamedItem("id").Value; if (id.IsNullOrWhiteSpace()) throw new InvalidOperationException("Node has no ID attribute."); - var cache = ApplicationContext.Current.ApplicationCache.RequestCache; - var key = CacheKeyPrefix + id; // dont bother with preview, wont change during request in v7 - return (IPublishedContent) cache.GetCacheItem(key, () => (new XmlPublishedContent(node, isPreviewing)).CreateModel()); + var key = CacheKeyPrefix + id; // dont bother with preview, wont change during request in Xml cache + return (IPublishedContent) cacheProvider.GetCacheItem(key, () => (new XmlPublishedContent(node, isPreviewing, cacheProvider, contentTypeCache)).CreateModel()); } - public static void ClearRequest() - { - ApplicationContext.Current.ApplicationCache.RequestCache.ClearCacheByKeySearch(CacheKeyPrefix); - } + public static void ClearRequest() + { + Current.ApplicationCache.RequestCache.ClearCacheByKeySearch(CacheKeyPrefix); + } - private const string CacheKeyPrefix = "CONTENTCACHE_XMLPUBLISHEDCONTENT_"; - } -} \ No newline at end of file + private const string CacheKeyPrefix = "CONTENTCACHE_XMLPUBLISHEDCONTENT_"; + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs index 704a6a9bc9..632698c37e 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs @@ -1,86 +1,77 @@ -using System; +using System; using System.Xml; using System.Xml.Serialization; -using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Xml; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { - /// - /// Represents an IDocumentProperty which is created based on an Xml structure. - /// - [Serializable] - [XmlType(Namespace = "http://umbraco.org/webservices/")] - internal class XmlPublishedProperty : PublishedPropertyBase - { - private readonly string _xmlValue; // the raw, xml node value + /// + /// Represents an IDocumentProperty which is created based on an Xml structure. + /// + [Serializable] + [XmlType(Namespace = "http://umbraco.org/webservices/")] + internal class XmlPublishedProperty : PublishedPropertyBase + { + private readonly string _sourceValue; // the raw, xml node value - // in v7 we're not using XPath value so don't allocate that Lazy. - // as for the rest... we're single threaded here, keep it simple - //private readonly Lazy _sourceValue; - //private readonly Lazy _objectValue; - //private readonly Lazy _xpathValue; + // Xml cache not using XPath value... and as for the rest... + // we're single threaded here, keep it simple private object _objectValue; private bool _objectValueComputed; private readonly bool _isPreviewing; + private readonly IPublishedContent _content; /// /// Gets the raw value of the property. /// - public override object DataValue { get { return _xmlValue; } } + public override object GetSourceValue(int? languageId = null, string segment = null) => _sourceValue; // in the Xml cache, everything is a string, and to have a value // you want to have a non-null, non-empty string. - public override bool HasValue + public override bool HasValue(int? languageId = null, string segment = null) => _sourceValue.Trim().Length > 0; + + public override object GetValue(int? languageId = null, string segment = null) { - get { return _xmlValue.Trim().Length > 0; } - } + // NOT caching the source (intermediate) value since we'll never need it + // everything in Xml cache is per-request anyways + // also, properties should not be shared between requests and therefore + // are single threaded, so the following code should be safe & fast - public override object Value - { - get - { - // NOT caching the source (intermediate) value since we'll never need it - // everything in Xml cache in v7 is per-request anyways - // also, properties should not be shared between requests and therefore - // are single threaded, so the following code should be safe & fast - - if (_objectValueComputed) return _objectValue; - var sourceValue = PropertyType.ConvertDataToSource(_xmlValue, _isPreviewing); - _objectValue = PropertyType.ConvertSourceToObject(sourceValue, _isPreviewing); - _objectValueComputed = true; - return _objectValue; - } + if (_objectValueComputed) return _objectValue; + var inter = PropertyType.ConvertSourceToInter(_content, _sourceValue, _isPreviewing); + // initial reference cache level always is .Content + _objectValue = PropertyType.ConvertInterToObject(_content, PropertyCacheLevel.Element, inter, _isPreviewing); + _objectValueComputed = true; + return _objectValue; } - public override object XPathValue { get { throw new NotImplementedException(); } } + public override object GetXPathValue(int? languageId = null, string segment = null) { throw new NotImplementedException(); } - public XmlPublishedProperty(PublishedPropertyType propertyType, bool isPreviewing, XmlNode propertyXmlData) - : this(propertyType, isPreviewing) - { - if (propertyXmlData == null) - throw new ArgumentNullException("propertyXmlData", "Property xml source is null"); - _xmlValue = XmlHelper.GetNodeValue(propertyXmlData); + public XmlPublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, XmlNode propertyXmlData) + : this(propertyType, content, isPreviewing) + { + if (propertyXmlData == null) + throw new ArgumentNullException(nameof(propertyXmlData), "Property xml source is null"); + _sourceValue = XmlHelper.GetNodeValue(propertyXmlData); } - public XmlPublishedProperty(PublishedPropertyType propertyType, bool isPreviewing, string propertyData) - : this(propertyType, isPreviewing) + public XmlPublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, string propertyData) + : this(propertyType, content, isPreviewing) { if (propertyData == null) - throw new ArgumentNullException("propertyData"); - _xmlValue = propertyData; + throw new ArgumentNullException(nameof(propertyData)); + _sourceValue = propertyData; } - public XmlPublishedProperty(PublishedPropertyType propertyType, bool isPreviewing) - : base(propertyType) + public XmlPublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing) + : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { - _xmlValue = string.Empty; + _sourceValue = string.Empty; + _content = content; _isPreviewing = isPreviewing; - - //_sourceValue = new Lazy(() => PropertyType.ConvertDataToSource(_xmlValue, _isPreviewing)); - //_objectValue = new Lazy(() => PropertyType.ConvertSourceToObject(_sourceValue.Value, _isPreviewing)); - //_xpathValue = new Lazy(() => PropertyType.ConvertSourceToXPath(_sourceValue.Value, _isPreviewing)); } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs new file mode 100644 index 0000000000..60b5aa1643 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs @@ -0,0 +1,2071 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using NPoco; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; +using Umbraco.Core.Services.Implement; +using Umbraco.Core.Strings; +using Umbraco.Core.Xml; +using Umbraco.Web.Cache; +using Umbraco.Web.Composing; +using Umbraco.Web.Scheduling; +using File = System.IO.File; +using Task = System.Threading.Tasks.Task; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + /// + /// Represents the Xml storage for the Xml published cache. + /// + /// + /// One instance of is instanciated by the and + /// then passed to all instances that are created (one per request). + /// This class should *not* be public. + /// + internal class XmlStore : IDisposable + { + private readonly IDocumentRepository _documentRepository; + private readonly IMediaRepository _mediaRepository; + private readonly IMemberRepository _memberRepository; + private readonly IGlobalSettings _globalSettings; + private XmlStoreFilePersister _persisterTask; + private volatile bool _released; + private bool _withRepositoryEvents; + + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly PublishedContentTypeCache _contentTypeCache; + private readonly IEnumerable _segmentProviders; + private readonly RoutesCache _routesCache; + private readonly ServiceContext _serviceContext; // fixme WHY + private readonly IScopeProvider _scopeProvider; + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The default constructor will boot the cache, load data from file or database, /// wire events in order to manage changes, etc. + public XmlStore(ServiceContext serviceContext, IScopeProvider scopeProvider, RoutesCache routesCache, PublishedContentTypeCache contentTypeCache, + IEnumerable segmentProviders, IPublishedSnapshotAccessor publishedSnapshotAccessor, MainDom mainDom, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IGlobalSettings globalSettings) + : this(serviceContext, scopeProvider, routesCache, contentTypeCache, segmentProviders, publishedSnapshotAccessor, mainDom, false, false, documentRepository, mediaRepository, memberRepository, globalSettings) + { } + + // internal for unit tests + // no file nor db, no config check + // fixme - er, we DO have a DB? + internal XmlStore(ServiceContext serviceContext, IScopeProvider scopeProvider, RoutesCache routesCache, PublishedContentTypeCache contentTypeCache, + IEnumerable segmentProviders, IPublishedSnapshotAccessor publishedSnapshotAccessor, MainDom mainDom, + bool testing, bool enableRepositoryEvents, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IGlobalSettings globalSettings) + { + if (testing == false) + EnsureConfigurationIsValid(); + + _serviceContext = serviceContext; + _scopeProvider = scopeProvider; + _routesCache = routesCache; + _contentTypeCache = contentTypeCache; + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _documentRepository = documentRepository; + _mediaRepository = mediaRepository; + _memberRepository = memberRepository; + _globalSettings = globalSettings; + _xmlFileName = IOHelper.MapPath(SystemFiles.GetContentCacheXml(_globalSettings)); + _segmentProviders = segmentProviders; + + if (testing) + { + _xmlFileEnabled = false; + } + else + { + InitializeFilePersister(mainDom); + } + + Initialize(testing, enableRepositoryEvents); + } + + // internal for unit tests + // initialize with an xml document + // no events, no file nor db, no config check + internal XmlStore(XmlDocument xmlDocument, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository) + { + _xmlDocument = xmlDocument; + _documentRepository = documentRepository; + _mediaRepository = mediaRepository; + _memberRepository = memberRepository; + _xmlFileEnabled = false; + _xmlFileName = IOHelper.MapPath(SystemFiles.GetContentCacheXml(UmbracoConfig.For.GlobalSettings())); + // do not plug events, we may not have what it takes to handle them + } + + // internal for unit tests + // initialize with a function returning an xml document + // no events, no file nor db, no config check + internal XmlStore(Func getXmlDocument, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository) + { + _documentRepository = documentRepository; + _mediaRepository = mediaRepository; + _memberRepository = memberRepository; + GetXmlDocument = getXmlDocument ?? throw new ArgumentNullException(nameof(getXmlDocument)); + _xmlFileEnabled = false; + _xmlFileName = IOHelper.MapPath(SystemFiles.GetContentCacheXml(UmbracoConfig.For.GlobalSettings())); + // do not plug events, we may not have what it takes to handle them + } + + private void InitializeFilePersister(MainDom mainDom) + { + if (SyncToXmlFile == false) return; + + var logger = Current.Logger; + + // there's always be one task keeping a ref to the runner + // so it's safe to just create it as a local var here + var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions + { + LongRunning = true, + KeepAlive = true, + Hosted = false // main domain will take care of stopping the runner (see below) + }, logger); + + // create (and add to runner) + _persisterTask = new XmlStoreFilePersister(runner, this, logger); + + var registered = mainDom.Register( + null, + () => + { + // once released, the cache still works but does not write to file anymore, + // which is OK with database server messenger but will cause data loss with + // another messenger... + + runner.Shutdown(false, true); // wait until flushed + _persisterTask = null; // fail fast + _released = true; + }); + + // failed to become the main domain, we will never use the file + if (registered == false) + runner.Shutdown(false, true); + + _released = registered == false; + } + + private void Initialize(bool testing, bool enableRepositoryEvents) + { + if (testing == false || enableRepositoryEvents) + InitializeRepositoryEvents(); + if (testing) + return; + + // not so soon! if eg installing we may not be able to load content yet + // so replace this by LazyInitializeContent() called in Xml ppty getter + //InitializeContent(); + } + + private void InitializeRepositoryEvents() + { + // plug repository event handlers + // these trigger within the transaction to ensure consistency + // and are used to maintain the central, database-level XML cache + DocumentRepository.ScopeEntityRemove += OnContentRemovingEntity; + DocumentRepository.ScopeVersionRemove += OnContentRemovingVersion; + DocumentRepository.ScopedEntityRefresh += OnContentRefreshedEntity; + MediaRepository.ScopeEntityRemove += OnMediaRemovingEntity; + MediaRepository.ScopeVersionRemove += OnMediaRemovingVersion; + MediaRepository.ScopedEntityRefresh += OnMediaRefreshedEntity; + MemberRepository.ScopeEntityRemove += OnMemberRemovingEntity; + MemberRepository.ScopeVersionRemove += OnMemberRemovingVersion; + MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity; + + // plug + ContentTypeService.UowRefreshedEntity += OnContentTypeRefreshedEntity; + MediaTypeService.UowRefreshedEntity += OnMediaTypeRefreshedEntity; + MemberTypeService.UowRefreshedEntity += OnMemberTypeRefreshedEntity; + + _withRepositoryEvents = true; + } + + private void ClearEvents() + { + DocumentRepository.ScopeEntityRemove -= OnContentRemovingEntity; + DocumentRepository.ScopeVersionRemove -= OnContentRemovingVersion; + DocumentRepository.ScopedEntityRefresh -= OnContentRefreshedEntity; + MediaRepository.ScopeEntityRemove -= OnMediaRemovingEntity; + MediaRepository.ScopeVersionRemove -= OnMediaRemovingVersion; + MediaRepository.ScopedEntityRefresh -= OnMediaRefreshedEntity; + MemberRepository.ScopeEntityRemove -= OnMemberRemovingEntity; + MemberRepository.ScopeVersionRemove -= OnMemberRemovingVersion; + MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity; + + ContentTypeService.UowRefreshedEntity -= OnContentTypeRefreshedEntity; + MediaTypeService.UowRefreshedEntity -= OnMediaTypeRefreshedEntity; + MemberTypeService.UowRefreshedEntity -= OnMemberTypeRefreshedEntity; + + _withRepositoryEvents = false; + } + + private void LazyInitializeContent() + { + if (_xml != null) return; + + // and populate the cache + using (var safeXml = GetSafeXmlWriter()) + { + if (_xml != null) return; // double-check + + // if we don't use the file then LoadXmlLocked will not even + // read from the file and will go straight to database + LoadXmlLocked(safeXml, out bool registerXmlChange); + + // if we use the file and registerXmlChange is true this will + // write to file, else it will not + safeXml.AcceptChanges(registerXmlChange); + } + } + + public void Dispose() + { + ClearEvents(); + } + + #endregion + + #region Configuration + + // gathering configuration options here to document what they mean + + private readonly bool _xmlFileEnabled = true; + + // whether the disk cache is enabled + private bool XmlFileEnabled => _xmlFileEnabled && UmbracoConfig.For.UmbracoSettings().Content.XmlCacheEnabled; + + // whether the disk cache is enabled and to update the disk cache when xml changes + private bool SyncToXmlFile => XmlFileEnabled && UmbracoConfig.For.UmbracoSettings().Content.ContinouslyUpdateXmlDiskCache; + + // whether the disk cache is enabled and to reload from disk cache if it changes + private bool SyncFromXmlFile => XmlFileEnabled && UmbracoConfig.For.UmbracoSettings().Content.XmlContentCheckForDiskChanges; + + // whether _xml is immutable or not (achieved by cloning before changing anything) + private static bool XmlIsImmutable => UmbracoConfig.For.UmbracoSettings().Content.CloneXmlContent; + + // whether to keep version of everything (incl. medias & members) in cmsPreviewXml + // for audit purposes - false by default, not in umbracoSettings.config + // whether to... no idea what that one does + // it is false by default and not in UmbracoSettings.config anymore - ignoring + /* + private static bool GlobalPreviewStorageEnabled + { + get { return UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled; } + } + */ + + // ensures config is valid + private void EnsureConfigurationIsValid() + { + if (SyncToXmlFile && SyncFromXmlFile) + throw new Exception("Cannot run with both ContinouslyUpdateXmlDiskCache and XmlContentCheckForDiskChanges being true."); + + if (XmlIsImmutable == false) + //Current.Logger.Warn("Running with CloneXmlContent being false is a bad idea."); + Current.Logger.Warn("CloneXmlContent is false - ignored, we always clone."); + + // note: if SyncFromXmlFile then we should also disable / warn that local edits are going to cause issues... + } + + #endregion + + #region Xml + + /// + /// Gets or sets the delegate used to retrieve the Xml content, used for unit tests, else should + /// be null and then the default content will be used. For non-preview content only. + /// + /// + /// The default content ONLY works when in the context an Http Request mostly because the + /// 'content' object heavily relies on HttpContext, SQL connections and a bunch of other stuff + /// that when run inside of a unit test fails. + /// + public Func GetXmlDocument { get; set; } + + private XmlDocument _xmlDocument; // supplied xml document (for tests) + private volatile XmlDocument _xml; // master xml document + private readonly AsyncLock _xmlLock = new AsyncLock(); // protects _xml + + // to be used by PublishedContentCache only + // for non-preview content only + public XmlDocument Xml + { + get + { + if (_xml != null) + return _xml; + + if (_xmlDocument != null) + { + _xml = _xmlDocument; + _xmlDocument = null; + return _xml; + } + + if (GetXmlDocument != null) + return _xml = GetXmlDocument(); + + LazyInitializeContent(); + ReloadXmlFromFileIfChanged(); + return _xml; + } + } + + // Gets the temp. Xml managed by SafeXmlReaderWrite, if any + public XmlDocument TempXml => SafeXmlReaderWriter.Get(_scopeProvider)?.Xml; + + // assumes xml lock + private void SetXmlLocked(XmlDocument xml, bool registerXmlChange) + { + // this is the ONLY place where we write to _xml + _xml = xml; + + _routesCache?.Clear(); // anytime we set _xml + + if (registerXmlChange == false || SyncToXmlFile == false) + return; + + _persisterTask = _persisterTask?.Touch(); + } + + private static XmlDocument EnsureSchema(string contentTypeAlias, XmlDocument xml) + { + string subset = null; + + // get current doctype + var n = xml.FirstChild; + while (n.NodeType != XmlNodeType.DocumentType && n.NextSibling != null) + n = n.NextSibling; + if (n.NodeType == XmlNodeType.DocumentType) + subset = ((XmlDocumentType)n).InternalSubset; + + // ensure it contains the content type + if (subset != null && subset.Contains($"")) + return xml; + + // alas, that does not work, replacing a doctype is ignored and GetElementById fails + // + //// remove current doctype, set new doctype + //xml.RemoveChild(n); + //subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset); + //var doctype = xml.CreateDocumentType("root", null, null, subset); + //xml.InsertAfter(doctype, xml.FirstChild); + + var xml2 = new XmlDocument(); + subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset); + var doctype = xml2.CreateDocumentType("root", null, null, subset); + xml2.AppendChild(doctype); + xml2.AppendChild(xml2.ImportNode(xml.DocumentElement, true)); + return xml2; + } + + private static void InitializeXml(XmlDocument xml, string dtd) + { + // prime the xml document with an inline dtd and a root element + xml.LoadXml(string.Format("{0}{1}{0}", Environment.NewLine, dtd)); + } + + /// + /// Generates the complete (simplified) XML DTD. + /// + /// The DTD as a string + private string GetDtd() + { + var dtd = new StringBuilder(); + dtd.AppendLine(" x.Alias.ToSafeAlias()).WhereNotNull(); + foreach (var alias in aliases) + { + dtdInner.AppendLine($""); + dtdInner.AppendLine($""); + } + dtd.Append(dtdInner); + } + catch (Exception exception) + { + Current.Logger.Error("Failed to build a DTD for the Xml cache.", exception); + } + + dtd.AppendLine("]>"); + return dtd.ToString(); + } + + // try to load from file, otherwise database + // assumes xml lock (file is always locked) + private void LoadXmlLocked(SafeXmlReaderWriter safeXml, out bool registerXmlChange) + { + Current.Logger.Debug("Loading Xml..."); + + // try to get it from the file + if (XmlFileEnabled && (safeXml.Xml = LoadXmlFromFile()) != null) + { + registerXmlChange = false; // loaded from disk, do NOT write back to disk! + return; + } + + // get it from the database, and register + LoadXmlTreeFromDatabaseLocked(safeXml); + registerXmlChange = true; + } + + public XmlNode GetMediaXmlNode(int mediaId) + { + // there's only one version for medias + + const string sql = @"SELECT umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.Level, +cmsContentXml.xml, 1 AS published +FROM umbracoNode +JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) +WHERE umbracoNode.nodeObjectType = @nodeObjectType +AND (umbracoNode.id=@id)"; + + XmlDto xmlDto; + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.MediaTree); + var xmlDtos = scope.Database.Query(sql, + new + { + nodeObjectType = Constants.ObjectTypes.Media, + id = mediaId + }); + xmlDto = xmlDtos.FirstOrDefault(); + scope.Complete(); + } + + if (xmlDto == null) return null; + + var doc = new XmlDocument(); + var xml = doc.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml))); + return xml; + } + + public XmlNode GetMemberXmlNode(int memberId) + { + // there's only one version for members + + const string sql = @"SELECT umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.Level, +cmsContentXml.xml, 1 AS published +FROM umbracoNode +JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) +WHERE umbracoNode.nodeObjectType = @nodeObjectType +AND (umbracoNode.id=@id)"; + + XmlDto xmlDto; + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.MemberTree); + var xmlDtos = scope.Database.Query(sql, + new + { + nodeObjectType = Constants.ObjectTypes.Member, + id = memberId + }); + xmlDto = xmlDtos.FirstOrDefault(); + scope.Complete(); + } + + if (xmlDto == null) return null; + + var doc = new XmlDocument(); + var xml = doc.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml))); + return xml; + } + + private static readonly string PreviewXmlNodeSql = $@"SELECT umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.Level, +cmsPreviewXml.xml, {Constants.DatabaseSchema.Tables.Document}.published +FROM umbracoNode +JOIN cmsPreviewXml ON (cmsPreviewXml.nodeId=umbracoNode.id) +JOIN {Constants.DatabaseSchema.Tables.Document} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId=umbracoNode.id) +WHERE umbracoNode.nodeObjectType = @nodeObjectType +AND (umbracoNode.id=@id)"; + + public XmlNode GetPreviewXmlNode(int contentId) + { + var sql = PreviewXmlNodeSql; + + XmlDto xmlDto; + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTree); + var xmlDtos = scope.Database.Query(sql, + new + { + nodeObjectType = Constants.ObjectTypes.Document, + id = contentId + }); + xmlDto = xmlDtos.FirstOrDefault(); + scope.Complete(); + } + if (xmlDto == null) return null; + + var doc = new XmlDocument(); + var xml = doc.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml))); + if (xml?.Attributes == null) return null; + + if (xmlDto.Published == false) + xml.Attributes.Append(doc.CreateAttribute("isDraft")); + return xml; + } + + public XmlDocument GetMediaXml() + { + // this is not efficient at all, not cached, nothing + // just here to replicate what uQuery was doing and show it can be done + // but really - should not be used + + return LoadMoreXmlFromDatabase(Constants.ObjectTypes.Media); + } + + public XmlDocument GetMemberXml() + { + // this is not efficient at all, not cached, nothing + // just here to replicate what uQuery was doing and show it can be done + // but really - should not be used + + return LoadMoreXmlFromDatabase(Constants.ObjectTypes.Member); + } + + public XmlDocument GetPreviewXml(int contentId, bool includeSubs) + { + var content = _serviceContext.ContentService.GetById(contentId); + + var doc = (XmlDocument)Xml.Clone(); + if (content == null) return doc; + + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTree); + var sqlSyntax = scope.SqlContext.SqlSyntax; + + var sql = ReadCmsPreviewXmlSql1; + sql += " @path LIKE " + sqlSyntax.GetConcat("umbracoNode.Path", "',%"); // concat(umbracoNode.path, ',%') + if (includeSubs) sql += " OR umbracoNode.path LIKE " + sqlSyntax.GetConcat("@path", "',%"); // concat(@path, ',%') + sql += ReadCmsPreviewXmlSql2; + + var xmlDtos = scope.Database.Query(sql, + new + { + nodeObjectType = Constants.ObjectTypes.Document, + path = content.Path, + }); + + foreach (var xmlDto in xmlDtos) + { + var xml = xmlDto.XmlNode = doc.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml))); + if (xml?.Attributes == null) continue; + if (xmlDto.Published == false) + xml.Attributes.Append(doc.CreateAttribute("isDraft")); + doc = AddOrUpdateXmlNode(doc, xmlDto); + } + + scope.Complete(); + } + + return doc; + } + + // NOTE + // - this is NOT a reader/writer lock and each lock is exclusive + // - these locks are NOT reentrant / recursive + // + // should we have async versions that would do: ? + // var releaser = await _xmlLock.LockAsync(); + // + // fixme - not sure about the "resync current published snapshot" thing here, see 7.6... + + // gets a locked safe read access to the main xml + private SafeXmlReaderWriter GetSafeXmlReader() + { + return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xml, + ResyncCurrentPublishedSnapshot, + (xml, registerXmlChange) => + { + SetXmlLocked(xml, registerXmlChange); + ResyncCurrentPublishedSnapshot(xml); + }, false); + } + + // gets a locked safe write access to the main xml (cloned) + private SafeXmlReaderWriter GetSafeXmlWriter() + { + return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xml, + ResyncCurrentPublishedSnapshot, + (xml, registerXmlChange) => + { + SetXmlLocked(xml, registerXmlChange); + ResyncCurrentPublishedSnapshot(xml); + }, true); + } + + private const string ChildNodesXPath = "./* [@id]"; + private const string DataNodesXPath = "./* [not(@id)]"; + + #endregion + + #region File + + private readonly string _xmlFileName; + private DateTime _lastFileRead; // last time the file was read + private DateTime _nextFileCheck; // last time we checked whether the file was changed + + public void EnsureFilePermission() + { + // FIXME - but do we really have a store, initialized, at that point? + var filename = _xmlFileName + ".temp"; + File.WriteAllText(filename, "TEMP"); + File.Delete(filename); + } + + // not used - just try to read the file + //private bool XmlFileExists + //{ + // get + // { + // // check that the file exists and has content (is not empty) + // var fileInfo = new FileInfo(_xmlFileName); + // return fileInfo.Exists && fileInfo.Length > 0; + // } + //} + + private DateTime XmlFileLastWriteTime + { + get + { + var fileInfo = new FileInfo(_xmlFileName); + return fileInfo.Exists ? fileInfo.LastWriteTimeUtc : DateTime.MinValue; + } + } + + // invoked by XmlStoreFilePersister ONLY and that one manages the MainDom, ie it + // will NOT try to save once the current app domain is not the main domain anymore + // (no need to test _released) + internal void SaveXmlToFile() + { + Current.Logger.Info("Save Xml to file..."); + + try + { + var xml = _xml; // capture (atomic + volatile), immutable anyway + if (xml == null) return; + + // delete existing file, if any + DeleteXmlFile(); + + // ensure cache directory exists + var directoryName = Path.GetDirectoryName(_xmlFileName); + if (directoryName == null) + throw new Exception($"Invalid XmlFileName \"{_xmlFileName}\"."); + if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) + Directory.CreateDirectory(directoryName); + + // save + using (var fs = new FileStream(_xmlFileName, FileMode.Create, FileAccess.Write, FileShare.Read, bufferSize: 4096, useAsync: true)) + { + var bytes = Encoding.UTF8.GetBytes(SaveXmlToString(xml)); + fs.Write(bytes, 0, bytes.Length); + } + + Current.Logger.Info("Saved Xml to file."); + } + catch (Exception e) + { + // if something goes wrong remove the file + DeleteXmlFile(); + + Current.Logger.Error("Failed to save Xml to file.", e); + } + } + + // invoked by XmlStoreFilePersister ONLY and that one manages the MainDom, ie it + // will NOT try to save once the current app domain is not the main domain anymore + // (no need to test _released) + internal async Task SaveXmlToFileAsync() + { + Current.Logger.Info("Save Xml to file..."); + + try + { + var xml = _xml; // capture (atomic + volatile), immutable anyway + if (xml == null) return; + + // delete existing file, if any + DeleteXmlFile(); + + // ensure cache directory exists + var directoryName = Path.GetDirectoryName(_xmlFileName); + if (directoryName == null) + throw new Exception($"Invalid XmlFileName \"{_xmlFileName}\"."); + if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) + Directory.CreateDirectory(directoryName); + + // save + using (var fs = new FileStream(_xmlFileName, FileMode.Create, FileAccess.Write, FileShare.Read, bufferSize: 4096, useAsync: true)) + { + var bytes = Encoding.UTF8.GetBytes(SaveXmlToString(xml)); + await fs.WriteAsync(bytes, 0, bytes.Length); + } + + Current.Logger.Info("Saved Xml to file."); + } + catch (Exception e) + { + // if something goes wrong remove the file + DeleteXmlFile(); + + Current.Logger.Error("Failed to save Xml to file.", e); + } + } + + private static string SaveXmlToString(XmlDocument xml) + { + // using that one method because we want to have proper indent + // and in addition, writing async is never fully async because + // althouth the writer is async, xml.WriteTo() will not async + + // that one almost works but... "The elements are indented as long as the element + // does not contain mixed content. Once the WriteString or WriteWhitespace method + // is called to write out a mixed element content, the XmlWriter stops indenting. + // The indenting resumes once the mixed content element is closed." - says MSDN + // about XmlWriterSettings.Indent + + // so ImportContent must also make sure of ignoring whitespaces! + + var sb = new StringBuilder(); + using (var xmlWriter = XmlWriter.Create(sb, new XmlWriterSettings + { + Indent = true, + Encoding = Encoding.UTF8, + //OmitXmlDeclaration = true + })) + { + //xmlWriter.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""); + xml.WriteTo(xmlWriter); // already contains the xml declaration + } + return sb.ToString(); + } + + private XmlDocument LoadXmlFromFile() + { + // do NOT try to load if we are not the main domain anymore + if (_released) return null; + + Current.Logger.Info("Load Xml from file..."); + + try + { + var xml = new XmlDocument(); + using (var fs = new FileStream(_xmlFileName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + xml.Load(fs); + } + _lastFileRead = DateTime.UtcNow; + Current.Logger.Info("Loaded Xml from file."); + return xml; + } + catch (FileNotFoundException) + { + Current.Logger.Warn("Failed to load Xml, file does not exist."); + return null; + } + catch (Exception e) + { + Current.Logger.Error("Failed to load Xml from file.", e); + try + { + DeleteXmlFile(); + } + catch + { + // don't make it worse: could be that we failed to read because we cannot + // access the file, in which case we won't be able to delete it either + } + return null; + } + } + + private void DeleteXmlFile() + { + if (File.Exists(_xmlFileName) == false) return; + File.SetAttributes(_xmlFileName, FileAttributes.Normal); + File.Delete(_xmlFileName); + } + + private void ReloadXmlFromFileIfChanged() + { + if (SyncFromXmlFile == false) return; + + var now = DateTime.UtcNow; + if (now < _nextFileCheck) return; + + // time to check + _nextFileCheck = now.AddSeconds(1); // check every 1s + if (XmlFileLastWriteTime <= _lastFileRead) return; + + Current.Logger.Debug("Xml file change detected, reloading."); + + // time to read + + using (var safeXml = GetSafeXmlWriter()) + { + LoadXmlLocked(safeXml, out bool registerXmlChange); // updates _lastFileRead + safeXml.AcceptChanges(registerXmlChange); + } + } + + #endregion + + #region Database + + private static readonly string ReadTreeCmsContentXmlSql = $@"SELECT + umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.level, umbracoNode.path, + cmsContentXml.xml, cmsContentXml.rv, {Constants.DatabaseSchema.Tables.Document}.published +FROM umbracoNode +JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) +JOIN {Constants.DatabaseSchema.Tables.Document} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId=umbracoNode.id) +WHERE umbracoNode.nodeObjectType = @nodeObjectType AND {Constants.DatabaseSchema.Tables.Document}.published=1 +ORDER BY umbracoNode.level, umbracoNode.sortOrder"; + + private static readonly string ReadBranchCmsContentXmlSql = $@"SELECT + umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.level, umbracoNode.path, + cmsContentXml.xml, cmsContentXml.rv, {Constants.DatabaseSchema.Tables.Document}.published +FROM umbracoNode +JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) +JOIN {Constants.DatabaseSchema.Tables.Document} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId=umbracoNode.id) +WHERE umbracoNode.nodeObjectType = @nodeObjectType AND {Constants.DatabaseSchema.Tables.Document}.published=1 AND (umbracoNode.id = @id OR umbracoNode.path LIKE @path) +ORDER BY umbracoNode.level, umbracoNode.sortOrder"; + + private static readonly string ReadCmsContentXmlForContentTypesSql = $@"SELECT + umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.level, umbracoNode.path, + cmsContentXml.xml, cmsContentXml.rv, {Constants.DatabaseSchema.Tables.Document}.published +FROM umbracoNode +JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) +JOIN {Constants.DatabaseSchema.Tables.Document} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId=umbracoNode.id) +JOIN {Constants.DatabaseSchema.Tables.Content} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId={Constants.DatabaseSchema.Tables.Content}.nodeId) +WHERE umbracoNode.nodeObjectType = @nodeObjectType AND {Constants.DatabaseSchema.Tables.Document}.published=1 AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ids) +ORDER BY umbracoNode.level, umbracoNode.sortOrder"; + + private const string ReadMoreCmsContentXmlSql = @"SELECT + umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.level, umbracoNode.path, + cmsContentXml.xml, cmsContentXml.rv, 1 AS published +FROM umbracoNode +JOIN cmsContentXml ON (cmsContentXml.nodeId=umbracoNode.id) +WHERE umbracoNode.nodeObjectType = @nodeObjectType +ORDER BY umbracoNode.level, umbracoNode.sortOrder"; + + private static readonly string ReadCmsPreviewXmlSql1 = $@"SELECT + umbracoNode.id, umbracoNode.parentId, umbracoNode.sortOrder, umbracoNode.level, umbracoNode.path, + cmsPreviewXml.xml, cmsPreviewXml.rv, {Constants.DatabaseSchema.Tables.Document}.published +FROM umbracoNode +JOIN cmsPreviewXml ON (cmsPreviewXml.nodeId=umbracoNode.id) +JOIN {Constants.DatabaseSchema.Tables.Document} ON ({Constants.DatabaseSchema.Tables.Document}.nodeId=umbracoNode.id) +WHERE umbracoNode.nodeObjectType = @nodeObjectType AND {Constants.DatabaseSchema.Tables.Document}.published=1 +AND (umbracoNode.path=@path OR"; // @path LIKE concat(umbracoNode.path, ',%')"; + + private const string ReadCmsPreviewXmlSql2 = @") +ORDER BY umbracoNode.level, umbracoNode.sortOrder"; + + // ReSharper disable once ClassNeverInstantiated.Local + private class XmlDto + { + // ReSharper disable UnusedAutoPropertyAccessor.Local + + public int Id { get; set; } + public long Rv { get; set; } + public int ParentId { get; set; } + //public int SortOrder { get; set; } + public int Level { get; set; } + public string Path { get; set; } + public string Xml { get; set; } + public bool Published { get; set; } + + [Ignore] + public XmlNode XmlNode { get; set; } + + // ReSharper restore UnusedAutoPropertyAccessor.Local + } + + // assumes xml lock + private void LoadXmlTreeFromDatabaseLocked(SafeXmlReaderWriter safeXml) + { + // initialise the document ready for the composition of content + var xml = new XmlDocument(); + InitializeXml(xml, GetDtd()); + + XmlNode parent = null; + var parentId = 0; + + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTree); + + // get xml + var xmlDtos = scope.Database.Query(ReadTreeCmsContentXmlSql, + new { nodeObjectType = Constants.ObjectTypes.Document }); + + foreach (var xmlDto in xmlDtos) + { + xmlDto.XmlNode = ImportContent(xml, xmlDto); // parse into a DOM node + + if (parent == null || parentId != xmlDto.ParentId) + { + parent = xmlDto.ParentId == -1 + ? xml.DocumentElement + : xml.GetElementById(xmlDto.ParentId.ToInvariantString()); + + if (parent == null) continue; + + parentId = xmlDto.ParentId; + } + + parent.AppendChild(xmlDto.XmlNode); + } + + scope.Complete(); + } + + safeXml.Xml = xml; + } + + private XmlDocument LoadMoreXmlFromDatabase(Guid nodeObjectType) + { + var xmlDoc = new XmlDocument(); + + using (var scope = _scopeProvider.CreateScope()) + { + if (nodeObjectType == Constants.ObjectTypes.Document) + scope.ReadLock(Constants.Locks.ContentTree); + else if (nodeObjectType == Constants.ObjectTypes.Media) + scope.ReadLock(Constants.Locks.MediaTree); + else if (nodeObjectType == Constants.ObjectTypes.Member) + scope.ReadLock(Constants.Locks.MemberTree); + + var xmlDtos = scope.Database.Query(ReadMoreCmsContentXmlSql, + new { /*@nodeObjectType =*/ nodeObjectType }); + + // Initialise the document ready for the final composition of content + InitializeXml(xmlDoc, string.Empty); + + XmlNode parent = null; + var parentId = 0; + + foreach (var xmlDto in xmlDtos) + { + // and parse it into a DOM node + var node = xmlDoc.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml), new XmlReaderSettings + { + IgnoreWhitespace = true + })); + + if (parent == null || parentId != xmlDto.ParentId) + { + parent = xmlDto.ParentId == -1 + ? xmlDoc.DocumentElement + : xmlDoc.GetElementById(xmlDto.ParentId.ToInvariantString()); + + if (parent == null) + continue; + + parentId = xmlDto.ParentId; + } + + parent.AppendChild(node); + } + + scope.Complete(); + } + + return xmlDoc; + } + + // internal - used by umbraco.content.RefreshContentFromDatabase[Async] + internal void ReloadXmlFromDatabase() + { + // event - cancel + + // nobody should work on the Xml while we load + using (var safeXml = GetSafeXmlWriter()) + { + LoadXmlTreeFromDatabaseLocked(safeXml); + safeXml.AcceptChanges(); + } + } + + #endregion + + #region Handle Distributed Notifications for Memory Xml + + // NOT using events, see notes in IPublishedCachesService + + public void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) + { + draftChanged = publishedChanged = false; + if (_xml == null) return; // not initialized yet! + + draftChanged = true; // by default - we don't track drafts + publishedChanged = false; + + // process all changes on one xml clone + using (var safeXml = GetSafeXmlWriter()) + { + foreach (var payload in payloads) + { + Current.Logger.Debug($"Notified {payload.ChangeTypes} for content {payload.Id}."); + + if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) + { + LoadXmlTreeFromDatabaseLocked(safeXml); + publishedChanged = true; + continue; + } + + if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) + { + var toRemove = safeXml.Xml.GetElementById(payload.Id.ToInvariantString()); + if (toRemove != null) + { + if (toRemove.ParentNode == null) throw new Exception("oops"); + toRemove.ParentNode.RemoveChild(toRemove); + publishedChanged = true; + } + continue; + } + + if (payload.ChangeTypes.HasTypesNone(TreeChangeTypes.RefreshNode | TreeChangeTypes.RefreshBranch)) + { + // ?! + continue; + } + + var content = _serviceContext.ContentService.GetById(payload.Id); + var current = safeXml.Xml.GetElementById(payload.Id.ToInvariantString()); + + if (content == null || content.Published == false || content.Trashed) + { + // no published version + Current.Logger.Debug($"Notified, content {payload.Id} has no published version."); + if (current != null) + { + // remove from xml if exists + if (current.ParentNode == null) throw new Exception("oops"); + current.ParentNode.RemoveChild(current); + publishedChanged = true; + } + + continue; + } + + // else we have a published version + + // get xml + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTree); + + // that query is yielding results so will only load what's needed + var xmlDtos = scope.Database.Query(ReadBranchCmsContentXmlSql, + new + { + nodeObjectType = Constants.ObjectTypes.Document, + path = content.Path + ",%", + id = content.Id + }); + + // 'using' the enumerator ensures that the enumeration is properly terminated even if abandonned + // otherwise, it would leak an open reader & an un-released database connection + // see PetaPoco.Query(Type[] types, Delegate cb, string sql, params object[] args) + // and read http://blogs.msdn.com/b/oldnewthing/archive/2008/08/14/8862242.aspx + // + using (var dtos = xmlDtos.GetEnumerator()) + { + if (dtos.MoveNext() == false) + { + // gone fishing, remove (possible race condition) + Current.Logger.Debug($"Notifified, content {payload.Id} gone fishing."); + if (current != null) + { + // remove from xml if exists + if (current.ParentNode == null) throw new Exception("oops"); + current.ParentNode.RemoveChild(current); + publishedChanged = true; + } + continue; + } + + if (dtos.Current.Id != content.Id) + throw new Exception("oops"); // first one should be 'current' + var currentDto = dtos.Current; + + // note: if anything eg parentId or path or level has changed, then rv has changed too + var currentRv = current == null ? -1 : int.Parse(current.Attributes["rv"].Value); + + // if exists and unchanged and not refreshing the branch, skip entirely + if (current != null + && currentRv == currentDto.Rv + && payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch) == false) + continue; + + currentDto.XmlNode = ImportContent(safeXml.Xml, currentDto); + + // note: Examine would not be able to do the path trick below, and we cannot help for + // unpublished content, so it *is* possible that Examine is inconsistent for a while, + // though events should get it consistent eventually. + + // note: if path has changed we must do a branch refresh, even if the event is not requiring + // it, otherwise we would update the local node and not its children, who would then have + // inconsistent level (and path) attributes. + + var refreshBranch = current == null + || payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch) + || current.Attributes["path"].Value != currentDto.Path; + + if (refreshBranch) + { + // remove node if exists + if (current != null) + { + if (current.ParentNode == null) throw new Exception("oops"); + current.ParentNode.RemoveChild(current); + } + + // insert node + var newParent = currentDto.ParentId == -1 + ? safeXml.Xml.DocumentElement + : safeXml.Xml.GetElementById(currentDto.ParentId.ToInvariantString()); + if (newParent == null) continue; + newParent.AppendChild(currentDto.XmlNode); + XmlHelper.SortNode(newParent, ChildNodesXPath, currentDto.XmlNode, + x => x.AttributeValue("sortOrder")); + + // add branch (don't try to be clever) + while (dtos.MoveNext()) + { + // dtos are ordered by sortOrder already + var dto = dtos.Current; + + // if node is already there, somewhere, remove + var n = safeXml.Xml.GetElementById(dto.Id.ToInvariantString()); + if (n != null) + { + if (n.ParentNode == null) throw new Exception("oops"); + n.ParentNode.RemoveChild(n); + } + + // find parent, add node + var p = safeXml.Xml.GetElementById(dto.ParentId.ToInvariantString()); // branch, so parentId > 0 + // takes care of out-of-sync & masked + p?.AppendChild(dto.XmlNode); + } + } + else + { + // in-place + safeXml.Xml = AddOrUpdateXmlNode(safeXml.Xml, currentDto); + } + } + + scope.Complete(); + } + + publishedChanged = true; + } + + if (publishedChanged) + safeXml.AcceptChanges(); + } + } + + public void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) + { + if (_xml == null) return; // not initialized yet! + + // see ContentTypeServiceBase + // in all cases we just want to clear the content type cache + // the type will be reloaded if/when needed + foreach (var payload in payloads) + _contentTypeCache.ClearContentType(payload.Id); + + // process content types / content cache + // only those that have been changed - with impact on content - RefreshMain + // for those that have been removed, content is removed already + var ids = payloads + .Where(x => x.ItemType == typeof(IContentType).Name && x.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshMain)) + .Select(x => x.Id) + .ToArray(); + + foreach (var payload in payloads) + Current.Logger.Debug($"Notified {payload.ChangeTypes} for content type {payload.Id}."); + + if (ids.Length > 0) // must have refreshes, not only removes + RefreshContentTypes(ids); + + // ignore media and member types - we're not caching them + } + + public void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) + { + if (_xml == null) return; // not initialized yet! + + // see above + // in all cases we just want to clear the content type cache + // the types will be reloaded if/when needed + foreach (var payload in payloads) + _contentTypeCache.ClearDataType(payload.Id); + + foreach (var payload in payloads) + Current.Logger.Debug($"Notified {(payload.Removed ? "Removed" : "Refreshed")} for data type {payload.Id}."); + + // that's all we need to do as the changes have NO impact whatsoever on the Xml content + + // ignore media and member types - we're not caching them + } + + private void ResyncCurrentPublishedSnapshot(XmlDocument xml) + { + var publishedSnapshot = (PublishedShapshot) _publishedSnapshotAccessor.PublishedSnapshot; + if (publishedSnapshot == null) return; + ((PublishedContentCache) publishedSnapshot.Content).Resync(xml); + ((PublishedMediaCache) publishedSnapshot.Media).Resync(); + + // not trying to resync members or domains, which are not cached really + } + + #endregion + + #region Manage change + + private void RefreshContentTypes(IEnumerable ids) + { + using (var safeXml = GetSafeXmlWriter()) + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTree); + var xmlDtos = scope.Database.Query(ReadCmsContentXmlForContentTypesSql, + new { nodeObjectType = Constants.ObjectTypes.Document, /*@ids =*/ ids }); + + foreach (var xmlDto in xmlDtos) + { + xmlDto.XmlNode = safeXml.Xml.ReadNode(XmlReader.Create(new StringReader(xmlDto.Xml))); + safeXml.Xml = AddOrUpdateXmlNode(safeXml.Xml, xmlDto); + } + + scope.Complete(); + safeXml.AcceptChanges(); + } + } + + // nothing to do, we have no cache + //private void RefreshMediaTypes(IEnumerable ids) + //{ } + + // nothing to do, we have no cache + //private void RefreshMemberTypes(IEnumerable ids) + //{ } + + // adds or updates a node (docNode) into a cache (xml) + private static XmlDocument AddOrUpdateXmlNode(XmlDocument xml, XmlDto xmlDto) + { + // sanity checks + var docNode = xmlDto.XmlNode; + if (xmlDto.Id != docNode.AttributeValue("id")) + throw new ArgumentException("Values of id and docNode/@id are different."); + if (xmlDto.ParentId != docNode.AttributeValue("parentID")) + throw new ArgumentException("Values of parentId and docNode/@parentID are different."); + + // find the document in the cache + XmlNode currentNode = xml.GetElementById(xmlDto.Id.ToInvariantString()); + + // if the document is not there already then it's a new document + // we must make sure that its document type exists in the schema + if (currentNode == null) + { + var xml2 = EnsureSchema(docNode.Name, xml); + if (ReferenceEquals(xml, xml2) == false) + docNode = xml2.ImportNode(docNode, true); + xml = xml2; + } + + // find the parent + XmlNode parentNode = xmlDto.Level == 1 + ? xml.DocumentElement + : xml.GetElementById(xmlDto.ParentId.ToInvariantString()); + + // no parent = cannot do anything + if (parentNode == null) + return xml; + + // insert/move the node under the parent + if (currentNode == null) + { + // document not there, new node, append + currentNode = docNode; + parentNode.AppendChild(currentNode); + } + else + { + // document found... we could just copy the currentNode children nodes over under + // docNode, then remove currentNode and insert docNode... the code below tries to + // be clever and faster, though only benchmarking could tell whether it's worth the + // pain... + + // first copy current parent ID - so we can compare with target parent + var moving = currentNode.AttributeValue("parentID") != xmlDto.ParentId; + + if (docNode.Name == currentNode.Name) + { + // name has not changed, safe to just update the current node + // by transfering values eg copying the attributes, and importing the data elements + TransferValuesFromDocumentXmlToPublishedXml(docNode, currentNode); + + // if moving, move the node to the new parent + // else it's already under the right parent + // (but maybe the sort order has been updated) + if (moving) + parentNode.AppendChild(currentNode); // remove then append to parentNode + } + else + { + // name has changed, must use docNode (with new name) + // move children nodes from currentNode to docNode (already has properties) + var children = currentNode.SelectNodes(ChildNodesXPath); + if (children == null) throw new Exception("oops"); + foreach (XmlNode child in children) + docNode.AppendChild(child); // remove then append to docNode + + // and put docNode in the right place - if parent has not changed, then + // just replace, else remove currentNode and insert docNode under the right parent + // (but maybe not at the right position due to sort order) + if (moving) + { + if (currentNode.ParentNode == null) throw new Exception("oops"); + currentNode.ParentNode.RemoveChild(currentNode); + parentNode.AppendChild(docNode); + } + else + { + // replacing might screw the sort order + parentNode.ReplaceChild(docNode, currentNode); + } + + currentNode = docNode; + } + } + + var attrs = currentNode.Attributes; + if (attrs == null) throw new Exception("oops."); + + var attr = attrs["rv"] ?? attrs.Append(xml.CreateAttribute("rv")); + attr.Value = xmlDto.Rv.ToString(CultureInfo.InvariantCulture); + + attr = attrs["path"] ?? attrs.Append(xml.CreateAttribute("path")); + attr.Value = xmlDto.Path; + + // if the nodes are not ordered, must sort + // (see U4-509 + has to work with ReplaceChild too) + //XmlHelper.SortNodesIfNeeded(parentNode, childNodesXPath, x => x.AttributeValue("sortOrder")); + + // but... + // if we assume that nodes are always correctly sorted + // then we just need to ensure that currentNode is at the right position. + // should be faster that moving all the nodes around. + XmlHelper.SortNode(parentNode, ChildNodesXPath, currentNode, x => x.AttributeValue("sortOrder")); + return xml; + } + + private static void TransferValuesFromDocumentXmlToPublishedXml(XmlNode documentNode, XmlNode publishedNode) + { + // remove all attributes from the published node + if (publishedNode.Attributes == null) throw new Exception("oops"); + publishedNode.Attributes.RemoveAll(); + + // remove all data nodes from the published node + var dataNodes = publishedNode.SelectNodes(DataNodesXPath); + if (dataNodes == null) throw new Exception("oops"); + foreach (XmlNode n in dataNodes) + publishedNode.RemoveChild(n); + + // append all attributes from the document node to the published node + if (documentNode.Attributes == null) throw new Exception("oops"); + foreach (XmlAttribute att in documentNode.Attributes) + ((XmlElement)publishedNode).SetAttribute(att.Name, att.Value); + + // find the first child node, if any + var childNodes = publishedNode.SelectNodes(ChildNodesXPath); + if (childNodes == null) throw new Exception("oops"); + var firstChildNode = childNodes.Count == 0 ? null : childNodes[0]; + + // append all data nodes from the document node to the published node + dataNodes = documentNode.SelectNodes(DataNodesXPath); + if (dataNodes == null) throw new Exception("oops"); + foreach (XmlNode n in dataNodes) + { + if (publishedNode.OwnerDocument == null) throw new Exception("oops"); + var imported = publishedNode.OwnerDocument.ImportNode(n, true); + if (firstChildNode == null) + publishedNode.AppendChild(imported); + else + publishedNode.InsertBefore(imported, firstChildNode); + } + } + + private static XmlNode ImportContent(XmlDocument xml, XmlDto dto) + { + var node = xml.ReadNode(XmlReader.Create(new StringReader(dto.Xml), new XmlReaderSettings + { + IgnoreWhitespace = true + })); + + if (node == null) throw new Exception("oops"); + if (node.Attributes == null) throw new Exception("oops"); + + var attr = xml.CreateAttribute("rv"); + attr.Value = dto.Rv.ToString(CultureInfo.InvariantCulture); + node.Attributes.Append(attr); + + attr = xml.CreateAttribute("path"); + attr.Value = dto.Path; + node.Attributes.Append(attr); + + return node; + } + + #endregion + + #region Handle Repository Events For Database Xml + + // we need them to be "repository" events ie to trigger from within the repository transaction, + // because they need to be consistent with the content that is being refreshed/removed - and that + // should be guaranteed by a DB transaction + // it is not the case at the moment, instead a global lock is used whenever content is modified - well, + // almost: rollback or unpublish do not implement it - nevertheless + + private static void OnContentRemovingEntity(DocumentRepository sender, DocumentRepository.ScopedEntityEventArgs args) + { + OnRemovedEntity(args.Scope.Database, args.Entity); + } + + private static void OnMediaRemovingEntity(MediaRepository sender, MediaRepository.ScopedEntityEventArgs args) + { + OnRemovedEntity(args.Scope.Database, args.Entity); + } + + private static void OnMemberRemovingEntity(MemberRepository sender, MemberRepository.ScopedEntityEventArgs args) + { + OnRemovedEntity(args.Scope.Database, args.Entity); + } + + private static void OnRemovedEntity(IUmbracoDatabase db, IContentBase item) + { + var parms = new { id = item.Id }; + db.Execute("DELETE FROM cmsContentXml WHERE nodeId=@id", parms); + db.Execute("DELETE FROM cmsPreviewXml WHERE nodeId=@id", parms); + + // note: could be optimized by using "WHERE nodeId IN (...)" delete clauses + } + + private static void OnContentRemovingVersion(DocumentRepository sender, DocumentRepository.ScopedVersionEventArgs args) + { + OnRemovedVersion(args.Scope.Database, args.EntityId, args.VersionId); + } + + private static void OnMediaRemovingVersion(MediaRepository sender, MediaRepository.ScopedVersionEventArgs args) + { + OnRemovedVersion(args.Scope.Database, args.EntityId, args.VersionId); + } + + private static void OnMemberRemovingVersion(MemberRepository sender, MemberRepository.ScopedVersionEventArgs args) + { + OnRemovedVersion(args.Scope.Database, args.EntityId, args.VersionId); + } + + private static void OnRemovedVersion(IUmbracoDatabase db, int entityId, int versionId) + { + // we do not version cmsPreviewXml anymore - nothing to do here + } + + private static readonly string[] PropertiesImpactingAllVersions = { "SortOrder", "ParentId", "Level", "Path", "Trashed" }; + + private static bool HasChangesImpactingAllVersions(IContent icontent) + { + var content = (Core.Models.Content)icontent; + + // UpdateDate will be dirty + // Published may be dirty if saving a Published entity + // so cannot do this (would always be true): + //return content.IsEntityDirty(); + + // have to be more precise & specify properties + return PropertiesImpactingAllVersions.Any(content.IsPropertyDirty); + } + + private void OnContentRefreshedEntity(DocumentRepository sender, DocumentRepository.ScopedEntityEventArgs args) + { + var db = args.Scope.Database; + var entity = args.Entity; + + // serialize edit values for preview + var editXml = EntityXmlSerializer.Serialize(_serviceContext.ContentService, _serviceContext.DataTypeService, _serviceContext.UserService, _serviceContext.LocalizationService, _segmentProviders, entity, false).ToDataString(); + + // change below to write only one row - not one per version + var dto1 = new PreviewXmlDto + { + NodeId = entity.Id, + Xml = editXml + }; + OnRepositoryRefreshed(db, dto1); + + // if unpublishing, remove from table + + if (((Content) entity).PublishedState == PublishedState.Unpublishing) + { + db.Execute("DELETE FROM cmsContentXml WHERE nodeId=@id", new { id = entity.Id }); + return; + } + + // need to update the published xml if we're saving the published version, + // or having an impact on that version - we update the published xml even when masked + + // fixme - in the repo... either its 'unpublished' and 'publishing', or 'published' and 'published', this has changed! + // fixme - what are we serializing really? which properties? + + // if not publishing, no change to published xml + if (((Content) entity).PublishedState != PublishedState.Publishing) + return; + + // serialize published values for content cache + var publishedXml = EntityXmlSerializer.Serialize(_serviceContext.ContentService, _serviceContext.DataTypeService, _serviceContext.UserService, _serviceContext.LocalizationService, _segmentProviders, entity, true).ToDataString(); + var dto2 = new ContentXmlDto { NodeId = entity.Id, Xml = publishedXml }; + OnRepositoryRefreshed(db, dto2); + + } + + private void OnMediaRefreshedEntity(MediaRepository sender, MediaRepository.ScopedEntityEventArgs args) + { + var db = args.Scope.Database; + var entity = args.Entity; + + // for whatever reason we delete some xml when the media is trashed + // at least that's what the MediaService implementation did + if (entity.Trashed) + db.Execute("DELETE FROM cmsContentXml WHERE nodeId=@id", new { id = entity.Id }); + + var xml = EntityXmlSerializer.Serialize(_serviceContext.MediaService, _serviceContext.DataTypeService, _serviceContext.UserService, _serviceContext.LocalizationService, _segmentProviders, entity).ToDataString(); + + var dto1 = new ContentXmlDto { NodeId = entity.Id, Xml = xml }; + OnRepositoryRefreshed(db, dto1); + } + + private void OnMemberRefreshedEntity(MemberRepository sender, MemberRepository.ScopedEntityEventArgs args) + { + var db = args.Scope.Database; + var entity = args.Entity; + + var xml = EntityXmlSerializer.Serialize(_serviceContext.DataTypeService, _serviceContext.LocalizationService, entity).ToDataString(); + + var dto1 = new ContentXmlDto { NodeId = entity.Id, Xml = xml }; + OnRepositoryRefreshed(db, dto1); + } + + private static void OnRepositoryRefreshed(IUmbracoDatabase db, ContentXmlDto dto) + { + // use a custom SQL to update row version on each update + //db.InsertOrUpdate(dto); + + db.InsertOrUpdate(dto, + "SET xml=@xml, rv=rv+1 WHERE nodeId=@id", + new + { + xml = dto.Xml, + id = dto.NodeId + }); + } + + private static void OnRepositoryRefreshed(IUmbracoDatabase db, PreviewXmlDto dto) + { + // cannot simply update because of PetaPoco handling of the composite key ;-( + // read http://stackoverflow.com/questions/11169144/how-to-modify-petapoco-class-to-work-with-composite-key-comprising-of-non-numeri + // it works in https://github.com/schotime/PetaPoco and then https://github.com/schotime/NPoco but not here + // + // not important anymore as we don't manage version anymore, + // but: + // + // also + // use a custom SQL to update row version on each update + //db.InsertOrUpdate(dto); + + db.InsertOrUpdate(dto, + "SET xml=@xml, rv=rv+1 WHERE nodeId=@id", + new + { + xml = dto.Xml, + id = dto.NodeId, + }); + } + + private void OnContentTypeRefreshedEntity(IContentTypeService sender, ContentTypeChange.EventArgs args) + { + const ContentTypeChangeTypes types // only for those that have been refreshed + = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther | ContentTypeChangeTypes.Create; + var contentTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); + if (contentTypeIds.Any()) + RebuildContentAndPreviewXml(contentTypeIds: contentTypeIds); + } + + private void OnMediaTypeRefreshedEntity(IMediaTypeService sender, ContentTypeChange.EventArgs args) + { + const ContentTypeChangeTypes types // only for those that have been refreshed + = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther | ContentTypeChangeTypes.Create; + var mediaTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); + if (mediaTypeIds.Any()) + RebuildMediaXml(contentTypeIds: mediaTypeIds); + } + + private void OnMemberTypeRefreshedEntity(IMemberTypeService sender, ContentTypeChange.EventArgs args) + { + const ContentTypeChangeTypes types // only for those that have been refreshed + = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther | ContentTypeChangeTypes.Create; + var memberTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); + if (memberTypeIds.Any()) + RebuildMemberXml(contentTypeIds: memberTypeIds); + } + + #endregion + + #region Rebuild Database Xml + + // RepositoryCacheMode.Scoped because we do NOT want to use the L2 cache that may be out-of-sync + // hopefully this does not cause issues and we're not nested in another scope w/different mode + // fixme - well, guess what? + // original code made sure the repository used no cache + // now we're using the Scoped scope cache mode + // and then? + + public void RebuildContentAndPreviewXml(int groupSize = 5000, IEnumerable contentTypeIds = null) + { + var contentTypeIdsA = contentTypeIds?.ToArray(); + + using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.None)) + { + scope.WriteLock(Constants.Locks.ContentTree); + RebuildContentXmlLocked(scope, groupSize, contentTypeIdsA); + RebuildPreviewXmlLocked(scope, groupSize, contentTypeIdsA); + scope.Complete(); + } + } + + public void RebuildContentXml(int groupSize = 5000, IEnumerable contentTypeIds = null) + { + using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.None)) + { + scope.WriteLock(Constants.Locks.ContentTree); + RebuildContentXmlLocked(scope, groupSize, contentTypeIds); + scope.Complete(); + } + } + + // assumes content tree lock + private void RebuildContentXmlLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) + { + var contentTypeIdsA = contentTypeIds?.ToArray(); + var contentObjectType = Constants.ObjectTypes.Document; + var db = scope.Database; + + // remove all - if anything fails the transaction will rollback + if (contentTypeIds == null || contentTypeIdsA.Length == 0) + { + // must support SQL-CE + // db.Execute(@"DELETE cmsContentXml + //FROM cmsContentXml + //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) + //WHERE umbracoNode.nodeObjectType=@objType", + db.Execute(@"DELETE FROM cmsContentXml +WHERE cmsContentXml.nodeId IN ( + SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType +)", + new { objType = contentObjectType }); + } + else + { + // assume number of ctypes won't blow IN(...) + // must support SQL-CE + // db.Execute(@"DELETE cmsContentXml + //FROM cmsContentXml + //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) + //JOIN {Constants.DatabaseSchema.Tables.Content} ON (cmsContentXml.nodeId={Constants.DatabaseSchema.Tables.Content}.nodeId) + //WHERE umbracoNode.nodeObjectType=@objType + //AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)", + db.Execute($@"DELETE FROM cmsContentXml +WHERE cmsContentXml.nodeId IN ( + SELECT id FROM umbracoNode + JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id + WHERE umbracoNode.nodeObjectType=@objType + AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) +)", + new { objType = contentObjectType, ctypes = contentTypeIdsA }); + } + + // insert back - if anything fails the transaction will rollback + var query = scope.SqlContext.Query().Where(x => x.Published); + if (contentTypeIds != null && contentTypeIdsA.Length > 0) + query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) + + long pageIndex = 0; + long processed = 0; + long total; + do + { + var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + const bool published = true; // contentXml contains published content! + var items = descendants.Select(c => new ContentXmlDto { NodeId = c.Id, Xml = + EntityXmlSerializer.Serialize(_serviceContext.ContentService, _serviceContext.DataTypeService, _serviceContext.UserService, _serviceContext.LocalizationService, _segmentProviders, c, published).ToDataString() }).ToArray(); + db.BulkInsertRecords(items); + processed += items.Length; + } while (processed < total); + } + + public void RebuildPreviewXml(int groupSize = 5000, IEnumerable contentTypeIds = null) + { + using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.None)) + { + scope.WriteLock(Constants.Locks.ContentTree); + RebuildPreviewXmlLocked(scope, groupSize, contentTypeIds); + scope.Complete(); + scope.Complete(); + } + } + + // assumes content tree lock + private void RebuildPreviewXmlLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) + { + var contentTypeIdsA = contentTypeIds?.ToArray(); + var contentObjectType = Constants.ObjectTypes.Document; + var db = scope.Database; + + // remove all - if anything fails the transaction will rollback + if (contentTypeIds == null || contentTypeIdsA.Length == 0) + { + // must support SQL-CE + // db.Execute(@"DELETE cmsPreviewXml + //FROM cmsPreviewXml + //JOIN umbracoNode ON (cmsPreviewXml.nodeId=umbracoNode.Id) + //WHERE umbracoNode.nodeObjectType=@objType", + db.Execute(@"DELETE FROM cmsPreviewXml +WHERE cmsPreviewXml.nodeId IN ( + SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType +)", + new { objType = contentObjectType }); + } + else + { + // assume number of ctypes won't blow IN(...) + // must support SQL-CE + // db.Execute(@"DELETE cmsPreviewXml + //FROM cmsPreviewXml + //JOIN umbracoNode ON (cmsPreviewXml.nodeId=umbracoNode.Id) + //JOIN {Constants.DatabaseSchema.Tables.Content} ON (cmsPreviewXml.nodeId={Constants.DatabaseSchema.Tables.Content}.nodeId) + //WHERE umbracoNode.nodeObjectType=@objType + //AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)", + db.Execute($@"DELETE FROM cmsPreviewXml +WHERE cmsPreviewXml.nodeId IN ( + SELECT id FROM umbracoNode + JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id + WHERE umbracoNode.nodeObjectType=@objType + AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) +)", + new { objType = contentObjectType, ctypes = contentTypeIdsA }); + } + + // insert back - if anything fails the transaction will rollback + var query = scope.SqlContext.Query(); + if (contentTypeIds != null && contentTypeIdsA.Length > 0) + query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) + + long pageIndex = 0; + long processed = 0; + long total; + do + { + // .GetPagedResultsByQuery implicitely adds ({Constants.DatabaseSchema.Tables.Document}.newest = 1) which + // is what we want for preview (ie latest version of a content, published or not) + var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + const bool published = true; // previewXml contains edit content! + var items = descendants.Select(c => new PreviewXmlDto + { + NodeId = c.Id, + Xml = EntityXmlSerializer.Serialize(_serviceContext.ContentService, _serviceContext.DataTypeService, _serviceContext.UserService, _serviceContext.LocalizationService, _segmentProviders, c, published).ToDataString() + }).ToArray(); + db.BulkInsertRecords(items); + processed += items.Length; + } while (processed < total); + } + + public void RebuildMediaXml(int groupSize = 5000, IEnumerable contentTypeIds = null) + { + using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.None)) + { + scope.WriteLock(Constants.Locks.MediaTree); + RebuildMediaXmlLocked(scope, groupSize, contentTypeIds); + scope.Complete(); + } + } + + // assumes media tree lock + public void RebuildMediaXmlLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) + { + var contentTypeIdsA = contentTypeIds?.ToArray(); + var mediaObjectType = Constants.ObjectTypes.Media; + var db = scope.Database; + + // remove all - if anything fails the transaction will rollback + if (contentTypeIds == null || contentTypeIdsA.Length == 0) + { + // must support SQL-CE + // db.Execute(@"DELETE cmsContentXml + //FROM cmsContentXml + //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) + //WHERE umbracoNode.nodeObjectType=@objType", + db.Execute(@"DELETE FROM cmsContentXml +WHERE cmsContentXml.nodeId IN ( + SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType +)", + new { objType = mediaObjectType }); + } + else + { + // assume number of ctypes won't blow IN(...) + // must support SQL-CE + // db.Execute(@"DELETE cmsContentXml + //FROM cmsContentXml + //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) + //JOIN {Constants.DatabaseSchema.Tables.Content} ON (cmsContentXml.nodeId={Constants.DatabaseSchema.Tables.Content}.nodeId) + //WHERE umbracoNode.nodeObjectType=@objType + //AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)", + db.Execute($@"DELETE FROM cmsContentXml +WHERE cmsContentXml.nodeId IN ( + SELECT id FROM umbracoNode + JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id + WHERE umbracoNode.nodeObjectType=@objType + AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) +)", + new { objType = mediaObjectType, ctypes = contentTypeIdsA }); + } + + // insert back - if anything fails the transaction will rollback + var query = scope.SqlContext.Query(); + if (contentTypeIds != null && contentTypeIdsA.Length > 0) + query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) + + long pageIndex = 0; + long processed = 0; + long total; + do + { + var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var items = descendants.Select(m => new ContentXmlDto { NodeId = m.Id, Xml = + EntityXmlSerializer.Serialize(_serviceContext.MediaService, _serviceContext.DataTypeService, _serviceContext.UserService, _serviceContext.LocalizationService, _segmentProviders, m).ToDataString() }).ToArray(); + db.BulkInsertRecords(items); + processed += items.Length; + } while (processed < total); + } + + public void RebuildMemberXml(int groupSize = 5000, IEnumerable contentTypeIds = null) + { + using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.None)) + { + scope.WriteLock(Constants.Locks.MemberTree); + RebuildMemberXmlLocked(scope, groupSize, contentTypeIds); + scope.Complete(); + } + } + + // assumes member tree lock + public void RebuildMemberXmlLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) + { + var contentTypeIdsA = contentTypeIds?.ToArray(); + var memberObjectType = Constants.ObjectTypes.Member; + var db = scope.Database; + + // remove all - if anything fails the transaction will rollback + if (contentTypeIds == null || contentTypeIdsA.Length == 0) + { + // must support SQL-CE + // db.Execute(@"DELETE cmsContentXml + //FROM cmsContentXml + //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) + //WHERE umbracoNode.nodeObjectType=@objType", + db.Execute(@"DELETE FROM cmsContentXml +WHERE cmsContentXml.nodeId IN ( + SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType +)", + new { objType = memberObjectType }); + } + else + { + // assume number of ctypes won't blow IN(...) + // must support SQL-CE + // db.Execute(@"DELETE cmsContentXml + //FROM cmsContentXml + //JOIN umbracoNode ON (cmsContentXml.nodeId=umbracoNode.Id) + //JOIN {Constants.DatabaseSchema.Tables.Content} ON (cmsContentXml.nodeId={Constants.DatabaseSchema.Tables.Content}.nodeId) + //WHERE umbracoNode.nodeObjectType=@objType + //AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)", + db.Execute($@"DELETE FROM cmsContentXml +WHERE cmsContentXml.nodeId IN ( + SELECT id FROM umbracoNode + JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id + WHERE umbracoNode.nodeObjectType=@objType + AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) +)", + new { objType = memberObjectType, ctypes = contentTypeIdsA }); + } + + // insert back - if anything fails the transaction will rollback + var query = scope.SqlContext.Query(); + if (contentTypeIds != null && contentTypeIdsA.Length > 0) + query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) + + long pageIndex = 0; + long processed = 0; + long total; + do + { + var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, "Path", Direction.Ascending, true); + var items = descendants.Select(m => new ContentXmlDto { NodeId = m.Id, Xml = EntityXmlSerializer.Serialize(_serviceContext.DataTypeService, _serviceContext.LocalizationService, m).ToDataString() }).ToArray(); + db.BulkInsertRecords(items); + processed += items.Length; + } while (processed < total); + } + + public bool VerifyContentAndPreviewXml() + { + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.ContentTree); + var ok = VerifyContentAndPreviewXmlLocked(scope); + scope.Complete(); + return ok; + } + } + + // assumes content tree lock + private static bool VerifyContentAndPreviewXmlLocked(IScope scope) + { + // every published content item should have a corresponding row in cmsContentXml + // every content item should have a corresponding row in cmsPreviewXml + // and that row should have the key="..." attribute + + var contentObjectType = Constants.ObjectTypes.Document; + var db = scope.Database; + + var count = db.ExecuteScalar($@"SELECT COUNT(*) +FROM umbracoNode +JOIN {Constants.DatabaseSchema.Tables.Document} ON (umbracoNode.id={Constants.DatabaseSchema.Tables.Document}.nodeId and {Constants.DatabaseSchema.Tables.Document}.published=1) +LEFT JOIN cmsContentXml ON (umbracoNode.id=cmsContentXml.nodeId) +WHERE umbracoNode.nodeObjectType=@objType +AND cmsContentXml.nodeId IS NULL OR cmsContentXml.xml NOT LIKE '% key=""' +", new { objType = contentObjectType }); + + if (count > 0) return false; + + count = db.ExecuteScalar(@"SELECT COUNT(*) +FROM umbracoNode +LEFT JOIN cmsPreviewXml ON (umbracoNode.id=cmsPreviewXml.nodeId) +WHERE umbracoNode.nodeObjectType=@objType +AND cmsPreviewXml.nodeId IS NULL OR cmsPreviewXml.xml NOT LIKE '% key=""' +", new { objType = contentObjectType }); + + return count == 0; + } + + public bool VerifyMediaXml() + { + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.MediaTree); + var ok = VerifyMediaXmlLocked(scope); + scope.Complete(); + return ok; + } + } + + // assumes media tree lock + public bool VerifyMediaXmlLocked(IScope scope) + { + // every non-trashed media item should have a corresponding row in cmsContentXml + // and that row should have the key="..." attribute + // fixme - where's the trashed test here? + + var mediaObjectType = Constants.ObjectTypes.Media; + var db = scope.Database; + + var count = db.ExecuteScalar($@"SELECT COUNT(*) +FROM umbracoNode +JOIN {Constants.DatabaseSchema.Tables.Document} ON (umbracoNode.id={Constants.DatabaseSchema.Tables.Document}.nodeId and {Constants.DatabaseSchema.Tables.Document}.published=1) +LEFT JOIN cmsContentXml ON (umbracoNode.id=cmsContentXml.nodeId) +WHERE umbracoNode.nodeObjectType=@objType +AND cmsContentXml.nodeId IS NULL OR cmsContentXml.xml NOT LIKE '% key=""' +", new { objType = mediaObjectType }); + + return count == 0; + } + + public bool VerifyMemberXml() + { + using (var scope = _scopeProvider.CreateScope()) + { + scope.ReadLock(Constants.Locks.MemberTree); + var ok = VerifyMemberXmlLocked(scope); + scope.Complete(); + return ok; + } + } + + // assumes member tree lock + public bool VerifyMemberXmlLocked(IScope scope) + { + // every member item should have a corresponding row in cmsContentXml + + var memberObjectType = Constants.ObjectTypes.Member; + var db = scope.Database; + + var count = db.ExecuteScalar(@"SELECT COUNT(*) +FROM umbracoNode +LEFT JOIN cmsContentXml ON (umbracoNode.id=cmsContentXml.nodeId) +WHERE umbracoNode.nodeObjectType=@objType +AND cmsContentXml.nodeId IS NULL +", new { objType = memberObjectType }); + + return count == 0; + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStoreFilePersister.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStoreFilePersister.cs new file mode 100644 index 0000000000..6b247bdf0d --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStoreFilePersister.cs @@ -0,0 +1,182 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Web.Scheduling; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + /// + /// This is the background task runner that persists the xml file to the file system + /// + /// + /// This is used so that all file saving is done on a web aware worker background thread and all logic is performed async so this + /// process will not interfere with any web requests threads. This is also done as to not require any global locks and to ensure that + /// if multiple threads are performing publishing tasks that the file will be persisted in accordance with the final resulting + /// xml structure since the file writes are queued. + /// + internal class XmlStoreFilePersister : LatchedBackgroundTaskBase + { + private readonly IBackgroundTaskRunner _runner; + private readonly ILogger _logger; + private readonly XmlStore _store; + private readonly object _locko = new object(); + private bool _released; + private Timer _timer; + private DateTime _initialTouch; + private readonly AsyncLock _runLock = new AsyncLock(); // ensure we run once at a time + + // note: + // as long as the runner controls the runs, we know that we run once at a time, but + // when the AppDomain goes down and the runner has completed and yet the persister is + // asked to save, then we need to run immediately - but the runner may be running, so + // we need to make sure there's no collision - hence _runLock + + private const int WaitMilliseconds = 4000; // save the cache 4s after the last change (ie every 4s min) + private const int MaxWaitMilliseconds = 30000; // save the cache after some time (ie no more than 30s of changes) + + // save the cache when the app goes down + public override bool RunsOnShutdown => _timer != null; + + // initialize the first instance, which is inactive (not touched yet) + public XmlStoreFilePersister(IBackgroundTaskRunner runner, XmlStore store, ILogger logger) + : this(runner, store, logger, false) + { } + + // initialize further instances, which are active (touched) + private XmlStoreFilePersister(IBackgroundTaskRunner runner, XmlStore store, ILogger logger, bool touched) + { + _runner = runner; + _store = store; + _logger = logger; + + if (_runner.TryAdd(this) == false) + { + _runner = null; // runner's down + _released = true; // don't mess with timer + return; + } + + // runner could decide to run it anytime now + + if (touched == false) return; + + _logger.Debug(() => $"Created, save in {WaitMilliseconds}ms."); + _initialTouch = DateTime.Now; + _timer = new Timer(_ => TimerRelease()); + _timer.Change(WaitMilliseconds, 0); + } + + public XmlStoreFilePersister Touch() + { + // if _released is false then we're going to setup a timer + // then the runner wants to shutdown & run immediately + // this sets _released to true & the timer will trigger eventualy & who cares? + // if _released is true, either it's a normal release, or + // a runner shutdown, in which case we won't be able to + // add a new task, and so we'll run immediately + + var ret = this; + var runNow = false; + + lock (_locko) + { + if (_released) // our timer has triggered OR the runner is shutting down + { + _logger.Debug("Touched, was released..."); + + // release: has run or is running, too late, return a new task (adds itself to runner) + if (_runner == null) + { + _logger.Debug("Runner is down, run now."); + runNow = true; + } + else + { + _logger.Debug("Create new..."); + ret = new XmlStoreFilePersister(_runner, _store, _logger, true); + if (ret._runner == null) + { + // could not enlist with the runner, runner is completed, must run now + _logger.Debug("Runner is down, run now."); + runNow = true; + } + } + } + + else if (_timer == null) // we don't have a timer yet + { + _logger.Debug(() => $"Touched, was idle, start and save in {WaitMilliseconds}ms."); + _initialTouch = DateTime.Now; + _timer = new Timer(_ => TimerRelease()); + _timer.Change(WaitMilliseconds, 0); + } + + else // we have a timer + { + // change the timer to trigger in WaitMilliseconds unless we've been touched first more + // than MaxWaitMilliseconds ago and then leave the time unchanged + + if (DateTime.Now - _initialTouch < TimeSpan.FromMilliseconds(MaxWaitMilliseconds)) + { + _logger.Debug(() => $"Touched, was waiting, can delay, save in {WaitMilliseconds}ms."); + _timer.Change(WaitMilliseconds, 0); + } + else + { + _logger.Debug("Touched, was waiting, cannot delay."); + } + } + } + + // note: this comes from 7.x where it was not possible to lock the entire content service + // in our case, the XmlStore configures everything so that it is not possible to access content + // when going down, so this should never happen. + + if (runNow) + //Run(); + _logger.Warn("Cannot write now because we are going down, changes may be lost."); + + return ret; // this, by default, unless we created a new one + } + + private void TimerRelease() + { + lock (_locko) + { + _logger.Debug("Timer: release."); + _released = true; + + Release(); + } + } + + public override bool IsAsync => false; + + public override void Run() + { + lock (_locko) + { + _logger.Debug("Run now (sync)."); + // not really needed but safer (it's only us invoking Run, but the method is public...) + _released = true; + } + + using (_runLock.Lock()) + { + _store.SaveXmlToFile(); + } + } + + protected override void DisposeResources() + { + base.DisposeResources(); + + // stop the timer + if (_timer == null) return; + _timer.Change(Timeout.Infinite, Timeout.Infinite); + _timer.Dispose(); + } + } +} diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 6091c6c7dc..7bdd196f20 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1,98 +1,49 @@ -using System; +using System; using System.Collections.Generic; -using System.ComponentModel; using System.Data; using System.Globalization; using System.Linq; using System.Web; +using Examine; using Examine.LuceneEngine.SearchCriteria; -using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Services; -using Umbraco.Web.Models; using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Web.Routing; -using ContentType = umbraco.cms.businesslogic.ContentType; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Composing; namespace Umbraco.Web { + using Examine = global::Examine; + /// /// Provides extension methods for IPublishedContent. /// - public static class PublishedContentExtensions + public static class PublishedContentExtensions { - #region Key - - public static Guid GetKey(this IPublishedContent content) - { - // fast - var contentWithKey = content as IPublishedContentWithKey; - if (contentWithKey != null) return contentWithKey.Key; - - // try to unwrap (models...) - var contentWrapped = content as PublishedContentWrapped; - while (contentWrapped != null) - { - content = contentWrapped.Unwrap(); - contentWrapped = content as PublishedContentWrapped; - } - - // again - contentWithKey = content as IPublishedContentWithKey; - if (contentWithKey != null) return contentWithKey.Key; - - LogHelper.Debug(typeof(PublishedContentExtensions), string.Format("Could not get key for IPublishedContent with id {0} of type {1}.", content.Id, content.GetType().FullName)); - return Guid.Empty; - } - - #endregion - #region Urls /// - /// Gets the url for the content. - /// - /// The content. - /// The url for the content. - [Obsolete("NiceUrl() is obsolete, use the Url() method instead")] - public static string NiceUrl(this IPublishedContent content) - { - return content.Url(); - } - - /// - /// Gets the url for the content. - /// - /// The content. - /// The url for the content. - /// Better use the Url property but that method is here to complement UrlAbsolute(). - public static string Url(this IPublishedContent content) - { - return content.Url; - } - - /// - /// Gets the absolute url for the content. - /// + /// Gets the url for the content. + /// /// The content. - /// The absolute url for the content. - [Obsolete("NiceUrlWithDomain() is obsolete, use the UrlAbsolute() method instead.")] - public static string NiceUrlWithDomain(this IPublishedContent content) - { - return content.UrlAbsolute(); - } + /// The url for the content. + /// Better use the Url property but that method is here to complement UrlAbsolute(). + public static string Url(this IPublishedContent content) + { + return content.Url; + } - /// - /// Gets the absolute url for the content. - /// - /// The content. - /// The absolute url for the content. + /// + /// Gets the absolute url for the content. + /// + /// The content. + /// The absolute url for the content. //[Obsolete("UrlWithDomain() is obsolete, use the UrlAbsolute() method instead.")] public static string UrlWithDomain(this IPublishedContent content) - { - return content.UrlAbsolute(); - } + { + return content.UrlAbsolute(); + } /// /// Gets the absolute url for the content. @@ -122,63 +73,20 @@ namespace Umbraco.Web #region Template /// - /// Returns the current template Alias - /// - /// - /// - public static string GetTemplateAlias(this IPublishedContent content) - { - var template = ApplicationContext.Current.Services.FileService.GetTemplate(content.TemplateId); - return template == null ? string.Empty : template.Alias; - } - - #endregion - - #region IsComposedOf - - /// - /// Gets a value indicating whether the content is of a content type composed of the given alias + /// Returns the current template Alias /// - /// The content. - /// The content type alias. - /// A value indicating whether the content is of a content type composed of a content type identified by the alias. - public static bool IsComposedOf(this IPublishedContent content, string alias) + /// + /// + public static string GetTemplateAlias(this IPublishedContent content) { - return content.ContentType.CompositionAliases.Contains(alias); - } - - #endregion - - #region HasProperty - - /// - /// Gets a value indicating whether the content has a property identified by its alias. - /// - /// The content. - /// The property alias. - /// A value indicating whether the content has the property identified by the alias. - /// The content may have a property, and that property may not have a value. - public static bool HasProperty(this IPublishedContent content, string alias) - { - return content.ContentType.GetPropertyType(alias) != null; + var template = Current.Services.FileService.GetTemplate(content.TemplateId); + return template == null ? string.Empty : template.Alias; } #endregion #region HasValue - /// - /// Gets a value indicating whether the content has a value for a property identified by its alias. - /// - /// The content. - /// The property alias. - /// A value indicating whether the content has a value for the property identified by the alias. - /// Returns true if GetProperty(alias) is not null and GetProperty(alias).HasValue is true. - public static bool HasValue(this IPublishedContent content, string alias) - { - return content.HasValue(alias, false); - } - /// /// Gets a value indicating whether the content has a value for a property identified by its alias. /// @@ -190,24 +98,7 @@ namespace Umbraco.Web public static bool HasValue(this IPublishedContent content, string alias, bool recurse) { var prop = content.GetProperty(alias, recurse); - return prop != null && prop.HasValue; - } - - /// - /// Returns one of two strings depending on whether the content has a value for a property identified by its alias. - /// - /// The content. - /// The property alias. - /// The value to return if the content has a value for the property. - /// The value to return if the content has no value for the property. - /// Either or depending on whether the content - /// has a value for the property identified by the alias. - public static IHtmlString HasValue(this IPublishedContent content, string alias, - string valueIfTrue, string valueIfFalse = null) - { - return content.HasValue(alias, false) - ? new HtmlString(valueIfTrue) - : new HtmlString(valueIfFalse ?? string.Empty); + return prop != null && prop.HasValue(); } /// @@ -230,63 +121,7 @@ namespace Umbraco.Web #endregion - #region GetPropertyValue - - /// - /// Gets the value of a content's property identified by its alias. - /// - /// The content. - /// The property alias. - /// The value of the content's property identified by the alias. - /// - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, returns null. - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static object GetPropertyValue(this IPublishedContent content, string alias) - { - var property = content.GetProperty(alias); - return property == null ? null : property.Value; - } - - /// - /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. - /// - /// The content. - /// The property alias. - /// The default value. - /// The value of the content's property identified by the alias, if it exists, otherwise a default value. - /// - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, returns . - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static object GetPropertyValue(this IPublishedContent content, string alias, string defaultValue) - { - var property = content.GetProperty(alias); - return property == null || property.HasValue == false ? defaultValue : property.Value; - } - - /// - /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. - /// - /// The content. - /// The property alias. - /// The default value. - /// The value of the content's property identified by the alias, if it exists, otherwise a default value. - /// - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, returns . - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static object GetPropertyValue(this IPublishedContent content, string alias, object defaultValue) - { - var property = content.GetProperty(alias); - return property == null || property.HasValue == false ? defaultValue : property.Value; - } + #region Value /// /// Recursively gets the value of a content's property identified by its alias. @@ -302,10 +137,10 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static object GetPropertyValue(this IPublishedContent content, string alias, bool recurse) + public static object Value(this IPublishedContent content, string alias, bool recurse) { var property = content.GetProperty(alias, recurse); - return property == null ? null : property.Value; + return property?.GetValue(); } /// @@ -323,52 +158,15 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static object GetPropertyValue(this IPublishedContent content, string alias, bool recurse, object defaultValue) + public static object Value(this IPublishedContent content, string alias, bool recurse, object defaultValue) { var property = content.GetProperty(alias, recurse); - return property == null || property.HasValue == false ? defaultValue : property.Value; + return property == null || property.HasValue() == false ? defaultValue : property.GetValue(); } #endregion - #region GetPropertyValue - - /// - /// Gets the value of a content's property identified by its alias, converted to a specified type. - /// - /// The target property type. - /// The content. - /// The property alias. - /// The value of the content's property identified by the alias, converted to the specified type. - /// - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns default(T). - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static T GetPropertyValue(this IPublishedContent content, string alias) - { - return content.GetPropertyValue(alias, false, false, default(T)); - } - - /// - /// Gets the value of a content's property identified by its alias, converted to a specified type, if it exists, otherwise a default value. - /// - /// The target property type. - /// The content. - /// The property alias. - /// The default value. - /// The value of the content's property identified by the alias, converted to the specified type, if it exists, otherwise a default value. - /// - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns . - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static T GetPropertyValue(this IPublishedContent content, string alias, T defaultValue) - { - return content.GetPropertyValue(alias, false, true, defaultValue); - } + #region Value /// /// Recursively gets the value of a content's property identified by its alias, converted to a specified type. @@ -385,9 +183,9 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static T GetPropertyValue(this IPublishedContent content, string alias, bool recurse) + public static T Value(this IPublishedContent content, string alias, bool recurse) { - return content.GetPropertyValue(alias, recurse, false, default(T)); + return content.Value(alias, recurse, false, default(T)); } /// @@ -406,215 +204,83 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static T GetPropertyValue(this IPublishedContent content, string alias, bool recurse, T defaultValue) + public static T Value(this IPublishedContent content, string alias, bool recurse, T defaultValue) { - return content.GetPropertyValue(alias, recurse, true, defaultValue); + return content.Value(alias, recurse, true, defaultValue); } - internal static T GetPropertyValue(this IPublishedContent content, string alias, bool recurse, bool withDefaultValue, T defaultValue) + internal static T Value(this IPublishedContent content, string alias, bool recurse, bool withDefaultValue, T defaultValue) { var property = content.GetProperty(alias, recurse); if (property == null) return defaultValue; - return property.GetValue(withDefaultValue, defaultValue); - } - - #endregion - - // copied over from Core.PublishedContentExtensions - should be obsoleted - [Obsolete("GetRecursiveValue() is obsolete, use GetPropertyValue().")] - public static string GetRecursiveValue(this IPublishedContent content, string alias) - { - var value = content.GetPropertyValue(alias, true); - return value == null ? string.Empty : value.ToString(); - } - - #region Search - - public static IEnumerable Search(this IPublishedContent content, string term, bool useWildCards = true, string searchProvider = null) - { - var searcher = Examine.ExamineManager.Instance.DefaultSearchProvider; - if (string.IsNullOrEmpty(searchProvider) == false) - searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider]; - - var t = term.Escape().Value; - if (useWildCards) - t = term.MultipleCharacterWildcard().Value; - - var luceneQuery = "+__Path:(" + content.Path.Replace("-", "\\-") + "*) +" + t; - var crit = searcher.CreateSearchCriteria().RawQuery(luceneQuery); - - return content.Search(crit, searcher); - } - - public static IEnumerable SearchDescendants(this IPublishedContent content, string term, bool useWildCards = true, string searchProvider = null) - { - return content.Search(term, useWildCards, searchProvider); - } - - public static IEnumerable SearchChildren(this IPublishedContent content, string term, bool useWildCards = true, string searchProvider = null) - { - var searcher = Examine.ExamineManager.Instance.DefaultSearchProvider; - if (string.IsNullOrEmpty(searchProvider) == false) - searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider]; - - var t = term.Escape().Value; - if (useWildCards) - t = term.MultipleCharacterWildcard().Value; - - var luceneQuery = "+parentID:" + content.Id + " +" + t; - var crit = searcher.CreateSearchCriteria().RawQuery(luceneQuery); - - return content.Search(crit, searcher); - } - - public static IEnumerable Search(this IPublishedContent content, Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) - { - var s = Examine.ExamineManager.Instance.DefaultSearchProvider; - if (searchProvider != null) - s = searchProvider; - - var results = s.Search(criteria); - return results.ConvertSearchResultToPublishedContent(UmbracoContext.Current.ContentCache); - } - - #endregion - - #region ToContentSet - - /// - /// Returns the content enumerable as a content set. - /// - /// The content enumerable. - /// A content set wrapping the content enumerable. - public static PublishedContentSet ToContentSet(this IEnumerable source) - where T : class, IPublishedContent - { - return new PublishedContentSet(source); - } - - /// - /// Returns the ordered content enumerable as an ordered content set. - /// - /// The ordered content enumerable. - /// A ordered content set wrapping the ordered content enumerable. - public static PublishedContentOrderedSet ToContentSet(this IOrderedEnumerable source) - where T : class, IPublishedContent - { - return new PublishedContentOrderedSet(source); - } - - #endregion - - #region Dynamic Linq Extensions - - [Obsolete("This method uses dynamics which will be removed in future versions, use strongly typed syntax instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IQueryable OrderBy(this IEnumerable source, string predicate) - { - var dList = new DynamicPublishedContentList(source); - return dList.OrderBy(predicate); - } - - [Obsolete("This method uses dynamics which will be removed in future versions, use strongly typed syntax instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IQueryable Where(this IEnumerable list, string predicate) - { - // wrap in DynamicPublishedContentList so that the ContentSet is correct - // though that code is somewhat ugly. - - var dlist = new DynamicPublishedContentList(new DynamicPublishedContentList(list) - .Where(predicate)); - - return dlist.AsQueryable(); - } - - [Obsolete("This method uses dynamics which will be removed in future versions, use strongly typed syntax instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IEnumerable> GroupBy(this IEnumerable list, string predicate) - { - var dList = new DynamicPublishedContentList(list); - return dList.GroupBy(predicate); - } - - [Obsolete("This method uses dynamics which will be removed in future versions, use strongly typed syntax instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IQueryable Select(this IEnumerable list, string predicate, params object[] values) - { - var dList = new DynamicPublishedContentList(list); - return dList.Select(predicate); - } - - [Obsolete("This method uses dynamics which will be removed in future versions, use strongly typed syntax instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static HtmlString Where(this IPublishedContent content, string predicate, string valueIfTrue) - { - if (content == null) throw new ArgumentNullException("content"); - return content.Where(predicate, valueIfTrue, string.Empty); - } - - [Obsolete("This method uses dynamics which will be removed in future versions, use strongly typed syntax instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static HtmlString Where(this IPublishedContent content, string predicate, string valueIfTrue, string valueIfFalse) - { - if (content == null) throw new ArgumentNullException("content"); - return new HtmlString(content.Where(predicate) ? valueIfTrue : valueIfFalse); - } - - [Obsolete("This method uses dynamics which will be removed in future versions, use strongly typed syntax instead: Where(x => x.IsVisible())")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static bool Where(this IPublishedContent content, string predicate) - { - if (content == null) throw new ArgumentNullException("content"); - var dynamicDocumentList = new DynamicPublishedContentList { content.AsDynamicOrNull() }; - var filtered = dynamicDocumentList.Where(predicate); - return filtered.Count() == 1; + return property.Value(withDefaultValue, defaultValue); } #endregion - #region AsDynamic + #region Search - [Obsolete("The use of dynamics has been deprecated, use strongly typed syntax instead, dynamics will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static dynamic AsDynamic(this IPublishedContent content) - { - if (content == null) throw new ArgumentNullException("content"); - return new DynamicPublishedContent(content); - } + public static IEnumerable Search(this IPublishedContent content, string term, bool useWildCards = true, string indexName = null) + { + //TODO: we should pass in the IExamineManager? - [Obsolete("The use of dynamics has been deprecated, use strongly typed syntax instead, dynamics will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - internal static DynamicPublishedContent AsDynamicOrNull(this IPublishedContent content) - { - return content == null ? null : new DynamicPublishedContent(content); - } + var searcher = string.IsNullOrEmpty(indexName) + ? Examine.ExamineManager.Instance.GetSearcher(Constants.Examine.ExternalIndexer) + : Examine.ExamineManager.Instance.GetSearcher(indexName); + + if (searcher == null) + throw new InvalidOperationException("No searcher found for index " + indexName); + + var t = term.Escape().Value; + if (useWildCards) + t = term.MultipleCharacterWildcard().Value; + + var luceneQuery = "+__Path:(" + content.Path.Replace("-", "\\-") + "*) +" + t; + var crit = searcher.CreateCriteria().RawQuery(luceneQuery); + + return content.Search(crit, searcher); + } + + public static IEnumerable SearchDescendants(this IPublishedContent content, string term, bool useWildCards = true, string indexName = null) + { + return content.Search(term, useWildCards, indexName); + } + + public static IEnumerable SearchChildren(this IPublishedContent content, string term, bool useWildCards = true, string indexName = null) + { + //TODO: we should pass in the IExamineManager? + + var searcher = string.IsNullOrEmpty(indexName) + ? Examine.ExamineManager.Instance.GetSearcher(Constants.Examine.ExternalIndexer) + : Examine.ExamineManager.Instance.GetSearcher(indexName); + + if (searcher == null) + throw new InvalidOperationException("No searcher found for index " + indexName); + + var t = term.Escape().Value; + if (useWildCards) + t = term.MultipleCharacterWildcard().Value; + + var luceneQuery = "+parentID:" + content.Id + " +" + t; + var crit = searcher.CreateCriteria().RawQuery(luceneQuery); + + return content.Search(crit, searcher); + } + + public static IEnumerable Search(this IPublishedContent content, Examine.SearchCriteria.ISearchCriteria criteria, Examine.ISearcher searchProvider = null) + { + //TODO: we should pass in the IExamineManager? + + var s = searchProvider ?? Examine.ExamineManager.Instance.GetSearcher(Constants.Examine.ExternalIndexer); + + var results = s.Search(criteria); + return results.ToPublishedSearchResults(UmbracoContext.Current.ContentCache); + } #endregion - #region ContentSet - - public static int Position(this IPublishedContent content) - { - return content.GetIndex(); - } - - public static int Index(this IPublishedContent content) - { - return content.GetIndex(); - } - - private static int GetIndex(this IPublishedContent content, IEnumerable set) - { - var index = set.FindIndex(n => n.Id == content.Id); - if (index < 0) - throw new IndexOutOfRangeException("Could not find content in the content set."); - return index; - } - - #endregion - - #region IsSomething: misc. + #region IsSomething: misc. /// /// Gets a value indicating whether the content is visible. @@ -630,7 +296,7 @@ namespace Umbraco.Web // rely on the property converter - will return default bool value, ie false, if property // is not defined, or has no value, else will return its value. - return content.GetPropertyValue(Constants.Conventions.Content.NaviHide) == false; + return content.Value(Constants.Conventions.Content.NaviHide) == false; } /// @@ -639,185 +305,34 @@ namespace Umbraco.Web /// The content to determine content type of. /// The alias of the content type to test against. /// True if the content is of the specified content type; otherwise false. - public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias) - { - return content.DocumentTypeAlias.InvariantEquals(docTypeAlias); - } - - /// - /// Determines whether the specified content is a specified content type or it's derived types. - /// - /// The content to determine content type of. - /// The alias of the content type to test against. - /// When true, recurses up the content type tree to check inheritance; when false just calls IsDocumentType(this IPublishedContent content, string docTypeAlias). - /// True if the content is of the specified content type or a derived content type; otherwise false. - public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias, bool recursive) - { - return content.DocumentTypeAlias.InvariantEquals(docTypeAlias) || (recursive && content.IsComposedOf(docTypeAlias)); - } - - public static bool IsNull(this IPublishedContent content, string alias, bool recurse) - { - return content.HasValue(alias, recurse) == false; - } - - public static bool IsNull(this IPublishedContent content, string alias) - { - return content.HasValue(alias) == false; - } - - #endregion - - #region IsSomething: position in set - - public static bool IsFirst(this IPublishedContent content) + public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias) { - return content.GetIndex() == 0; + return content.DocumentTypeAlias.InvariantEquals(docTypeAlias); } - public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue) + /// + /// Determines whether the specified content is a specified content type or it's derived types. + /// + /// The content to determine content type of. + /// The alias of the content type to test against. + /// When true, recurses up the content type tree to check inheritance; when false just calls IsDocumentType(this IPublishedContent content, string docTypeAlias). + /// True if the content is of the specified content type or a derived content type; otherwise false. + public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias, bool recursive) { - return content.IsFirst(valueIfTrue, string.Empty); + if (content.IsDocumentType(docTypeAlias)) + return true; + + return recursive && content.IsComposedOf(docTypeAlias); } - public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) + public static bool IsNull(this IPublishedContent content, string alias, bool recurse) { - return new HtmlString(content.IsFirst() ? valueIfTrue : valueIfFalse); + return content.HasValue(alias, recurse) == false; } - public static bool IsNotFirst(this IPublishedContent content) + public static bool IsNull(this IPublishedContent content, string alias) { - return content.IsFirst() == false; - } - - public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue) - { - return content.IsNotFirst(valueIfTrue, string.Empty); - } - - public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(content.IsNotFirst() ? valueIfTrue : valueIfFalse); - } - - public static bool IsPosition(this IPublishedContent content, int index) - { - return content.GetIndex() == index; - } - - public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue) - { - return content.IsPosition(index, valueIfTrue, string.Empty); - } - - public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(content.IsPosition(index) ? valueIfTrue : valueIfFalse); - } - - public static bool IsModZero(this IPublishedContent content, int modulus) - { - return content.GetIndex() % modulus == 0; - } - - public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue) - { - return content.IsModZero(modulus, valueIfTrue, string.Empty); - } - - public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(content.IsModZero(modulus) ? valueIfTrue : valueIfFalse); - } - - public static bool IsNotModZero(this IPublishedContent content, int modulus) - { - return content.IsModZero(modulus) == false; - } - - public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue) - { - return content.IsNotModZero(modulus, valueIfTrue, string.Empty); - } - - public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(content.IsNotModZero(modulus) ? valueIfTrue : valueIfFalse); - } - - public static bool IsNotPosition(this IPublishedContent content, int index) - { - return content.IsPosition(index) == false; - } - - public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue) - { - return content.IsNotPosition(index, valueIfTrue, string.Empty); - } - - public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(content.IsNotPosition(index) ? valueIfTrue : valueIfFalse); - } - - public static bool IsLast(this IPublishedContent content) - { - return content.GetIndex() == content.ContentSet.Count() - 1; - } - - public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue) - { - return content.IsLast(valueIfTrue, string.Empty); - } - - public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(content.IsLast() ? valueIfTrue : valueIfFalse); - } - - public static bool IsNotLast(this IPublishedContent content) - { - return content.IsLast() == false; - } - - public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue) - { - return content.IsNotLast(valueIfTrue, string.Empty); - } - - public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(content.IsNotLast() ? valueIfTrue : valueIfFalse); - } - - public static bool IsEven(this IPublishedContent content) - { - return content.GetIndex() % 2 == 0; - } - - public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue) - { - return content.IsEven(valueIfTrue, string.Empty); - } - - public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(content.IsEven() ? valueIfTrue : valueIfFalse); - } - - public static bool IsOdd(this IPublishedContent content) - { - return content.GetIndex() % 2 == 1; - } - - public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue) - { - return content.IsOdd(valueIfTrue, string.Empty); - } - - public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(content.IsOdd() ? valueIfTrue : valueIfFalse); + return content.HasValue(alias) == false; } #endregion @@ -825,24 +340,24 @@ namespace Umbraco.Web #region IsSomething: equality public static bool IsEqual(this IPublishedContent content, IPublishedContent other) - { - return content.Id == other.Id; - } + { + return content.Id == other.Id; + } public static HtmlString IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsEqual(other, valueIfTrue, string.Empty); } - public static HtmlString IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(content.IsEqual(other) ? valueIfTrue : valueIfFalse); - } + public static HtmlString IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) + { + return new HtmlString(content.IsEqual(other) ? valueIfTrue : valueIfFalse); + } - public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other) - { - return content.IsEqual(other) == false; - } + public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other) + { + return content.IsEqual(other) == false; + } public static HtmlString IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { @@ -850,9 +365,9 @@ namespace Umbraco.Web } public static HtmlString IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(content.IsNotEqual(other) ? valueIfTrue : valueIfFalse); - } + { + return new HtmlString(content.IsNotEqual(other) ? valueIfTrue : valueIfFalse); + } #endregion @@ -861,62 +376,62 @@ namespace Umbraco.Web public static bool IsDescendant(this IPublishedContent content, IPublishedContent other) { return content.Ancestors().Any(x => x.Id == other.Id); - } + } - public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue) - { - return content.IsDescendant(other, valueIfTrue, string.Empty); - } + public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue) + { + return content.IsDescendant(other, valueIfTrue, string.Empty); + } - public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { + public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) + { return new HtmlString(content.IsDescendant(other) ? valueIfTrue : valueIfFalse); - } + } - public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other) - { + public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other) + { return content.AncestorsOrSelf().Any(x => x.Id == other.Id); } - public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) - { + public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) + { return content.IsDescendantOrSelf(other, valueIfTrue, string.Empty); } - public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { + public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) + { return new HtmlString(content.IsDescendantOrSelf(other) ? valueIfTrue : valueIfFalse); } - public static bool IsAncestor(this IPublishedContent content, IPublishedContent other) - { + public static bool IsAncestor(this IPublishedContent content, IPublishedContent other) + { // avoid using Descendants(), that's expensive - return other.Ancestors().Any(x => x.Id == content.Id); - } + return other.Ancestors().Any(x => x.Id == content.Id); + } - public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue) - { + public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue) + { return content.IsAncestor(other, valueIfTrue, string.Empty); } - public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { + public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) + { return new HtmlString(content.IsAncestor(other) ? valueIfTrue : valueIfFalse); } - public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other) - { + public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other) + { // avoid using DescendantsOrSelf(), that's expensive return other.AncestorsOrSelf().Any(x => x.Id == content.Id); } - public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) - { + public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) + { return content.IsAncestorOrSelf(other, valueIfTrue, string.Empty); } - public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { + public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) + { return new HtmlString(content.IsAncestorOrSelf(other) ? valueIfTrue : valueIfFalse); } @@ -924,14 +439,14 @@ namespace Umbraco.Web #region Axes: ancestors, ancestors-or-self - // as per XPath 1.0 specs §2.2, + // as per XPath 1.0 specs �2.2, // - the ancestor axis contains the ancestors of the context node; the ancestors of the context node consist // of the parent of context node and the parent's parent and so on; thus, the ancestor axis will always // include the root node, unless the context node is the root node. // - the ancestor-or-self axis contains the context node and the ancestors of the context node; thus, // the ancestor axis will always include the root node. // - // as per XPath 2.0 specs §3.2.1.1, + // as per XPath 2.0 specs �3.2.1.1, // - the ancestor axis is defined as the transitive closure of the parent axis; it contains the ancestors // of the context node (the parent, the parent of the parent, and so on) - The ancestor axis includes the // root node of the tree in which the context node is found, unless the context node is the root node. @@ -941,7 +456,7 @@ namespace Umbraco.Web // the ancestor and ancestor-or-self axis are reverse axes ie they contain the context node or nodes that // are before the context node in document order. // - // document order is defined by §2.4.1 as: + // document order is defined by �2.4.1 as: // - the root node is the first node. // - every node occurs before all of its children and descendants. // - the relative order of siblings is the order in which they occur in the children property of their parent node. @@ -1213,7 +728,7 @@ namespace Umbraco.Web /// Enumerates bottom-up ie walking up the tree (parent, grand-parent, etc). internal static IEnumerable EnumerateAncestors(this IPublishedContent content, bool orSelf) { - if (content == null) throw new ArgumentNullException("content"); + if (content == null) throw new ArgumentNullException(nameof(content)); if (orSelf) yield return content; while ((content = content.Parent) != null) yield return content; @@ -1221,7 +736,7 @@ namespace Umbraco.Web #endregion - #region Axes: descendants, descendants-or-self + #region Axes: descendants, descendants-or-self /// /// Returns all DescendantsOrSelf of all content referenced @@ -1252,12 +767,12 @@ namespace Umbraco.Web } - // as per XPath 1.0 specs §2.2, + // as per XPath 1.0 specs �2.2, // - the descendant axis contains the descendants of the context node; a descendant is a child or a child of a child and so on; thus // the descendant axis never contains attribute or namespace nodes. // - the descendant-or-self axis contains the context node and the descendants of the context node. // - // as per XPath 2.0 specs §3.2.1.1, + // as per XPath 2.0 specs �3.2.1.1, // - the descendant axis is defined as the transitive closure of the child axis; it contains the descendants of the context node (the // children, the children of the children, and so on). // - the descendant-or-self axis contains the context node and the descendants of the context node. @@ -1265,7 +780,7 @@ namespace Umbraco.Web // the descendant and descendant-or-self axis are forward axes ie they contain the context node or nodes that are after the context // node in document order. // - // document order is defined by §2.4.1 as: + // document order is defined by �2.4.1 as: // - the root node is the first node. // - every node occurs before all of its children and descendants. // - the relative order of siblings is the order in which they occur in the children property of their parent node. @@ -1282,9 +797,9 @@ namespace Umbraco.Web } public static IEnumerable Descendants(this IPublishedContent content, string contentTypeAlias) - { - return content.DescendantsOrSelf(false, p => p.DocumentTypeAlias == contentTypeAlias); - } + { + return content.DescendantsOrSelf(false, p => p.DocumentTypeAlias == contentTypeAlias); + } public static IEnumerable Descendants(this IPublishedContent content) where T : class, IPublishedContent @@ -1304,14 +819,14 @@ namespace Umbraco.Web } public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level) - { - return content.DescendantsOrSelf(true, p => p.Level >= level); - } + { + return content.DescendantsOrSelf(true, p => p.Level >= level); + } public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string contentTypeAlias) - { - return content.DescendantsOrSelf(true, p => p.DocumentTypeAlias == contentTypeAlias); - } + { + return content.DescendantsOrSelf(true, p => p.DocumentTypeAlias == contentTypeAlias); + } public static IEnumerable DescendantsOrSelf(this IPublishedContent content) where T : class, IPublishedContent @@ -1386,324 +901,78 @@ namespace Umbraco.Web internal static IEnumerable EnumerateDescendants(this IPublishedContent content, bool orSelf) { - if (content == null) throw new ArgumentNullException("content"); + if (content == null) throw new ArgumentNullException(nameof(content)); if (orSelf) yield return content; - foreach (var child in content.Children) - foreach (var child2 in child.EnumerateDescendants()) - yield return child2; + foreach (var desc in content.Children.SelectMany(x => x.EnumerateDescendants())) + yield return desc; } internal static IEnumerable EnumerateDescendants(this IPublishedContent content) { yield return content; - foreach (var child in content.Children) - foreach (var child2 in child.EnumerateDescendants()) - yield return child2; + foreach (var desc in content.Children.SelectMany(x => x.EnumerateDescendants())) + yield return desc; } #endregion - #region Axes: following-sibling, preceding-sibling, following, preceding + pseudo-axes up, down, next, previous + #region Axes: following-sibling, preceding-sibling, following, preceding + pseudo-axes up, down, next, previous // up pseudo-axe ~ ancestors // bogus, kept for backward compatibility but we should get rid of it // better use ancestors - public static IPublishedContent Up(this IPublishedContent content) - { - return content.Parent; - } + public static IPublishedContent Up(this IPublishedContent content) + { + return content.Parent; + } - public static IPublishedContent Up(this IPublishedContent content, int number) - { + public static IPublishedContent Up(this IPublishedContent content, int number) + { if (number < 0) - throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); - return number == 0 ? content : content.EnumerateAncestors(false).Skip(number).FirstOrDefault(); - } + throw new ArgumentOutOfRangeException(nameof(number), "Must be greater than, or equal to, zero."); + return number == 0 ? content : content.EnumerateAncestors(false).Skip(number).FirstOrDefault(); + } - public static IPublishedContent Up(this IPublishedContent content, string contentTypeAlias) - { - return string.IsNullOrEmpty(contentTypeAlias) + public static IPublishedContent Up(this IPublishedContent content, string contentTypeAlias) + { + return string.IsNullOrEmpty(contentTypeAlias) ? content.Parent : content.Ancestor(contentTypeAlias); - } + } // down pseudo-axe ~ children (not descendants) // bogus, kept for backward compatibility but we should get rid of it // better use descendants - public static IPublishedContent Down(this IPublishedContent content) - { - return content.Children.FirstOrDefault(); - } + public static IPublishedContent Down(this IPublishedContent content) + { + return content.Children.FirstOrDefault(); + } - public static IPublishedContent Down(this IPublishedContent content, int number) - { + public static IPublishedContent Down(this IPublishedContent content, int number) + { if (number < 0) - throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); - if (number == 0) return content; + throw new ArgumentOutOfRangeException(nameof(number), "Must be greater than, or equal to, zero."); + if (number == 0) return content; content = content.Children.FirstOrDefault(); while (content != null && --number > 0) content = content.Children.FirstOrDefault(); - return content; - } + return content; + } - public static IPublishedContent Down(this IPublishedContent content, string contentTypeAlias) - { - if (string.IsNullOrEmpty(contentTypeAlias)) - return content.Children.FirstOrDefault(); + public static IPublishedContent Down(this IPublishedContent content, string contentTypeAlias) + { + if (string.IsNullOrEmpty(contentTypeAlias)) + return content.Children.FirstOrDefault(); // note: this is what legacy did, but with a broken Descendant // so fixing Descendant will change how it works... - return content.Descendant(contentTypeAlias); - } - - // next pseudo-axe ~ following within the content set - // bogus, kept for backward compatibility but we should get rid of it - - public static IPublishedContent Next(this IPublishedContent content) - { - return content.ContentSet.ElementAtOrDefault(content.GetIndex() + 1); - } - - public static IPublishedContent Next(this IPublishedContent current, Func func) { - IPublishedContent next = current.Next(); - while (next != null) { - if (func(next)) return next; - next = next.Next(); - } - return null; - } - - public static IPublishedContent Next(this IPublishedContent content, int number) - { - if (number < 0) - throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); - return number == 0 ? content : content.ContentSet.ElementAtOrDefault(content.GetIndex() + number); - } - - public static IPublishedContent Next(this IPublishedContent content, string contentTypeAlias) - { - return content.Next(contentTypeAlias, false); - } - - public static IPublishedContent Next(this IPublishedContent content, string contentTypeAlias, bool wrap) - { - return content.Next(content.ContentSet, x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias), wrap); - } - - public static T Next(this IPublishedContent content) - where T : class, IPublishedContent - { - return content.Next(false); - } - - public static T Next(this IPublishedContent content, bool wrap) - where T : class, IPublishedContent - { - return content.Next(content.ContentSet, x => x is T, wrap) as T; - } - - static IPublishedContent Next(this IPublishedContent content, IEnumerable axis, Func predicate, bool wrap) - { - var b4 = true; - IPublishedContent wrapped = null; - foreach (var c in axis) - { - if (b4) - { - if (c.Id == content.Id) - b4 = false; - else if (wrap && wrapped == null && predicate(c)) - wrapped = c; - continue; - } - if (predicate(c)) - return c; - } - - return wrapped; - } - - // previous pseudo-axe ~ preceding within the content set - // bogus, kept for backward compatibility but we should get rid of it - - public static IPublishedContent Previous(this IPublishedContent content) - { - return content.ContentSet.ElementAtOrDefault(content.GetIndex() - 1); - } - - public static IPublishedContent Previous(this IPublishedContent current, Func func) { - IPublishedContent prev = current.Previous(); - while (prev != null) { - if (func(prev)) return prev; - prev = prev.Previous(); - } - return null; - } - - public static IPublishedContent Previous(this IPublishedContent content, int number) - { - if (number < 0) - throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); - return number == 0 ? content : content.ContentSet.ElementAtOrDefault(content.GetIndex() - number); - } - - public static IPublishedContent Previous(this IPublishedContent content, string contentTypeAlias) - { - return content.Previous(contentTypeAlias, false); - } - - public static IPublishedContent Previous(this IPublishedContent content, string contentTypeAlias, bool wrap) - { - return content.Next(content.ContentSet.Reverse(), x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias), wrap); - } - - public static T Previous(this IPublishedContent content) - where T : class, IPublishedContent - { - return content.Previous(false); - } - - public static T Previous(this IPublishedContent content, bool wrap) - where T : class, IPublishedContent - { - return content.Next(content.ContentSet.Reverse(), x => x is T, wrap) as T; - } - - // - - [Obsolete("Obsolete, use FollowingSibling or PrecedingSibling instead.")] - public static IPublishedContent Sibling(this IPublishedContent content, int number) - { - if (number < 0) - throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); - number += 1; // legacy is zero-based - return content.FollowingSibling(number); - } - - // contentTypeAlias is case-insensitive - [Obsolete("Obsolete, use FollowingSibling or PrecedingSibling instead.")] - public static IPublishedContent Sibling(this IPublishedContent content, string contentTypeAlias) - { - // note: the original implementation seems to loop on all siblings - // ie if it reaches the end of the set, it starts again at the beginning. - // so here we wrap, although it's not consistent... but anyway those - // methods should be obsoleted. - - return content.FollowingSibling(contentTypeAlias, true); - } - - // following-sibling, preceding-sibling axes - - public static IPublishedContent FollowingSibling(this IPublishedContent content) - { - return content.Siblings().ElementAtOrDefault(content.GetIndex(content.Siblings()) + 1); - } - - public static IPublishedContent FollowingSibling(this IPublishedContent content, int number) - { - if (number < 0) - throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); - return number == 0 ? content : content.Siblings().ElementAtOrDefault(content.GetIndex(content.Siblings()) + number); - } - - // contentTypeAlias is case-insensitive - public static IPublishedContent FollowingSibling(this IPublishedContent content, string contentTypeAlias) - { - return content.FollowingSibling(contentTypeAlias, false); - } - - // contentTypeAlias is case-insensitive - // note: not sure that one makes a lot of sense but it is here for backward compatibility - public static IPublishedContent FollowingSibling(this IPublishedContent content, string contentTypeAlias, bool wrap) - { - return content.Next(content.Siblings(), x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias), wrap); - } - - public static T FollowingSibling(this IPublishedContent content) - where T : class, IPublishedContent - { - return content.FollowingSibling(false); - } - - public static T FollowingSibling(this IPublishedContent content, bool wrap) - where T : class, IPublishedContent - { - return content.Next(content.Siblings(), x => x is T, wrap) as T; - } - - public static IPublishedContent PrecedingSibling(this IPublishedContent content) - { - return content.Siblings().ElementAtOrDefault(content.GetIndex(content.Siblings()) - 1); - } - - public static IPublishedContent PrecedingSibling(this IPublishedContent content, int number) - { - if (number < 0) - throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); - return number == 0 ? content : content.Siblings().ElementAtOrDefault(content.GetIndex(content.Siblings()) - number); - } - - // contentTypeAlias is case-insensitive - public static IPublishedContent PrecedingSibling(this IPublishedContent content, string contentTypeAlias) - { - return content.PrecedingSibling(contentTypeAlias, false); - } - - // contentTypeAlias is case-insensitive - // note: not sure that one makes a lot of sense but it is here for backward compatibility - public static IPublishedContent PrecedingSibling(this IPublishedContent content, string contentTypeAlias, bool wrap) - { - return content.Next(content.Siblings().Reverse(), x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias), wrap); - } - - public static T PrecedingSibling(this IPublishedContent content) - where T : class, IPublishedContent - { - return content.PrecedingSibling(false); - } - - public static T PrecedingSibling(this IPublishedContent content, bool wrap) - where T : class, IPublishedContent - { - return content.Next(content.Siblings().Reverse(), x => x is T, wrap) as T; - } - - // following, preceding axes - NOT IMPLEMENTED - - // utilities - - public static IEnumerable Siblings(this IPublishedContent content) - { - // content.Parent, content.Children and cache.GetAtRoot() should be fast enough, - // or cached by the content cache, so that we don't have to implement cache here. - - // returns the true tree siblings, even if the content is in a set - // get the root docs if parent is null - - // note: I don't like having to refer to the "current" content cache here, but - // what else? would need root content to have a special, non-null but hidden, - // parent... - - - - var siblings = content.Parent == null - ? content.ItemType == PublishedItemType.Media ? UmbracoContext.Current.MediaCache.GetAtRoot() : UmbracoContext.Current.ContentCache.GetAtRoot() - : content.Parent.Children; - - // make sure we run it once - return siblings.ToArray(); - } - - public static IEnumerable Siblings(this IPublishedContent content) - where T : class, IPublishedContent - { - return content.Siblings().OfType(); + return content.Descendant(contentTypeAlias); } #endregion @@ -1721,7 +990,7 @@ namespace Umbraco.Web public static T Parent(this IPublishedContent content) where T : class, IPublishedContent { - if (content == null) throw new ArgumentNullException("content"); + if (content == null) throw new ArgumentNullException(nameof(content)); return content.Parent as T; } @@ -1730,17 +999,17 @@ namespace Umbraco.Web #region Axes: children /// - /// Gets the children of the content. - /// - /// The content. - /// The children of the content. - /// + /// Gets the children of the content. + /// + /// The content. + /// The children of the content. + /// /// Children are sorted by their sortOrder. /// This method exists for consistency, it is the same as calling content.Children as a property. - /// - public static IEnumerable Children(this IPublishedContent content) + /// + public static IEnumerable Children(this IPublishedContent content) { - if (content == null) throw new ArgumentNullException("content"); + if (content == null) throw new ArgumentNullException(nameof(content)); return content.Children; } @@ -1821,151 +1090,138 @@ namespace Umbraco.Web /// Gets the children of the content in a DataTable. /// /// The content. + /// A service context. /// An optional content type alias. /// The children of the content. - public static DataTable ChildrenAsTable(this IPublishedContent content, string contentTypeAliasFilter = "") - { - return GenerateDataTable(content, contentTypeAliasFilter); - } + public static DataTable ChildrenAsTable(this IPublishedContent content, ServiceContext services, string contentTypeAliasFilter = "") + { + return GenerateDataTable(content, services, contentTypeAliasFilter); + } - /// + /// /// Gets the children of the content in a DataTable. /// /// The content. + /// A service context. /// An optional content type alias. /// The children of the content. - private static DataTable GenerateDataTable(IPublishedContent content, string contentTypeAliasFilter = "") - { - var firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace() - ? content.Children.Any() - ? content.Children.ElementAt(0) - : null - : content.Children.FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAliasFilter); - if (firstNode == null) - return new DataTable(); //no children found + private static DataTable GenerateDataTable(IPublishedContent content, ServiceContext services, string contentTypeAliasFilter = "") + { + var firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace() + ? content.Children.Any() + ? content.Children.ElementAt(0) + : null + : content.Children.FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAliasFilter); + if (firstNode == null) + return new DataTable(); //no children found - //use new utility class to create table so that we don't have to maintain code in many places, just one - var dt = Core.DataTableExtensions.GenerateDataTable( - //pass in the alias of the first child node since this is the node type we're rendering headers for - firstNode.DocumentTypeAlias, - //pass in the callback to extract the Dictionary of all defined aliases to their names - alias => GetPropertyAliasesAndNames(alias), - //pass in a callback to populate the datatable, yup its a bit ugly but it's already legacy and we just want to maintain code in one place. - () => - { - //create all row data - var tableData = Core.DataTableExtensions.CreateTableData(); - //loop through each child and create row data for it - foreach (var n in content.Children.OrderBy(x => x.SortOrder)) - { - if (contentTypeAliasFilter.IsNullOrWhiteSpace() == false) - { - if (n.DocumentTypeAlias != contentTypeAliasFilter) - continue; //skip this one, it doesn't match the filter - } + //use new utility class to create table so that we don't have to maintain code in many places, just one + var dt = Core.DataTableExtensions.GenerateDataTable( + //pass in the alias of the first child node since this is the node type we're rendering headers for + firstNode.DocumentTypeAlias, + //pass in the callback to extract the Dictionary of all defined aliases to their names + alias => GetPropertyAliasesAndNames(services, alias), + //pass in a callback to populate the datatable, yup its a bit ugly but it's already legacy and we just want to maintain code in one place. + () => + { + //create all row data + var tableData = Core.DataTableExtensions.CreateTableData(); + //loop through each child and create row data for it + foreach (var n in content.Children.OrderBy(x => x.SortOrder)) + { + if (contentTypeAliasFilter.IsNullOrWhiteSpace() == false) + { + if (n.DocumentTypeAlias != contentTypeAliasFilter) + continue; //skip this one, it doesn't match the filter + } - var standardVals = new Dictionary - { - { "Id", n.Id }, - { "NodeName", n.Name }, - { "NodeTypeAlias", n.DocumentTypeAlias }, - { "CreateDate", n.CreateDate }, - { "UpdateDate", n.UpdateDate }, - { "CreatorName", n.CreatorName }, - { "WriterName", n.WriterName }, - { "Url", n.Url } - }; + var standardVals = new Dictionary + { + { "Id", n.Id }, + { "NodeName", n.Name }, + { "NodeTypeAlias", n.DocumentTypeAlias }, + { "CreateDate", n.CreateDate }, + { "UpdateDate", n.UpdateDate }, + { "CreatorName", n.CreatorName }, + { "WriterName", n.WriterName }, + { "Url", n.Url } + }; - var userVals = new Dictionary(); - foreach (var p in from IPublishedProperty p in n.Properties where p.DataValue != null select p) + var userVals = new Dictionary(); + foreach (var p in from IPublishedProperty p in n.Properties where p.GetSourceValue() != null select p) { // probably want the "object value" of the property here... - userVals[p.PropertyTypeAlias] = p.Value; - } - //add the row data - Core.DataTableExtensions.AddRowData(tableData, standardVals, userVals); - } - return tableData; - } - ); - return dt; - } + userVals[p.Alias] = p.GetValue(); + } + //add the row data + Core.DataTableExtensions.AddRowData(tableData, standardVals, userVals); + } + return tableData; + } + ); + return dt; + } #endregion #region Axes: custom - // todo: in v8, rename this 'Root' /// - /// Gets the 'site' content for this content. + /// Gets the root content for this content. /// /// The content. /// The 'site' content ie AncestorOrSelf(1). - public static IPublishedContent Site(this IPublishedContent content) + public static IPublishedContent Root(this IPublishedContent content) { return content.AncestorOrSelf(1); } #endregion - #region OfTypes - - // the .OfType() filter is nice when there's only one type - // this is to support filtering with multiple types - - public static IEnumerable OfTypes(this IEnumerable contents, params Type[] types) - { - return contents.Where(x => types.Contains(x.GetType())); - } - - public static IEnumerable OfTypes(this IEnumerable contents, params string[] types) - { - types = types.Select(x => x.ToLowerInvariant()).ToArray(); - return contents.Where(x => types.Contains(x.DocumentTypeAlias.ToLowerInvariant())); - } - - public static T OfType(this IPublishedContent content) - where T : class, IPublishedContent - { - return content as T; - } - - #endregion - #region PropertyAliasesAndNames - private static Func> _getPropertyAliasesAndNames; + private static Func> _getPropertyAliasesAndNames; - /// - /// This is used only for unit tests to set the delegate to look up aliases/names dictionary of a content type - /// - internal static Func> GetPropertyAliasesAndNames - { - get - { - return _getPropertyAliasesAndNames ?? (_getPropertyAliasesAndNames = alias => - { - var userFields = ContentType.GetAliasesAndNames(alias); - //ensure the standard fields are there - var allFields = new Dictionary() - { - {"Id", "Id"}, - {"NodeName", "NodeName"}, - {"NodeTypeAlias", "NodeTypeAlias"}, - {"CreateDate", "CreateDate"}, - {"UpdateDate", "UpdateDate"}, - {"CreatorName", "CreatorName"}, - {"WriterName", "WriterName"}, - {"Url", "Url"} - }; - foreach (var f in userFields.Where(f => allFields.ContainsKey(f.Key) == false)) - { - allFields.Add(f.Key, f.Value); - } - return allFields; - }); - } - set { _getPropertyAliasesAndNames = value; } + /// + /// This is used only for unit tests to set the delegate to look up aliases/names dictionary of a content type + /// + internal static Func> GetPropertyAliasesAndNames + { + get { return _getPropertyAliasesAndNames ?? GetAliasesAndNames; } + set { _getPropertyAliasesAndNames = value; } + } + + private static Dictionary GetAliasesAndNames(ServiceContext services, string alias) + { + var type = services.ContentTypeService.Get(alias) + ?? services.MediaTypeService.Get(alias) + ?? (IContentTypeBase)services.MemberTypeService.Get(alias); + var fields = GetAliasesAndNames(type); + + // ensure the standard fields are there + var stdFields = new Dictionary + { + {"Id", "Id"}, + {"NodeName", "NodeName"}, + {"NodeTypeAlias", "NodeTypeAlias"}, + {"CreateDate", "CreateDate"}, + {"UpdateDate", "UpdateDate"}, + {"CreatorName", "CreatorName"}, + {"WriterName", "WriterName"}, + {"Url", "Url"} + }; + + foreach (var field in stdFields.Where(x => fields.ContainsKey(x.Key) == false)) + { + fields[field.Key] = field.Value; + } + + return fields; + } + + private static Dictionary GetAliasesAndNames(IContentTypeBase contentType) + { + return contentType.PropertyTypes.ToDictionary(x => x.Alias, x => x.Name); } #endregion @@ -1982,9 +1238,9 @@ namespace Umbraco.Web public static CultureInfo GetCulture(this IPublishedContent content, Uri current = null) { return Models.ContentExtensions.GetCulture(UmbracoContext.Current, - ApplicationContext.Current.Services.DomainService, - ApplicationContext.Current.Services.LocalizationService, - ApplicationContext.Current.Services.ContentService, + Current.Services.DomainService, + Current.Services.LocalizationService, + Current.Services.ContentService, content.Id, content.Path, current); } diff --git a/src/Umbraco.Web/PublishedContentPropertyExtension.cs b/src/Umbraco.Web/PublishedContentPropertyExtension.cs index b890618fa6..3314bc09fc 100644 --- a/src/Umbraco.Web/PublishedContentPropertyExtension.cs +++ b/src/Umbraco.Web/PublishedContentPropertyExtension.cs @@ -1,6 +1,5 @@ using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Web.PropertyEditors; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web { @@ -9,26 +8,26 @@ namespace Umbraco.Web /// public static class PublishedPropertyExtension { - #region GetValue + #region Value - public static T GetValue(this IPublishedProperty property) + public static T Value(this IPublishedProperty property, int? languageId = null, string segment = null) { - return property.GetValue(false, default(T)); + return property.Value(false, default(T), languageId, segment); } - public static T GetValue(this IPublishedProperty property, T defaultValue) + public static T Value(this IPublishedProperty property, T defaultValue, int? languageId = null, string segment = null) { - return property.GetValue(true, defaultValue); + return property.Value(true, defaultValue, languageId, segment); } - internal static T GetValue(this IPublishedProperty property, bool withDefaultValue, T defaultValue) + internal static T Value(this IPublishedProperty property, bool withDefaultValue, T defaultValue, int? languageId = null, string segment = null) { - if (property.HasValue == false && withDefaultValue) return defaultValue; + if (property.HasValue(languageId, segment) == false && withDefaultValue) return defaultValue; // else we use .Value so we give the converter a chance to handle the default value differently // eg for IEnumerable it may return Enumerable.Empty instead of null - var value = property.Value; + var value = property.GetValue(languageId, segment); // if value is null (strange but why not) it still is OK to call TryConvertTo // because it's an extension method (hence no NullRef) which will return a diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 8cbbfd3fda..6bb649f8cd 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -6,546 +6,298 @@ using System.Xml.XPath; using Examine; using Examine.LuceneEngine.Providers; using Examine.LuceneEngine.SearchCriteria; -using Examine.Providers; using Examine.SearchCriteria; -using umbraco; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; using Umbraco.Core.Xml; -using Umbraco.Web.Models; using Umbraco.Web.PublishedCache; namespace Umbraco.Web { + using Examine = global::Examine; + /// /// A class used to query for published content, media items /// - public class PublishedContentQuery : ITypedPublishedContentQuery, IDynamicPublishedContentQuery + public class PublishedContentQuery : IPublishedContentQuery { - private readonly ITypedPublishedContentQuery _typedContentQuery; - private readonly IDynamicPublishedContentQuery _dynamicContentQuery; - private readonly ContextualPublishedContentCache _contentCache; - private readonly ContextualPublishedMediaCache _mediaCache; + private readonly IPublishedContentQuery _query; + private readonly IPublishedContentCache _contentCache; + private readonly IPublishedMediaCache _mediaCache; /// /// Constructor used to return results from the caches /// /// /// - public PublishedContentQuery(ContextualPublishedContentCache contentCache, ContextualPublishedMediaCache mediaCache) + public PublishedContentQuery(IPublishedContentCache contentCache, IPublishedMediaCache mediaCache) { - if (contentCache == null) throw new ArgumentNullException("contentCache"); - if (mediaCache == null) throw new ArgumentNullException("mediaCache"); - _contentCache = contentCache; - _mediaCache = mediaCache; + _contentCache = contentCache ?? throw new ArgumentNullException(nameof(contentCache)); + _mediaCache = mediaCache ?? throw new ArgumentNullException(nameof(mediaCache)); } /// - /// Constructor used to wrap the ITypedPublishedContentQuery and IDynamicPublishedContentQuery objects passed in + /// Constructor used to wrap the ITypedPublishedContentQuery object passed in /// - /// - /// - public PublishedContentQuery(ITypedPublishedContentQuery typedContentQuery, IDynamicPublishedContentQuery dynamicContentQuery) + /// + public PublishedContentQuery(IPublishedContentQuery query) { - if (typedContentQuery == null) throw new ArgumentNullException("typedContentQuery"); - if (dynamicContentQuery == null) throw new ArgumentNullException("dynamicContentQuery"); - _typedContentQuery = typedContentQuery; - _dynamicContentQuery = dynamicContentQuery; + _query = query ?? throw new ArgumentNullException(nameof(query)); } #region Content - public IPublishedContent TypedContent(int id) + public IPublishedContent Content(int id) { - return _typedContentQuery == null - ? TypedDocumentById(id, _contentCache) - : _typedContentQuery.TypedContent(id); + return _query == null + ? ItemById(id, _contentCache) + : _query.Content(id); } - public IPublishedContent TypedContent(Guid id) + public IPublishedContent Content(Guid id) { - return _typedContentQuery == null - ? TypedDocumentById(id, _contentCache) - : _typedContentQuery.TypedContent(id); + return _query == null + ? ItemById(id, _contentCache) + : _query.Content(id); } - public IPublishedContent TypedContentSingleAtXPath(string xpath, params XPathVariable[] vars) + public IPublishedContent Content(Udi id) { - return _typedContentQuery == null - ? TypedDocumentByXPath(xpath, vars, _contentCache) - : _typedContentQuery.TypedContentSingleAtXPath(xpath, vars); - } - - public IEnumerable TypedContent(IEnumerable ids) - { - return _typedContentQuery == null - ? TypedDocumentsByIds(_contentCache, ids) - : _typedContentQuery.TypedContent(ids); + if (!(id is GuidUdi udi)) return null; + return _query == null + ? ItemById(udi.Guid, _contentCache) + : _query.Content(udi.Guid); } - public IEnumerable TypedContent(IEnumerable ids) + public IPublishedContent ContentSingleAtXPath(string xpath, params XPathVariable[] vars) { - return _typedContentQuery == null - ? TypedDocumentsByIds(_contentCache, ids) - : _typedContentQuery.TypedContent(ids); + return _query == null + ? ItemByXPath(xpath, vars, _contentCache) + : _query.ContentSingleAtXPath(xpath, vars); } - public IEnumerable TypedContentAtXPath(string xpath, params XPathVariable[] vars) + public IEnumerable Content(IEnumerable ids) { - return _typedContentQuery == null - ? TypedDocumentsByXPath(xpath, vars, _contentCache) - : _typedContentQuery.TypedContentAtXPath(xpath, vars); + return _query == null + ? ItemsByIds(_contentCache, ids) + : _query.Content(ids); } - public IEnumerable TypedContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) + public IEnumerable Content(IEnumerable ids) { - return _typedContentQuery == null - ? TypedDocumentsByXPath(xpath, vars, _contentCache) - : _typedContentQuery.TypedContentAtXPath(xpath, vars); + return _query == null + ? ItemsByIds(_contentCache, ids) + : _query.Content(ids); } - public IEnumerable TypedContentAtRoot() + public IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars) { - return _typedContentQuery == null - ? TypedDocumentsAtRoot(_contentCache) - : _typedContentQuery.TypedContentAtRoot(); + return _query == null + ? ItemsByXPath(xpath, vars, _contentCache) + : _query.ContentAtXPath(xpath, vars); } - public dynamic Content(int id) + public IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) { - return _dynamicContentQuery == null - ? DocumentById(id, _contentCache, DynamicNull.Null) - : _dynamicContentQuery.Content(id); + return _query == null + ? ItemsByXPath(xpath, vars, _contentCache) + : _query.ContentAtXPath(xpath, vars); } - public dynamic Content(Guid id) + public IEnumerable ContentAtRoot() { - return _dynamicContentQuery == null - ? DocumentById(id, _contentCache, DynamicNull.Null) - : _dynamicContentQuery.Content(id); - } - - public dynamic ContentSingleAtXPath(string xpath, params XPathVariable[] vars) - { - return _dynamicContentQuery == null - ? DocumentByXPath(xpath, vars, _contentCache, DynamicNull.Null) - : _dynamicContentQuery.ContentSingleAtXPath(xpath, vars); - } - - public dynamic ContentSingleAtXPath(XPathExpression xpath, params XPathVariable[] vars) - { - return _dynamicContentQuery == null - ? DocumentByXPath(xpath, vars, _contentCache, DynamicNull.Null) - : _dynamicContentQuery.ContentSingleAtXPath(xpath, vars); - } - - public dynamic Content(IEnumerable ids) - { - return _dynamicContentQuery == null - ? DocumentByIds(_contentCache, ids.ToArray()) - : _dynamicContentQuery.Content(ids); - } - - public dynamic Content(IEnumerable ids) - { - return _dynamicContentQuery == null - ? DocumentByIds(_contentCache, ids.ToArray()) - : _dynamicContentQuery.Content(ids); - } - - public dynamic ContentAtXPath(string xpath, params XPathVariable[] vars) - { - return _dynamicContentQuery == null - ? DocumentsByXPath(xpath, vars, _contentCache) - : _dynamicContentQuery.ContentAtXPath(xpath, vars); - } - - public dynamic ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) - { - return _dynamicContentQuery == null - ? DocumentsByXPath(xpath, vars, _contentCache) - : _dynamicContentQuery.ContentAtXPath(xpath, vars); - } - - public dynamic ContentAtRoot() - { - return _dynamicContentQuery == null - ? DocumentsAtRoot(_contentCache) - : _dynamicContentQuery.ContentAtRoot(); + return _query == null + ? ItemsAtRoot(_contentCache) + : _query.ContentAtRoot(); } #endregion #region Media - - public IPublishedContent TypedMedia(int id) + + public IPublishedContent Media(int id) { - return _typedContentQuery == null - ? TypedDocumentById(id, _mediaCache) - : _typedContentQuery.TypedMedia(id); + return _query == null + ? ItemById(id, _mediaCache) + : _query.Media(id); } - public IEnumerable TypedMedia(IEnumerable ids) + public IPublishedContent Media(Guid id) { - return _typedContentQuery == null - ? TypedDocumentsByIds(_mediaCache, ids) - : _typedContentQuery.TypedMedia(ids); + return _query == null + ? ItemById(id, _mediaCache) + : _query.Media(id); } - public IEnumerable TypedMediaAtRoot() + public IPublishedContent Media(Udi id) { - return _typedContentQuery == null - ? TypedDocumentsAtRoot(_mediaCache) - : _typedContentQuery.TypedMediaAtRoot(); + if (!(id is GuidUdi udi)) return null; + return _query == null + ? ItemById(udi.Guid, _mediaCache) + : _query.Media(udi.Guid); } - public dynamic Media(int id) + public IEnumerable Media(IEnumerable ids) { - return _dynamicContentQuery == null - ? DocumentById(id, _mediaCache, DynamicNull.Null) - : _dynamicContentQuery.Media(id); - } - - public dynamic Media(IEnumerable ids) - { - return _dynamicContentQuery == null - ? DocumentByIds(_mediaCache, ids) - : _dynamicContentQuery.Media(ids); + return _query == null + ? ItemsByIds(_mediaCache, ids) + : _query.Media(ids); } - public dynamic MediaAtRoot() + public IEnumerable Media(IEnumerable ids) { - return _dynamicContentQuery == null - ? DocumentsAtRoot(_mediaCache) - : _dynamicContentQuery.MediaAtRoot(); + return _query == null + ? ItemsByIds(_mediaCache, ids) + : _query.Media(ids); } + public IEnumerable MediaAtRoot() + { + return _query == null + ? ItemsAtRoot(_mediaCache) + : _query.MediaAtRoot(); + } + + #endregion #region Used by Content/Media - private IPublishedContent TypedDocumentById(int id, ContextualPublishedCache cache) + private static IPublishedContent ItemById(int id, IPublishedCache cache) { var doc = cache.GetById(id); return doc; } - private IPublishedContent TypedDocumentById(Guid id, ContextualPublishedCache cache) + private static IPublishedContent ItemById(Guid id, IPublishedCache cache) { - // todo: in v8, implement in a more efficient way - var legacyXml = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema; - var xpath = legacyXml ? "//node [@key=$guid]" : "//* [@key=$guid]"; - var doc = cache.GetSingleByXPath(xpath, new XPathVariable("guid", id.ToString())); + var doc = cache.GetById(id); return doc; } - private IPublishedContent TypedDocumentByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) + private static IPublishedContent ItemByXPath(string xpath, XPathVariable[] vars, IPublishedCache cache) { var doc = cache.GetSingleByXPath(xpath, vars); return doc; } //NOTE: Not used? - //private IPublishedContent TypedDocumentByXPath(XPathExpression xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) + //private IPublishedContent ItemByXPath(XPathExpression xpath, XPathVariable[] vars, IPublishedCache cache) //{ // var doc = cache.GetSingleByXPath(xpath, vars); // return doc; - //} + //} - private IEnumerable TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable ids) + private static IEnumerable ItemsByIds(IPublishedCache cache, IEnumerable ids) { - return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); + return ids.Select(eachId => ItemById(eachId, cache)).WhereNotNull(); } - private IEnumerable TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable ids) + private IEnumerable ItemsByIds(IPublishedCache cache, IEnumerable ids) { - // todo: in v8, implement in a more efficient way - return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull(); + return ids.Select(eachId => ItemById(eachId, cache)).WhereNotNull(); } - private IEnumerable TypedDocumentsByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) + private static IEnumerable ItemsByXPath(string xpath, XPathVariable[] vars, IPublishedCache cache) { var doc = cache.GetByXPath(xpath, vars); return doc; } - private IEnumerable TypedDocumentsByXPath(XPathExpression xpath, XPathVariable[] vars, ContextualPublishedContentCache cache) + private static IEnumerable ItemsByXPath(XPathExpression xpath, XPathVariable[] vars, IPublishedCache cache) { var doc = cache.GetByXPath(xpath, vars); return doc; } - private IEnumerable TypedDocumentsAtRoot(ContextualPublishedCache cache) + private static IEnumerable ItemsAtRoot(IPublishedCache cache) { return cache.GetAtRoot(); } - private dynamic DocumentById(int id, ContextualPublishedCache cache, object ifNotFound) - { - var doc = cache.GetById(id); - return doc == null - ? ifNotFound - : new DynamicPublishedContent(doc).AsDynamic(); - } - - private dynamic DocumentById(Guid id, ContextualPublishedCache cache, object ifNotFound) - { - var doc = TypedDocumentById(id, cache); - return doc == null - ? ifNotFound - : new DynamicPublishedContent(doc).AsDynamic(); - } - - private dynamic DocumentByXPath(string xpath, XPathVariable[] vars, ContextualPublishedCache cache, object ifNotFound) - { - var doc = cache.GetSingleByXPath(xpath, vars); - return doc == null - ? ifNotFound - : new DynamicPublishedContent(doc).AsDynamic(); - } - - private dynamic DocumentByXPath(XPathExpression xpath, XPathVariable[] vars, ContextualPublishedCache cache, object ifNotFound) - { - var doc = cache.GetSingleByXPath(xpath, vars); - return doc == null - ? ifNotFound - : new DynamicPublishedContent(doc).AsDynamic(); - } - - private dynamic DocumentByIds(ContextualPublishedCache cache, IEnumerable ids) - { - var dNull = DynamicNull.Null; - var nodes = ids.Select(eachId => DocumentById(eachId, cache, dNull)) - .Where(x => TypeHelper.IsTypeAssignableFrom(x) == false) - .Cast(); - return new DynamicPublishedContentList(nodes); - } - - private dynamic DocumentByIds(ContextualPublishedCache cache, IEnumerable ids) - { - var dNull = DynamicNull.Null; - var nodes = ids.Select(eachId => DocumentById(eachId, cache, dNull)) - .Where(x => TypeHelper.IsTypeAssignableFrom(x) == false) - .Cast(); - return new DynamicPublishedContentList(nodes); - } - - private dynamic DocumentsByXPath(string xpath, XPathVariable[] vars, ContextualPublishedCache cache) - { - return new DynamicPublishedContentList( - cache.GetByXPath(xpath, vars) - .Select(publishedContent => new DynamicPublishedContent(publishedContent)) - ); - } - - private dynamic DocumentsByXPath(XPathExpression xpath, XPathVariable[] vars, ContextualPublishedCache cache) - { - return new DynamicPublishedContentList( - cache.GetByXPath(xpath, vars) - .Select(publishedContent => new DynamicPublishedContent(publishedContent)) - ); - } - - private dynamic DocumentsAtRoot(ContextualPublishedCache cache) - { - return new DynamicPublishedContentList( - cache.GetAtRoot() - .Select(publishedContent => new DynamicPublishedContent(publishedContent)) - ); - } - #endregion #region Search - /// - /// Searches content - /// - /// - /// - /// - /// - public dynamic Search(string term, bool useWildCards = true, string searchProvider = null) + /// + public IEnumerable Search(string term, bool useWildCards = true, string indexName = null) { - return _dynamicContentQuery == null - ? new DynamicPublishedContentList( - TypedSearch(term, useWildCards, searchProvider)) - : _dynamicContentQuery.Search(term, useWildCards, searchProvider); + return Search(0, 0, out _, term, useWildCards, indexName); } - /// - /// Searhes content - /// - /// - /// - /// - public dynamic Search(ISearchCriteria criteria, BaseSearchProvider searchProvider = null) + /// + public IEnumerable Search(int skip, int take, out int totalRecords, string term, bool useWildCards = true, string indexName = null) { - return _dynamicContentQuery == null - ? new DynamicPublishedContentList( - TypedSearch(criteria, searchProvider)) - : _dynamicContentQuery.Search(criteria, searchProvider); - } + //TODO: Can we inject IExamineManager? - /// - /// Searches content - /// - /// - /// - /// - /// - /// - /// - /// - public IEnumerable TypedSearch(int skip, int take, out int totalRecords, string term, bool useWildCards = true, string searchProvider = null) - { - if (_typedContentQuery != null) return _typedContentQuery.TypedSearch(skip, take, out totalRecords, term, useWildCards, searchProvider); + if (_query != null) return _query.Search(skip, take, out totalRecords, term, useWildCards, indexName); + + var indexer = string.IsNullOrEmpty(indexName) + ? Examine.ExamineManager.Instance.GetIndexer(Constants.Examine.ExternalIndexer) + : Examine.ExamineManager.Instance.GetIndexer(indexName); + + if (indexer == null) throw new InvalidOperationException("No index found by name " + indexName); + + var searcher = indexer.GetSearcher(); - var searcher = ExamineManager.Instance.DefaultSearchProvider; - if (string.IsNullOrEmpty(searchProvider) == false) - searcher = ExamineManager.Instance.SearchProviderCollection[searchProvider]; - - //if both are zero, use the native Examine API if (skip == 0 && take == 0) { var results = searcher.Search(term, useWildCards); totalRecords = results.TotalItemCount; - return results.ConvertSearchResultToPublishedContent(_contentCache); + return results.ToPublishedSearchResults(_contentCache); } - var luceneSearcher = searcher as BaseLuceneSearcher; - //if the searcher is not a base lucene searcher, we'll have to use Linq Take (edge case) - if (luceneSearcher == null) - { - var results = searcher.Search(term, useWildCards); - totalRecords = results.TotalItemCount; - return results - .Skip(skip) //uses Examine Skip - .ConvertSearchResultToPublishedContent(_contentCache) - .Take(take); //uses Linq Take - } - - //create criteria for all fields - var allSearchFieldCriteria = SearchAllFields(term, useWildCards, luceneSearcher); - - return TypedSearch(skip, take, out totalRecords, allSearchFieldCriteria, searcher); + var criteria = SearchAllFields(term, useWildCards, searcher, indexer); + return Search(skip, take, out totalRecords, criteria, searcher); } - - - /// - /// Searches content - /// - /// - /// - /// - /// - public IEnumerable TypedSearch(string term, bool useWildCards = true, string searchProvider = null) - { - int total; - return TypedSearch(0, 0, out total, term, useWildCards, searchProvider); + + /// + public IEnumerable Search(ISearchCriteria criteria, Examine.ISearcher searchProvider = null) + { + return Search(0, 0, out _, criteria, searchProvider); } - - /// - /// Searhes content - /// - /// - /// - /// - /// - /// - /// - public IEnumerable TypedSearch(int skip, int take, out int totalRecords, ISearchCriteria criteria, BaseSearchProvider searchProvider = null) + + /// + public IEnumerable Search(int skip, int take, out int totalRecords, ISearchCriteria criteria, Examine.ISearcher searchProvider = null) { - if (_typedContentQuery != null) return _typedContentQuery.TypedSearch(skip, take, out totalRecords, criteria, searchProvider); + if (_query != null) return _query.Search(skip, take, out totalRecords, criteria, searchProvider); - var s = ExamineManager.Instance.DefaultSearchProvider; - if (searchProvider != null) - s = searchProvider; + //TODO: Can we inject IExamineManager? - //if both are zero, use the simple native Examine API - if (skip == 0 && take == 0) - { - var r = s.Search(criteria); - totalRecords = r.TotalItemCount; - return r.ConvertSearchResultToPublishedContent(_contentCache); - } + var searcher = searchProvider ?? Examine.ExamineManager.Instance.GetSearcher(Constants.Examine.ExternalIndexer); - var maxResults = skip + take; + var results = skip == 0 && take == 0 + ? searcher.Search(criteria) + : searcher.Search(criteria, maxResults: skip + take); - var results = s.Search(criteria, - //don't return more results than we need for the paging - //this is the 'trick' - we need to be able to load enough search results to fill - //all items to the maxResults - maxResults: maxResults); - - totalRecords = results.TotalItemCount; - - //use examine to skip, this will ensure the lucene data is not loaded for those items - var records = results.Skip(skip); - - return records.ConvertSearchResultToPublishedContent(_contentCache); - } - - /// - /// Searhes content - /// - /// - /// - /// - public IEnumerable TypedSearch(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) - { - var total = 0; - return TypedSearch(0, 0, out total, criteria, searchProvider); + totalRecords = results.TotalItemCount; + return results.ToPublishedSearchResults(_contentCache); } /// - /// Helper method to create an ISearchCriteria for searching all fields in a + /// Creates an ISearchCriteria for searching all fields in a . /// - /// - /// - /// - /// - /// - /// This is here because some of this stuff is internal in Examine - /// - private ISearchCriteria SearchAllFields(string searchText, bool useWildcards, BaseLuceneSearcher searcher) + private ISearchCriteria SearchAllFields(string searchText, bool useWildcards, Examine.ISearcher searcher, Examine.IIndexer indexer) { - var sc = searcher.CreateSearchCriteria(); + var sc = searcher.CreateCriteria(); - if (_examineGetSearchFields == null) - { - //get the GetSearchFields method from BaseLuceneSearcher - _examineGetSearchFields = typeof(BaseLuceneSearcher).GetMethod("GetSearchFields", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); - } - //get the results of searcher.BaseLuceneSearcher() using ugly reflection since it's not public - var searchFields = (IEnumerable)_examineGetSearchFields.Invoke(searcher, null); + //if we're dealing with a lucene searcher, we can get all of it's indexed fields, + //else we can get the defined fields for the index. + var searchFields = (searcher is BaseLuceneSearcher luceneSearcher) + ? luceneSearcher.GetAllIndexedFields() + : indexer.FieldDefinitionCollection.Keys; //this is what Examine does internally to create ISearchCriteria for searching all fields var strArray = searchText.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + sc = useWildcards == false ? sc.GroupedOr(searchFields, strArray).Compile() - : sc.GroupedOr(searchFields, strArray.Select(x => new CustomExamineValue(Examineness.ComplexWildcard, x.MultipleCharacterWildcard().Value)).ToArray()).Compile(); + : sc.GroupedOr(searchFields, strArray.Select(x => (IExamineValue)new ExamineValue(Examineness.ComplexWildcard, x.MultipleCharacterWildcard().Value)).ToArray()).Compile(); return sc; - } - - private static MethodInfo _examineGetSearchFields; + } - //support class since Examine doesn't expose it's own ExamineValue class publicly - private class CustomExamineValue : IExamineValue - { - public CustomExamineValue(Examineness vagueness, string value) - { - this.Examineness = vagueness; - this.Value = value; - this.Level = 1f; - } - public Examineness Examineness { get; private set; } - public string Value { get; private set; } - public float Level { get; private set; } - } #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PublishedContentQueryExtensions.cs b/src/Umbraco.Web/PublishedContentQueryExtensions.cs deleted file mode 100644 index c40daf5e82..0000000000 --- a/src/Umbraco.Web/PublishedContentQueryExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using Umbraco.Core; -using Umbraco.Core.Models; - -namespace Umbraco.Web -{ - public static class PublishedContentQueryExtensions - { - /// - /// Gets a content item from the cache - /// - /// - /// - /// - public static IPublishedContent TypedContent(this ITypedPublishedContentQuery contentQuery, Udi id) - { - var guidUdi = id as GuidUdi; - if (guidUdi == null) - throw new InvalidOperationException("UDIs for content items must be " + typeof(GuidUdi)); - return contentQuery.TypedContent(guidUdi.Guid); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedElementExtensions.cs b/src/Umbraco.Web/PublishedElementExtensions.cs new file mode 100644 index 0000000000..c2a818a926 --- /dev/null +++ b/src/Umbraco.Web/PublishedElementExtensions.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web +{ + /// + /// Provides extension methods for IPublishedElement. + /// + public static class PublishedElementExtensions + { + #region IsComposedOf + + /// + /// Gets a value indicating whether the content is of a content type composed of the given alias + /// + /// The content. + /// The content type alias. + /// A value indicating whether the content is of a content type composed of a content type identified by the alias. + public static bool IsComposedOf(this IPublishedElement content, string alias) + { + return content.ContentType.CompositionAliases.Contains(alias); + } + + #endregion + + #region HasProperty + + /// + /// Gets a value indicating whether the content has a property identified by its alias. + /// + /// The content. + /// The property alias. + /// A value indicating whether the content has the property identified by the alias. + /// The content may have a property, and that property may not have a value. + public static bool HasProperty(this IPublishedElement content, string alias) + { + return content.ContentType.GetPropertyType(alias) != null; + } + + #endregion + + #region HasValue + + /// + /// Gets a value indicating whether the content has a value for a property identified by its alias. + /// + /// Returns true if GetProperty(alias) is not null and GetProperty(alias).HasValue is true. + public static bool HasValue(this IPublishedElement content, string alias, int? languageId = null, string segment = null) + { + var prop = content.GetProperty(alias); + return prop != null && prop.HasValue(languageId, segment); + } + + /// + /// Returns one of two strings depending on whether the content has a value for a property identified by its alias. + /// + /// The content. + /// The property alias. + /// The value to return if the content has a value for the property. + /// The value to return if the content has no value for the property. + /// Either or depending on whether the content + /// has a value for the property identified by the alias. + public static IHtmlString HasValue(this IPublishedElement content, string alias, + string valueIfTrue, string valueIfFalse = null) + { + return content.HasValue(alias) + ? new HtmlString(valueIfTrue) + : new HtmlString(valueIfFalse ?? string.Empty); + } + + #endregion + + #region Value + + /// + /// Gets the value of a content's property identified by its alias. + /// + /// The content. + /// The property alias. + /// The variation language. + /// The variation segment. + /// The value of the content's property identified by the alias. + /// + /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. + /// If no property with the specified alias exists, or if the property has no value, returns null. + /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. + /// The alias is case-insensitive. + /// + public static object Value(this IPublishedElement content, string alias, int? languageId = null, string segment = null) + { + var property = content.GetProperty(alias); + return property?.GetValue(languageId, segment); + } + + /// + /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. + /// + /// The content. + /// The property alias. + /// The default value. + /// The variation language. + /// The variation segment. + /// The value of the content's property identified by the alias, if it exists, otherwise a default value. + /// + /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. + /// If no property with the specified alias exists, or if the property has no value, returns . + /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. + /// The alias is case-insensitive. + /// + public static object Value(this IPublishedElement content, string alias, string defaultValue, int? languageId = null, string segment = null) // fixme - kill + { + var property = content.GetProperty(alias); + return property == null || property.HasValue(languageId, segment) == false ? defaultValue : property.GetValue(languageId, segment); + } + + /// + /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. + /// + /// The content. + /// The property alias. + /// The default value. + /// The variation language. + /// The variation segment. + /// The value of the content's property identified by the alias, if it exists, otherwise a default value. + /// + /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. + /// If no property with the specified alias exists, or if the property has no value, returns . + /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. + /// The alias is case-insensitive. + /// + public static object Value(this IPublishedElement content, string alias, object defaultValue, int? languageId = null, string segment = null) + { + var property = content.GetProperty(alias); + return property == null || property.HasValue(languageId, segment) == false ? defaultValue : property.GetValue(languageId, segment); + } + + #endregion + + #region Value + + /// + /// Gets the value of a content's property identified by its alias, converted to a specified type. + /// + /// The target property type. + /// The content. + /// The property alias. + /// The variation language. + /// The variation segment. + /// The value of the content's property identified by the alias, converted to the specified type. + /// + /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. + /// If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns default(T). + /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. + /// The alias is case-insensitive. + /// + public static T Value(this IPublishedElement content, string alias, int? languageId = null, string segment = null) + { + return content.Value(alias, false, default(T), languageId, segment); + } + + /// + /// Gets the value of a content's property identified by its alias, converted to a specified type, if it exists, otherwise a default value. + /// + /// The target property type. + /// The content. + /// The property alias. + /// The default value. + /// The variation language. + /// The variation segment. + /// The value of the content's property identified by the alias, converted to the specified type, if it exists, otherwise a default value. + /// + /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. + /// If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns . + /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. + /// The alias is case-insensitive. + /// + public static T Value(this IPublishedElement content, string alias, T defaultValue, int? languageId = null, string segment = null) + { + return content.Value(alias, true, defaultValue, languageId, segment); + } + + internal static T Value(this IPublishedElement content, string alias, bool withDefaultValue, T defaultValue, int? languageId = null, string segment = null) // fixme uh? + { + var property = content.GetProperty(alias); + if (property == null) return defaultValue; + + return property.Value(withDefaultValue, defaultValue, languageId, segment); + } + + #endregion + + #region Value or Umbraco.Field - WORK IN PROGRESS + + // trying to reproduce Umbraco.Field so we can get rid of it + // + // what we want: + // - alt aliases + // - recursion + // - default value + // - before & after (if value) + // + // convertLineBreaks: should be an extension string.ConvertLineBreaks() + // stripParagraphs: should be an extension string.StripParagraphs() + // format: should use the standard .ToString(format) + // + // see UmbracoComponentRenderer.Field - which is ugly ;-( + + // recurse first, on each alias (that's how it's done in Field) + // TODO: strongly typed properties howto? + // there is no strongly typed recurse, etc => needs to be in ModelsBuilder? + + public static IHtmlString Value(this IPublishedElement content, string aliases, Func format, string alt = "") + { + if (format == null) format = x => x.ToString(); + + var property = aliases.Split(',') + .Where(x => string.IsNullOrWhiteSpace(x) == false) + .Select(x => content.GetProperty(x.Trim())) + .FirstOrDefault(x => x != null); + + return property != null + ? new HtmlString(format(property.Value())) + : new HtmlString(alt); + } + + // fixme - move that one! + public static IHtmlString Value(this IPublishedContent content, string aliases, Func format, string alt = "", bool recurse = false) + { + if (format == null) format = x => x.ToString(); + + var property = aliases.Split(',') + .Where(x => string.IsNullOrWhiteSpace(x) == false) + .Select(x => content.GetProperty(x.Trim(), recurse)) + .FirstOrDefault(x => x != null); + + return property != null + ? new HtmlString(format(property.Value())) + : new HtmlString(alt); + } + + #endregion + + #region ToIndexedArray + + public static IndexedArrayItem[] ToIndexedArray(this IEnumerable source) + where TContent : class, IPublishedElement + { + var set = source.Select((content, index) => new IndexedArrayItem(content, index)).ToArray(); + foreach (var setItem in set) setItem.TotalCount = set.Length; + return set; + } + + #endregion + + #region OfTypes + + // the .OfType() filter is nice when there's only one type + // this is to support filtering with multiple types + public static IEnumerable OfTypes(this IEnumerable contents, params string[] types) + where T : IPublishedElement + { + types = types.Select(x => x.ToLowerInvariant()).ToArray(); + return contents.Where(x => types.Contains(x.ContentType.Alias.ToLowerInvariant())); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedModels/DummyClassSoThatPublishedModelsNamespaceExists.cs b/src/Umbraco.Web/PublishedModels/DummyClassSoThatPublishedModelsNamespaceExists.cs new file mode 100644 index 0000000000..9691cb5a94 --- /dev/null +++ b/src/Umbraco.Web/PublishedModels/DummyClassSoThatPublishedModelsNamespaceExists.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Web.PublishedModels +{ + // this is here so that Umbraco.Web.PublishedModels namespace exists in views + // even if people are not using models at all - because we are referencing it + // when compiling views - hopefully noone will ever create an actual model + // with that name + internal class DummyClassSoThatPublishedModelsNamespaceExists + { } +} diff --git a/src/Umbraco.Web/RelatedLinksTypeConverter.cs b/src/Umbraco.Web/RelatedLinksTypeConverter.cs index 2debe5f3ac..e06b5b5c62 100644 --- a/src/Umbraco.Web/RelatedLinksTypeConverter.cs +++ b/src/Umbraco.Web/RelatedLinksTypeConverter.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Composing; using Umbraco.Web.Models; namespace Umbraco.Web @@ -23,7 +24,7 @@ namespace Umbraco.Web public RelatedLinksTypeConverter() { - + } private static readonly Type[] ConvertableTypes = new[] @@ -86,12 +87,12 @@ namespace Umbraco.Web if (UmbracoContext.Current == null) { - LogHelper.Warn("Cannot create an UmbracoHelper the UmbracoContext is null"); + Current.Logger.Warn("Cannot create an UmbracoHelper the UmbracoContext is null"); return null; } //DO NOT assign to _umbracoHelper variable, this is a singleton class and we cannot assign this based on an UmbracoHelper which is request based - return new UmbracoHelper(UmbracoContext.Current); + return new UmbracoHelper(UmbracoContext.Current, Current.Services, Current.ApplicationCache); } } } diff --git a/src/Umbraco.Web/RenderFieldCaseType.cs b/src/Umbraco.Web/RenderFieldCaseType.cs index ebb9b386c4..5b4a08cdf8 100644 --- a/src/Umbraco.Web/RenderFieldCaseType.cs +++ b/src/Umbraco.Web/RenderFieldCaseType.cs @@ -1,13 +1,13 @@ -namespace Umbraco.Web +namespace Umbraco.Web { - /// - /// Used in the .Field method when rendering an Umbraco field to specify what case type it should be - /// - public enum RenderFieldCaseType - { - Upper, - Lower, - Title, - Unchanged - } -} \ No newline at end of file + /// + /// Used in the .Field method when rendering an Umbraco field to specify what case type it should be + /// + public enum RenderFieldCaseType + { + Upper, + Lower, + Title, + Unchanged + } +} diff --git a/src/Umbraco.Web/RenderFieldEncodingType.cs b/src/Umbraco.Web/RenderFieldEncodingType.cs index 001fb2023d..f1df312784 100644 --- a/src/Umbraco.Web/RenderFieldEncodingType.cs +++ b/src/Umbraco.Web/RenderFieldEncodingType.cs @@ -1,13 +1,13 @@ -namespace Umbraco.Web +namespace Umbraco.Web { - /// - /// Used in the .Field method to render an Umbraco field to specify what encoding to use - /// - public enum RenderFieldEncodingType - { - Url, - Html, - Unchanged - } -} \ No newline at end of file + /// + /// Used in the .Field method to render an Umbraco field to specify what encoding to use + /// + public enum RenderFieldEncodingType + { + Url, + Html, + Unchanged + } +} diff --git a/src/Umbraco.Web/RouteCollectionExtensions.cs b/src/Umbraco.Web/RouteCollectionExtensions.cs index 586ecf6476..0a5a7d40a6 100644 --- a/src/Umbraco.Web/RouteCollectionExtensions.cs +++ b/src/Umbraco.Web/RouteCollectionExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Web.Mvc; @@ -9,7 +9,7 @@ using Umbraco.Web.Mvc; namespace Umbraco.Web { public static class RouteCollectionExtensions - { + { /// /// Maps an Umbraco route with an UmbracoVirtualNodeRouteHandler /// @@ -55,81 +55,81 @@ namespace Umbraco.Web return apiRoute; } - public static void IgnoreStandardExclusions(this RouteCollection routes) - { - // Ignore standard stuff... - using (routes.GetWriteLock()) - { - var exclusions = new Dictionary() - { - {"{resource}.axd/{*pathInfo}", null}, - {"{*allaxd}", new { allaxd = @".*\.axd(/.*)?" }}, - {"{*allashx}", new { allashx = @".*\.ashx(/.*)?" }}, - {"{*allaspx}", new { allaspx = @".*\.aspx(/.*)?" }}, - {"{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" }}, - }; - //ensure they're not re-added - foreach (var e in exclusions.Where(e => !routes.OfType().Any(x => x.Url == e.Key))) - { - if (e.Value == null) - { - routes.IgnoreRoute(e.Key); - } - else - { - routes.IgnoreRoute(e.Key, e.Value); - } - } - } - } + public static void IgnoreStandardExclusions(this RouteCollection routes) + { + // Ignore standard stuff... + using (routes.GetWriteLock()) + { + var exclusions = new Dictionary() + { + {"{resource}.axd/{*pathInfo}", null}, + {"{*allaxd}", new { allaxd = @".*\.axd(/.*)?" }}, + {"{*allashx}", new { allashx = @".*\.ashx(/.*)?" }}, + {"{*allaspx}", new { allaspx = @".*\.aspx(/.*)?" }}, + {"{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" }}, + }; + //ensure they're not re-added + foreach (var e in exclusions.Where(e => !routes.OfType().Any(x => x.Url == e.Key))) + { + if (e.Value == null) + { + routes.IgnoreRoute(e.Key); + } + else + { + routes.IgnoreRoute(e.Key, e.Value); + } + } + } + } - /// - /// Extension method to manually regsiter an area - /// - /// - /// - public static void RegisterArea(this RouteCollection routes) - where T : AreaRegistration, new() - { + /// + /// Extension method to manually regsiter an area + /// + /// + /// + public static void RegisterArea(this RouteCollection routes) + where T : AreaRegistration, new() + { - // instantiate the area registration - var area = new T(); + // instantiate the area registration + var area = new T(); - // create a context, which is just the name and routes collection - var context = new AreaRegistrationContext(area.AreaName, routes); + // create a context, which is just the name and routes collection + var context = new AreaRegistrationContext(area.AreaName, routes); - // register it! - area.RegisterArea(context); - } + // register it! + area.RegisterArea(context); + } - ///// - ///// Extension method to manually regsiter an area from the container - ///// - ///// - ///// - ///// - //public static void RegisterArea(this RouteCollection routes, Framework.DependencyManagement.IDependencyResolver container) - // where T : AreaRegistration - //{ + ///// + ///// Extension method to manually regsiter an area from the container + ///// + ///// + ///// + ///// + //public static void RegisterArea(this RouteCollection routes, Framework.DependencyManagement.IDependencyResolver container) + // where T : AreaRegistration + //{ - // var area = container.Resolve() as AreaRegistration; - // if (area == null) - // { - // throw new InvalidCastException("Could not resolve type " + typeof(T).FullName + " to AreaRegistration"); - // } + // var area = container.Resolve() as AreaRegistration; + // if (area == null) + // { + // throw new InvalidCastException("Could not resolve type " + typeof(T).FullName + " to AreaRegistration"); + // } - // // create a context, which is just the name and routes collection - // var context = new AreaRegistrationContext(area.AreaName, routes); + // // create a context, which is just the name and routes collection + // var context = new AreaRegistrationContext(area.AreaName, routes); - // // register it! - // area.RegisterArea(context); - //} + // // register it! + // area.RegisterArea(context); + //} - public static void RegisterArea(this RouteCollection routes, T area) where T : AreaRegistration - { - area.RegisterArea(new AreaRegistrationContext(area.AreaName, routes)); - } + public static void RegisterArea(this RouteCollection routes, T area) where T : AreaRegistration + { + area.RegisterArea(new AreaRegistrationContext(area.AreaName, routes)); + } - } -} \ No newline at end of file + } +} diff --git a/src/Umbraco.Web/RouteDataExtensions.cs b/src/Umbraco.Web/RouteDataExtensions.cs deleted file mode 100644 index 036d30c746..0000000000 --- a/src/Umbraco.Web/RouteDataExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Web.Routing; - -namespace Umbraco.Web -{ - public static class RouteDataExtensions - { - /// - /// Tries to get the Umbraco context from the DataTokens - /// - /// - /// - /// - /// This is useful when working on async threads since the UmbracoContext is not copied over explicitly - /// - public static UmbracoContext GetUmbracoContext(this RouteData routeData) - { - if (routeData == null) throw new ArgumentNullException("routeData"); - - if (routeData.DataTokens.ContainsKey(Core.Constants.Web.UmbracoContextDataToken)) - { - var umbCtx = routeData.DataTokens[Core.Constants.Web.UmbracoContextDataToken] as UmbracoContext; - return umbCtx; - } - return null; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index 6629b96c6a..3c72a55ede 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Web.Composing; using Umbraco.Web.PublishedCache; namespace Umbraco.Web.Routing @@ -11,6 +14,15 @@ namespace Umbraco.Web.Routing /// public class AliasUrlProvider : IUrlProvider { + private readonly IGlobalSettings _globalSettings; + private readonly IRequestHandlerSection _requestConfig; + + public AliasUrlProvider(IGlobalSettings globalSettings, IRequestHandlerSection requestConfig) + { + _globalSettings = globalSettings; + _requestConfig = requestConfig; + } + // note - at the moment we seem to accept pretty much anything as an alias // without any form of validation ... could even prob. kill the XPath ... // ok, this is somewhat experimental and is NOT enabled by default @@ -58,11 +70,11 @@ namespace Umbraco.Web.Routing var node = umbracoContext.ContentCache.GetById(id); string umbracoUrlName = null; if (node.HasProperty(Constants.Conventions.Content.UrlAlias)) - umbracoUrlName = node.GetPropertyValue(Constants.Conventions.Content.UrlAlias); + umbracoUrlName = node.Value(Constants.Conventions.Content.UrlAlias); if (string.IsNullOrWhiteSpace(umbracoUrlName)) return Enumerable.Empty(); - var domainHelper = new DomainHelper(umbracoContext.Application.Services.DomainService); + var domainHelper = new DomainHelper(umbracoContext.PublishedShapshot.Domains); var n = node; var domainUris = domainHelper.DomainsForNode(n.Id, current, false); @@ -78,12 +90,12 @@ namespace Umbraco.Web.Routing if (domainUris == null) { var uri = new Uri(path, UriKind.Relative); - return new[] { UriUtility.UriFromUmbraco(uri).ToString() }; + return new[] { UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString() }; } return domainUris .Select(domainUri => new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path))) - .Select(uri => UriUtility.UriFromUmbraco(uri).ToString()); + .Select(uri => UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString()); } #endregion @@ -94,18 +106,12 @@ namespace Umbraco.Web.Routing { get { + //TODO: The ContentFinderResolver instance should be injected in ctor with DI instead of using singleton! + // finder - if (ContentFinderResolver.Current.ContainsType()) + if (Current.ContentFinders.Any(x => x is ContentFinderByUrlAlias)) return true; - // handler wrapped into a finder - if (ContentFinderResolver.Current.ContainsType>()) - return true; - - // handler wrapped into special finder - if (ContentFinderResolver.Current.ContainsType() - && NotFoundHandlerHelper.IsNotFoundHandlerEnabled()) - return true; // anything else, we can't detect return false; diff --git a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs index e6f4a487ec..eb3e481a7b 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs @@ -1,65 +1,65 @@ -using System; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; +using Umbraco.Core.Logging; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Routing { - /// - /// Provides an implementation of that handles page identifiers. - /// - /// - /// Handles /1234 where 1234 is the identified of a document. - /// - public class ContentFinderByIdPath : IContentFinder + /// + /// Provides an implementation of that handles page identifiers. + /// + /// + /// Handles /1234 where 1234 is the identified of a document. + /// + public class ContentFinderByIdPath : IContentFinder { - private readonly IWebRoutingSection _webRoutingSection; + private readonly ILogger _logger; + private readonly IWebRoutingSection _webRoutingSection; - public ContentFinderByIdPath() + public ContentFinderByIdPath(ILogger logger) : this(UmbracoConfig.For.UmbracoSettings().WebRouting) - { - - } + { + _logger = logger; + } - public ContentFinderByIdPath(IWebRoutingSection webRoutingSection) - { - _webRoutingSection = webRoutingSection; - } + public ContentFinderByIdPath(IWebRoutingSection webRoutingSection) + { + _webRoutingSection = webRoutingSection; + } - /// - /// Tries to find and assign an Umbraco document to a PublishedContentRequest. - /// - /// The PublishedContentRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(PublishedContentRequest docRequest) + /// + /// Tries to find and assign an Umbraco document to a PublishedContentRequest. + /// + /// The PublishedContentRequest. + /// A value indicating whether an Umbraco document was found and assigned. + public bool TryFindContent(PublishedRequest frequest) { - if (docRequest.RoutingContext.UmbracoContext != null && docRequest.RoutingContext.UmbracoContext.InPreviewMode == false + if (frequest.UmbracoContext != null && frequest.UmbracoContext.InPreviewMode == false && _webRoutingSection.DisableFindContentByIdPath) return false; IPublishedContent node = null; - var path = docRequest.Uri.GetAbsolutePathDecoded(); + var path = frequest.Uri.GetAbsolutePathDecoded(); var nodeId = -1; - if (path != "/") // no id if "/" + if (path != "/") // no id if "/" { - var noSlashPath = path.Substring(1); + var noSlashPath = path.Substring(1); - if (!Int32.TryParse(noSlashPath, out nodeId)) + if (int.TryParse(noSlashPath, out nodeId) == false) nodeId = -1; if (nodeId > 0) { - LogHelper.Debug("Id={0}", () => nodeId); - node = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(nodeId); + _logger.Debug(() => $"Id={nodeId}"); + node = frequest.UmbracoContext.ContentCache.GetById(nodeId); if (node != null) { - docRequest.PublishedContent = node; - LogHelper.Debug("Found node with id={0}", () => docRequest.PublishedContent.Id); + frequest.PublishedContent = node; + _logger.Debug(() => $"Found node with id={frequest.PublishedContent.Id}"); } else { @@ -69,9 +69,9 @@ namespace Umbraco.Web.Routing } if (nodeId == -1) - LogHelper.Debug("Not a node id"); + _logger.Debug("Not a node id"); return node != null; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs b/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs index 6922510de8..a687d43dbe 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByLegacy404.cs @@ -1,81 +1,91 @@ using System.Globalization; using System.Linq; -using System.Web; using Umbraco.Core; -using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; -using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; namespace Umbraco.Web.Routing { - /// - /// Provides an implementation of that runs the legacy 404 logic. - /// - public class ContentFinderByLegacy404 : IContentFinder - { - /// - /// Tries to find and assign an Umbraco document to a PublishedContentRequest. - /// - /// The PublishedContentRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(PublishedContentRequest pcr) - { - LogHelper.Debug("Looking for a page to handle 404."); + /// + /// Provides an implementation of that runs the legacy 404 logic. + /// + public class ContentFinderByLegacy404 : IContentLastChanceFinder + { + + private readonly ILogger _logger; + private readonly IEntityService _entityService; + private readonly IContentSection _contentConfigSection; + + public ContentFinderByLegacy404(ILogger logger, IEntityService entityService, IContentSection contentConfigSection) + { + _logger = logger; + _entityService = entityService; + _contentConfigSection = contentConfigSection; + } + + /// + /// Tries to find and assign an Umbraco document to a PublishedContentRequest. + /// + /// The PublishedContentRequest. + /// A value indicating whether an Umbraco document was found and assigned. + public bool TryFindContent(PublishedRequest frequest) + { + _logger.Debug("Looking for a page to handle 404."); // try to find a culture as best as we can - var errorCulture = CultureInfo.CurrentUICulture; - if (pcr.HasDomain) - { - errorCulture = CultureInfo.GetCultureInfo(pcr.UmbracoDomain.LanguageIsoCode); - } - else - { - var route = pcr.Uri.GetAbsolutePathDecoded(); - var pos = route.LastIndexOf('/'); - IPublishedContent node = null; - while (pos > 1) - { - route = route.Substring(0, pos); - node = pcr.RoutingContext.UmbracoContext.ContentCache.GetByRoute(route); - if (node != null) break; + var errorCulture = CultureInfo.CurrentUICulture; + if (frequest.HasDomain) + { + errorCulture = frequest.Domain.Culture; + } + else + { + var route = frequest.Uri.GetAbsolutePathDecoded(); + var pos = route.LastIndexOf('/'); + IPublishedContent node = null; + while (pos > 1) + { + route = route.Substring(0, pos); + node = frequest.UmbracoContext.ContentCache.GetByRoute(route); + if (node != null) break; pos = route.LastIndexOf('/'); } - if (node != null) - { - var d = DomainHelper.FindWildcardDomainInPath(pcr.RoutingContext.UmbracoContext.Application.Services.DomainService.GetAll(true), node.Path, null); - if (d != null && string.IsNullOrWhiteSpace(d.LanguageIsoCode) == false) - errorCulture = CultureInfo.GetCultureInfo(d.LanguageIsoCode); - } + if (node != null) + { + var d = DomainHelper.FindWildcardDomainInPath(frequest.UmbracoContext.PublishedShapshot.Domains.GetAll(true), node.Path, null); + if (d != null) + errorCulture = d.Culture; + } } - // TODO - replace the whole logic - var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId( - //TODO: The IContentSection should be ctor injected into this class in v8! - UmbracoConfig.For.UmbracoSettings().Content.Error404Collection.ToArray(), - pcr.RoutingContext.UmbracoContext.Application.Services.EntityService, - new PublishedContentQuery(pcr.RoutingContext.UmbracoContext.ContentCache, pcr.RoutingContext.UmbracoContext.MediaCache), + var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId( + _contentConfigSection.Error404Collection.ToArray(), + _entityService, + new PublishedContentQuery(frequest.UmbracoContext.ContentCache, frequest.UmbracoContext.MediaCache), errorCulture); - IPublishedContent content = null; + IPublishedContent content = null; if (error404.HasValue) - { - LogHelper.Debug("Got id={0}.", () => error404.Value); + { + _logger.Debug(() => $"Got id={error404.Value}."); - content = pcr.RoutingContext.UmbracoContext.ContentCache.GetById(error404.Value); + content = frequest.UmbracoContext.ContentCache.GetById(error404.Value); - LogHelper.Debug(content == null - ? "Could not find content with that id." - : "Found corresponding content."); - } - else - { - LogHelper.Debug("Got nothing."); - } + _logger.Debug(content == null + ? "Could not find content with that id." + : "Found corresponding content."); + } + else + { + _logger.Debug("Got nothing."); + } - pcr.PublishedContent = content; - pcr.SetIs404(); - return content != null; - } - } + frequest.PublishedContent = content; + frequest.Is404 = true; + return content != null; + } + } } diff --git a/src/Umbraco.Web/Routing/ContentFinderByNiceUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByNiceUrl.cs index 529d027ea1..094c0addaf 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByNiceUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByNiceUrl.cs @@ -1,56 +1,63 @@ -using Umbraco.Core.Logging; -using Umbraco.Core.Models; +using Umbraco.Core.Logging; using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Routing { - /// - /// Provides an implementation of that handles page nice urls. - /// - /// - /// Handles /foo/bar where /foo/bar is the nice url of a document. - /// + /// + /// Provides an implementation of that handles page nice urls. + /// + /// + /// Handles /foo/bar where /foo/bar is the nice url of a document. + /// public class ContentFinderByNiceUrl : IContentFinder { - /// - /// Tries to find and assign an Umbraco document to a PublishedContentRequest. - /// - /// The PublishedContentRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public virtual bool TryFindContent(PublishedContentRequest docRequest) - { - string route; - if (docRequest.HasDomain) - route = docRequest.Domain.RootNodeId.ToString() + DomainHelper.PathRelativeToDomain(docRequest.DomainUri, docRequest.Uri.GetAbsolutePathDecoded()); - else - route = docRequest.Uri.GetAbsolutePathDecoded(); + protected ILogger Logger { get; } - var node = FindContent(docRequest, route); + public ContentFinderByNiceUrl(ILogger logger) + { + Logger = logger; + } + + /// + /// Tries to find and assign an Umbraco document to a PublishedContentRequest. + /// + /// The PublishedContentRequest. + /// A value indicating whether an Umbraco document was found and assigned. + public virtual bool TryFindContent(PublishedRequest frequest) + { + string route; + if (frequest.HasDomain) + route = frequest.Domain.ContentId + DomainHelper.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()); + else + route = frequest.Uri.GetAbsolutePathDecoded(); + + var node = FindContent(frequest, route); return node != null; } - /// - /// Tries to find an Umbraco document for a PublishedContentRequest and a route. - /// - /// The document request. - /// The route. - /// The document node, or null. - protected IPublishedContent FindContent(PublishedContentRequest docreq, string route) + /// + /// Tries to find an Umbraco document for a PublishedContentRequest and a route. + /// + /// The document request. + /// The route. + /// The document node, or null. + protected IPublishedContent FindContent(PublishedRequest docreq, string route) { - LogHelper.Debug("Test route \"{0}\"", () => route); + Logger.Debug(() => $"Test route \"{route}\""); - var node = docreq.RoutingContext.UmbracoContext.ContentCache.GetByRoute(route); + var node = docreq.UmbracoContext.ContentCache.GetByRoute(route); if (node != null) { docreq.PublishedContent = node; - LogHelper.Debug("Got content, id={0}", () => node.Id); + Logger.Debug(() => $"Got content, id={node.Id}"); } else { - LogHelper.Debug("No match."); + Logger.Debug("No match."); } - return node; + return node; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Routing/ContentFinderByNiceUrlAndTemplate.cs b/src/Umbraco.Web/Routing/ContentFinderByNiceUrlAndTemplate.cs index 246b109dea..d0eaa55b79 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByNiceUrlAndTemplate.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByNiceUrlAndTemplate.cs @@ -1,7 +1,9 @@ -using Umbraco.Core.Logging; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; namespace Umbraco.Web.Routing { @@ -14,19 +16,23 @@ namespace Umbraco.Web.Routing /// public class ContentFinderByNiceUrlAndTemplate : ContentFinderByNiceUrl { + public ContentFinderByNiceUrlAndTemplate(ILogger logger) + : base(logger) + { } + /// /// Tries to find and assign an Umbraco document to a PublishedContentRequest. /// - /// The PublishedContentRequest. + /// The PublishedContentRequest. /// A value indicating whether an Umbraco document was found and assigned. /// If successful, also assigns the template. - public override bool TryFindContent(PublishedContentRequest docRequest) + public override bool TryFindContent(PublishedRequest frequest) { IPublishedContent node = null; - string path = docRequest.Uri.GetAbsolutePathDecoded(); + var path = frequest.Uri.GetAbsolutePathDecoded(); - if (docRequest.HasDomain) - path = DomainHelper.PathRelativeToDomain(docRequest.DomainUri, path); + if (frequest.HasDomain) + path = DomainHelper.PathRelativeToDomain(frequest.Domain.Uri, path); if (path != "/") // no template if "/" { @@ -34,28 +40,28 @@ namespace Umbraco.Web.Routing var templateAlias = path.Substring(pos + 1); path = pos == 0 ? "/" : path.Substring(0, pos); - var template = ApplicationContext.Current.Services.FileService.GetTemplate(templateAlias); + var template = Current.Services.FileService.GetTemplate(templateAlias); if (template != null) { - LogHelper.Debug("Valid template: \"{0}\"", () => templateAlias); + Logger.Debug(() => $"Valid template: \"{templateAlias}\""); - var route = docRequest.HasDomain ? (docRequest.Domain.RootNodeId.ToString() + path) : path; - node = FindContent(docRequest, route); + var route = frequest.HasDomain ? (frequest.Domain.ContentId.ToString() + path) : path; + node = FindContent(frequest, route); if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates == false && node != null) - docRequest.TemplateModel = template; + frequest.TemplateModel = template; } else { - LogHelper.Debug("Not a valid template: \"{0}\"", () => templateAlias); + Logger.Debug(() => $"Not a valid template: \"{templateAlias}\""); } } else { - LogHelper.Debug("No template in path \"/\""); + Logger.Debug("No template in path \"/\""); } return node != null; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandler.cs b/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandler.cs deleted file mode 100644 index 3231186014..0000000000 --- a/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandler.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using Umbraco.Core.Logging; -using umbraco.interfaces; - -namespace Umbraco.Web.Routing -{ - /// - /// Provides an implementation of that runs a legacy NotFoundHandler. - /// - /// Provided for backward compatibility. - public class ContentFinderByNotFoundHandler : IContentFinder - { - /// - /// Tries to find and assign an Umbraco document to a PublishedContentRequest. - /// - /// The PublishedContentRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(PublishedContentRequest pcr) - { - var type = typeof(THandler); - var handler = GetHandler(type); - - if (handler == null) - return false; - - var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers(); - LogHelper.Debug>("Running for legacy url='{0}'.", () => url); - - if (handler.Execute(url) && handler.redirectID > 0) - { - LogHelper.Debug>("Handler '{0}' returned id={1}.", () => type.FullName, () => handler.redirectID); - - var content = pcr.RoutingContext.UmbracoContext.ContentCache.GetById(handler.redirectID); - - LogHelper.Debug>(content == null - ? "Could not find content with that id." - : "Found corresponding content."); - - pcr.PublishedContent = content; - return content != null; - } - - LogHelper.Debug>("Handler '{0}' returned nothing.", () => type.FullName); - return false; - } - - INotFoundHandler GetHandler(Type type) - { - try - { - return Activator.CreateInstance(type) as INotFoundHandler; - } - catch (Exception e) - { - LogHelper.Error>(string.Format("Error instanciating handler {0}, ignoring.", type.FullName), e); - return null; - } - } - } -} diff --git a/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandlers.cs b/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandlers.cs deleted file mode 100644 index cb80429167..0000000000 --- a/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandlers.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Logging; -using umbraco.interfaces; - -namespace Umbraco.Web.Routing -{ - /// - /// Provides an implementation of that runs legacy INotFoundHandler. - /// - public class ContentFinderByNotFoundHandlers : IContentFinder - { - // notes - // - // at the moment we load the legacy INotFoundHandler - // excluding those that have been replaced by proper finders, - // and run them. - - /// - /// Tries to find and assign an Umbraco document to a PublishedContentRequest. - /// - /// The PublishedContentRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(PublishedContentRequest docRequest) - { - HandlePageNotFound(docRequest); - return docRequest.HasPublishedContent; - } - - #region Copied over and adapted from presentation.requestHandler - - private static void HandlePageNotFound(PublishedContentRequest docRequest) - { - var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers(); - LogHelper.Debug("Running for legacy url='{0}'.", () => url); - - foreach (var handler in NotFoundHandlerHelper.GetNotFoundHandlers()) - { - var handlerName = handler.GetType().FullName; - LogHelper.Debug("Handler '{0}'.", () => handlerName); - - var finder = NotFoundHandlerHelper.SubsituteFinder(handler); - if (finder != null) - { - var finderName = finder.GetType().FullName; - LogHelper.Debug("Replace handler '{0}' by new finder '{1}'.", () => handlerName, () => finderName); - - // can't find a document => continue with other handlers - if (finder.TryFindContent(docRequest) == false) - continue; - - // found a document => break, don't run other handlers, we're done - - // in theory an IContentFinder can return true yet set no document - // but none of the substitued finders (see SubstituteFinder) do it. - - // do NOT set docRequest.PublishedContent again here - // as it would clear any template that the finder might have set - - LogHelper.Debug("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id); - if (docRequest.Is404) - LogHelper.Debug("Finder '{0}' set status to 404.", () => finderName); - - LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => docRequest.PublishedContent.Id); - break; - } - - // else it's a legacy handler: run - - // can't find a document => continue with other handlers - if (handler.Execute(url) == false || handler.redirectID <= 0) - continue; - - // found a document ID => ensure it's a valid document - var redirectId = handler.redirectID; - docRequest.PublishedContent = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectId); - - if (docRequest.HasPublishedContent == false) - { - // the handler said it could handle the url, and returned a content ID - // yet that content ID is invalid... should we run the other handlers? - // I don't think so, not here, let the "last chance" finder take care. - // so, break. - - LogHelper.Debug("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId); - break; - } - - // found a valid document => break, don't run other handlers, we're done - - LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId); - - if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404) - { - LogHelper.Debug("Handler '{0}' set status code to 404.", () => handlerName); - docRequest.Is404 = true; - } - - break; - } - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs b/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs index 4732895311..481bb4d905 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs @@ -1,28 +1,28 @@ -namespace Umbraco.Web.Routing +namespace Umbraco.Web.Routing { - /// - /// This looks up a document by checking for the umbPageId of a request/query string - /// - /// - /// This is used by library.RenderTemplate and also some of the macro rendering functionality like in - /// insertMacro.aspx and macroResultWrapper.aspx - /// + /// + /// This looks up a document by checking for the umbPageId of a request/query string + /// + /// + /// This is used by library.RenderTemplate and also some of the macro rendering functionality like in + /// macroResultWrapper.aspx + /// public class ContentFinderByPageIdQuery : IContentFinder - { - public bool TryFindContent(PublishedContentRequest docRequest) - { - int pageId; - if (int.TryParse(docRequest.RoutingContext.UmbracoContext.HttpContext.Request["umbPageID"], out pageId)) - { - var doc = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(pageId); + { + public bool TryFindContent(PublishedRequest frequest) + { + int pageId; + if (int.TryParse(frequest.UmbracoContext.HttpContext.Request["umbPageID"], out pageId)) + { + var doc = frequest.UmbracoContext.ContentCache.GetById(pageId); - if (doc != null) - { - docRequest.PublishedContent = doc; - return true; - } - } - return false; - } - } -} \ No newline at end of file + if (doc != null) + { + frequest.PublishedContent = doc; + return true; + } + } + return false; + } + } +} diff --git a/src/Umbraco.Web/Routing/ContentFinderByProfile.cs b/src/Umbraco.Web/Routing/ContentFinderByProfile.cs deleted file mode 100644 index 4a9034460a..0000000000 --- a/src/Umbraco.Web/Routing/ContentFinderByProfile.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core; - -namespace Umbraco.Web.Routing -{ - /// - /// Provides an implementation of that handles profiles. - /// - /// - /// Handles /profile/login where /profile is the profile page nice url and login the login of a member. - /// This should rather be done with a rewriting rule. There would be multiple profile pages in multi-sites/multi-langs setups. - /// We keep it for backward compatility reasons. - /// - public class ContentFinderByProfile : ContentFinderByNiceUrl - { - /// - /// Tries to find and assign an Umbraco document to a PublishedContentRequest. - /// - /// The PublishedContentRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public override bool TryFindContent(PublishedContentRequest docRequest) - { - IPublishedContent node = null; - var path = docRequest.Uri.GetAbsolutePathDecoded(); - - bool isProfile = false; - var pos = path.LastIndexOf('/'); - if (pos > 0) - { - var memberLogin = path.Substring(pos + 1); - path = path.Substring(0, pos); - - if (path == GlobalSettings.ProfileUrl) - { - isProfile = true; - LogHelper.Debug("Path \"{0}\" is the profile path", () => path); - - var route = docRequest.HasDomain ? (docRequest.Domain.RootNodeId.ToString() + path) : path; - node = FindContent(docRequest, route); - - if (node != null) - { - //TODO: Should be handled by Context Items class manager (http://issues.umbraco.org/issue/U4-61) - docRequest.RoutingContext.UmbracoContext.HttpContext.Items["umbMemberLogin"] = memberLogin; - } - else - { - LogHelper.Debug("No document matching profile path?"); - } - } - } - - if (!isProfile) - { - LogHelper.Debug("Not the profile path"); - } - - return node != null; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs index c6af13e874..b8bc5f19fb 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs @@ -2,6 +2,7 @@ using System.Web; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Services; namespace Umbraco.Web.Routing { @@ -14,47 +15,53 @@ namespace Umbraco.Web.Routing /// public class ContentFinderByRedirectUrl : IContentFinder { + private readonly IRedirectUrlService _redirectUrlService; + private readonly ILogger _logger; + + public ContentFinderByRedirectUrl(IRedirectUrlService redirectUrlService, ILogger logger) + { + _redirectUrlService = redirectUrlService; + _logger = logger; + } + /// /// Tries to find and assign an Umbraco document to a PublishedContentRequest. /// - /// The PublishedContentRequest. + /// The PublishedContentRequest. /// A value indicating whether an Umbraco document was found and assigned. /// Optionally, can also assign the template or anything else on the document request, although that is not required. - public bool TryFindContent(PublishedContentRequest contentRequest) + public bool TryFindContent(PublishedRequest frequest) { - var route = contentRequest.HasDomain - ? contentRequest.UmbracoDomain.RootContentId + DomainHelper.PathRelativeToDomain(contentRequest.DomainUri, contentRequest.Uri.GetAbsolutePathDecoded()) - : contentRequest.Uri.GetAbsolutePathDecoded(); + var route = frequest.HasDomain + ? frequest.Domain.ContentId + DomainHelper.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()) + : frequest.Uri.GetAbsolutePathDecoded(); - var service = contentRequest.RoutingContext.UmbracoContext.Application.Services.RedirectUrlService; - var redirectUrl = service.GetMostRecentRedirectUrl(route); + var redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route); // From: http://stackoverflow.com/a/22468386/5018 // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 - // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads + // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads // to problems if you rename a page back to it's original name or create a new page with the original name - contentRequest.Cacheability = HttpCacheability.NoCache; - contentRequest.CacheExtensions = new List { "no-store, must-revalidate" }; - contentRequest.Headers = new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }; + frequest.Cacheability = HttpCacheability.NoCache; + frequest.CacheExtensions = new List { "no-store, must-revalidate" }; + frequest.Headers = new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }; if (redirectUrl == null) { - LogHelper.Debug("No match for route: \"{0}\".", () => route); + _logger.Debug(() => $"No match for route: \"{route}\"."); return false; } - var content = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectUrl.ContentId); + var content = frequest.UmbracoContext.ContentCache.GetById(redirectUrl.ContentId); var url = content == null ? "#" : content.Url; if (url.StartsWith("#")) { - LogHelper.Debug("Route \"{0}\" matches content {1} which has no url.", - () => route, () => redirectUrl.ContentId); + _logger.Debug(() => $"Route \"{route}\" matches content {redirectUrl.ContentId} which has no url."); return false; } - LogHelper.Debug("Route \"{0}\" matches content {1} with url \"{2}\", redirecting.", - () => route, () => content.Id, () => url); - contentRequest.SetRedirectPermanent(url); + _logger.Debug(() => $"Route \"{route}\" matches content {content.Id} with url \"{url}\", redirecting."); + frequest.SetRedirectPermanent(url); return true; } } diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs index e0121a4b3c..1117a57592 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs @@ -1,52 +1,58 @@ -using System; +using System; using System.Text; using System.Linq; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Core.Models; using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; using Umbraco.Web.PublishedCache; namespace Umbraco.Web.Routing { - /// - /// Provides an implementation of that handles page aliases. - /// - /// - /// Handles /just/about/anything where /just/about/anything is contained in the umbracoUrlAlias property of a document. - /// The alias is the full path to the document. There can be more than one alias, separated by commas. - /// + /// + /// Provides an implementation of that handles page aliases. + /// + /// + /// Handles /just/about/anything where /just/about/anything is contained in the umbracoUrlAlias property of a document. + /// The alias is the full path to the document. There can be more than one alias, separated by commas. + /// public class ContentFinderByUrlAlias : IContentFinder { - /// - /// Tries to find and assign an Umbraco document to a PublishedContentRequest. - /// - /// The PublishedContentRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(PublishedContentRequest docRequest) - { - IPublishedContent node = null; + protected ILogger Logger { get; } - if (docRequest.Uri.AbsolutePath != "/") // no alias if "/" - { - node = FindContentByAlias(docRequest.RoutingContext.UmbracoContext.ContentCache, - docRequest.HasDomain ? docRequest.Domain.RootNodeId : 0, - docRequest.Uri.GetAbsolutePathDecoded()); - - if (node != null) - { - docRequest.PublishedContent = node; - LogHelper.Debug("Path \"{0}\" is an alias for id={1}", () => docRequest.Uri.AbsolutePath, () => docRequest.PublishedContent.Id); - } - } - - return node != null; - } - - private static IPublishedContent FindContentByAlias(ContextualPublishedContentCache cache, int rootNodeId, string alias) + public ContentFinderByUrlAlias(ILogger logger) { - if (alias == null) throw new ArgumentNullException("alias"); + Logger = logger; + } + + /// + /// Tries to find and assign an Umbraco document to a PublishedContentRequest. + /// + /// The PublishedContentRequest. + /// A value indicating whether an Umbraco document was found and assigned. + public bool TryFindContent(PublishedRequest frequest) + { + IPublishedContent node = null; + + if (frequest.Uri.AbsolutePath != "/") // no alias if "/" + { + node = FindContentByAlias(frequest.UmbracoContext.ContentCache, + frequest.HasDomain ? frequest.Domain.ContentId : 0, + frequest.Uri.GetAbsolutePathDecoded()); + + if (node != null) + { + frequest.PublishedContent = node; + Logger.Debug(() => $"Path \"{frequest.Uri.AbsolutePath}\" is an alias for id={frequest.PublishedContent.Id}"); + } + } + + return node != null; + } + + private static IPublishedContent FindContentByAlias(IPublishedContentCache cache, int rootNodeId, string alias) + { + if (alias == null) throw new ArgumentNullException(nameof(alias)); // the alias may be "foo/bar" or "/foo/bar" // there may be spaces as in "/foo/bar, /foo/nil" @@ -54,7 +60,7 @@ namespace Umbraco.Web.Routing alias = alias.TrimStart('/'); var xpathBuilder = new StringBuilder(); - xpathBuilder.Append(XPathStringsDefinition.Root); + xpathBuilder.Append(XPathStrings.Root); if (rootNodeId > 0) xpathBuilder.AppendFormat(XPathStrings.DescendantDocumentById, rootNodeId); @@ -76,61 +82,16 @@ namespace Umbraco.Web.Routing #region XPath Strings - class XPathStringsDefinition - { - public int Version { get; private set; } + static class XPathStrings + { + public static string Root => "/root"; + public const string DescendantDocumentById = "//* [@isDoc and @id={0}]"; + public const string DescendantDocumentByAlias = "//* [@isDoc and (" + + "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" + + " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" + + ")]"; + } - public static string Root { get { return "/root"; } } - public string DescendantDocumentById { get; private set; } - public string DescendantDocumentByAlias { get; private set; } - - public XPathStringsDefinition(int version) - { - Version = version; - - switch (version) - { - // legacy XML schema - case 0: - DescendantDocumentById = "//node [@id={0}]"; - DescendantDocumentByAlias = "//node[(" - + "contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',{0},')" - + " or contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',/{0},')" - + ")]"; - break; - - // default XML schema as of 4.10 - case 1: - DescendantDocumentById = "//* [@isDoc and @id={0}]"; - DescendantDocumentByAlias = "//* [@isDoc and (" - + "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" - + " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" - + ")]"; - break; - - default: - throw new Exception(string.Format("Unsupported Xml schema version '{0}').", version)); - } - } - } - - static XPathStringsDefinition _xPathStringsValue; - static XPathStringsDefinition XPathStrings - { - get - { - // in theory XPathStrings should be a static variable that - // we should initialize in a static ctor - but then test cases - // that switch schemas fail - so cache and refresh when needed, - // ie never when running the actual site - - var version = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? 0 : 1; - if (_xPathStringsValue == null || _xPathStringsValue.Version != version) - _xPathStringsValue = new XPathStringsDefinition(version); - return _xPathStringsValue; - } - } - - #endregion + #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Routing/ContentFinderCollection.cs b/src/Umbraco.Web/Routing/ContentFinderCollection.cs new file mode 100644 index 0000000000..9eca0d5d72 --- /dev/null +++ b/src/Umbraco.Web/Routing/ContentFinderCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Routing +{ + public class ContentFinderCollection : BuilderCollectionBase + { + public ContentFinderCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Web/Routing/ContentFinderCollectionBuilder.cs b/src/Umbraco.Web/Routing/ContentFinderCollectionBuilder.cs new file mode 100644 index 0000000000..7d9ef952c4 --- /dev/null +++ b/src/Umbraco.Web/Routing/ContentFinderCollectionBuilder.cs @@ -0,0 +1,14 @@ +using LightInject; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Routing +{ + public class ContentFinderCollectionBuilder : OrderedCollectionBuilderBase + { + public ContentFinderCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override ContentFinderCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Web/Routing/ContentFinderResolver.cs b/src/Umbraco.Web/Routing/ContentFinderResolver.cs deleted file mode 100644 index b069f86315..0000000000 --- a/src/Umbraco.Web/Routing/ContentFinderResolver.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.Routing -{ - /// - /// Resolves IPublishedContentFinder objects. - /// - public sealed class ContentFinderResolver : ManyObjectsResolverBase - { - /// - /// Initializes a new instance of the class with an initial list of finder types. - /// - /// - /// - /// The list of finder types - /// The resolver is created by the WebBootManager and thus the constructor remains internal. - internal ContentFinderResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable finders) - : base(serviceProvider, logger, finders) - { } - - /// - /// Initializes a new instance of the class with an initial list of finder types. - /// - /// - /// The list of finder types - /// - /// The resolver is created by the WebBootManager and thus the constructor remains internal. - internal ContentFinderResolver(IServiceProvider serviceProvider, ILogger logger, params Type[] finders) - : base(serviceProvider, logger, finders) - { } - - /// - /// Gets the finders. - /// - public IEnumerable Finders - { - get { return Values; } - } - } -} diff --git a/src/Umbraco.Web/Routing/ContentLastChanceFinderByNotFoundHandlers.cs b/src/Umbraco.Web/Routing/ContentLastChanceFinderByNotFoundHandlers.cs deleted file mode 100644 index ed2bad7860..0000000000 --- a/src/Umbraco.Web/Routing/ContentLastChanceFinderByNotFoundHandlers.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using umbraco.interfaces; - -namespace Umbraco.Web.Routing -{ - /// - /// Provides an implementation of that runs legacy INotFoundHandler in "last chance" situation. - /// - public class ContentLastChanceFinderByNotFoundHandlers : IContentFinder - { - // notes - // - // at the moment we load the legacy INotFoundHandler - // excluding those that have been replaced by proper finders, - // and run them. - - /// - /// Tries to find and assign an Umbraco document to a PublishedContentRequest. - /// - /// The PublishedContentRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(PublishedContentRequest docRequest) - { - HandlePageNotFound(docRequest); - return docRequest.HasPublishedContent; - } - - #region Copied over and adapted from presentation.requestHandler - - private static void HandlePageNotFound(PublishedContentRequest docRequest) - { - var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers(); - LogHelper.Debug("Running for legacy url='{0}'.", () => url); - - var handler = NotFoundHandlerHelper.GetNotFoundLastChanceHandler(); - var handlerName = handler.GetType().FullName; - LogHelper.Debug("Handler '{0}'.", () => handlerName); - - var finder = NotFoundHandlerHelper.SubsituteFinder(handler); - if (finder != null) - { - var finderName = finder.GetType().FullName; - LogHelper.Debug("Replace handler '{0}' by new finder '{1}'.", () => handlerName, () => finderName); - - // can't find a document => exit - if (finder.TryFindContent(docRequest) == false) - return; - - // found a document => we're done - - // in theory an IContentFinder can return true yet set no document - // but none of the substitued finders (see SubstituteFinder) do it. - - // do NOT set docRequest.PublishedContent again here - // as it would clear any template that the finder might have set - - LogHelper.Debug("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id); - if (docRequest.Is404) - LogHelper.Debug("Finder '{0}' set status to 404.", () => finderName); - - LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => docRequest.PublishedContent.Id); - return; - } - - // else it's a legacy handler, run - - // can't find a document => exit - if (handler.Execute(url) == false || handler.redirectID <= 0) - return; - - // found a document ID => ensure it's a valid document - var redirectId = handler.redirectID; - docRequest.PublishedContent = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectId); - - if (docRequest.HasPublishedContent == false) - { - // the handler said it could handle the url, and returned a content ID - // yet that content ID is invalid... exit. - - LogHelper.Debug("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId); - return; - } - - // found a valid document => return - - LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId); - - if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404) - { - LogHelper.Debug("Handler '{0}' set status code to 404.", () => handlerName); - docRequest.Is404 = true; - } - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/ContentLastChanceFinderResolver.cs b/src/Umbraco.Web/Routing/ContentLastChanceFinderResolver.cs deleted file mode 100644 index 7e1faf6daf..0000000000 --- a/src/Umbraco.Web/Routing/ContentLastChanceFinderResolver.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.Routing -{ - - /// - /// Resolves the last chance IPublishedContentFinder object. - /// - public sealed class ContentLastChanceFinderResolver : SingleObjectResolverBase - { - /// - /// Initializes a new instance of the class with no finder. - /// - /// The resolver is created by the WebBootManager and thus the constructor remains internal. - internal ContentLastChanceFinderResolver() - : base(true) - { } - - /// - /// Initializes a new instance of the class with an instance of a finder. - /// - /// An instance of a finder. - /// The resolver is created by the WebBootManager and thus the constructor remains internal. - internal ContentLastChanceFinderResolver(IContentFinder finder) - : base(finder, true) - { } - - /// - /// Sets the last chance finder. - /// - /// The finder. - /// For developers, at application startup. - public void SetFinder(IContentFinder finder) - { - Value = finder; - } - - /// - /// Gets the last chance finder. - /// - public IContentFinder Finder - { - get { return Value; } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs b/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs index 989cabe388..251ddf9cdd 100644 --- a/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs +++ b/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs @@ -5,7 +5,7 @@ namespace Umbraco.Web.Routing { /// /// This url provider is used purely to deal with umbraco custom routes that utilize UmbracoVirtualNodeRouteHandler and will return - /// the URL returned from the current PublishedContentRequest.PublishedContent (virtual node) if the request is in fact a virtual route and + /// the URL returned from the current PublishedContentRequest.PublishedContent (virtual node) if the request is in fact a virtual route and /// the id that is being requested matches the id of the current PublishedContentRequest.PublishedContent. /// internal class CustomRouteUrlProvider : IUrlProvider @@ -21,8 +21,8 @@ namespace Umbraco.Web.Routing public string GetUrl(UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode) { if (umbracoContext == null) return null; - if (umbracoContext.PublishedContentRequest == null) return null; - if (umbracoContext.PublishedContentRequest.PublishedContent == null) return null; + if (umbracoContext.PublishedRequest == null) return null; + if (umbracoContext.PublishedRequest.PublishedContent == null) return null; if (umbracoContext.HttpContext == null) return null; if (umbracoContext.HttpContext.Request == null) return null; if (umbracoContext.HttpContext.Request.RequestContext == null) return null; @@ -30,14 +30,14 @@ namespace Umbraco.Web.Routing if (umbracoContext.HttpContext.Request.RequestContext.RouteData.DataTokens == null) return null; if (umbracoContext.HttpContext.Request.RequestContext.RouteData.DataTokens.ContainsKey(Umbraco.Core.Constants.Web.CustomRouteDataToken) == false) return null; //ok so it's a custom route with published content assigned, check if the id being requested for is the same id as the assigned published content - return id == umbracoContext.PublishedContentRequest.PublishedContent.Id - ? umbracoContext.PublishedContentRequest.PublishedContent.Url + return id == umbracoContext.PublishedRequest.PublishedContent.Id + ? umbracoContext.PublishedRequest.PublishedContent.Url : null; } /// - /// This always returns null because this url provider is used purely to deal with Umbraco custom routes with - /// UmbracoVirtualNodeRouteHandler, we really only care about the normal URL so that RedirectToCurrentUmbracoPage() works + /// This always returns null because this url provider is used purely to deal with Umbraco custom routes with + /// UmbracoVirtualNodeRouteHandler, we really only care about the normal URL so that RedirectToCurrentUmbracoPage() works /// with SurfaceControllers /// /// @@ -49,4 +49,4 @@ namespace Umbraco.Web.Routing return null; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 6b3c0fe69b..93fde8f0b9 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -1,14 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; - -using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; -using Umbraco.Web.PublishedCache; -using umbraco.cms.businesslogic.web; namespace Umbraco.Web.Routing { @@ -18,16 +13,14 @@ namespace Umbraco.Web.Routing public class DefaultUrlProvider : IUrlProvider { private readonly IRequestHandlerSection _requestSettings; + private readonly ILogger _logger; + private readonly IGlobalSettings _globalSettings; - [Obsolete("Use the ctor that specifies the IRequestHandlerSection")] - public DefaultUrlProvider() - : this(UmbracoConfig.For.UmbracoSettings().RequestHandler) - { - } - - public DefaultUrlProvider(IRequestHandlerSection requestSettings) + public DefaultUrlProvider(IRequestHandlerSection requestSettings, ILogger logger, IGlobalSettings globalSettings) { _requestSettings = requestSettings; + _logger = logger; + _globalSettings = globalSettings; } #region GetUrl @@ -59,13 +52,12 @@ namespace Umbraco.Web.Routing { if (string.IsNullOrWhiteSpace(route)) { - LogHelper.Debug( - "Couldn't find any page with nodeId={0}. This is most likely caused by the page not being published.", - () => id); + _logger.Debug(() => + $"Couldn't find any page with nodeId={id}. This is most likely caused by the page not being published."); return null; } - var domainHelper = new DomainHelper(umbracoContext.Application.Services.DomainService); + var domainHelper = new DomainHelper(umbracoContext.PublishedShapshot.Domains); // extract domainUri and path // route is / or / @@ -101,13 +93,12 @@ namespace Umbraco.Web.Routing if (string.IsNullOrWhiteSpace(route)) { - LogHelper.Debug( - "Couldn't find any page with nodeId={0}. This is most likely caused by the page not being published.", - () => id); + _logger.Debug(() => + $"Couldn't find any page with nodeId={id}. This is most likely caused by the page not being published."); return null; } - var domainHelper = new DomainHelper(umbracoContext.Application.Services.DomainService); + var domainHelper = new DomainHelper(umbracoContext.PublishedShapshot.Domains); // extract domainUri and path // route is / or / @@ -179,7 +170,7 @@ namespace Umbraco.Web.Routing // UriFromUmbraco will handle vdir // meaning it will add vdir into domain urls too! - return UriUtility.UriFromUmbraco(uri); + return UriUtility.UriFromUmbraco(uri, _globalSettings, _requestSettings); } string CombinePaths(string path1, string path2) @@ -201,7 +192,7 @@ namespace Umbraco.Web.Routing // UriFromUmbraco will handle vdir // meaning it will add vdir into domain urls too! - return uris.Select(UriUtility.UriFromUmbraco); + return uris.Select(x => UriUtility.UriFromUmbraco(x, _globalSettings, _requestSettings)); } #endregion diff --git a/src/Umbraco.Web/Routing/Domain.cs b/src/Umbraco.Web/Routing/Domain.cs new file mode 100644 index 0000000000..b9116c6b51 --- /dev/null +++ b/src/Umbraco.Web/Routing/Domain.cs @@ -0,0 +1,65 @@ +using System.Globalization; + +namespace Umbraco.Web.Routing +{ + /// + /// Represents a published snapshot domain. + /// + public class Domain + { + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the domain. + /// The name of the domain. + /// The identifier of the content which supports the domain. + /// The culture of the domain. + /// A value indicating whether the domain is a wildcard domain. + public Domain(int id, string name, int contentId, CultureInfo culture, bool isWildcard) + { + Id = id; + Name = name; + ContentId = contentId; + Culture = culture; + IsWildcard = isWildcard; + } + + /// + /// Initializes a new instance of the class. + /// + /// An origin domain. + protected Domain(Domain domain) + { + Id = domain.Id; + Name = domain.Name; + ContentId = domain.ContentId; + Culture = domain.Culture; + IsWildcard = domain.IsWildcard; + } + + /// + /// Gets the unique identifier of the domain. + /// + public int Id { get; } + + /// + /// Gets the name of the domain. + /// + public string Name { get; } + + /// + /// Gets the identifier of the content which supports the domain. + /// + public int ContentId { get; } + + /// + /// Gets the culture of the domain. + /// + public CultureInfo Culture { get; } + + /// + /// Gets a value indicating whether the domain is a wildcard domain. + /// + public bool IsWildcard { get; } + } +} diff --git a/src/Umbraco.Web/Routing/DomainAndUri.cs b/src/Umbraco.Web/Routing/DomainAndUri.cs index 6395e937df..8aaeec52ef 100644 --- a/src/Umbraco.Web/Routing/DomainAndUri.cs +++ b/src/Umbraco.Web/Routing/DomainAndUri.cs @@ -1,74 +1,49 @@ using System; -using System.ComponentModel; using Umbraco.Core; -using umbraco.cms.businesslogic.web; -using Umbraco.Core.Models; namespace Umbraco.Web.Routing { /// - /// Represents an Umbraco domain and its normalized uri. + /// Represents a published snapshot domain with its normalized uri. /// /// /// In Umbraco it is valid to create domains with name such as example.com, https://www.example.com, example.com/foo/. /// The normalized uri of a domain begins with a scheme and ends with no slash, eg http://example.com/, https://www.example.com/, http://example.com/foo/. /// - public class DomainAndUri + public class DomainAndUri : Domain { - /// - /// Initializes a new instance of the class with a Domain and a uri scheme. + /// Initializes a new instance of the class. /// - /// The domain. - /// The uri scheme. - public DomainAndUri(IDomain domain, string scheme) + /// The original domain. + /// The context current Uri. + public DomainAndUri(Domain domain, Uri currentUri) + : base(domain) { - UmbracoDomain = domain; try { - Uri = new Uri(UriUtility.TrimPathEndSlash(UriUtility.StartWithScheme(domain.DomainName, scheme))); + // turn "/en" into "http://whatever.com/en" so it becomes a parseable uri + var name = Name.StartsWith("/") && currentUri != null + ? currentUri.GetLeftPart(UriPartial.Authority) + Name + : Name; + var scheme = currentUri?.Scheme ?? Uri.UriSchemeHttp; + Uri = new Uri(UriUtility.TrimPathEndSlash(UriUtility.StartWithScheme(name, scheme))); } catch (UriFormatException) { - var name = domain.DomainName.ToCSharpString(); - throw new ArgumentException(string.Format("Failed to parse invalid domain: node id={0}, hostname=\"{1}\"." - + " Hostname should be a valid uri.", domain.RootContentId, name), "domain"); + throw new ArgumentException($"Failed to parse invalid domain: node id={domain.ContentId}, hostname=\"{Name.ToCSharpString()}\"." + + " Hostname should be a valid uri.", nameof(domain)); } } - [Obsolete("This should not be used, use the other contructor specifying the non legacy IDomain instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public DomainAndUri(Domain domain, string scheme) - : this(domain.DomainEntity, scheme) - { - - } - - - [Obsolete("This should not be used, use the non-legacy property called UmbracoDomain instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public Domain Domain - { - get { return new Domain(UmbracoDomain); } - } - /// - /// Gets the Umbraco domain. + /// Gets the normalized uri of the domain, within the current context. /// - public IDomain UmbracoDomain { get; private set; } + public Uri Uri { get; } - /// - /// Gets or sets the normalized uri of the domain. - /// - public Uri Uri { get; private set; } - - /// - /// Gets a string that represents the instance. - /// - /// A string that represents the current instance. public override string ToString() { - return string.Format("{{ \"{0}\", \"{1}\" }}", UmbracoDomain.DomainName, Uri); + return $"{{ \"{Name}\", \"{Uri}\" }}"; } } } diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index 88488db83f..79ccb9fbd4 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -2,27 +2,21 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Services; +using Umbraco.Web.Composing; +using Umbraco.Web.PublishedCache; // published snapshot namespace Umbraco.Web.Routing { /// /// Provides utilities to handle domains. /// - public class DomainHelper - { - private readonly IDomainService _domainService; + public class DomainHelper + { + private readonly IDomainCache _domainCache; - [Obsolete("Use the contructor specifying all dependencies instead")] - public DomainHelper() - : this(ApplicationContext.Current.Services.DomainService) + public DomainHelper(IDomainCache domainCache) { - } - - public DomainHelper(IDomainService domainService) - { - _domainService = domainService; + _domainCache = domainCache; } #region Domain for Node @@ -42,14 +36,14 @@ namespace Umbraco.Web.Routing return null; // get the domains on that node - var domains = _domainService.GetAssignedDomains(nodeId, false).ToArray(); + var domains = _domainCache.GetAssigned(nodeId, false).ToArray(); // none? - if (domains.Any() == false) + if (domains.Length == 0) return null; // else filter - var helper = SiteDomainHelperResolver.Current.Helper; + var helper = Current.SiteDomainHelper; var domainAndUri = DomainForUri(domains, current, domainAndUris => helper.MapDomain(current, domainAndUris)); if (domainAndUri == null) @@ -65,7 +59,7 @@ namespace Umbraco.Web.Routing /// True if the node has domains, else false. internal bool NodeHasDomains(int nodeId) { - return nodeId > 0 && _domainService.GetAssignedDomains(nodeId, false).Any(); + return nodeId > 0 && _domainCache.GetAssigned(nodeId, false).Any(); } /// @@ -84,17 +78,17 @@ namespace Umbraco.Web.Routing return null; // get the domains on that node - var domains = _domainService.GetAssignedDomains(nodeId, false).ToArray(); + var domains = _domainCache.GetAssigned(nodeId, false).ToArray(); // none? - if (domains.Any() == false) + if (domains.Length == 0) return null; // get the domains and their uris var domainAndUris = DomainsForUri(domains, current).ToArray(); // filter - var helper = SiteDomainHelperResolver.Current.Helper; + var helper = Current.SiteDomainHelper; return helper.MapDomains(current, domainAndUris, excludeDefault).ToArray(); } @@ -114,20 +108,19 @@ namespace Umbraco.Web.Routing /// the right one, unless it is null, in which case the method returns null. /// The filter, if any, will be called only with a non-empty argument, and _must_ return something. /// - internal static DomainAndUri DomainForUri(IEnumerable domains, Uri current, Func filter = null) + internal static DomainAndUri DomainForUri(IEnumerable domains, Uri current, Func filter = null) { // sanitize the list to have proper uris for comparison (scheme, path end with /) // we need to end with / because example.com/foo cannot match example.com/foobar // we need to order so example.com/foo matches before example.com/ - var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme; var domainsAndUris = domains .Where(d => d.IsWildcard == false) - .Select(SanitizeForBackwardCompatibility) - .Select(d => new DomainAndUri(d, scheme)) + //.Select(SanitizeForBackwardCompatibility) + .Select(d => new DomainAndUri(d, current)) .OrderByDescending(d => d.Uri.ToString()) .ToArray(); - if (domainsAndUris.Any() == false) + if (domainsAndUris.Length == 0) return null; DomainAndUri domainAndUri; @@ -171,13 +164,12 @@ namespace Umbraco.Web.Routing /// The group of domains. /// The uri, or null. /// The domains and their normalized uris, that match the specified uri. - internal static IEnumerable DomainsForUri(IEnumerable domains, Uri current) + internal static IEnumerable DomainsForUri(IEnumerable domains, Uri current) { - var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme; return domains .Where(d => d.IsWildcard == false) - .Select(SanitizeForBackwardCompatibility) - .Select(d => new DomainAndUri(d, scheme)) + //.Select(SanitizeForBackwardCompatibility) + .Select(d => new DomainAndUri(d, current)) .OrderByDescending(d => d.Uri.ToString()); } @@ -185,27 +177,6 @@ namespace Umbraco.Web.Routing #region Utilities - /// - /// Sanitize a Domain. - /// - /// The Domain to sanitize. - /// The sanitized domain. - /// This is a _really_ nasty one that should be removed at some point. Some people were - /// using hostnames such as "/en" which happened to work pre-4.10 but really make no sense at - /// all... and 4.10 throws on them, so here we just try to find a way so 4.11 does not throw. - /// But really... no. - private static IDomain SanitizeForBackwardCompatibility(IDomain domain) - { - var context = System.Web.HttpContext.Current; - if (context != null && domain.DomainName.StartsWith("/")) - { - // turn "/en" into "http://whatever.com/en" so it becomes a parseable uri - var authority = context.Request.Url.GetLeftPart(UriPartial.Authority); - domain.DomainName = authority + domain.DomainName; - } - return domain; - } - /// /// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node. /// @@ -214,7 +185,7 @@ namespace Umbraco.Web.Routing /// The current domain root node identifier, or null. /// A value indicating if there is another domain defined down in the path. /// Looks _under_ rootNodeId but not _at_ rootNodeId. - internal static bool ExistsDomainInPath(IEnumerable domains, string path, int? rootNodeId) + internal static bool ExistsDomainInPath(IEnumerable domains, string path, int? rootNodeId) { return FindDomainInPath(domains, path, rootNodeId) != null; } @@ -227,7 +198,7 @@ namespace Umbraco.Web.Routing /// The current domain root node identifier, or null. /// The deepest non-wildcard Domain in the path, or null. /// Looks _under_ rootNodeId but not _at_ rootNodeId. - internal static IDomain FindDomainInPath(IEnumerable domains, string path, int? rootNodeId) + internal static Domain FindDomainInPath(IEnumerable domains, string path, int? rootNodeId) { var stopNodeId = rootNodeId ?? -1; @@ -235,7 +206,7 @@ namespace Umbraco.Web.Routing .Reverse() .Select(int.Parse) .TakeWhile(id => id != stopNodeId) - .Select(id => domains.FirstOrDefault(d => d.RootContentId == id && d.IsWildcard == false)) + .Select(id => domains.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == false)) .SkipWhile(domain => domain == null) .FirstOrDefault(); } @@ -248,7 +219,7 @@ namespace Umbraco.Web.Routing /// The current domain root node identifier, or null. /// The deepest wildcard Domain in the path, or null. /// Looks _under_ rootNodeId but not _at_ rootNodeId. - internal static IDomain FindWildcardDomainInPath(IEnumerable domains, string path, int? rootNodeId) + internal static Domain FindWildcardDomainInPath(IEnumerable domains, string path, int? rootNodeId) { var stopNodeId = rootNodeId ?? -1; @@ -256,7 +227,7 @@ namespace Umbraco.Web.Routing .Reverse() .Select(int.Parse) .TakeWhile(id => id != stopNodeId) - .Select(id => domains.FirstOrDefault(d => d.RootContentId == id && d.IsWildcard)) + .Select(id => domains.FirstOrDefault(d => d.ContentId == id && d.IsWildcard)) .FirstOrDefault(domain => domain != null); } diff --git a/src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs b/src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs index 3eefbc4d0c..375d81fe4c 100644 --- a/src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs +++ b/src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Web.Routing +namespace Umbraco.Web.Routing { /// /// Represents the outcome of trying to route an incoming request. @@ -31,14 +31,9 @@ namespace Umbraco.Web.Routing /// NotReady = 11, - /// - /// Umbraco was not configured. - /// - NotConfigured = 12, - /// /// There was no content at all. /// - NoContent = 13 + NoContent = 12 } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Routing/IContentFinder.cs b/src/Umbraco.Web/Routing/IContentFinder.cs index c29df43465..bf905a54ba 100644 --- a/src/Umbraco.Web/Routing/IContentFinder.cs +++ b/src/Umbraco.Web/Routing/IContentFinder.cs @@ -1,16 +1,16 @@ -namespace Umbraco.Web.Routing +namespace Umbraco.Web.Routing { - /// - /// Provides a method to try to find and assign an Umbraco document to a PublishedContentRequest. - /// - public interface IContentFinder + /// + /// Provides a method to try to find and assign an Umbraco document to a PublishedContentRequest. + /// + public interface IContentFinder { - /// - /// Tries to find and assign an Umbraco document to a PublishedContentRequest. - /// - /// The PublishedContentRequest. - /// A value indicating whether an Umbraco document was found and assigned. - /// Optionally, can also assign the template or anything else on the document request, although that is not required. - bool TryFindContent(PublishedContentRequest contentRequest); + /// + /// Tries to find and assign an Umbraco document to a PublishedContentRequest. + /// + /// The PublishedContentRequest. + /// A value indicating whether an Umbraco document was found and assigned. + /// Optionally, can also assign the template or anything else on the document request, although that is not required. + bool TryFindContent(PublishedRequest frequest); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Routing/IContentLastChanceFinder.cs b/src/Umbraco.Web/Routing/IContentLastChanceFinder.cs new file mode 100644 index 0000000000..91343b7d49 --- /dev/null +++ b/src/Umbraco.Web/Routing/IContentLastChanceFinder.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Web.Routing +{ + /// + /// Provides a method to try to find and assign an Umbraco document to a PublishedContentRequest + /// when everything else has failed. + /// + /// Identical to but required in order to differenciate them in ioc. + public interface IContentLastChanceFinder : IContentFinder + { } +} diff --git a/src/Umbraco.Web/Routing/IUrlProvider.cs b/src/Umbraco.Web/Routing/IUrlProvider.cs index 79b27f888a..ae38f6d422 100644 --- a/src/Umbraco.Web/Routing/IUrlProvider.cs +++ b/src/Umbraco.Web/Routing/IUrlProvider.cs @@ -11,13 +11,13 @@ namespace Umbraco.Web.Routing { /// /// Gets the nice url of a published content. - /// + /// /// The Umbraco context. /// The published content id. - /// The current absolute url. - /// The url mode. - /// The url for the published content. - /// + /// The current absolute url. + /// The url mode. + /// The url for the published content. + /// /// The url is absolute or relative depending on mode and on current. /// If the provider is unable to provide a url, it should return null. /// diff --git a/src/Umbraco.Web/Routing/LegacyRequestInitializer.cs b/src/Umbraco.Web/Routing/LegacyRequestInitializer.cs index 9baec728f1..9be9b76547 100644 --- a/src/Umbraco.Web/Routing/LegacyRequestInitializer.cs +++ b/src/Umbraco.Web/Routing/LegacyRequestInitializer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web; namespace Umbraco.Web.Routing @@ -8,21 +8,19 @@ namespace Umbraco.Web.Routing /// internal class LegacyRequestInitializer { - private readonly Uri _requestUrl; - private readonly HttpContextBase _httpContext; + private readonly Uri _requestUrl; + private readonly HttpContextBase _httpContext; public LegacyRequestInitializer(Uri requestUrl, HttpContextBase httpContext) { - _requestUrl = requestUrl; - _httpContext = httpContext; + _requestUrl = requestUrl; + _httpContext = httpContext; } - public void InitializeRequest() + public void InitializeRequest() { var uri = _requestUrl; - global::umbraco.presentation.UmbracoContext.Current = new global::umbraco.presentation.UmbracoContext(_httpContext); - // legacy - umbOriginalUrl used by default.aspx to rewritepath so forms are happy // legacy - umbOriginalUrl used by presentation/umbraco/urlRewriter/UrlRewriterFormWriter which handles
_customHandlerTypes; - private static Type _customLastChanceHandlerType; - - static void InitializeNotFoundHandlers() - { - // initialize handlers - // create the definition cache - - LogHelper.Debug("Registering custom handlers."); - - var customHandlerTypes = new List(); - Type customHandlerType = null; - - var customHandlers = new XmlDocument(); - customHandlers.Load(Core.IO.IOHelper.MapPath(Core.IO.SystemFiles.NotFoundhandlersConfig)); - - foreach (XmlNode n in customHandlers.DocumentElement.SelectNodes("notFound")) - { - if (customHandlerType != null) - { - LogHelper.Debug("Registering '{0}'.", () => customHandlerType.FullName); - customHandlerTypes.Add(customHandlerType); - } - - var assemblyName = n.Attributes.GetNamedItem("assembly").Value; - var typeName = n.Attributes.GetNamedItem("type").Value; - - var ns = assemblyName; - var nsAttr = n.Attributes.GetNamedItem("namespace"); - if (nsAttr != null && string.IsNullOrWhiteSpace(nsAttr.Value) == false) - ns = nsAttr.Value; - - LogHelper.Debug("Configured: '{0}.{1},{2}'.", () => ns, () => typeName, () => assemblyName); - - customHandlerType = null; - try - { - var assembly = Assembly.Load(new AssemblyName(assemblyName)); - customHandlerType = assembly.GetType(ns + "." + typeName); - } - catch (Exception e) - { - LogHelper.Error("Error: could not load handler, ignoring.", e); - } - } - - // what shall we do with the last one, assuming it's not null? - // if the last chance finder wants a handler, then use the last one as the last chance handler - // else assume that the last one is a normal handler since noone else wants it, and add it to the list - if (customHandlerType != null) - { - var lastChanceFinder = ContentLastChanceFinderResolver.Current.Finder; // can be null - var finderWantsHandler = lastChanceFinder != null && - lastChanceFinder.GetType() == typeof(ContentLastChanceFinderByNotFoundHandlers); - - if (finderWantsHandler) - { - LogHelper.Debug("Registering '{0}' as \"last chance\" handler.", () => customHandlerType.FullName); - _customLastChanceHandlerType = customHandlerType; - } - else - { - LogHelper.Debug("Registering '{0}'.", () => customHandlerType.FullName); - customHandlerTypes.Add(customHandlerType); - _customLastChanceHandlerType = null; - } - } - - _customHandlerTypes = customHandlerTypes.ToArray(); - } - - public static IEnumerable GetNotFoundHandlers() - { - // instanciate new handlers - // using definition cache - - var handlers = new List(); - - foreach (var type in _customHandlerTypes) - { - try - { - var handler = Activator.CreateInstance(type) as INotFoundHandler; - if (handler != null) - handlers.Add(handler); - } - catch (Exception e) - { - LogHelper.Error(string.Format("Error instanciating handler {0}, ignoring.", type.FullName), e); - } - } - - return handlers; - } - - public static bool IsNotFoundHandlerEnabled() - { - return _customHandlerTypes.Contains(typeof(T)); - } - - public static INotFoundHandler GetNotFoundLastChanceHandler() - { - if (_customLastChanceHandlerType == null) return null; - - try - { - var handler = Activator.CreateInstance(_customLastChanceHandlerType) as INotFoundHandler; - if (handler != null) - return handler; - } - catch (Exception e) - { - LogHelper.Error(string.Format("Error instanciating handler {0}, ignoring.", _customLastChanceHandlerType.FullName), e); - } - - return null; - } - - public static IContentFinder SubsituteFinder(INotFoundHandler handler) - { - IContentFinder finder = null; - - if (handler is global::umbraco.SearchForAlias) - finder = new ContentFinderByUrlAlias(); - else if (handler is global::umbraco.SearchForProfile) - finder = new ContentFinderByProfile(); - else if (handler is global::umbraco.SearchForTemplate) - finder = new ContentFinderByNiceUrlAndTemplate(); - else if (handler is global::umbraco.handle404) - finder = new ContentFinderByLegacy404(); - - return finder; - } - - /// - /// Returns the Umbraco page id to use as the Not Found page based on the configured 404 pages and the current request - /// - /// - /// - /// The server name attached to the request, normally would be the source of HttpContext.Current.Request.ServerVariables["SERVER_NAME"] - /// - /// - /// - /// - /// - internal static int? GetCurrentNotFoundPageId( - IContentErrorPage[] error404Collection, - string requestServerName, - IEntityService entityService, - ITypedPublishedContentQuery publishedContentQuery, - IDomainService domainService) - { - throw new NotImplementedException(); - } - - internal static int? GetCurrentNotFoundPageId( - IContentErrorPage[] error404Collection, - IEntityService entityService, - ITypedPublishedContentQuery publishedContentQuery, - CultureInfo errorCulture) - { - if (error404Collection.Length > 1) - { - // test if a 404 page exists with current culture thread - var cultureErr = error404Collection.FirstOrDefault(x => x.Culture == errorCulture.Name) - ?? error404Collection.FirstOrDefault(x => x.Culture == "default"); // there should be a default one! - - if (cultureErr != null) - return GetContentIdFromErrorPageConfig(cultureErr, entityService, publishedContentQuery); - } - else - { - return GetContentIdFromErrorPageConfig(error404Collection.First(), entityService, publishedContentQuery); - } - - return null; - } - - /// - /// Returns the content id based on the configured IContentErrorPage section - /// - /// - /// - /// - /// - internal static int? GetContentIdFromErrorPageConfig(IContentErrorPage errorPage, IEntityService entityService, ITypedPublishedContentQuery publishedContentQuery) - { - if (errorPage.HasContentId) return errorPage.ContentId; - - if (errorPage.HasContentKey) - { - //need to get the Id for the GUID - //TODO: When we start storing GUIDs into the IPublishedContent, then we won't have to look this up - // but until then we need to look it up in the db. For now we've implemented a cached service for - // converting Int -> Guid and vice versa. - var found = entityService.GetIdForKey(errorPage.ContentKey, UmbracoObjectTypes.Document); - if (found) - { - return found.Result; - } - return null; - } - - if (errorPage.ContentXPath.IsNullOrWhiteSpace() == false) - { - try - { - //we have an xpath statement to execute - var xpathResult = UmbracoXPathPathSyntaxParser.ParseXPathQuery( - xpathExpression: errorPage.ContentXPath, - nodeContextId: null, - getPath: nodeid => - { - var ent = entityService.Get(nodeid); - return ent.Path.Split(',').Reverse(); - }, - publishedContentExists: i => publishedContentQuery.TypedContent(i) != null); - - //now we'll try to execute the expression - var nodeResult = publishedContentQuery.TypedContentSingleAtXPath(xpathResult); - if (nodeResult != null) - return nodeResult.Id; - } - catch (Exception ex) - { - LogHelper.Error("Could not parse xpath expression: " + errorPage.ContentXPath, ex); - return null; - } - } - return null; - } - - } -} +using System; +using System.Globalization; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Xml; +using Umbraco.Web.Composing; + +namespace Umbraco.Web.Routing +{ + /// + /// Used to determine the node to display when content is not found based on the configured error404 elements in umbracoSettings.config + /// + internal class NotFoundHandlerHelper + { + /// + /// Returns the Umbraco page id to use as the Not Found page based on the configured 404 pages and the current request + /// + /// + /// + /// The server name attached to the request, normally would be the source of HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + /// + /// + /// + /// + /// + internal static int? GetCurrentNotFoundPageId( + IContentErrorPage[] error404Collection, + string requestServerName, + IEntityService entityService, + IPublishedContentQuery publishedContentQuery, + IDomainService domainService) + { + throw new NotImplementedException(); + } + + internal static int? GetCurrentNotFoundPageId( + IContentErrorPage[] error404Collection, + IEntityService entityService, + IPublishedContentQuery publishedContentQuery, + CultureInfo errorCulture) + { + if (error404Collection.Length > 1) + { + // test if a 404 page exists with current culture thread + var cultureErr = error404Collection.FirstOrDefault(x => x.Culture == errorCulture.Name) + ?? error404Collection.FirstOrDefault(x => x.Culture == "default"); // there should be a default one! + + if (cultureErr != null) + return GetContentIdFromErrorPageConfig(cultureErr, entityService, publishedContentQuery); + } + else + { + return GetContentIdFromErrorPageConfig(error404Collection.First(), entityService, publishedContentQuery); + } + + return null; + } + + /// + /// Returns the content id based on the configured IContentErrorPage section + /// + /// + /// + /// + /// + internal static int? GetContentIdFromErrorPageConfig(IContentErrorPage errorPage, IEntityService entityService, IPublishedContentQuery publishedContentQuery) + { + if (errorPage.HasContentId) return errorPage.ContentId; + + if (errorPage.HasContentKey) + { + //need to get the Id for the GUID + //TODO: When we start storing GUIDs into the IPublishedContent, then we won't have to look this up + // but until then we need to look it up in the db. For now we've implemented a cached service for + // converting Int -> Guid and vice versa. + var found = entityService.GetId(errorPage.ContentKey, UmbracoObjectTypes.Document); + if (found) + { + return found.Result; + } + return null; + } + + if (errorPage.ContentXPath.IsNullOrWhiteSpace() == false) + { + try + { + //we have an xpath statement to execute + var xpathResult = UmbracoXPathPathSyntaxParser.ParseXPathQuery( + xpathExpression: errorPage.ContentXPath, + nodeContextId: null, + getPath: nodeid => + { + var ent = entityService.Get(nodeid); + return ent.Path.Split(',').Reverse(); + }, + publishedContentExists: i => publishedContentQuery.Content(i) != null); + + //now we'll try to execute the expression + var nodeResult = publishedContentQuery.ContentSingleAtXPath(xpathResult); + if (nodeResult != null) + return nodeResult.Id; + } + catch (Exception ex) + { + Current.Logger.Error("Could not parse xpath expression: " + errorPage.ContentXPath, ex); + return null; + } + } + return null; + } + + } +} diff --git a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs index d737885020..7038725dff 100644 --- a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs +++ b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs @@ -2,53 +2,50 @@ namespace Umbraco.Web.Routing { - /// - /// Gets executed when no document can be found in Umbraco - /// - internal class PublishedContentNotFoundHandler : IHttpHandler - { - string _message; + /// + /// Gets executed when no document can be found in Umbraco + /// + internal class PublishedContentNotFoundHandler : IHttpHandler + { + private readonly string _message; - public PublishedContentNotFoundHandler() - { } + public PublishedContentNotFoundHandler() + { } - public PublishedContentNotFoundHandler(string message) - { - _message = message; - } + public PublishedContentNotFoundHandler(string message) + { + _message = message; + } - public void ProcessRequest(HttpContext context) - { - WriteOutput(context); - } + public void ProcessRequest(HttpContext context) + { + WriteOutput(context); + } - internal void WriteOutput(HttpContext context) - { - var response = context.Response; + internal void WriteOutput(HttpContext context) + { + var response = context.Response; - response.Clear(); + response.Clear(); - var docreq = UmbracoContext.Current.PublishedContentRequest; - var reason = "Cannot render the page at url '{0}'."; - if (!docreq.HasPublishedContent) - reason = "No umbraco document matches the url '{0}'."; - else if (!docreq.HasTemplate) - reason = "No template exists to render the document at url '{0}'."; + var frequest = UmbracoContext.Current.PublishedRequest; + var reason = "Cannot render the page at url '{0}'."; + if (frequest.HasPublishedContent == false) + reason = "No umbraco document matches the url '{0}'."; + else if (frequest.HasTemplate == false) + reason = "No template exists to render the document at url '{0}'."; - response.Write("

Page not found

"); - response.Write("

"); - response.Write(string.Format(reason, HttpUtility.HtmlEncode(UmbracoContext.Current.OriginalRequestUrl.PathAndQuery))); - response.Write("

"); - if (!string.IsNullOrWhiteSpace(_message)) - response.Write("

" + _message + "

"); - response.Write("

This page can be replaced with a custom 404. Check the documentation for \"custom 404\".

"); - response.Write("

This page is intentionally left ugly ;-)

"); - response.Write(""); - } + response.Write("

Page not found

"); + response.Write("

"); + response.Write(string.Format(reason, HttpUtility.HtmlEncode(UmbracoContext.Current.OriginalRequestUrl.PathAndQuery))); + response.Write("

"); + if (string.IsNullOrWhiteSpace(_message) == false) + response.Write("

" + _message + "

"); + response.Write("

This page can be replaced with a custom 404. Check the documentation for \"custom 404\".

"); + response.Write("

This page is intentionally left ugly ;-)

"); + response.Write(""); + } - public bool IsReusable - { - get { return false; } - } - } + public bool IsReusable => false; + } } diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs deleted file mode 100644 index 641821ddd6..0000000000 --- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs +++ /dev/null @@ -1,640 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using System.Web; -using System.Web.Security; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Models; - -using umbraco; -using umbraco.cms.businesslogic.web; -using RenderingEngine = Umbraco.Core.RenderingEngine; - -namespace Umbraco.Web.Routing -{ - /// - /// Represents a request for one specified Umbraco IPublishedContent to be rendered - /// by one specified template, using one specified Culture and RenderingEngine. - /// - public class PublishedContentRequest - { - private bool _readonly; - private bool _readonlyUri; - - /// - /// Triggers before the published content request is prepared. - /// - /// When the event triggers, no preparation has been done. It is still possible to - /// modify the request's Uri property, for example to restore its original, public-facing value - /// that might have been modified by an in-between equipement such as a load-balancer. - public static event EventHandler Preparing; - - /// - /// Triggers once the published content request has been prepared, but before it is processed. - /// - /// When the event triggers, preparation is done ie domain, culture, document, template, - /// rendering engine, etc. have been setup. It is then possible to change anything, before - /// the request is actually processed and rendered by Umbraco. - public static event EventHandler Prepared; - - // the engine that does all the processing - // because in order to keep things clean and separated, - // the content request is just a data holder - private readonly PublishedContentRequestEngine _engine; - - // the cleaned up uri - // the cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - private Uri _uri; - - /// - /// Initializes a new instance of the class with a specific Uri and routing context. - /// - /// The request Uri. - /// A routing context. - /// A callback method to return the roles for the provided login name when required - /// - public PublishedContentRequest(Uri uri, RoutingContext routingContext, IWebRoutingSection routingConfig, Func> getRolesForLogin) - { - if (uri == null) throw new ArgumentNullException("uri"); - if (routingContext == null) throw new ArgumentNullException("routingContext"); - - Uri = uri; - RoutingContext = routingContext; - _getRolesForLoginCallback = getRolesForLogin; - - _engine = new PublishedContentRequestEngine( - routingConfig, - this); - - RenderingEngine = RenderingEngine.Unknown; - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use the constructor specifying all dependencies instead")] - public PublishedContentRequest(Uri uri, RoutingContext routingContext) - : this(uri, routingContext, UmbracoConfig.For.UmbracoSettings().WebRouting, s => Roles.Provider.GetRolesForUser(s)) - { - } - - /// - /// Gets the engine associated to the request. - /// - internal PublishedContentRequestEngine Engine { get { return _engine; } } - - /// - /// Prepares the request. - /// - public void Prepare() - { - _engine.PrepareRequest(); - } - - /// - /// Called to configure the request - /// - /// - /// This public method is legacy, Prepare() has been made public now which should be used and ensures the domains are assigned and - /// if a public content item is already assigned Prepare() now ensures that the finders are not executed. - /// - [Obsolete("Use Prepare() instead which configures the request and wires up everything correctly")] - public void ConfigureRequest() - { - _engine.ConfigureRequest(); - } - - /// - /// Updates the request when there is no template to render the content. - /// - internal void UpdateOnMissingTemplate() - { - var __readonly = _readonly; - _readonly = false; - _engine.UpdateRequestOnMissingTemplate(); - _readonly = __readonly; - } - - /// - /// Triggers the Preparing event. - /// - internal void OnPreparing() - { - var handler = Preparing; - if (handler != null) handler(this, EventArgs.Empty); - _readonlyUri = true; - } - - /// - /// Triggers the Prepared event. - /// - internal void OnPrepared() - { - var handler = Prepared; - if (handler != null) handler(this, EventArgs.Empty); - - if (HasPublishedContent == false) - Is404 = true; // safety - - _readonly = true; - } - - /// - /// Gets or sets the cleaned up Uri used for routing. - /// - /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - public Uri Uri - { - get - { - return _uri; - } - set - { - if (_readonlyUri) - throw new InvalidOperationException("Cannot modify Uri after Preparing has triggered."); - _uri = value; - } - } - - private void EnsureWriteable() - { - if (_readonly) - throw new InvalidOperationException("Cannot modify a PublishedContentRequest once it is read-only."); - } - - #region PublishedContent - - /// - /// The requested IPublishedContent, if any, else null. - /// - private IPublishedContent _publishedContent; - - /// - /// The initial requested IPublishedContent, if any, else null. - /// - /// The initial requested content is the content that was found by the finders, - /// before anything such as 404, redirect... took place. - private IPublishedContent _initialPublishedContent; - - /// - /// Gets or sets the requested content. - /// - /// Setting the requested content clears Template. - public IPublishedContent PublishedContent - { - get { return _publishedContent; } - set - { - EnsureWriteable(); - _publishedContent = value; - IsInternalRedirectPublishedContent = false; - TemplateModel = null; - } - } - - /// - /// Sets the requested content, following an internal redirect. - /// - /// The requested content. - /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will - /// preserve or reset the template, if any. - public void SetInternalRedirectPublishedContent(IPublishedContent content) - { - if (content == null) throw new ArgumentNullException("content"); - EnsureWriteable(); - - // unless a template has been set already by the finder, - // template should be null at that point. - - // IsInternalRedirect if IsInitial, or already IsInternalRedirect - var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; - - // redirecting to self - if (content.Id == PublishedContent.Id) // neither can be null - { - // no need to set PublishedContent, we're done - IsInternalRedirectPublishedContent = isInternalRedirect; - return; - } - - // else - - // save - var template = _template; - var renderingEngine = RenderingEngine; - - // set published content - this resets the template, and sets IsInternalRedirect to false - PublishedContent = content; - IsInternalRedirectPublishedContent = isInternalRedirect; - - // must restore the template if it's an internal redirect & the config option is set - if (isInternalRedirect && UmbracoConfig.For.UmbracoSettings().WebRouting.InternalRedirectPreservesTemplate) - { - // restore - _template = template; - RenderingEngine = renderingEngine; - } - } - - /// - /// Gets the initial requested content. - /// - /// The initial requested content is the content that was found by the finders, - /// before anything such as 404, redirect... took place. - public IPublishedContent InitialPublishedContent { get { return _initialPublishedContent; } } - - /// - /// Gets value indicating whether the current published content is the initial one. - /// - public bool IsInitialPublishedContent - { - get - { - return _initialPublishedContent != null && _initialPublishedContent == _publishedContent; - } - } - - /// - /// Indicates that the current PublishedContent is the initial one. - /// - public void SetIsInitialPublishedContent() - { - EnsureWriteable(); - - // note: it can very well be null if the initial content was not found - _initialPublishedContent = _publishedContent; - IsInternalRedirectPublishedContent = false; - } - - /// - /// Gets or sets a value indicating whether the current published content has been obtained - /// from the initial published content following internal redirections exclusively. - /// - /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to - /// apply the internal redirect or not, when content is not the initial content. - public bool IsInternalRedirectPublishedContent { get; private set; } - - /// - /// Gets a value indicating whether the content request has a content. - /// - public bool HasPublishedContent - { - get { return PublishedContent != null; } - } - - #endregion - - #region Template - - /// - /// The template model, if any, else null. - /// - private ITemplate _template; - - /// - /// Gets or sets the template model to use to display the requested content. - /// - internal ITemplate TemplateModel - { - get - { - return _template; - } - - set - { - _template = value; - RenderingEngine = RenderingEngine.Unknown; // reset - - if (_template != null) - RenderingEngine = _engine.FindTemplateRenderingEngine(_template.Alias); - } - } - - /// - /// Gets the alias of the template to use to display the requested content. - /// - public string TemplateAlias - { - get - { - return _template == null ? null : _template.Alias; - } - } - - /// - /// Tries to set the template to use to display the requested content. - /// - /// The alias of the template. - /// A value indicating whether a valid template with the specified alias was found. - /// - /// Successfully setting the template does refresh RenderingEngine. - /// If setting the template fails, then the previous template (if any) remains in place. - /// - public bool TrySetTemplate(string alias) - { - EnsureWriteable(); - - if (string.IsNullOrWhiteSpace(alias)) - { - TemplateModel = null; - return true; - } - - // NOTE - can we stil get it with whitespaces in it due to old legacy bugs? - alias = alias.Replace(" ", ""); - - var model = ApplicationContext.Current.Services.FileService.GetTemplate(alias); - if (model == null) - return false; - - TemplateModel = model; - return true; - } - - /// - /// Sets the template to use to display the requested content. - /// - /// The template. - /// Setting the template does refresh RenderingEngine. - public void SetTemplate(ITemplate template) - { - EnsureWriteable(); - TemplateModel = template; - } - - /// - /// Resets the template. - /// - /// The RenderingEngine becomes unknown. - public void ResetTemplate() - { - EnsureWriteable(); - TemplateModel = null; - } - - /// - /// Gets a value indicating whether the content request has a template. - /// - public bool HasTemplate - { - get { return _template != null; } - } - - #endregion - - #region Domain and Culture - - [Obsolete("Do not use this property, use the non-legacy UmbracoDomain property instead")] - public Domain Domain - { - get { return new Domain(UmbracoDomain); } - } - - //TODO: Should we publicize the setter now that we are using a non-legacy entity?? - /// - /// Gets or sets the content request's domain. - /// - public IDomain UmbracoDomain { get; internal set; } - - /// - /// Gets or sets the content request's domain Uri. - /// - /// The Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". - public Uri DomainUri { get; internal set; } - - /// - /// Gets a value indicating whether the content request has a domain. - /// - public bool HasDomain - { - get { return UmbracoDomain != null; } - } - - private CultureInfo _culture; - - /// - /// Gets or sets the content request's culture. - /// - public CultureInfo Culture - { - get { return _culture; } - set - { - EnsureWriteable(); - _culture = value; - } - } - - // note: do we want to have an ordered list of alternate cultures, - // to allow for fallbacks when doing dictionnary lookup and such? - - #endregion - - #region Rendering - - /// - /// Gets or sets whether the rendering engine is MVC or WebForms. - /// - public RenderingEngine RenderingEngine { get; internal set; } - - #endregion - - /// - /// Gets or sets the current RoutingContext. - /// - public RoutingContext RoutingContext { get; private set; } - - /// - /// Returns the current members roles if a member is logged in - /// - /// - /// - /// - /// This ensures that the callback is only executed once in case this method is accessed a few times - /// - public IEnumerable GetRolesForLogin(string username) - { - string[] roles; - if (_rolesForLogin.TryGetValue(username, out roles)) - return roles; - - roles = _getRolesForLoginCallback(username).ToArray(); - _rolesForLogin[username] = roles; - return roles; - } - - private readonly IDictionary _rolesForLogin = new Dictionary(); - private readonly Func> _getRolesForLoginCallback; - - /// - /// The "umbraco page" object. - /// - private page _umbracoPage; - - /// - /// Gets or sets the "umbraco page" object. - /// - /// - /// This value is only used for legacy/webforms code. - /// - internal page UmbracoPage - { - get - { - if (_umbracoPage == null) - throw new InvalidOperationException("The UmbracoPage object has not been initialized yet."); - - return _umbracoPage; - } - set { _umbracoPage = value; } - } - - #region Status - - /// - /// Gets or sets a value indicating whether the requested content could not be found. - /// - /// This is set in the PublishedContentRequestBuilder. - public bool Is404 { get; internal set; } - - /// - /// Indicates that the requested content could not be found. - /// - /// This is for public access, in custom content finders or Prepared event handlers, - /// where we want to allow developers to indicate a request is 404 but not to cancel it. - public void SetIs404() - { - EnsureWriteable(); - Is404 = true; - } - - /// - /// Gets a value indicating whether the content request triggers a redirect (permanent or not). - /// - public bool IsRedirect { get { return string.IsNullOrWhiteSpace(RedirectUrl) == false; } } - - /// - /// Gets or sets a value indicating whether the redirect is permanent. - /// - public bool IsRedirectPermanent { get; private set; } - - /// - /// Gets or sets the url to redirect to, when the content request triggers a redirect. - /// - public string RedirectUrl { get; private set; } - - /// - /// Indicates that the content request should trigger a redirect (302). - /// - /// The url to redirect to. - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - public void SetRedirect(string url) - { - EnsureWriteable(); - RedirectUrl = url; - IsRedirectPermanent = false; - } - - /// - /// Indicates that the content request should trigger a permanent redirect (301). - /// - /// The url to redirect to. - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - public void SetRedirectPermanent(string url) - { - EnsureWriteable(); - RedirectUrl = url; - IsRedirectPermanent = true; - } - - /// - /// Indicates that the content requet should trigger a redirect, with a specified status code. - /// - /// The url to redirect to. - /// The status code (300-308). - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - public void SetRedirect(string url, int status) - { - EnsureWriteable(); - - if (status < 300 || status > 308) - throw new ArgumentOutOfRangeException("status", "Valid redirection status codes 300-308."); - - RedirectUrl = url; - IsRedirectPermanent = (status == 301 || status == 308); - if (status != 301 && status != 302) // default redirect statuses - ResponseStatusCode = status; - } - - /// - /// Gets or sets the content request http response status code. - /// - /// Does not actually set the http response status code, only registers that the response - /// should use the specified code. The code will or will not be used, in due time. - public int ResponseStatusCode { get; private set; } - - /// - /// Gets or sets the content request http response status description. - /// - /// Does not actually set the http response status description, only registers that the response - /// should use the specified description. The description will or will not be used, in due time. - public string ResponseStatusDescription { get; private set; } - - /// - /// Sets the http response status code, along with an optional associated description. - /// - /// The http status code. - /// The description. - /// Does not actually set the http response status code and description, only registers that - /// the response should use the specified code and description. The code and description will or will - /// not be used, in due time. - public void SetResponseStatus(int code, string description = null) - { - EnsureWriteable(); - - // .Status is deprecated - // .SubStatusCode is IIS 7+ internal, ignore - ResponseStatusCode = code; - ResponseStatusDescription = description; - } - - #endregion - - /// - /// Gets or sets the System.Web.HttpCacheability - /// - // Note: we used to set a default value here but that would then be the default - // for ALL requests, we shouldn't overwrite it though if people are using [OutputCache] for example - // see: https://our.umbraco.org/forum/using-umbraco-and-getting-started/79715-output-cache-in-umbraco-752 - internal HttpCacheability Cacheability { get; set; } - - /// - /// Gets or sets a list of Extensions to append to the Response.Cache object - /// - private List _cacheExtensions = new List(); - internal List CacheExtensions - { - get { return _cacheExtensions; } - set { _cacheExtensions = value; } - } - - /// - /// Gets or sets a dictionary of Headers to append to the Response object - /// - private Dictionary _headers = new Dictionary(); - internal Dictionary Headers - { - get { return _headers; } - set { _headers = value; } - } - - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs deleted file mode 100644 index afbfe1c4f6..0000000000 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ /dev/null @@ -1,768 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Globalization; -using System.IO; -using System.Web.Security; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Security; - -using umbraco; -using umbraco.cms.businesslogic.web; -using umbraco.cms.businesslogic.language; -using umbraco.cms.businesslogic.member; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Web.Security; -using RenderingEngine = Umbraco.Core.RenderingEngine; - -namespace Umbraco.Web.Routing -{ - internal class PublishedContentRequestEngine - { - private readonly PublishedContentRequest _pcr; - private readonly RoutingContext _routingContext; - private readonly IWebRoutingSection _webRoutingSection; - - /// - /// Initializes a new instance of the class with a content request. - /// - /// - /// The content request. - public PublishedContentRequestEngine( - IWebRoutingSection webRoutingSection, - PublishedContentRequest pcr) - { - if (pcr == null) throw new ArgumentException("pcr is null."); - if (webRoutingSection == null) throw new ArgumentNullException("webRoutingSection"); - - _pcr = pcr; - _webRoutingSection = webRoutingSection; - - _routingContext = pcr.RoutingContext; - if (_routingContext == null) throw new ArgumentException("pcr.RoutingContext is null."); - - var umbracoContext = _routingContext.UmbracoContext; - if (umbracoContext == null) throw new ArgumentException("pcr.RoutingContext.UmbracoContext is null."); - if (umbracoContext.RoutingContext != _routingContext) throw new ArgumentException("RoutingContext confusion."); - // no! not set yet. - //if (umbracoContext.PublishedContentRequest != _pcr) throw new ArgumentException("PublishedContentRequest confusion."); - } - - protected ProfilingLogger ProfilingLogger - { - get { return _routingContext.UmbracoContext.Application.ProfilingLogger; } - } - - protected ServiceContext Services - { - get { return _routingContext.UmbracoContext.Application.Services; } - - } - - #region Public - - /// - /// Tries to route the request. - /// - internal bool TryRouteRequest() - { - // disabled - is it going to change the routing? - //_pcr.OnPreparing(); - - FindDomain(); - if (_pcr.IsRedirect) return false; - if (_pcr.HasPublishedContent) return true; - FindPublishedContent(); - if (_pcr.IsRedirect) return false; - - // don't handle anything - we just want to ensure that we find the content - //HandlePublishedContent(); - //FindTemplate(); - //FollowExternalRedirect(); - //HandleWildcardDomains(); - - // disabled - we just want to ensure that we find the content - //_pcr.OnPrepared(); - - return _pcr.HasPublishedContent; - } - - /// - /// Prepares the request. - /// - /// - /// Returns false if the request was not successfully prepared - /// - public bool PrepareRequest() - { - // note - at that point the original legacy module did something do handle IIS custom 404 errors - // ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support - // "directory urls" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain - // to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk. - // - // to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors - // so that they point to a non-existing page eg /redirect-404.aspx - // TODO: SD: We need more information on this for when we release 4.10.0 as I'm not sure what this means. - - // trigger the Preparing event - at that point anything can still be changed - // the idea is that it is possible to change the uri - // - _pcr.OnPreparing(); - - //find domain - FindDomain(); - - // if request has been flagged to redirect then return - // whoever called us is in charge of actually redirecting - if (_pcr.IsRedirect) - { - return false; - } - - // set the culture on the thread - once, so it's set when running document lookups - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture; - - //find the published content if it's not assigned. This could be manually assigned with a custom route handler, or - // with something like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. Those in turn call this method - // to setup the rest of the pipeline but we don't want to run the finders since there's one assigned. - if (_pcr.PublishedContent == null) - { - // find the document & template - FindPublishedContentAndTemplate(); - } - - // handle wildcard domains - HandleWildcardDomains(); - - // set the culture on the thread -- again, 'cos it might have changed due to a finder or wildcard domain - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture; - - // trigger the Prepared event - at that point it is still possible to change about anything - // even though the request might be flagged for redirection - we'll redirect _after_ the event - // - // also, OnPrepared() will make the PublishedContentRequest readonly, so nothing can change - // - _pcr.OnPrepared(); - - // we don't take care of anything so if the content has changed, it's up to the user - // to find out the appropriate template - - //complete the PCR and assign the remaining values - return ConfigureRequest(); - } - - /// - /// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method - /// finalizes the PCR with the values assigned. - /// - /// - /// Returns false if the request was not successfully configured - /// - /// - /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values - /// but need to finalize it themselves. - /// - public bool ConfigureRequest() - { - if (_pcr.HasPublishedContent == false) - { - return false; - } - - // set the culture on the thread -- again, 'cos it might have changed in the event handler - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture; - - // if request has been flagged to redirect, or has no content to display, - // then return - whoever called us is in charge of actually redirecting - if (_pcr.IsRedirect || _pcr.HasPublishedContent == false) - { - return false; - } - - // we may be 404 _and_ have a content - - // can't go beyond that point without a PublishedContent to render - // it's ok not to have a template, in order to give MVC a chance to hijack routes - - // note - the page() ctor below will cause the "page" to get the value of all its - // "elements" ie of all the IPublishedContent property. If we use the object value, - // that will trigger macro execution - which can't happen because macro execution - // requires that _pcr.UmbracoPage is already initialized = catch-22. The "legacy" - // pipeline did _not_ evaluate the macros, ie it is using the data value, and we - // have to keep doing it because of that catch-22. - - // assign the legacy page back to the docrequest - // handlers like default.aspx will want it and most macros currently need it - _pcr.UmbracoPage = new page(_pcr); - - // used by many legacy objects - _routingContext.UmbracoContext.HttpContext.Items["pageID"] = _pcr.PublishedContent.Id; - _routingContext.UmbracoContext.HttpContext.Items["pageElements"] = _pcr.UmbracoPage.Elements; - - return true; - } - - /// - /// Updates the request when there is no template to render the content. - /// - /// This is called from Mvc when there's a document to render but no template. - public void UpdateRequestOnMissingTemplate() - { - // clear content - var content = _pcr.PublishedContent; - _pcr.PublishedContent = null; - - HandlePublishedContent(); // will go 404 - FindTemplate(); - - // if request has been flagged to redirect then return - // whoever called us is in charge of redirecting - if (_pcr.IsRedirect) - return; - - if (_pcr.HasPublishedContent == false) - { - // means the engine could not find a proper document to handle 404 - // restore the saved content so we know it exists - _pcr.PublishedContent = content; - return; - } - - if (_pcr.HasTemplate == false) - { - // means we may have a document, but we have no template - // at that point there isn't much we can do and there is no point returning - // to Mvc since Mvc can't do much either - return; - } - - // see note in PrepareRequest() - - // assign the legacy page back to the docrequest - // handlers like default.aspx will want it and most macros currently need it - _pcr.UmbracoPage = new page(_pcr); - - // these two are used by many legacy objects - _routingContext.UmbracoContext.HttpContext.Items["pageID"] = _pcr.PublishedContent.Id; - _routingContext.UmbracoContext.HttpContext.Items["pageElements"] = _pcr.UmbracoPage.Elements; - } - - #endregion - - #region Domain - - /// - /// Finds the site root (if any) matching the http request, and updates the PublishedContentRequest accordingly. - /// - /// A value indicating whether a domain was found. - internal bool FindDomain() - { - const string tracePrefix = "FindDomain: "; - - // note - we are not handling schemes nor ports here. - - ProfilingLogger.Logger.Debug("{0}Uri=\"{1}\"", () => tracePrefix, () => _pcr.Uri); - - // try to find a domain matching the current request - var domainAndUri = DomainHelper.DomainForUri(Services.DomainService.GetAll(false), _pcr.Uri); - - // handle domain - if (domainAndUri != null && domainAndUri.UmbracoDomain.LanguageIsoCode.IsNullOrWhiteSpace() == false) - { - // matching an existing domain - ProfilingLogger.Logger.Debug("{0}Matches domain=\"{1}\", rootId={2}, culture=\"{3}\"", - () => tracePrefix, - () => domainAndUri.UmbracoDomain.DomainName, - () => domainAndUri.UmbracoDomain.RootContentId, - () => domainAndUri.UmbracoDomain.LanguageIsoCode); - - _pcr.UmbracoDomain = domainAndUri.UmbracoDomain; - _pcr.DomainUri = domainAndUri.Uri; - _pcr.Culture = new CultureInfo(domainAndUri.UmbracoDomain.LanguageIsoCode); - - // canonical? not implemented at the moment - // if (...) - // { - // _pcr.RedirectUrl = "..."; - // return true; - // } - } - else - { - // not matching any existing domain - ProfilingLogger.Logger.Debug("{0}Matches no domain", () => tracePrefix); - - var defaultLanguage = Services.LocalizationService.GetAllLanguages().FirstOrDefault(); - _pcr.Culture = defaultLanguage == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultLanguage.IsoCode); - } - - ProfilingLogger.Logger.Debug("{0}Culture=\"{1}\"", () => tracePrefix, () => _pcr.Culture.Name); - - return _pcr.UmbracoDomain != null; - } - - /// - /// Looks for wildcard domains in the path and updates Culture accordingly. - /// - internal void HandleWildcardDomains() - { - const string tracePrefix = "HandleWildcardDomains: "; - - if (_pcr.HasPublishedContent == false) - return; - - var nodePath = _pcr.PublishedContent.Path; - ProfilingLogger.Logger.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => nodePath); - var rootNodeId = _pcr.HasDomain ? _pcr.UmbracoDomain.RootContentId : (int?)null; - var domain = DomainHelper.FindWildcardDomainInPath(Services.DomainService.GetAll(true), nodePath, rootNodeId); - - if (domain != null && domain.LanguageIsoCode.IsNullOrWhiteSpace() == false) - { - _pcr.Culture = new CultureInfo(domain.LanguageIsoCode); - ProfilingLogger.Logger.Debug("{0}Got domain on node {1}, set culture to \"{2}\".", () => tracePrefix, - () => domain.RootContentId, () => _pcr.Culture.Name); - } - else - { - ProfilingLogger.Logger.Debug("{0}No match.", () => tracePrefix); - } - } - - #endregion - - #region Rendering engine - - /// - /// Finds the rendering engine to use to render a template specified by its alias. - /// - /// The alias of the template. - /// The rendering engine, or Unknown if the template was not found. - internal RenderingEngine FindTemplateRenderingEngine(string alias) - { - if (string.IsNullOrWhiteSpace(alias)) - return RenderingEngine.Unknown; - - alias = alias.Replace('\\', '/'); // forward slashes only - - // NOTE: we could start with what's the current default? - - if (FindTemplateRenderingEngineInDirectory(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.MvcViews)), - alias, new[] { ".cshtml", ".vbhtml" })) - return RenderingEngine.Mvc; - - if (FindTemplateRenderingEngineInDirectory(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Masterpages)), - alias, new[] { ".master" })) - return RenderingEngine.WebForms; - - return RenderingEngine.Unknown; - } - - internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions) - { - if (directory == null || directory.Exists == false) - return false; - - var pos = alias.IndexOf('/'); - if (pos > 0) - { - // recurse - var subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault(); - alias = alias.Substring(pos + 1); - return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions); - } - - // look here - return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e))); - } - - #endregion - - #region Document and template - - /// - /// Finds the Umbraco document (if any) matching the request, and updates the PublishedContentRequest accordingly. - /// - /// A value indicating whether a document and template were found. - private void FindPublishedContentAndTemplate() - { - const string tracePrefix = "FindPublishedContentAndTemplate: "; - ProfilingLogger.Logger.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => _pcr.Uri.AbsolutePath); - - // run the document finders - FindPublishedContent(); - - // if request has been flagged to redirect then return - // whoever called us is in charge of actually redirecting - // -- do not process anything any further -- - if (_pcr.IsRedirect) - return; - - // not handling umbracoRedirect here but after LookupDocument2 - // so internal redirect, 404, etc has precedence over redirect - - // handle not-found, redirects, access... - HandlePublishedContent(); - - // find a template - FindTemplate(); - - // handle umbracoRedirect - FollowExternalRedirect(); - } - - /// - /// Tries to find the document matching the request, by running the IPublishedContentFinder instances. - /// - /// There is no finder collection. - internal void FindPublishedContent() - { - const string tracePrefix = "FindPublishedContent: "; - - // look for the document - // the first successful finder, if any, will set this.PublishedContent, and may also set this.Template - // some finders may implement caching - - using (ProfilingLogger.DebugDuration( - string.Format("{0}Begin finders", tracePrefix), - string.Format("{0}End finders, {1}", tracePrefix, (_pcr.HasPublishedContent ? "a document was found" : "no document was found")))) - { - if (_routingContext.PublishedContentFinders == null) - throw new InvalidOperationException("There is no finder collection."); - - //iterate but return on first one that finds it - var found = _routingContext.PublishedContentFinders.Any(finder => finder.TryFindContent(_pcr)); - } - - // indicate that the published content (if any) we have at the moment is the - // one that was found by the standard finders before anything else took place. - _pcr.SetIsInitialPublishedContent(); - } - - /// - /// Handles the published content (if any). - /// - /// - /// Handles "not found", internal redirects, access validation... - /// things that must be handled in one place because they can create loops - /// - private void HandlePublishedContent() - { - const string tracePrefix = "HandlePublishedContent: "; - - // because these might loop, we have to have some sort of infinite loop detection - int i = 0, j = 0; - const int maxLoop = 8; - do - { - ProfilingLogger.Logger.Debug("{0}{1}", () => tracePrefix, () => (i == 0 ? "Begin" : "Loop")); - - // handle not found - if (_pcr.HasPublishedContent == false) - { - _pcr.Is404 = true; - ProfilingLogger.Logger.Debug("{0}No document, try last chance lookup", () => tracePrefix); - - // if it fails then give up, there isn't much more that we can do - var lastChance = _routingContext.PublishedContentLastChanceFinder; - if (lastChance == null || lastChance.TryFindContent(_pcr) == false) - { - ProfilingLogger.Logger.Debug("{0}Failed to find a document, give up", () => tracePrefix); - break; - } - - ProfilingLogger.Logger.Debug("{0}Found a document", () => tracePrefix); - } - - // follow internal redirects as long as it's not running out of control ie infinite loop of some sort - j = 0; - while (FollowInternalRedirects() && j++ < maxLoop) - { } - if (j == maxLoop) // we're running out of control - break; - - // ensure access - if (_pcr.HasPublishedContent) - EnsurePublishedContentAccess(); - - // loop while we don't have page, ie the redirect or access - // got us to nowhere and now we need to run the notFoundLookup again - // as long as it's not running out of control ie infinite loop of some sort - - } while (_pcr.HasPublishedContent == false && i++ < maxLoop); - - if (i == maxLoop || j == maxLoop) - { - ProfilingLogger.Logger.Debug("{0}Looks like we're running into an infinite loop, abort", () => tracePrefix); - _pcr.PublishedContent = null; - } - - ProfilingLogger.Logger.Debug("{0}End", () => tracePrefix); - } - - /// - /// Follows internal redirections through the umbracoInternalRedirectId document property. - /// - /// A value indicating whether redirection took place and led to a new published document. - /// - /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. - /// As per legacy, if the redirect does not work, we just ignore it. - /// - private bool FollowInternalRedirects() - { - const string tracePrefix = "FollowInternalRedirects: "; - - if (_pcr.PublishedContent == null) - throw new InvalidOperationException("There is no PublishedContent."); - - // don't try to find a redirect if the property doesn't exist - if (_pcr.PublishedContent.HasProperty(Constants.Conventions.Content.InternalRedirectId) == false) - return false; - - var redirect = false; - IPublishedContent internalRedirectNode = null; - var internalRedirectId = - _pcr.PublishedContent.GetPropertyValue(Constants.Conventions.Content.InternalRedirectId, -1); - var valueValid = false; - if (internalRedirectId > 0) - { - valueValid = true; - // Try and get the redirect node from a legacy integer ID - internalRedirectNode = _routingContext.UmbracoContext.ContentCache.GetById(internalRedirectId); - } - else - { - var udiInternalRedirectId = - _pcr.PublishedContent.GetPropertyValue(Constants.Conventions.Content.InternalRedirectId); - if (udiInternalRedirectId != null) - { - valueValid = true; - // Try and get the redirect node from a UDI Guid - internalRedirectNode = - _routingContext.UmbracoContext.ContentCache.GetById(udiInternalRedirectId.Guid); - } - } - - if (valueValid == false) - { - // bad redirect - log and display the current page (legacy behavior) - ProfilingLogger - .Logger.Debug( - "{0}Failed to redirect, value of '{1}' is not an int nor a GuidUdi", - () => tracePrefix, () => Constants.Conventions.Content.InternalRedirectId); - } - - if (internalRedirectNode == null) - { - ProfilingLogger.Logger.Debug( - "{0}Failed to redirect, value of '{1}' does not lead to a published document", () => tracePrefix, - () => Constants.Conventions.Content.InternalRedirectId); - } - else if (internalRedirectNode.Id == _pcr.PublishedContent.Id) - { - // redirect to self - ProfilingLogger.Logger.Debug("{0}Redirecting to self, ignore", - () => tracePrefix); - } - else - { - // Redirect to another page - _pcr.SetInternalRedirectPublishedContent(internalRedirectNode); - redirect = true; - ProfilingLogger.Logger.Debug("{0}Redirecting to id={1}", () => tracePrefix, - () => internalRedirectNode.Id); - } - - return redirect; - } - - /// - /// Ensures that access to current node is permitted. - /// - /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. - private void EnsurePublishedContentAccess() - { - const string tracePrefix = "EnsurePublishedContentAccess: "; - - if (_pcr.PublishedContent == null) - throw new InvalidOperationException("There is no PublishedContent."); - - var path = _pcr.PublishedContent.Path; - - var publicAccessAttempt = Services.PublicAccessService.IsProtected(path); - - if (publicAccessAttempt) - { - ProfilingLogger.Logger.Debug("{0}Page is protected, check for access", () => tracePrefix); - - var membershipHelper = new MembershipHelper(_routingContext.UmbracoContext); - - if (membershipHelper.IsLoggedIn() == false) - { - ProfilingLogger.Logger.Debug("{0}Not logged in, redirect to login page", () => tracePrefix); - - var loginPageId = publicAccessAttempt.Result.LoginNodeId; - - if (loginPageId != _pcr.PublishedContent.Id) - _pcr.PublishedContent = _routingContext.UmbracoContext.ContentCache.GetById(loginPageId); - } - else if (Services.PublicAccessService.HasAccess(_pcr.PublishedContent.Id, Services.ContentService, _pcr.GetRolesForLogin(membershipHelper.CurrentUserName)) == false) - { - ProfilingLogger.Logger.Debug("{0}Current member has not access, redirect to error page", () => tracePrefix); - var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; - if (errorPageId != _pcr.PublishedContent.Id) - _pcr.PublishedContent = _routingContext.UmbracoContext.ContentCache.GetById(errorPageId); - } - else - { - ProfilingLogger.Logger.Debug("{0}Current member has access", () => tracePrefix); - } - } - else - { - ProfilingLogger.Logger.Debug("{0}Page is not protected", () => tracePrefix); - } - } - - /// - /// Finds a template for the current node, if any. - /// - private void FindTemplate() - { - // NOTE: at the moment there is only 1 way to find a template, and then ppl must - // use the Prepared event to change the template if they wish. Should we also - // implement an ITemplateFinder logic? - - const string tracePrefix = "FindTemplate: "; - - if (_pcr.PublishedContent == null) - { - _pcr.TemplateModel = null; - return; - } - - // read the alternate template alias, from querystring, form, cookie or server vars, - // only if the published content is the initial once, else the alternate template - // does not apply - // + optionnally, apply the alternate template on internal redirects - var useAltTemplate = _webRoutingSection.DisableAlternativeTemplates == false - && (_pcr.IsInitialPublishedContent - || (_webRoutingSection.InternalRedirectPreservesTemplate && _pcr.IsInternalRedirectPublishedContent)); - string altTemplate = useAltTemplate - ? _routingContext.UmbracoContext.HttpContext.Request[Constants.Conventions.Url.AltTemplate] - : null; - - if (string.IsNullOrWhiteSpace(altTemplate)) - { - // we don't have an alternate template specified. use the current one if there's one already, - // which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...), - // else lookup the template id on the document then lookup the template with that id. - - if (_pcr.HasTemplate) - { - ProfilingLogger.Logger.Debug("{0}Has a template already, and no alternate template.", () => tracePrefix); - return; - } - - // TODO: When we remove the need for a database for templates, then this id should be irrelavent, - // not sure how were going to do this nicely. - - var templateId = _pcr.PublishedContent.TemplateId; - - if (templateId > 0) - { - ProfilingLogger.Logger.Debug("{0}Look for template id={1}", () => tracePrefix, () => templateId); - var template = ApplicationContext.Current.Services.FileService.GetTemplate(templateId); - if (template == null) - throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render"); - _pcr.TemplateModel = template; - ProfilingLogger.Logger.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias); - } - else - { - ProfilingLogger.Logger.Debug("{0}No specified template.", () => tracePrefix); - } - } - else - { - // we have an alternate template specified. lookup the template with that alias - // this means the we override any template that a content lookup might have set - // so /path/to/page/template1?altTemplate=template2 will use template2 - - // ignore if the alias does not match - just trace - - if (_pcr.HasTemplate) - ProfilingLogger.Logger.Debug("{0}Has a template already, but also an alternate template.", () => tracePrefix); - ProfilingLogger.Logger.Debug("{0}Look for alternate template alias=\"{1}\"", () => tracePrefix, () => altTemplate); - - var template = ApplicationContext.Current.Services.FileService.GetTemplate(altTemplate); - if (template != null) - { - _pcr.TemplateModel = template; - ProfilingLogger.Logger.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias); - } - else - { - ProfilingLogger.Logger.Debug("{0}The template with alias=\"{1}\" does not exist, ignoring.", () => tracePrefix, () => altTemplate); - } - } - - if (_pcr.HasTemplate == false) - { - ProfilingLogger.Logger.Debug("{0}No template was found.", () => tracePrefix); - - // initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true - // then reset _pcr.Document to null to force a 404. - // - // but: because we want to let MVC hijack routes even though no template is defined, we decide that - // a missing template is OK but the request will then be forwarded to MVC, which will need to take - // care of everything. - // - // so, don't set _pcr.Document to null here - } - else - { - ProfilingLogger.Logger.Debug("{0}Running with template id={1} alias=\"{2}\"", () => tracePrefix, () => _pcr.TemplateModel.Id, () => _pcr.TemplateModel.Alias); - } - } - - /// - /// Follows external redirection through umbracoRedirect document property. - /// - /// As per legacy, if the redirect does not work, we just ignore it. - private void FollowExternalRedirect() - { - if (_pcr.HasPublishedContent == false) return; - - // don't try to find a redirect if the property doesn't exist - if (_pcr.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false) - return; - - var redirectId = _pcr.PublishedContent.GetPropertyValue(Constants.Conventions.Content.Redirect, -1); - - var redirectUrl = "#"; - if (redirectId > 0) - { - redirectUrl = _routingContext.UrlProvider.GetUrl(redirectId); - } - else - { - // might be a UDI instead of an int Id - var redirectUdi = _pcr.PublishedContent.GetPropertyValue(Constants.Conventions.Content.Redirect); - if (redirectUdi != null) - redirectUrl = _routingContext.UrlProvider.GetUrl(redirectUdi.Guid); - } - if (redirectUrl != "#") - { - _pcr.SetRedirect(redirectUrl); - } - } - #endregion - } -} diff --git a/src/Umbraco.Web/Routing/PublishedRequest.cs b/src/Umbraco.Web/Routing/PublishedRequest.cs new file mode 100644 index 0000000000..492837104d --- /dev/null +++ b/src/Umbraco.Web/Routing/PublishedRequest.cs @@ -0,0 +1,517 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Web; +using umbraco; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using RenderingEngine = Umbraco.Core.RenderingEngine; + +namespace Umbraco.Web.Routing +{ + /// + /// Represents a request for one specified Umbraco IPublishedContent to be rendered + /// by one specified template, using one specified Culture and RenderingEngine. + /// + public class PublishedRequest + { + private readonly PublishedRouter _publishedRouter; + + private bool _readonly; // after prepared + private bool _readonlyUri; // after preparing + private Uri _uri; // clean uri, no virtual dir, no trailing slash nor .aspx, nothing + private ITemplate _template; // template model if any else null + private bool _is404; + private DomainAndUri _domain; + private CultureInfo _culture; + private IPublishedContent _publishedContent; + private IPublishedContent _initialPublishedContent; // found by finders before 404, redirects, etc + private page _umbracoPage; // legacy + + /// + /// Initializes a new instance of the class. + /// + /// The published router. + /// The Umbraco context. + /// The request Uri. + internal PublishedRequest(PublishedRouter publishedRouter, UmbracoContext umbracoContext, Uri uri = null) + { + UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); + _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter)); + Uri = uri ?? umbracoContext.CleanedUmbracoUrl; + RenderingEngine = RenderingEngine.Unknown; + } + + /// + /// Gets the UmbracoContext. + /// + public UmbracoContext UmbracoContext { get; } + + /// + /// Gets or sets the cleaned up Uri used for routing. + /// + /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. + public Uri Uri + { + get => _uri; + set + { + if (_readonlyUri) + throw new InvalidOperationException("Cannot modify Uri after Preparing has triggered."); + _uri = value; + } + } + + // utility for ensuring it is ok to set some properties + private void EnsureWriteable() + { + if (_readonly) + throw new InvalidOperationException("Cannot modify a PublishedContentRequest once it is read-only."); + } + + /// + /// Prepares the request. + /// + public void Prepare() + { + _publishedRouter.PrepareRequest(this); + } + + #region Events + + /// + /// Triggers before the published content request is prepared. + /// + /// When the event triggers, no preparation has been done. It is still possible to + /// modify the request's Uri property, for example to restore its original, public-facing value + /// that might have been modified by an in-between equipement such as a load-balancer. + public static event EventHandler Preparing; + + /// + /// Triggers once the published content request has been prepared, but before it is processed. + /// + /// When the event triggers, preparation is done ie domain, culture, document, template, + /// rendering engine, etc. have been setup. It is then possible to change anything, before + /// the request is actually processed and rendered by Umbraco. + public static event EventHandler Prepared; + + /// + /// Triggers the Preparing event. + /// + internal void OnPreparing() + { + Preparing?.Invoke(this, EventArgs.Empty); + _readonlyUri = true; + } + + /// + /// Triggers the Prepared event. + /// + internal void OnPrepared() + { + Prepared?.Invoke(this, EventArgs.Empty); + + if (HasPublishedContent == false) + Is404 = true; // safety + + _readonly = true; + } + + #endregion + + #region PublishedContent + + /// + /// Gets or sets the requested content. + /// + /// Setting the requested content clears Template. + public IPublishedContent PublishedContent + { + get { return _publishedContent; } + set + { + EnsureWriteable(); + _publishedContent = value; + IsInternalRedirectPublishedContent = false; + TemplateModel = null; + } + } + + /// + /// Sets the requested content, following an internal redirect. + /// + /// The requested content. + /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will + /// preserve or reset the template, if any. + public void SetInternalRedirectPublishedContent(IPublishedContent content) + { + if (content == null) throw new ArgumentNullException(nameof(content)); + EnsureWriteable(); + + // unless a template has been set already by the finder, + // template should be null at that point. + + // IsInternalRedirect if IsInitial, or already IsInternalRedirect + var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; + + // redirecting to self + if (content.Id == PublishedContent.Id) // neither can be null + { + // no need to set PublishedContent, we're done + IsInternalRedirectPublishedContent = isInternalRedirect; + return; + } + + // else + + // save + var template = _template; + var renderingEngine = RenderingEngine; + + // set published content - this resets the template, and sets IsInternalRedirect to false + PublishedContent = content; + IsInternalRedirectPublishedContent = isInternalRedirect; + + // must restore the template if it's an internal redirect & the config option is set + if (isInternalRedirect && UmbracoConfig.For.UmbracoSettings().WebRouting.InternalRedirectPreservesTemplate) + { + // restore + _template = template; + RenderingEngine = renderingEngine; + } + } + + /// + /// Gets the initial requested content. + /// + /// The initial requested content is the content that was found by the finders, + /// before anything such as 404, redirect... took place. + public IPublishedContent InitialPublishedContent => _initialPublishedContent; + + /// + /// Gets value indicating whether the current published content is the initial one. + /// + public bool IsInitialPublishedContent => _initialPublishedContent != null && _initialPublishedContent == _publishedContent; + + /// + /// Indicates that the current PublishedContent is the initial one. + /// + public void SetIsInitialPublishedContent() + { + EnsureWriteable(); + + // note: it can very well be null if the initial content was not found + _initialPublishedContent = _publishedContent; + IsInternalRedirectPublishedContent = false; + } + + /// + /// Gets or sets a value indicating whether the current published content has been obtained + /// from the initial published content following internal redirections exclusively. + /// + /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to + /// apply the internal redirect or not, when content is not the initial content. + public bool IsInternalRedirectPublishedContent { get; private set; } + + /// + /// Gets a value indicating whether the content request has a content. + /// + public bool HasPublishedContent => PublishedContent != null; + + #endregion + + #region Template + + /// + /// Gets or sets the template model to use to display the requested content. + /// + internal ITemplate TemplateModel + { + get + { + return _template; + } + + set + { + _template = value; + RenderingEngine = RenderingEngine.Unknown; // reset + if (_template != null) + RenderingEngine = _publishedRouter.FindTemplateRenderingEngine(_template.Alias); + } + } + + /// + /// Gets the alias of the template to use to display the requested content. + /// + public string TemplateAlias => _template?.Alias; + + /// + /// Tries to set the template to use to display the requested content. + /// + /// The alias of the template. + /// A value indicating whether a valid template with the specified alias was found. + /// + /// Successfully setting the template does refresh RenderingEngine. + /// If setting the template fails, then the previous template (if any) remains in place. + /// + public bool TrySetTemplate(string alias) + { + EnsureWriteable(); + + if (string.IsNullOrWhiteSpace(alias)) + { + TemplateModel = null; + return true; + } + + // NOTE - can we stil get it with whitespaces in it due to old legacy bugs? + alias = alias.Replace(" ", ""); + + var model = _publishedRouter.GetTemplate(alias); + if (model == null) + return false; + + TemplateModel = model; + return true; + } + + /// + /// Sets the template to use to display the requested content. + /// + /// The template. + /// Setting the template does refresh RenderingEngine. + public void SetTemplate(ITemplate template) + { + EnsureWriteable(); + TemplateModel = template; + } + + /// + /// Resets the template. + /// + /// The RenderingEngine becomes unknown. + public void ResetTemplate() + { + EnsureWriteable(); + TemplateModel = null; + } + + /// + /// Gets a value indicating whether the content request has a template. + /// + public bool HasTemplate => _template != null; + + internal void UpdateOnMissingTemplate() + { + var __readonly = _readonly; + _readonly = false; + _publishedRouter.UpdateRequestOnMissingTemplate(this); + _readonly = __readonly; + } + + #endregion + + #region Domain and Culture + + /// + /// Gets or sets the content request's domain. + /// + /// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example, + /// the Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". + public DomainAndUri Domain + { + get { return _domain; } + set + { + EnsureWriteable(); + _domain = value; + } + } + + /// + /// Gets a value indicating whether the content request has a domain. + /// + public bool HasDomain => Domain != null; + + /// + /// Gets or sets the content request's culture. + /// + public CultureInfo Culture + { + get { return _culture; } + set + { + EnsureWriteable(); + _culture = value; + } + } + + // note: do we want to have an ordered list of alternate cultures, + // to allow for fallbacks when doing dictionnary lookup and such? + + #endregion + + #region Rendering + + /// + /// Gets or sets whether the rendering engine is MVC or WebForms. + /// + public RenderingEngine RenderingEngine { get; internal set; } + + #endregion + + #region Status + + /// + /// Gets or sets a value indicating whether the requested content could not be found. + /// + /// This is set in the PublishedContentRequestBuilder and can also be used in + /// custom content finders or Prepared event handlers, where we want to allow developers + /// to indicate a request is 404 but not to cancel it. + public bool Is404 + { + get { return _is404; } + set + { + EnsureWriteable(); + _is404 = value; + } + } + + /// + /// Gets a value indicating whether the content request triggers a redirect (permanent or not). + /// + public bool IsRedirect => string.IsNullOrWhiteSpace(RedirectUrl) == false; + + /// + /// Gets or sets a value indicating whether the redirect is permanent. + /// + public bool IsRedirectPermanent { get; private set; } + + /// + /// Gets or sets the url to redirect to, when the content request triggers a redirect. + /// + public string RedirectUrl { get; private set; } + + /// + /// Indicates that the content request should trigger a redirect (302). + /// + /// The url to redirect to. + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + public void SetRedirect(string url) + { + EnsureWriteable(); + RedirectUrl = url; + IsRedirectPermanent = false; + } + + /// + /// Indicates that the content request should trigger a permanent redirect (301). + /// + /// The url to redirect to. + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + public void SetRedirectPermanent(string url) + { + EnsureWriteable(); + RedirectUrl = url; + IsRedirectPermanent = true; + } + + /// + /// Indicates that the content requet should trigger a redirect, with a specified status code. + /// + /// The url to redirect to. + /// The status code (300-308). + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + public void SetRedirect(string url, int status) + { + EnsureWriteable(); + + if (status < 300 || status > 308) + throw new ArgumentOutOfRangeException(nameof(status), "Valid redirection status codes 300-308."); + + RedirectUrl = url; + IsRedirectPermanent = (status == 301 || status == 308); + if (status != 301 && status != 302) // default redirect statuses + ResponseStatusCode = status; + } + + /// + /// Gets or sets the content request http response status code. + /// + /// Does not actually set the http response status code, only registers that the response + /// should use the specified code. The code will or will not be used, in due time. + public int ResponseStatusCode { get; private set; } + + /// + /// Gets or sets the content request http response status description. + /// + /// Does not actually set the http response status description, only registers that the response + /// should use the specified description. The description will or will not be used, in due time. + public string ResponseStatusDescription { get; private set; } + + /// + /// Sets the http response status code, along with an optional associated description. + /// + /// The http status code. + /// The description. + /// Does not actually set the http response status code and description, only registers that + /// the response should use the specified code and description. The code and description will or will + /// not be used, in due time. + public void SetResponseStatus(int code, string description = null) + { + EnsureWriteable(); + + // .Status is deprecated + // .SubStatusCode is IIS 7+ internal, ignore + ResponseStatusCode = code; + ResponseStatusDescription = description; + } + + #endregion + + #region Response Cache + + /// + /// Gets or sets the System.Web.HttpCacheability + /// + // Note: we used to set a default value here but that would then be the default + // for ALL requests, we shouldn't overwrite it though if people are using [OutputCache] for example + // see: https://our.umbraco.org/forum/using-umbraco-and-getting-started/79715-output-cache-in-umbraco-752 + internal HttpCacheability Cacheability { get; set; } + + /// + /// Gets or sets a list of Extensions to append to the Response.Cache object. + /// + internal List CacheExtensions { get; set; } = new List(); + + /// + /// Gets or sets a dictionary of Headers to append to the Response object. + /// + internal Dictionary Headers { get; set; } = new Dictionary(); + + #endregion + + #region Legacy + + // for legacy/webforms code - todo - get rid of it eventually + internal page UmbracoPage + { + get + { + if (_umbracoPage == null) + throw new InvalidOperationException("The UmbracoPage object has not been initialized yet."); + + return _umbracoPage; + } + set { _umbracoPage = value; } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs new file mode 100644 index 0000000000..ac07f1ad32 --- /dev/null +++ b/src/Umbraco.Web/Routing/PublishedRouter.cs @@ -0,0 +1,758 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Globalization; +using System.IO; +using System.Web.Security; +using umbraco; +using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; +using Umbraco.Web.Security; +using RenderingEngine = Umbraco.Core.RenderingEngine; + +namespace Umbraco.Web.Routing +{ + // fixme - make this public + // fixme - making sense to have an interface? + internal class PublishedRouter + { + private readonly IWebRoutingSection _webRoutingSection; + private readonly ContentFinderCollection _contentFinders; + private readonly IContentLastChanceFinder _contentLastChanceFinder; + private readonly ServiceContext _services; + private readonly ProfilingLogger _profilingLogger; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + public PublishedRouter( + IWebRoutingSection webRoutingSection, + ContentFinderCollection contentFinders, + IContentLastChanceFinder contentLastChanceFinder, + ServiceContext services, + ProfilingLogger proflog, + Func> getRolesForLogin = null) + { + _webRoutingSection = webRoutingSection ?? throw new ArgumentNullException(nameof(webRoutingSection)); // fixme usage? + _contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders)); + _contentLastChanceFinder = contentLastChanceFinder ?? throw new ArgumentNullException(nameof(contentLastChanceFinder)); + _services = services ?? throw new ArgumentNullException(nameof(services)); + _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); + _logger = proflog.Logger; + + GetRolesForLogin = getRolesForLogin ?? (s => Roles.Provider.GetRolesForUser(s)); + } + + // fixme + // in 7.7 this is cached in the PublishedContentRequest, which ... makes little sense + // killing it entirely, if we need cache, just implement it properly !! + // this is all soooo weird + public Func> GetRolesForLogin { get; } + + public PublishedRequest CreateRequest(UmbracoContext umbracoContext, Uri uri = null) + { + return new PublishedRequest(this, umbracoContext, uri ?? umbracoContext.CleanedUmbracoUrl); + } + + #region Request + + /// + /// Tries to route the request. + /// + internal bool TryRouteRequest(PublishedRequest request) + { + // disabled - is it going to change the routing? + //_pcr.OnPreparing(); + + FindDomain(request); + if (request.IsRedirect) return false; + if (request.HasPublishedContent) return true; + FindPublishedContent(request); + if (request.IsRedirect) return false; + + // don't handle anything - we just want to ensure that we find the content + //HandlePublishedContent(); + //FindTemplate(); + //FollowExternalRedirect(); + //HandleWildcardDomains(); + + // disabled - we just want to ensure that we find the content + //_pcr.OnPrepared(); + + return request.HasPublishedContent; + } + + /// + /// Prepares the request. + /// + /// + /// Returns false if the request was not successfully prepared + /// + public bool PrepareRequest(PublishedRequest request) + { + // note - at that point the original legacy module did something do handle IIS custom 404 errors + // ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support + // "directory urls" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain + // to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk. + // + // to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors + // so that they point to a non-existing page eg /redirect-404.aspx + // TODO: SD: We need more information on this for when we release 4.10.0 as I'm not sure what this means. + + // trigger the Preparing event - at that point anything can still be changed + // the idea is that it is possible to change the uri + // + request.OnPreparing(); + + //find domain + FindDomain(request); + + // if request has been flagged to redirect then return + // whoever called us is in charge of actually redirecting + if (request.IsRedirect) + { + return false; + } + + // set the culture on the thread - once, so it's set when running document lookups + Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; + + //find the published content if it's not assigned. This could be manually assigned with a custom route handler, or + // with something like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. Those in turn call this method + // to setup the rest of the pipeline but we don't want to run the finders since there's one assigned. + if (request.PublishedContent == null) + { + // find the document & template + FindPublishedContentAndTemplate(request); + } + + // handle wildcard domains + HandleWildcardDomains(request); + + // set the culture on the thread -- again, 'cos it might have changed due to a finder or wildcard domain + Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; + + // trigger the Prepared event - at that point it is still possible to change about anything + // even though the request might be flagged for redirection - we'll redirect _after_ the event + // + // also, OnPrepared() will make the PublishedContentRequest readonly, so nothing can change + // + request.OnPrepared(); + + // we don't take care of anything so if the content has changed, it's up to the user + // to find out the appropriate template + + //complete the PCR and assign the remaining values + return ConfigureRequest(request); + } + + /// + /// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method + /// finalizes the PCR with the values assigned. + /// + /// + /// Returns false if the request was not successfully configured + /// + /// + /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values + /// but need to finalize it themselves. + /// + public bool ConfigureRequest(PublishedRequest frequest) + { + if (frequest.HasPublishedContent == false) + { + return false; + } + + // set the culture on the thread -- again, 'cos it might have changed in the event handler + Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = frequest.Culture; + + // if request has been flagged to redirect, or has no content to display, + // then return - whoever called us is in charge of actually redirecting + if (frequest.IsRedirect || frequest.HasPublishedContent == false) + { + return false; + } + + // we may be 404 _and_ have a content + + // can't go beyond that point without a PublishedContent to render + // it's ok not to have a template, in order to give MVC a chance to hijack routes + + // note - the page() ctor below will cause the "page" to get the value of all its + // "elements" ie of all the IPublishedContent property. If we use the object value, + // that will trigger macro execution - which can't happen because macro execution + // requires that _pcr.UmbracoPage is already initialized = catch-22. The "legacy" + // pipeline did _not_ evaluate the macros, ie it is using the data value, and we + // have to keep doing it because of that catch-22. + + // assign the legacy page back to the request + // handlers like default.aspx will want it and most macros currently need it + frequest.UmbracoPage = new page(frequest); + + // used by many legacy objects + frequest.UmbracoContext.HttpContext.Items["pageID"] = frequest.PublishedContent.Id; + frequest.UmbracoContext.HttpContext.Items["pageElements"] = frequest.UmbracoPage.Elements; + + return true; + } + + /// + /// Updates the request when there is no template to render the content. + /// + /// This is called from Mvc when there's a document to render but no template. + public void UpdateRequestOnMissingTemplate(PublishedRequest request) + { + // clear content + var content = request.PublishedContent; + request.PublishedContent = null; + + HandlePublishedContent(request); // will go 404 + FindTemplate(request); + + // if request has been flagged to redirect then return + // whoever called us is in charge of redirecting + if (request.IsRedirect) + return; + + if (request.HasPublishedContent == false) + { + // means the engine could not find a proper document to handle 404 + // restore the saved content so we know it exists + request.PublishedContent = content; + return; + } + + if (request.HasTemplate == false) + { + // means we may have a document, but we have no template + // at that point there isn't much we can do and there is no point returning + // to Mvc since Mvc can't do much either + return; + } + + // see note in PrepareRequest() + + // assign the legacy page back to the docrequest + // handlers like default.aspx will want it and most macros currently need it + request.UmbracoPage = new page(request); + + // these two are used by many legacy objects + request.UmbracoContext.HttpContext.Items["pageID"] = request.PublishedContent.Id; + request.UmbracoContext.HttpContext.Items["pageElements"] = request.UmbracoPage.Elements; + } + + #endregion + + #region Domain + + /// + /// Finds the site root (if any) matching the http request, and updates the PublishedContentRequest accordingly. + /// + /// A value indicating whether a domain was found. + internal bool FindDomain(PublishedRequest request) + { + const string tracePrefix = "FindDomain: "; + + // note - we are not handling schemes nor ports here. + + _logger.Debug(() => $"{tracePrefix}Uri=\"{request.Uri}\""); + + // try to find a domain matching the current request + var domainAndUri = DomainHelper.DomainForUri(request.UmbracoContext.PublishedShapshot.Domains.GetAll(false), request.Uri); + + // handle domain - always has a contentId and a culture + if (domainAndUri != null) + { + // matching an existing domain + _logger.Debug(() => $"{tracePrefix}Matches domain=\"{domainAndUri.Name}\", rootId={domainAndUri.ContentId}, culture=\"{domainAndUri.Culture}\""); + + request.Domain = domainAndUri; + request.Culture = domainAndUri.Culture; + + // canonical? not implemented at the moment + // if (...) + // { + // _pcr.RedirectUrl = "..."; + // return true; + // } + } + else + { + // not matching any existing domain + _logger.Debug(() => $"{tracePrefix}Matches no domain"); + + var defaultLanguage = _services.LocalizationService.GetAllLanguages().FirstOrDefault(); + request.Culture = defaultLanguage == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultLanguage.IsoCode); + } + + _logger.Debug(() => $"{tracePrefix}Culture=\"{request.Culture.Name}\""); + + return request.Domain != null; + } + + /// + /// Looks for wildcard domains in the path and updates Culture accordingly. + /// + internal void HandleWildcardDomains(PublishedRequest request) + { + const string tracePrefix = "HandleWildcardDomains: "; + + if (request.HasPublishedContent == false) + return; + + var nodePath = request.PublishedContent.Path; + _logger.Debug(() => $"{tracePrefix}Path=\"{nodePath}\""); + var rootNodeId = request.HasDomain ? request.Domain.ContentId : (int?)null; + var domain = DomainHelper.FindWildcardDomainInPath(request.UmbracoContext.PublishedShapshot.Domains.GetAll(true), nodePath, rootNodeId); + + // always has a contentId and a culture + if (domain != null) + { + request.Culture = domain.Culture; + _logger.Debug(() => $"{tracePrefix}Got domain on node {domain.ContentId}, set culture to \"{request.Culture.Name}\"."); + } + else + { + _logger.Debug(() => $"{tracePrefix}No match."); + } + } + + #endregion + + #region Rendering engine + + /// + /// Finds the rendering engine to use to render a template specified by its alias. + /// + /// The alias of the template. + /// The rendering engine, or Unknown if the template was not found. + internal RenderingEngine FindTemplateRenderingEngine(string alias) + { + if (string.IsNullOrWhiteSpace(alias)) + return RenderingEngine.Unknown; + + alias = alias.Replace('\\', '/'); // forward slashes only + + // NOTE: we could start with what's the current default? + + // fixme - bad - we probably should be using the appropriate filesystems! + + if (FindTemplateRenderingEngineInDirectory(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.MvcViews)), + alias, new[] { ".cshtml", ".vbhtml" })) + return RenderingEngine.Mvc; + + if (FindTemplateRenderingEngineInDirectory(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Masterpages)), + alias, new[] { ".master" })) + return RenderingEngine.WebForms; + + return RenderingEngine.Unknown; + } + + internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions) + { + if (directory == null || directory.Exists == false) + return false; + + var pos = alias.IndexOf('/'); + if (pos > 0) + { + // recurse + var subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault(); + alias = alias.Substring(pos + 1); + return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions); + } + + // look here + return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e))); + } + + #endregion + + #region Document and template + + /// + /// Gets a template. + /// + /// The template alias + /// The template. + public ITemplate GetTemplate(string alias) + { + return _services.FileService.GetTemplate(alias); + } + + /// + /// Finds the Umbraco document (if any) matching the request, and updates the PublishedContentRequest accordingly. + /// + /// A value indicating whether a document and template were found. + private void FindPublishedContentAndTemplate(PublishedRequest request) + { + const string tracePrefix = "FindPublishedContentAndTemplate: "; + _logger.Debug(() => $"{tracePrefix}Path=\"{request.Uri.AbsolutePath}\""); + + // run the document finders + FindPublishedContent(request); + + // if request has been flagged to redirect then return + // whoever called us is in charge of actually redirecting + // -- do not process anything any further -- + if (request.IsRedirect) + return; + + // not handling umbracoRedirect here but after LookupDocument2 + // so internal redirect, 404, etc has precedence over redirect + + // handle not-found, redirects, access... + HandlePublishedContent(request); + + // find a template + FindTemplate(request); + + // handle umbracoRedirect + FollowExternalRedirect(request); + } + + /// + /// Tries to find the document matching the request, by running the IPublishedContentFinder instances. + /// + /// There is no finder collection. + internal void FindPublishedContent(PublishedRequest request) + { + const string tracePrefix = "FindPublishedContent: "; + + // look for the document + // the first successful finder, if any, will set this.PublishedContent, and may also set this.Template + // some finders may implement caching + + using (_profilingLogger.DebugDuration( + $"{tracePrefix}Begin finders", + $"{tracePrefix}End finders, {(request.HasPublishedContent ? "a document was found" : "no document was found")}")) + { + //iterate but return on first one that finds it + var found = _contentFinders.Any(finder => + { + _logger.Debug("Finder " + finder.GetType().FullName); + return finder.TryFindContent(request); + }); + } + + // indicate that the published content (if any) we have at the moment is the + // one that was found by the standard finders before anything else took place. + request.SetIsInitialPublishedContent(); + } + + /// + /// Handles the published content (if any). + /// + /// + /// Handles "not found", internal redirects, access validation... + /// things that must be handled in one place because they can create loops + /// + private void HandlePublishedContent(PublishedRequest request) + { + const string tracePrefix = "HandlePublishedContent: "; + + // because these might loop, we have to have some sort of infinite loop detection + int i = 0, j = 0; + const int maxLoop = 8; + do + { + _logger.Debug(() => $"{tracePrefix}{(i == 0 ? "Begin" : "Loop")}"); + + // handle not found + if (request.HasPublishedContent == false) + { + request.Is404 = true; + _logger.Debug(() => $"{tracePrefix}No document, try last chance lookup"); + + // if it fails then give up, there isn't much more that we can do + if (_contentLastChanceFinder.TryFindContent(request) == false) + { + _logger.Debug(() => $"{tracePrefix}Failed to find a document, give up"); + break; + } + + _logger.Debug(() => $"{tracePrefix}Found a document"); + } + + // follow internal redirects as long as it's not running out of control ie infinite loop of some sort + j = 0; + while (FollowInternalRedirects(request) && j++ < maxLoop) + { } + if (j == maxLoop) // we're running out of control + break; + + // ensure access + if (request.HasPublishedContent) + EnsurePublishedContentAccess(request); + + // loop while we don't have page, ie the redirect or access + // got us to nowhere and now we need to run the notFoundLookup again + // as long as it's not running out of control ie infinite loop of some sort + + } while (request.HasPublishedContent == false && i++ < maxLoop); + + if (i == maxLoop || j == maxLoop) + { + _logger.Debug(() => $"{tracePrefix}Looks like we're running into an infinite loop, abort"); + request.PublishedContent = null; + } + + _logger.Debug(() => $"{tracePrefix}End"); + } + + /// + /// Follows internal redirections through the umbracoInternalRedirectId document property. + /// + /// A value indicating whether redirection took place and led to a new published document. + /// + /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. + /// As per legacy, if the redirect does not work, we just ignore it. + /// + private bool FollowInternalRedirects(PublishedRequest request) + { + const string tracePrefix = "FollowInternalRedirects: "; + + if (request.PublishedContent == null) + throw new InvalidOperationException("There is no PublishedContent."); + + // don't try to find a redirect if the property doesn't exist + if (request.PublishedContent.HasProperty(Constants.Conventions.Content.InternalRedirectId) == false) + return false; + + var redirect = false; + var valid = false; + IPublishedContent internalRedirectNode = null; + var internalRedirectId = request.PublishedContent.Value(Constants.Conventions.Content.InternalRedirectId, -1); + + if (internalRedirectId > 0) + { + // try and get the redirect node from a legacy integer ID + valid = true; + internalRedirectNode = request.UmbracoContext.ContentCache.GetById(internalRedirectId); + } + else + { + var udiInternalRedirectId = request.PublishedContent.Value(Constants.Conventions.Content.InternalRedirectId); + if (udiInternalRedirectId != null) + { + // try and get the redirect node from a UDI Guid + valid = true; + internalRedirectNode = request.UmbracoContext.ContentCache.GetById(udiInternalRedirectId.Guid); + } + } + + if (valid == false) + { + // bad redirect - log and display the current page (legacy behavior) + _logger.Debug($"{tracePrefix}Failed to redirect to id={request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue()}: value is not an int nor a GuidUdi."); + } + + if (internalRedirectNode == null) + { + _logger.Debug($"{tracePrefix}Failed to redirect to id={request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue()}: no such published document."); + } + else if (internalRedirectId == request.PublishedContent.Id) + { + // redirect to self + _logger.Debug($"{tracePrefix}Redirecting to self, ignore"); + } + else + { + request.SetInternalRedirectPublishedContent(internalRedirectNode); // don't use .PublishedContent here + redirect = true; + _logger.Debug($"{tracePrefix}Redirecting to id={internalRedirectId}"); + } + + return redirect; + } + + /// + /// Ensures that access to current node is permitted. + /// + /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. + private void EnsurePublishedContentAccess(PublishedRequest request) + { + const string tracePrefix = "EnsurePublishedContentAccess: "; + + if (request.PublishedContent == null) + throw new InvalidOperationException("There is no PublishedContent."); + + var path = request.PublishedContent.Path; + + var publicAccessAttempt = _services.PublicAccessService.IsProtected(path); + + if (publicAccessAttempt) + { + _logger.Debug(() => $"{tracePrefix}Page is protected, check for access"); + + var membershipHelper = new MembershipHelper(request.UmbracoContext); + + if (membershipHelper.IsLoggedIn() == false) + { + _logger.Debug(() => $"{tracePrefix}Not logged in, redirect to login page"); + + var loginPageId = publicAccessAttempt.Result.LoginNodeId; + + if (loginPageId != request.PublishedContent.Id) + request.PublishedContent = request.UmbracoContext.PublishedShapshot.Content.GetById(loginPageId); + } + else if (_services.PublicAccessService.HasAccess(request.PublishedContent.Id, _services.ContentService, GetRolesForLogin(membershipHelper.CurrentUserName)) == false) + { + _logger.Debug(() => $"{tracePrefix}Current member has not access, redirect to error page"); + var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; + if (errorPageId != request.PublishedContent.Id) + request.PublishedContent = request.UmbracoContext.PublishedShapshot.Content.GetById(errorPageId); + } + else + { + _logger.Debug(() => $"{tracePrefix}Current member has access"); + } + } + else + { + _logger.Debug(() => $"{tracePrefix}Page is not protected"); + } + } + + /// + /// Finds a template for the current node, if any. + /// + private void FindTemplate(PublishedRequest request) + { + // NOTE: at the moment there is only 1 way to find a template, and then ppl must + // use the Prepared event to change the template if they wish. Should we also + // implement an ITemplateFinder logic? + + const string tracePrefix = "FindTemplate: "; + + if (request.PublishedContent == null) + { + request.TemplateModel = null; + return; + } + + // read the alternate template alias, from querystring, form, cookie or server vars, + // only if the published content is the initial once, else the alternate template + // does not apply + // + optionnally, apply the alternate template on internal redirects + var useAltTemplate = _webRoutingSection.DisableAlternativeTemplates == false + && (request.IsInitialPublishedContent + || (_webRoutingSection.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent)); + var altTemplate = useAltTemplate + ? request.UmbracoContext.HttpContext.Request[Constants.Conventions.Url.AltTemplate] + : null; + + if (string.IsNullOrWhiteSpace(altTemplate)) + { + // we don't have an alternate template specified. use the current one if there's one already, + // which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...), + // else lookup the template id on the document then lookup the template with that id. + + if (request.HasTemplate) + { + _logger.Debug("{0}Has a template already, and no alternate template."); + return; + } + + // TODO: When we remove the need for a database for templates, then this id should be irrelavent, + // not sure how were going to do this nicely. + + var templateId = request.PublishedContent.TemplateId; + + if (templateId > 0) + { + _logger.Debug(() => $"{tracePrefix}Look for template id={templateId}"); + var template = _services.FileService.GetTemplate(templateId); + if (template == null) + throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render"); + request.TemplateModel = template; + _logger.Debug(() => $"{tracePrefix}Got template id={template.Id} alias=\"{template.Alias}\""); + } + else + { + _logger.Debug(() => $"{tracePrefix}No specified template."); + } + } + else + { + // we have an alternate template specified. lookup the template with that alias + // this means the we override any template that a content lookup might have set + // so /path/to/page/template1?altTemplate=template2 will use template2 + + // ignore if the alias does not match - just trace + + if (request.HasTemplate) + _logger.Debug(() => $"{tracePrefix}Has a template already, but also an alternate template."); + _logger.Debug(() => $"{tracePrefix}Look for alternate template alias=\"{altTemplate}\""); + + var template = _services.FileService.GetTemplate(altTemplate); + if (template != null) + { + request.TemplateModel = template; + _logger.Debug(() => $"{tracePrefix}Got template id={template.Id} alias=\"{template.Alias}\""); + } + else + { + _logger.Debug(() => $"{tracePrefix}The template with alias=\"{altTemplate}\" does not exist, ignoring."); + } + } + + if (request.HasTemplate == false) + { + _logger.Debug(() => $"{tracePrefix}No template was found."); + + // initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true + // then reset _pcr.Document to null to force a 404. + // + // but: because we want to let MVC hijack routes even though no template is defined, we decide that + // a missing template is OK but the request will then be forwarded to MVC, which will need to take + // care of everything. + // + // so, don't set _pcr.Document to null here + } + else + { + _logger.Debug(() => $"{tracePrefix}Running with template id={request.TemplateModel.Id} alias=\"{request.TemplateModel.Alias}\""); + } + } + + /// + /// Follows external redirection through umbracoRedirect document property. + /// + /// As per legacy, if the redirect does not work, we just ignore it. + private void FollowExternalRedirect(PublishedRequest request) + { + if (request.HasPublishedContent == false) return; + + // don't try to find a redirect if the property doesn't exist + if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false) + return; + + var redirectId = request.PublishedContent.Value(Constants.Conventions.Content.Redirect, -1); + var redirectUrl = "#"; + if (redirectId > 0) + { + redirectUrl = request.UmbracoContext.UrlProvider.GetUrl(redirectId); + } + else + { + // might be a UDI instead of an int Id + var redirectUdi = request.PublishedContent.Value(Constants.Conventions.Content.Redirect); + if (redirectUdi != null) + redirectUrl = request.UmbracoContext.UrlProvider.GetUrl(redirectUdi.Guid); + } + if (redirectUrl != "#") + request.SetRedirect(redirectUrl); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs new file mode 100644 index 0000000000..5ea4160aae --- /dev/null +++ b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs @@ -0,0 +1,180 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Events; +using System.Collections.Generic; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Services.Implement; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; +using Umbraco.Web.Composing; + +namespace Umbraco.Web.Redirects +{ + /// + /// Implements an Application Event Handler for managing redirect urls tracking. + /// + /// + /// when content is renamed or moved, we want to create a permanent 301 redirect from it's old url + /// not managing domains because we don't know how to do it - changing domains => must create a higher level strategy using rewriting rules probably + /// recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same + /// + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public class RedirectTrackingComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + private const string ContextKey1 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.1"; + private const string ContextKey2 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.2"; + private const string ContextKey3 = "Umbraco.Web.Redirects.RedirectTrackingEventHandler.3"; + + protected void Initialize() + { + // events are weird + // on 'published' we 'could' get the old or the new route depending on event handlers order + // so it is not reliable. getting the old route in 'publishing' to be sure and storing in http + // context. then for the same reason, we have to process these old items only when the cache + // is ready + // when moving, the moved node is also published, which is causing all sorts of troubles with + // descendants, so when moving, we lock events so that neither 'published' nor 'publishing' + // are processed more than once + // we cannot rely only on ContentCacheRefresher because when CacheUpdated triggers the old + // route is gone + // + // this is all verrrry weird but it seems to work + + ContentService.Publishing += ContentService_Publishing; + ContentService.Published += ContentService_Published; + ContentService.Moving += ContentService_Moving; + ContentService.Moved += ContentService_Moved; + ContentCacheRefresher.CacheUpdated += ContentCacheRefresher_CacheUpdated; + + // kill all redirects once a content is deleted + //ContentService.Deleted += ContentService_Deleted; + // BUT, doing it here would prevent content deletion due to FK + // so the rows are actually deleted by the ContentRepository (see GetDeleteClauses) + + // rolled back items have to be published, so publishing will take care of that + } + + private static void ContentCacheRefresher_CacheUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) + { + // sanity checks + + if (args.MessageType != MessageType.RefreshByPayload) + throw new InvalidOperationException("ContentCacheRefresher MessageType should be ByPayload."); + + if (args.MessageObject == null) return; + var payloads = args.MessageObject as ContentCacheRefresher.JsonPayload[]; + if (payloads == null) + throw new InvalidOperationException("ContentCacheRefresher MessageObject should be JsonPayload[]."); + + // manage routes + + var removeKeys = new List(); + + foreach (var oldRoute in OldRoutes) + { + // assuming we cannot have 'CacheUpdated' for only part of the infos else we'd need + // to set a flag in 'Published' to indicate which entities have been refreshed ok + CreateRedirect(oldRoute.Key, oldRoute.Value.Item1, oldRoute.Value.Item2); + removeKeys.Add(oldRoute.Key); + } + + foreach (var k in removeKeys) + OldRoutes.Remove(k); + } + + private static Dictionary> OldRoutes + { + get + { + var oldRoutes = (Dictionary>) UmbracoContext.Current.HttpContext.Items[ContextKey3]; + if (oldRoutes == null) + UmbracoContext.Current.HttpContext.Items[ContextKey3] = oldRoutes = new Dictionary>(); + return oldRoutes; + } + } + + private static bool LockedEvents + { + get { return Moving && UmbracoContext.Current.HttpContext.Items[ContextKey2] != null; } + set + { + if (Moving && value) + UmbracoContext.Current.HttpContext.Items[ContextKey2] = true; + else + UmbracoContext.Current.HttpContext.Items.Remove(ContextKey2); + } + } + + private static bool Moving + { + get { return UmbracoContext.Current.HttpContext.Items[ContextKey1] != null; } + set + { + if (value) + UmbracoContext.Current.HttpContext.Items[ContextKey1] = true; + else + { + UmbracoContext.Current.HttpContext.Items.Remove(ContextKey1); + UmbracoContext.Current.HttpContext.Items.Remove(ContextKey2); + } + } + } + + private static void ContentService_Publishing(IContentService sender, PublishEventArgs args) + { + if (LockedEvents) return; + + var contentCache = UmbracoContext.Current.ContentCache; + foreach (var entity in args.PublishedEntities) + { + var entityContent = contentCache.GetById(entity.Id); + if (entityContent == null) continue; + foreach (var x in entityContent.DescendantsOrSelf()) + { + var route = contentCache.GetRouteById(x.Id); + if (IsNotRoute(route)) continue; + OldRoutes[x.Id] = Tuple.Create(x.Key, route); + } + } + + LockedEvents = true; // we only want to see the "first batch" + } + + private static void ContentService_Published(IContentService sender, PublishEventArgs e) + { + // look note in CacheUpdated + // we might want to set a flag on the entities we are seeing here + } + + private static void ContentService_Moving(IContentService sender, MoveEventArgs e) + { + //TODO: Use the new e.EventState to track state between Moving/Moved events! + Moving = true; + } + + private static void ContentService_Moved(IContentService sender, MoveEventArgs e) + { + Moving = false; + LockedEvents = false; + } + + private static void CreateRedirect(int contentId, Guid contentKey, string oldRoute) + { + var contentCache = UmbracoContext.Current.ContentCache; + var newRoute = contentCache.GetRouteById(contentId); + if (IsNotRoute(newRoute) || oldRoute == newRoute) return; + var redirectUrlService = Current.Services.RedirectUrlService; + redirectUrlService.Register(oldRoute, contentKey); + } + + private static bool IsNotRoute(string route) + { + // null if content not found + // err/- if collision or anomaly or ... + return route == null || route.StartsWith("err/"); + } + } +} diff --git a/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs deleted file mode 100644 index 77577e26e2..0000000000 --- a/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs +++ /dev/null @@ -1,328 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Publishing; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Umbraco.Core.Sync; -using Umbraco.Web.Cache; -using Umbraco.Web.PublishedCache; - -namespace Umbraco.Web.Routing -{ - /// - /// Implements an Application Event Handler for managing redirect urls tracking. - /// - /// - /// when content is renamed or moved, we want to create a permanent 301 redirect from it's old url - /// not managing domains because we don't know how to do it - changing domains => must create a higher level strategy using rewriting rules probably - /// recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same - /// - public class RedirectTrackingEventHandler : ApplicationEventHandler - { - private const string ContextKey1 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.1"; - private const string ContextKey2 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.2"; - private const string ContextKey3 = "Umbraco.Web.Routing.RedirectTrackingEventHandler.3"; - - /// - protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableRedirectUrlTracking) - { - ContentFinderResolver.Current.RemoveType(); - } - else - { - // if any of these dlls are loaded we don't want to run our finder - var dlls = new[] - { - "InfoCaster.Umbraco.UrlTracker", - "SEOChecker", - "Simple301", - "Terabyte.Umbraco.Modules.PermanentRedirect", - "CMUmbracoTools", - "PWUrlRedirect" - }; - - // assuming all assemblies have been loaded already - // check if any of them matches one of the above dlls - var found = AppDomain.CurrentDomain.GetAssemblies() - .Select(x => x.FullName.Split(',')[0]) - .Any(x => dlls.Contains(x)); - if (found) - ContentFinderResolver.Current.RemoveType(); - } - } - - /// - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - // don't let the event handlers kick in if Redirect Tracking is turned off in the config - if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableRedirectUrlTracking) return; - - // events are weird - // on 'published' we 'could' get the old or the new route depending on event handlers order - // so it is not reliable. getting the old route in 'publishing' to be sure and storing in http - // context. then for the same reason, we have to process these old items only when the cache - // is ready - // when moving, the moved node is also published, which is causing all sorts of troubles with - // descendants, so when moving, we lock events so that neither 'published' nor 'publishing' - // are processed more than once - // - // this is all verrrry weird but it seems to work - - ContentService.Publishing += ContentService_Publishing; - ContentService.Published += ContentService_Published; - ContentService.Moving += ContentService_Moving; - ContentService.Moved += ContentService_Moved; - PageCacheRefresher.CacheUpdated += PageCacheRefresher_CacheUpdated; - - // kill all redirects once a content is deleted - //ContentService.Deleted += ContentService_Deleted; - // BUT, doing it here would prevent content deletion due to FK - // so the rows are actually deleted by the ContentRepository (see GetDeleteClauses) - - // rolled back items have to be published, so publishing will take care of that - } - - /// - /// Tracks a documents URLs during publishing in the current request - /// - private static Dictionary> OldRoutes - { - get - { - var oldRoutes = RequestCache.GetCacheItem>>( - ContextKey3, - () => new Dictionary>()); - return oldRoutes; - } - } - - private static bool LockedEvents - { - get - { - return Moving && RequestCache.GetCacheItem(ContextKey2) != null; - } - set - { - if (Moving && value) - { - //this forces true into the cache - RequestCache.GetCacheItem(ContextKey2, () => true); - } - else - { - RequestCache.ClearCacheItem(ContextKey2); - } - } - } - - private static bool Moving - { - get { return RequestCache.GetCacheItem(ContextKey1) != null; } - set - { - if (value) - { - //this forces true into the cache - RequestCache.GetCacheItem(ContextKey1, () => true); - } - else - { - RequestCache.ClearCacheItem(ContextKey1); - RequestCache.ClearCacheItem(ContextKey2); - } - } - } - - /// - /// Before the items are published, we need to get it's current URL before it changes - /// - /// - /// - private static void ContentService_Publishing(IPublishingStrategy sender, PublishEventArgs args) - { - if (LockedEvents) return; - - var contentCache = GetPublishedCache(); - if (contentCache == null) return; - - // prepare entities - var entities = PrepareEntities(args.PublishedEntities); - - foreach (var entity in entities) - { - // for each entity, we want to save the 'old route' of any impacted entity. - // - // previously, we'd save the routes of all descendants - super safe but has an - // impact on perfs - assuming that the descendant routes will NOT change if the - // entity's segment does not change (else... outside of the scope of the simple, - // built -in, tracker) then we can compare the entity's old and new segments - // and avoid processing the descendants - - var process = true; - if (Moving == false) // always process descendants when moving - { - // SD: in 7.5.0 we re-lookup the entity that is published, which gets its - // current state in the DB, which we use to get the 'old' segment. In the - // future this will certainly cause some problems, to fix this we'd need to - // change the IUrlSegmentProvider to support being able to determine if a - // segment is going to change for an entity. See notes in IUrlSegmentProvider. - - var oldEntity = ApplicationContext.Current.Services.ContentService.GetById(entity.Id); - if (oldEntity == null) continue; - var oldSegment = oldEntity.GetUrlSegment(); - var newSegment = entity.GetUrlSegment(); - process = oldSegment != newSegment; - } - - // skip if no segment change - if (process == false) continue; - - // else save routes for all descendants - var entityContent = contentCache.GetById(entity.Id); - if (entityContent == null) continue; - foreach (var x in entityContent.DescendantsOrSelf()) - { - var route = contentCache.GetRouteById(x.Id); - if (IsNotRoute(route)) continue; - var wk = UnwrapToKey(x); - if (wk == null) continue; - - OldRoutes[x.Id] = Tuple.Create(wk.Key, route); - } - } - - LockedEvents = true; // we only want to see the "first batch" - } - - private static IEnumerable PrepareEntities(IEnumerable eventEntities) - { - // prepare entities - // - exclude entities without an identity (new entities) - // - exclude duplicates (in case publishing a parent and its children) - - var entities = new List(); - foreach (var e in eventEntities.Where(x => x.HasIdentity).OrderBy(x => x.Level)) - { - var pathIds = e.Path.Split(',').Select(int.Parse); - if (entities.Any(x => pathIds.Contains(x.Id))) continue; - entities.Add(e); - } - return entities; - } - - private static IPublishedContentWithKey UnwrapToKey(IPublishedContent content) - { - if (content == null) return null; - var withKey = content as IPublishedContentWithKey; - if (withKey != null) return withKey; - - var extended = content as PublishedContentExtended; - while (extended != null) - extended = (content = extended.Unwrap()) as PublishedContentExtended; - - withKey = content as IPublishedContentWithKey; - return withKey; - } - - /// - /// Executed when the cache updates, which means we can know what the new URL is for a given document - /// - /// - /// - private void PageCacheRefresher_CacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs cacheRefresherEventArgs) - { - // only on master / single, not on slaves! - if (IsSlaveServer) return; - - // simply getting OldRoutes will register it in the request cache, - // so whatever we do with it, try/finally it to ensure it's cleared - - try - { - foreach (var oldRoute in OldRoutes) - { - // assuming we cannot have 'CacheUpdated' for only part of the infos else we'd need - // to set a flag in 'Published' to indicate which entities have been refreshed ok - CreateRedirect(oldRoute.Key, oldRoute.Value.Item1, oldRoute.Value.Item2); - } - } - finally - { - OldRoutes.Clear(); - RequestCache.ClearCacheItem(ContextKey3); - } - } - - private static void ContentService_Published(IPublishingStrategy sender, PublishEventArgs e) - { - // look note in CacheUpdated - // we might want to set a flag on the entities we are seeing here - } - - private static void ContentService_Moving(IContentService sender, MoveEventArgs e) - { - //TODO: Use the new e.EventState to track state between Moving/Moved events! - Moving = true; - } - - private static void ContentService_Moved(IContentService sender, MoveEventArgs e) - { - Moving = false; - LockedEvents = false; - } - - private static void CreateRedirect(int contentId, Guid contentKey, string oldRoute) - { - - var contentCache = GetPublishedCache(); - if (contentCache == null) return; - - var newRoute = contentCache.GetRouteById(contentId); - if (IsNotRoute(newRoute) || oldRoute == newRoute) return; - var redirectUrlService = ApplicationContext.Current.Services.RedirectUrlService; - redirectUrlService.Register(oldRoute, contentKey); - } - - private static bool IsNotRoute(string route) - { - // null if content not found - return route == null; - } - - // gets a value indicating whether server is 'slave' - private static bool IsSlaveServer - { - get - { - var serverRole = ApplicationContext.Current.GetCurrentServerRole(); - return serverRole != ServerRole.Master && serverRole != ServerRole.Single; - } - } - - /// - /// Gets the current request cache to persist the values between handlers - /// - private static ContextualPublishedContentCache GetPublishedCache() - { - return UmbracoContext.Current == null ? null : UmbracoContext.Current.ContentCache; - } - - /// - /// Gets the current request cache to persist the values between handlers - /// - private static ICacheProvider RequestCache - { - get { return ApplicationContext.Current.ApplicationCache.RequestCache; } - } - } -} diff --git a/src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs b/src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs index ee93623252..389c7127e3 100644 --- a/src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs +++ b/src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs @@ -1,4 +1,4 @@ -using System.Web; +using System.Web; namespace Umbraco.Web.Routing { @@ -15,4 +15,4 @@ namespace Umbraco.Web.Routing Outcome = reason; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Routing/RoutingContext.cs b/src/Umbraco.Web/Routing/RoutingContext.cs deleted file mode 100644 index b999e2241a..0000000000 --- a/src/Umbraco.Web/Routing/RoutingContext.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Web.PublishedCache; - -namespace Umbraco.Web.Routing -{ - - /// - /// Provides context for the routing of a request. - /// - public class RoutingContext - { - private readonly Lazy _urlProvider; - private readonly Lazy> _publishedContentFinders; - private readonly Lazy _publishedContentLastChanceFinder; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The document lookups resolver. - /// - /// The nice urls provider. - internal RoutingContext( - UmbracoContext umbracoContext, - IEnumerable contentFinders, - IContentFinder contentLastChanceFinder, - UrlProvider urlProvider) - { - UmbracoContext = umbracoContext; - _publishedContentFinders = new Lazy>(() => contentFinders, false); - _publishedContentLastChanceFinder = new Lazy(() => contentLastChanceFinder, false); - _urlProvider = new Lazy(() => urlProvider, false); - } - - internal RoutingContext( - UmbracoContext umbracoContext, - Lazy> contentFinders, - Lazy contentLastChanceFinder, - Lazy urlProvider) - { - UmbracoContext = umbracoContext; - _publishedContentFinders = contentFinders; - _publishedContentLastChanceFinder = contentLastChanceFinder; - _urlProvider = urlProvider; - } - - /// - /// Gets the Umbraco context. - /// - public UmbracoContext UmbracoContext { get; private set; } - - /// - /// Gets the published content finders. - /// - internal IEnumerable PublishedContentFinders - { - get { return _publishedContentFinders.Value; } - } - - /// - /// Gets the published content last chance finder. - /// - internal IContentFinder PublishedContentLastChanceFinder - { - get { return _publishedContentLastChanceFinder.Value; } - } - - /// - /// Gets the urls provider. - /// - public UrlProvider UrlProvider - { - get { return _urlProvider.Value; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/SiteDomainHelper.cs b/src/Umbraco.Web/Routing/SiteDomainHelper.cs index 5170ce1f5a..1ceab6293a 100644 --- a/src/Umbraco.Web/Routing/SiteDomainHelper.cs +++ b/src/Umbraco.Web/Routing/SiteDomainHelper.cs @@ -4,38 +4,38 @@ using System.Linq; using System.Threading; using System.Text.RegularExpressions; using Umbraco.Core; - + namespace Umbraco.Web.Routing { - /// - /// Provides utilities to handle site domains. - /// - public class SiteDomainHelper : ISiteDomainHelper - { + /// + /// Provides utilities to handle site domains. + /// + public class SiteDomainHelper : ISiteDomainHelper + { #region Configure - + private static readonly ReaderWriterLockSlim ConfigLock = new ReaderWriterLockSlim(); - private static Dictionary _sites; + private static Dictionary _sites; private static Dictionary> _bindings; private static Dictionary> _qualifiedSites; // these are for unit tests *only* internal static Dictionary Sites { get { return _sites; } } - internal static Dictionary> Bindings { get { return _bindings; } } + internal static Dictionary> Bindings { get { return _bindings; } } // these are for validation - //private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$"; + //private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$"; private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$"; - private static readonly Regex DomainValidation = new Regex(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); - + private static readonly Regex DomainValidation = new Regex(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); + /// /// Returns a disposable object that represents safe write access to config. /// /// Should be used in a using(SiteDomainHelper.ConfigWriteLock) { ... } mode. protected static IDisposable ConfigWriteLock - { + { get { return new WriteLock(ConfigLock); } - } + } /// /// Returns a disposable object that represents safe read access to config. @@ -149,15 +149,15 @@ namespace Umbraco.Web.Routing { foreach (var key in keys.Where(key => !_sites.ContainsKey(key))) throw new ArgumentException(string.Format("Not an existing site key: {0}", key), "keys"); - + _bindings = _bindings ?? new Dictionary>(); - + var allkeys = _bindings .Where(kvp => keys.Contains(kvp.Key)) .SelectMany(kvp => kvp.Value) .Union(keys) .ToArray(); - + foreach (var key in allkeys) { if (!_bindings.ContainsKey(key)) @@ -193,19 +193,19 @@ namespace Umbraco.Web.Routing return MapDomain(domainAndUris, qualifiedSites, currentAuthority); } - /// - /// Filters a list of DomainAndUri to pick those that best matches the current request. - /// - /// The Uri of the current request. - /// The list of DomainAndUri to filter. + /// + /// Filters a list of DomainAndUri to pick those that best matches the current request. + /// + /// The Uri of the current request. + /// The list of DomainAndUri to filter. /// A value indicating whether to exclude the current/default domain. - /// The selected DomainAndUri items. - /// The filter must return something, even empty, else an exception will be thrown. - public virtual IEnumerable MapDomains(Uri current, DomainAndUri[] domainAndUris, bool excludeDefault) + /// The selected DomainAndUri items. + /// The filter must return something, even empty, else an exception will be thrown. + public virtual IEnumerable MapDomains(Uri current, DomainAndUri[] domainAndUris, bool excludeDefault) { var currentAuthority = current.GetLeftPart(UriPartial.Authority); KeyValuePair[] candidateSites = null; - IEnumerable ret = domainAndUris; + IEnumerable ret = domainAndUris; using (ConfigReadLock) // so nothing changes between GetQualifiedSites and access to bindings { @@ -252,7 +252,7 @@ namespace Umbraco.Web.Routing } } } - + // if we are able to filter, then filter, else return the whole lot return candidateSites == null ? ret : ret.Where(d => { @@ -260,27 +260,27 @@ namespace Umbraco.Web.Routing return candidateSites.Any(site => site.Value.Contains(authority)); }); } - - private static Dictionary GetQualifiedSites(Uri current) - { - using (ConfigReadLock) - { - return GetQualifiedSitesInsideLock(current); - } - } - - private static Dictionary GetQualifiedSitesInsideLock(Uri current) + + private static Dictionary GetQualifiedSites(Uri current) + { + using (ConfigReadLock) + { + return GetQualifiedSitesInsideLock(current); + } + } + + private static Dictionary GetQualifiedSitesInsideLock(Uri current) { // we do our best, but can't do the impossible if (_sites == null) return null; - + // cached? if (_qualifiedSites != null && _qualifiedSites.ContainsKey(current.Scheme)) return _qualifiedSites[current.Scheme]; - + _qualifiedSites = _qualifiedSites ?? new Dictionary>(); - + // convert sites into authority sites based upon current scheme // because some domains in the sites might not have a scheme -- and cache return _qualifiedSites[current.Scheme] = _sites @@ -288,7 +288,7 @@ namespace Umbraco.Web.Routing kvp => kvp.Key, kvp => kvp.Value.Select(d => new Uri(UriUtility.StartWithScheme(d, current.Scheme)).GetLeftPart(UriPartial.Authority)).ToArray() ); - + // .ToDictionary will evaluate and create the dictionary immediately // the new value is .ToArray so it will also be evaluated immediately // therefore it is safe to return and exit the configuration lock @@ -308,7 +308,7 @@ namespace Umbraco.Web.Routing // find a site that contains the current authority var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); - // if current belongs to a site - try to pick the first element + // if current belongs to a site - try to pick the first element // from domainAndUris that also belongs to that site var ret = currentSite.Equals(default(KeyValuePair)) ? null diff --git a/src/Umbraco.Web/Routing/SiteDomainHelperResolver.cs b/src/Umbraco.Web/Routing/SiteDomainHelperResolver.cs deleted file mode 100644 index daf1d932d8..0000000000 --- a/src/Umbraco.Web/Routing/SiteDomainHelperResolver.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.Routing -{ - /// - /// Resolves the implementation. - /// - public sealed class SiteDomainHelperResolver : SingleObjectResolverBase - { - - /// - /// Initializes a new instance of the class with an implementation. - /// - /// The implementation. - internal SiteDomainHelperResolver(ISiteDomainHelper helper) - : base(helper) - { } - - - /// - /// Can be used by developers at runtime to set their IDomainHelper at app startup - /// - /// - public void SetHelper(ISiteDomainHelper helper) - { - Value = helper; - } - - /// - /// Gets or sets the implementation. - /// - public ISiteDomainHelper Helper - { - get { return Value; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs b/src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs index 3516277275..0372011150 100644 --- a/src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs +++ b/src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs @@ -17,4 +17,4 @@ namespace Umbraco.Web.Routing HttpContext = httpContext; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index f237a7d886..3375ac1be2 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Web.PublishedCache; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Web.Composing; namespace Umbraco.Web.Routing { @@ -24,10 +25,9 @@ namespace Umbraco.Web.Routing /// The list of url providers. public UrlProvider(UmbracoContext umbracoContext, IWebRoutingSection routingSettings, IEnumerable urlProviders) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (routingSettings == null) throw new ArgumentNullException("routingSettings"); + if (routingSettings == null) throw new ArgumentNullException(nameof(routingSettings)); - _umbracoContext = umbracoContext; + _umbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); _urlProviders = urlProviders; var provider = UrlProviderMode.Auto; @@ -36,7 +36,7 @@ namespace Umbraco.Web.Routing if (Enum.TryParse(routingSettings.UrlProviderMode, out provider)) { Mode = provider; - } + } } /// @@ -47,9 +47,7 @@ namespace Umbraco.Web.Routing /// public UrlProvider(UmbracoContext umbracoContext, IEnumerable urlProviders, UrlProviderMode provider = UrlProviderMode.Auto) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - - _umbracoContext = umbracoContext; + _umbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); _urlProviders = urlProviders; Mode = provider; @@ -78,7 +76,7 @@ namespace Umbraco.Web.Routing /// public string GetUrl(Guid id) { - var intId = _umbracoContext.Application.Services.EntityService.GetIdForKey(id, UmbracoObjectTypes.Document); + var intId = Current.Services.EntityService.GetId(id, UmbracoObjectTypes.Document); return GetUrl(intId.Success ? intId.Result : -1); } @@ -95,7 +93,7 @@ namespace Umbraco.Web.Routing /// public string GetUrl(Guid id, bool absolute) { - var intId = _umbracoContext.Application.Services.EntityService.GetIdForKey(id, UmbracoObjectTypes.Document); + var intId = Current.Services.EntityService.GetId(id, UmbracoObjectTypes.Document); return GetUrl(intId.Success ? intId.Result : -1, absolute); } @@ -113,7 +111,7 @@ namespace Umbraco.Web.Routing /// public string GetUrl(Guid id, Uri current, bool absolute) { - var intId = _umbracoContext.Application.Services.EntityService.GetIdForKey(id, UmbracoObjectTypes.Document); + var intId = Current.Services.EntityService.GetId(id, UmbracoObjectTypes.Document); return GetUrl(intId.Success ? intId.Result : -1, current, absolute); } @@ -129,7 +127,7 @@ namespace Umbraco.Web.Routing /// public string GetUrl(Guid id, UrlProviderMode mode) { - var intId = _umbracoContext.Application.Services.EntityService.GetIdForKey(id, UmbracoObjectTypes.Document); + var intId = Current.Services.EntityService.GetId(id, UmbracoObjectTypes.Document); return GetUrl(intId.Success ? intId.Result : -1, mode); } @@ -218,7 +216,7 @@ namespace Umbraco.Web.Routing internal string GetUrlFromRoute(int id, string route) { var provider = _urlProviders.OfType().FirstOrDefault(); - var url = provider == null + var url = provider == null ? route // what else? : provider.GetUrlFromRoute(route, UmbracoContext.Current, id, _umbracoContext.CleanedUmbracoUrl, Mode); return url ?? "#"; diff --git a/src/Umbraco.Web/Routing/UrlProviderCollection.cs b/src/Umbraco.Web/Routing/UrlProviderCollection.cs new file mode 100644 index 0000000000..7e08c37f06 --- /dev/null +++ b/src/Umbraco.Web/Routing/UrlProviderCollection.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Routing +{ + public class UrlProviderCollection : BuilderCollectionBase + { + public UrlProviderCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Web/Routing/UrlProviderCollectionBuilder.cs b/src/Umbraco.Web/Routing/UrlProviderCollectionBuilder.cs new file mode 100644 index 0000000000..196c8a7a25 --- /dev/null +++ b/src/Umbraco.Web/Routing/UrlProviderCollectionBuilder.cs @@ -0,0 +1,14 @@ +using LightInject; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Routing +{ + public class UrlProviderCollectionBuilder : OrderedCollectionBuilderBase + { + public UrlProviderCollectionBuilder(IServiceContainer container) + : base(container) + { } + + protected override UrlProviderCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index d464b8962e..1d9a4a4236 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -1,16 +1,21 @@ using System; using System.Collections.Generic; -using System.Web.Security; using Umbraco.Core.Models; -using umbraco; +using Umbraco.Core.Services; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using LightInject; +using Umbraco.Web.Composing; namespace Umbraco.Web.Routing { internal static class UrlProviderExtensions { + // fixme inject + private static ILocalizedTextService TextService => Current.Services.TextService; + private static IContentService ContentService => Current.Services.ContentService; + private static ILogger Logger => Current.Logger; + /// /// Gets the URLs for the content item /// @@ -23,26 +28,26 @@ namespace Umbraco.Web.Routing /// public static IEnumerable GetContentUrls(this IContent content, UmbracoContext umbracoContext) { - if (content == null) throw new ArgumentNullException("content"); - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); var urls = new List(); - if (content.HasPublishedVersion == false) + if (content.Published == false) { - urls.Add(ui.Text("content", "itemNotPublished", umbracoContext.Security.CurrentUser)); + urls.Add(TextService.Localize("content/itemNotPublished")); return urls; } string url; - var urlProvider = umbracoContext.RoutingContext.UrlProvider; + var urlProvider = umbracoContext.UrlProvider; try { url = urlProvider.GetUrl(content.Id); } catch (Exception e) { - LogHelper.Error("GetUrl exception.", e); + Logger.Error("GetUrl exception.", e); url = "#ex"; } if (url == "#") @@ -52,18 +57,17 @@ namespace Umbraco.Web.Routing var parent = content; do { - parent = parent.ParentId > 0 ? parent.Parent() : null; + parent = parent.ParentId > 0 ? parent.Parent(ContentService) : null; } while (parent != null && parent.Published); - if (parent == null) // oops - internal error - urls.Add(ui.Text("content", "parentNotPublishedAnomaly", umbracoContext.Security.CurrentUser)); - else - urls.Add(ui.Text("content", "parentNotPublished", parent.Name, umbracoContext.Security.CurrentUser)); + urls.Add(parent == null + ? TextService.Localize("content/parentNotPublishedAnomaly") // oops - internal error + : TextService.Localize("content/parentNotPublished", new[] { parent.Name })); } else if (url == "#ex") { - urls.Add(ui.Text("content", "getUrlException", umbracoContext.Security.CurrentUser)); + urls.Add(TextService.Localize("content/getUrlException")); } else { @@ -71,12 +75,13 @@ namespace Umbraco.Web.Routing var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(UmbracoContext.Current.CleanedUmbracoUrl); uri = UriUtility.UriToUmbraco(uri); - var pcr = new PublishedContentRequest(uri, UmbracoContext.Current.RoutingContext, UmbracoConfig.For.UmbracoSettings().WebRouting, s => Roles.Provider.GetRolesForUser(s)); - pcr.Engine.TryRouteRequest(); + var r = Core.Composing.Current.Container.GetInstance(); // fixme inject or ? + var pcr = r.CreateRequest(UmbracoContext.Current, uri); + r.TryRouteRequest(pcr); if (pcr.HasPublishedContent == false) { - urls.Add(ui.Text("content", "routeError", "(error)", umbracoContext.Security.CurrentUser)); + urls.Add(TextService.Localize("content/routeError", new[] { "(error)" })); } else if (pcr.PublishedContent.Id != content.Id) { @@ -98,7 +103,7 @@ namespace Umbraco.Web.Routing s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")"; } - urls.Add(ui.Text("content", "routeError", s, umbracoContext.Security.CurrentUser)); + urls.Add(TextService.Localize("content/routeError", s)); } else { @@ -109,4 +114,4 @@ namespace Umbraco.Web.Routing return urls; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Routing/UrlProviderMode.cs b/src/Umbraco.Web/Routing/UrlProviderMode.cs index 9ca1b9f32a..de2963c30f 100644 --- a/src/Umbraco.Web/Routing/UrlProviderMode.cs +++ b/src/Umbraco.Web/Routing/UrlProviderMode.cs @@ -4,7 +4,7 @@ /// Specifies the type of urls that the url provider should produce. /// /// - /// The AutoLegacy option is equivalent to Auto but it also respects the legacy useDomainPrefixes setting. + /// The AutoLegacy option is equivalent to Auto but it also respects the legacy useDomainPrefixes setting. /// When that setting is true, then all urls are absolute. Otherwise, urls will be relative or absolute, depending on hostnames. /// The Relative option can lead to invalid results when combined with hostnames, but it is the only way to reproduce /// the true, pre-4.10, always-relative behavior of Umbraco. diff --git a/src/Umbraco.Web/Routing/UrlProviderResolver.cs b/src/Umbraco.Web/Routing/UrlProviderResolver.cs deleted file mode 100644 index 505e3ff52a..0000000000 --- a/src/Umbraco.Web/Routing/UrlProviderResolver.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.Routing -{ - /// - /// Resolves IUrlProvider objects. - /// - public sealed class UrlProviderResolver : ManyObjectsResolverBase - { - /// - /// Initializes a new instance of the class with an initial list of provider types. - /// - /// - /// The list of provider types. - /// - /// The resolver is created by the WebBootManager and thus the constructor remains internal. - internal UrlProviderResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable providerTypes) - : base(serviceProvider, logger, providerTypes) - { } - - /// - /// Initializes a new instance of the class with an initial list of provider types. - /// - /// - /// The list of provider types. - /// - /// The resolver is created by the WebBootManager and thus the constructor remains internal. - internal UrlProviderResolver(IServiceProvider serviceProvider, ILogger logger, params Type[] providerTypes) - : base(serviceProvider, logger, providerTypes) - { } - - /// - /// Gets the providers. - /// - public IEnumerable Providers - { - get { return Values; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/WebServicesRouteConstraint.cs b/src/Umbraco.Web/Routing/WebServicesRouteConstraint.cs index 0f9d6305b1..95adc01640 100644 --- a/src/Umbraco.Web/Routing/WebServicesRouteConstraint.cs +++ b/src/Umbraco.Web/Routing/WebServicesRouteConstraint.cs @@ -10,15 +10,15 @@ namespace Umbraco.Web.Routing //public class WebServicesRouteConstraint : IRouteConstraint //{ // public bool Match( - // HttpContextBase httpContext, - // Route route, - // string parameterName, - // RouteValueDictionary values, + // HttpContextBase httpContext, + // Route route, + // string parameterName, + // RouteValueDictionary values, // RouteDirection routeDirection) // { // if (routeDirection == RouteDirection.UrlGeneration) // { - + // } // return true; // } diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs new file mode 100644 index 0000000000..78610c01b8 --- /dev/null +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -0,0 +1,97 @@ +using System; +using System.Web; +using LightInject; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Runtime; + +namespace Umbraco.Web.Runtime +{ + /// + /// Represents the Web Umbraco runtime. + /// + /// On top of CoreRuntime, handles all of the web-related runtime aspects of Umbraco. + public class WebRuntime : CoreRuntime + { + private IProfiler _webProfiler; + + /// + /// Initializes a new instance of the class. + /// + /// + public WebRuntime(UmbracoApplicationBase umbracoApplication) + : base(umbracoApplication) + { } + + /// + public override void Boot(ServiceContainer container) + { + // create and start asap to profile boot + var debug = GlobalSettings.DebugMode; + if (debug) + { + _webProfiler = new WebProfiler(); + _webProfiler.Start(); + } + else + { + // should let it be null, that's how MiniProfiler is meant to work, + // but our own IProfiler expects an instance so let's get one + _webProfiler = new VoidProfiler(); + } + + base.Boot(container); + + // now (and only now) is the time to switch over to perWebRequest scopes + if (!(container.ScopeManagerProvider is MixedLightInjectScopeManagerProvider smp)) + throw new Exception("Container.ScopeManagerProvider is not MixedLightInjectScopeManagerProvider."); + smp.EnablePerWebRequestScope(); + } + + /// + public override void Compose(ServiceContainer container) + { + base.Compose(container); + + // replace CoreRuntime's IProfiler registration + container.RegisterSingleton(_ => _webProfiler); + + // replace CoreRuntime's CacheHelper registration + container.RegisterSingleton(_ => new CacheHelper( + // we need to have the dep clone runtime cache provider to ensure + // all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new HttpRuntimeCacheProvider(HttpRuntime.Cache)), + new StaticCacheProvider(), + // we need request based cache when running in web-based context + new HttpRequestCacheProvider(), + new IsolatedRuntimeCache(type => + // we need to have the dep clone runtime cache provider to ensure + // all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())))); + + container.RegisterSingleton(); // required for hybrid accessors + } + + #region Getters + + //protected override IProfiler GetProfiler() => new WebProfiler(); + + //protected override CacheHelper GetApplicationCache() => new CacheHelper( + // // we need to have the dep clone runtime cache provider to ensure + // // all entities are cached properly (cloned in and cloned out) + // new DeepCloneRuntimeCacheProvider(new HttpRuntimeCacheProvider(HttpRuntime.Cache)), + // new StaticCacheProvider(), + // // we need request based cache when running in web-based context + // new HttpRequestCacheProvider(), + // new IsolatedRuntimeCache(type => + // // we need to have the dep clone runtime cache provider to ensure + // // all entities are cached properly (cloned in and cloned out) + // new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); + + #endregion + } +} + diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs new file mode 100644 index 0000000000..d67bf50797 --- /dev/null +++ b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Web; +using System.Web.Configuration; +using System.Web.Http; +using System.Web.Http.Dispatcher; +using System.Web.Mvc; +using System.Web.Routing; +using ClientDependency.Core.CompositeFiles.Providers; +using ClientDependency.Core.Config; +using Examine; +using LightInject; +using Microsoft.AspNet.SignalR; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Dictionary; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Macros; +using Umbraco.Core.Profiling; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Runtime; +using Umbraco.Core.Services; +using Umbraco.Examine; +using Umbraco.Web.Cache; +using Umbraco.Web.Composing.CompositionRoots; +using Umbraco.Web.Dictionary; +using Umbraco.Web.Editors; +using Umbraco.Web.Features; +using Umbraco.Web.HealthCheck; +using Umbraco.Web.Install; +using Umbraco.Web.Media; +using Umbraco.Web.Media.ThumbnailProviders; +using Umbraco.Web.Mvc; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.Search; +using Umbraco.Web.Security; +using Umbraco.Web.Services; +using Umbraco.Web.SignalR; +using Umbraco.Web.Tour; +using Umbraco.Web.Trees; +using Umbraco.Web.UI.JavaScript; +using Umbraco.Web.WebApi; +using Umbraco.Web._Legacy.Actions; +using Current = Umbraco.Web.Composing.Current; + +namespace Umbraco.Web.Runtime +{ + [RequireComponent(typeof(CoreRuntimeComponent))] + public class WebRuntimeComponent : UmbracoComponentBase, IRuntimeComponent + { + public override void Compose(Composition composition) + { + base.Compose(composition); + + composition.Container.RegisterFrom(); + + //register the install components + //NOTE: i tried to not have these registered if we weren't installing or upgrading but post install when the site restarts + //it still needs to use the install controller so we can't do that + composition.Container.RegisterFrom(); + + var typeLoader = composition.Container.GetInstance(); + var logger = composition.Container.GetInstance(); + var proflog = composition.Container.GetInstance(); + + // register the http context and umbraco context accessors + // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when + // we have no http context, eg when booting Umbraco or in background threads, so instead + // let's use an hybrid accessor that can fall back to a ThreadStatic context. + composition.Container.RegisterSingleton(); + + // register the 'current' umbraco context - transient - for eg controllers + composition.Container.Register(factory => factory.GetInstance().UmbracoContext); + + // register a per-request HttpContextBase object + // is per-request so only one wrapper is created per request + composition.Container.Register(factory => new HttpContextWrapper(factory.GetInstance().HttpContext), new PerRequestLifeTime()); + + // register the published snapshot accessor - the "current" published snapshot is in the umbraco context + composition.Container.RegisterSingleton(); + + // register a per-request UmbracoContext object + // no real need to be per request but assuming it is faster + composition.Container.Register(factory => factory.GetInstance().UmbracoContext, new PerRequestLifeTime()); + + // register the umbraco helper + composition.Container.Register(new PerRequestLifeTime()); + + // register distributed cache + composition.Container.RegisterSingleton(f => new DistributedCache()); + + // replace some services + composition.Container.RegisterSingleton(); + composition.Container.RegisterSingleton(); + composition.Container.RegisterSingleton(); + composition.Container.RegisterSingleton(); + + composition.Container.RegisterSingleton(factory => ExamineManager.Instance); + + // IoC setup for LightInject for MVC/WebApi + // see comments on MixedLightInjectScopeManagerProvider for explainations of what we are doing here + if (!(composition.Container.ScopeManagerProvider is MixedLightInjectScopeManagerProvider smp)) + throw new Exception("Container.ScopeManagerProvider is not MixedLightInjectScopeManagerProvider."); + composition.Container.EnableMvc(); // does container.EnablePerWebRequestScope() + composition.Container.ScopeManagerProvider = smp; // reverts - we will do it last (in WebRuntime) + + composition.Container.RegisterMvcControllers(typeLoader, GetType().Assembly); + composition.Container.EnableWebApi(GlobalConfiguration.Configuration); + composition.Container.RegisterApiControllers(typeLoader, GetType().Assembly); + + XsltExtensionCollectionBuilder.Register(composition.Container) + .AddExtensionObjectProducer(() => typeLoader.GetXsltExtensions()); + + composition.Container.RegisterCollectionBuilder() + .Add(() => typeLoader.GetTypes()); // fixme which searchable trees?! + + composition.Container.RegisterCollectionBuilder() + .Add(() => typeLoader.GetTypes()); + + composition.Container.RegisterCollectionBuilder(); + + composition.Container.RegisterSingleton(); + + // set the default RenderMvcController + Current.DefaultRenderMvcControllerType = typeof(RenderMvcController); // fixme WRONG! + + composition.Container.RegisterCollectionBuilder() + .SetProducer(() => typeLoader.GetActions()); + + var surfaceControllerTypes = new SurfaceControllerTypeCollection(typeLoader.GetSurfaceControllers()); + composition.Container.RegisterInstance(surfaceControllerTypes); + + var umbracoApiControllerTypes = new UmbracoApiControllerTypeCollection(typeLoader.GetUmbracoApiControllers()); + composition.Container.RegisterInstance(umbracoApiControllerTypes); + + // both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be + // discovered when CoreBootManager configures the converters. We HAVE to remove one of them + // here because there cannot be two converters for one property editor - and we want the full + // RteMacroRenderingValueConverter that converts macros, etc. So remove TinyMceValueConverter. + // (the limited one, defined in Core, is there for tests) - same for others + composition.Container.GetInstance() + .Remove() + .Remove() + .Remove(); + + // add all known factories, devs can then modify this list on application + // startup either by binding to events or in their own global.asax + composition.Container.RegisterCollectionBuilder() + .Append(); + + composition.Container.RegisterCollectionBuilder() + //.Append() // not enabled by default + .Append() + .Append(); + + composition.Container.RegisterSingleton(); + + composition.Container.RegisterCollectionBuilder() + // all built-in finders in the correct order, + // devs can then modify this list on application startup + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + + composition.Container.RegisterSingleton(); + + composition.Container.RegisterCollectionBuilder() + .Add(typeLoader.GetThumbnailProviders()); + + composition.Container.RegisterCollectionBuilder() + .Append(typeLoader.GetImageUrlProviders()); + + composition.Container.RegisterSingleton(); + + // register *all* checks, except those marked [HideFromTypeFinder] of course + composition.Container.RegisterCollectionBuilder() + .Add(() => typeLoader.GetTypes()); + + composition.Container.RegisterCollectionBuilder() + .Add(() => typeLoader.GetTypes()); + + // auto-register views + composition.Container.RegisterAuto(typeof(UmbracoViewPage<>)); + + // register published router + composition.Container.Register(); + composition.Container.Register(_ => UmbracoConfig.For.UmbracoSettings().WebRouting); + + // register preview SignalR hub + composition.Container.Register(_ => GlobalHost.ConnectionManager.GetHubContext(), new PerContainerLifetime()); + } + + internal void Initialize( + IRuntimeState runtime, + IUmbracoContextAccessor umbracoContextAccessor, + SurfaceControllerTypeCollection surfaceControllerTypes, + UmbracoApiControllerTypeCollection apiControllerTypes, + IPublishedSnapshotService publishedSnapshotService, + IUserService userService, + IUmbracoSettingsSection umbracoSettings, + IGlobalSettings globalSettings, + UrlProviderCollection urlProviders) + { + // setup mvc and webapi services + SetupMvcAndWebApi(); + + // client dependency + ConfigureClientDependency(globalSettings); + + // Disable the X-AspNetMvc-Version HTTP Header + MvcHandler.DisableMvcResponseHeader = true; + + InstallHelper.DeleteLegacyInstaller(); + + // wrap view engines in the profiling engine + WrapViewEngines(ViewEngines.Engines); + + // add global filters + ConfigureGlobalFilters(); + + // set routes + CreateRoutes(umbracoContextAccessor, globalSettings, surfaceControllerTypes, apiControllerTypes); + + // get an http context + // at that moment, HttpContext.Current != null but its .Request property is null + var httpContext = new HttpContextWrapper(HttpContext.Current); + + // ensure there is an UmbracoContext + // (also sets the accessor) + // this is a *temp* UmbracoContext + UmbracoContext.EnsureContext( + umbracoContextAccessor, + new HttpContextWrapper(HttpContext.Current), + publishedSnapshotService, + new WebSecurity(httpContext, userService, globalSettings), + umbracoSettings, + urlProviders, + globalSettings); + + // ensure WebAPI is initialized, after everything + GlobalConfiguration.Configuration.EnsureInitialized(); + } + + private static void ConfigureGlobalFilters() + { + GlobalFilters.Filters.Add(new EnsurePartialViewMacroViewContextFilterAttribute()); + } + + // internal for tests + internal static void WrapViewEngines(IList viewEngines) + { + if (viewEngines == null || viewEngines.Count == 0) return; + + var originalEngines = viewEngines.Select(e => e).ToArray(); + viewEngines.Clear(); + foreach (var engine in originalEngines) + { + var wrappedEngine = engine is ProfilingViewEngine ? engine : new ProfilingViewEngine(engine); + viewEngines.Add(wrappedEngine); + } + } + + // internal for tests + internal static void CreateRoutes( + IUmbracoContextAccessor umbracoContextAccessor, + IGlobalSettings globalSettings, + SurfaceControllerTypeCollection surfaceControllerTypes, + UmbracoApiControllerTypeCollection apiControllerTypes) + { + var umbracoPath = globalSettings.GetUmbracoMvcArea(); + + // create the front-end route + var defaultRoute = RouteTable.Routes.MapRoute( + "Umbraco_default", + umbracoPath + "/RenderMvc/{action}/{id}", + new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional } + ); + defaultRoute.RouteHandler = new RenderRouteHandler(umbracoContextAccessor, ControllerBuilder.Current.GetControllerFactory()); + + // register install routes + RouteTable.Routes.RegisterArea(); + + // register all back office routes + RouteTable.Routes.RegisterArea(new BackOfficeArea(globalSettings)); + + // plugin controllers must come first because the next route will catch many things + RoutePluginControllers(globalSettings, surfaceControllerTypes, apiControllerTypes); + } + + private static void RoutePluginControllers( + IGlobalSettings globalSettings, + SurfaceControllerTypeCollection surfaceControllerTypes, + UmbracoApiControllerTypeCollection apiControllerTypes) + { + var umbracoPath = globalSettings.GetUmbracoMvcArea(); + + // need to find the plugin controllers and route them + var pluginControllers = surfaceControllerTypes.Concat(apiControllerTypes).ToArray(); + + // local controllers do not contain the attribute + var localControllers = pluginControllers.Where(x => PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace()); + foreach (var s in localControllers) + { + if (TypeHelper.IsTypeAssignableFrom(s)) + RouteLocalSurfaceController(s, umbracoPath); + else if (TypeHelper.IsTypeAssignableFrom(s)) + RouteLocalApiController(s, umbracoPath); + } + + // get the plugin controllers that are unique to each area (group by) + var pluginSurfaceControlleres = pluginControllers.Where(x => PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace() == false); + var groupedAreas = pluginSurfaceControlleres.GroupBy(controller => PluginController.GetMetadata(controller).AreaName); + // loop through each area defined amongst the controllers + foreach (var g in groupedAreas) + { + // create & register an area for the controllers (this will throw an exception if all controllers are not in the same area) + var pluginControllerArea = new PluginControllerArea(globalSettings, g.Select(PluginController.GetMetadata)); + RouteTable.Routes.RegisterArea(pluginControllerArea); + } + } + + private static void RouteLocalApiController(Type controller, string umbracoPath) + { + var meta = PluginController.GetMetadata(controller); + var url = umbracoPath + (meta.IsBackOffice ? "/BackOffice" : "") + "/Api/" + meta.ControllerName + "/{action}/{id}"; + var route = RouteTable.Routes.MapHttpRoute( + $"umbraco-api-{meta.ControllerName}", + url, // url to match + new { controller = meta.ControllerName, id = UrlParameter.Optional }, + new[] { meta.ControllerNamespace }); + if (route.DataTokens == null) // web api routes don't set the data tokens object + route.DataTokens = new RouteValueDictionary(); + route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "api"); //ensure the umbraco token is set + } + + private static void RouteLocalSurfaceController(Type controller, string umbracoPath) + { + var meta = PluginController.GetMetadata(controller); + var url = umbracoPath + "/Surface/" + meta.ControllerName + "/{action}/{id}"; + var route = RouteTable.Routes.MapRoute( + $"umbraco-surface-{meta.ControllerName}", + url, // url to match + new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, + new[] { meta.ControllerNamespace }); // look in this namespace to create the controller + route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "surface"); // ensure the umbraco token is set + route.DataTokens.Add("UseNamespaceFallback", false); // don't look anywhere else except this namespace! + // make it use our custom/special SurfaceMvcHandler + route.RouteHandler = new SurfaceRouteHandler(); + } + + private static void SetupMvcAndWebApi() + { + //don't output the MVC version header (security) + MvcHandler.DisableMvcResponseHeader = true; + + // set master controller factory + var controllerFactory = new MasterControllerFactory(() => Current.FilteredControllerFactories); + ControllerBuilder.Current.SetControllerFactory(controllerFactory); + + // set the render & plugin view engines + ViewEngines.Engines.Add(new RenderViewEngine()); + ViewEngines.Engines.Add(new PluginViewEngine()); + + //set model binder + ModelBinderProviders.BinderProviders.Add(ContentModelBinder.Instance); // is a provider + + ////add the profiling action filter + //GlobalFilters.Filters.Add(new ProfilingActionFilter()); + + GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), + new NamespaceHttpControllerSelector(GlobalConfiguration.Configuration)); + } + + private static void ConfigureClientDependency(IGlobalSettings globalSettings) + { + // Backwards compatibility - set the path and URL type for ClientDependency 1.5.1 [LK] + XmlFileMapper.FileMapDefaultFolder = "~/App_Data/TEMP/ClientDependency"; + BaseCompositeFileProcessingProvider.UrlTypeDefault = CompositeUrlType.Base64QueryStrings; + + // Now we need to detect if we are running umbracoLocalTempStorage as EnvironmentTemp and in that case we want to change the CDF file + // location to be there + if (globalSettings.LocalTempStorageLocation == LocalTempStorage.EnvironmentTemp) + { + var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); + var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", + //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 + appDomainHash); + + //set the file map and composite file default location to the %temp% location + BaseCompositeFileProcessingProvider.CompositeFilePathDefaultFolder + = XmlFileMapper.FileMapDefaultFolder + = Path.Combine(cachePath, "ClientDependency"); + } + + if (ConfigurationManager.GetSection("system.web/httpRuntime") is HttpRuntimeSection section) + { + //set the max url length for CDF to be the smallest of the max query length, max request length + ClientDependency.Core.CompositeFiles.CompositeDependencyHandler.MaxHandlerUrlLength = Math.Min(section.MaxQueryStringLength, section.MaxRequestLength); + } + + //Register a custom renderer - used to process property editor dependencies + var renderer = new DependencyPathRenderer(); + renderer.Initialize("Umbraco.DependencyPathRenderer", new NameValueCollection + { + { "compositeFileHandlerPath", ClientDependencySettings.Instance.CompositeFileHandlerPath } + }); + + ClientDependencySettings.Instance.MvcRendererCollection.Add(renderer); + } + } +} diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index 3ec1f06260..7516026852 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -6,6 +6,7 @@ using System.Web.Hosting; using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Logging; +using LightInject; namespace Umbraco.Web.Scheduling { @@ -39,7 +40,7 @@ namespace Umbraco.Web.Scheduling private readonly ILogger _logger; private readonly object _locker = new object(); - private readonly BufferBlock _tasks = new BufferBlock(new DataflowBlockOptions { }); + private readonly BufferBlock _tasks = new BufferBlock(new DataflowBlockOptions()); // in various places we are testing these vars outside a lock, so make them volatile private volatile bool _isRunning; // is running @@ -54,50 +55,82 @@ namespace Umbraco.Web.Scheduling private bool _terminated; // remember we've terminated private readonly TaskCompletionSource _terminatedSource = new TaskCompletionSource(); // enable awaiting termination - /// - /// Initializes a new instance of the class. - /// - /// A logger. - /// An optional action to execute when the main domain status is aquired. - /// An optional action to execute when the main domain status is released. - public BackgroundTaskRunner(ILogger logger, Action mainDomInstall = null, Action mainDomRelease = null) - : this(typeof(T).FullName, new BackgroundTaskRunnerOptions(), logger, mainDomInstall, mainDomRelease) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the runner. - /// A logger. - /// An optional action to execute when the main domain status is aquired. - /// An optional action to execute when the main domain status is released. - public BackgroundTaskRunner(string name, ILogger logger, Action mainDomInstall = null, Action mainDomRelease = null) - : this(name, new BackgroundTaskRunnerOptions(), logger, mainDomInstall, mainDomRelease) - { } - - /// - /// Initializes a new instance of the class with a set of options. - /// - /// The set of options. - /// A logger. - /// An optional action to execute when the main domain status is aquired. - /// An optional action to execute when the main domain status is released. - public BackgroundTaskRunner(BackgroundTaskRunnerOptions options, ILogger logger, Action mainDomInstall = null, Action mainDomRelease = null) - : this(typeof(T).FullName, options, logger, mainDomInstall, mainDomRelease) - { } - - /// - /// Initializes a new instance of the class with a set of options. - /// - /// The name of the runner. - /// The set of options. - /// A logger. - /// An optional action to execute when the main domain status is aquired. - /// An optional action to execute when the main domain status is released. - public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options, ILogger logger, Action mainDomInstall = null, Action mainDomRelease = null) + // fixme - this is temp + // at the moment MainDom is internal so we have to find a way to hook into it - temp + public class MainDomHook { - if (options == null) throw new ArgumentNullException("options"); - if (logger == null) throw new ArgumentNullException("logger"); + private MainDomHook(MainDom mainDom, Action install, Action release) + { + MainDom = mainDom; + Install = install; + Release = release; + } + + internal MainDom MainDom { get; } + public Action Install { get; } + public Action Release { get; } + + public static MainDomHook Create(Action install, Action release) + { + return new MainDomHook(Core.Composing.Current.Container.GetInstance(), install, release); + } + + public static MainDomHook CreateForTest(Action install, Action release) + { + return new MainDomHook(null, install, release); + } + + public bool Register() + { + if (MainDom != null) + return MainDom.Register(Install, Release); + + // tests + Install?.Invoke(); + return true; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// A logger. + /// An optional main domain hook. + public BackgroundTaskRunner(ILogger logger, MainDomHook hook = null) + : this(typeof(T).FullName, new BackgroundTaskRunnerOptions(), logger, hook) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the runner. + /// A logger. + /// An optional main domain hook. + public BackgroundTaskRunner(string name, ILogger logger, MainDomHook hook = null) + : this(name, new BackgroundTaskRunnerOptions(), logger, hook) + { } + + /// + /// Initializes a new instance of the class with a set of options. + /// + /// The set of options. + /// A logger. + /// An optional main domain hook. + public BackgroundTaskRunner(BackgroundTaskRunnerOptions options, ILogger logger, MainDomHook hook = null) + : this(typeof(T).FullName, options, logger, hook) + { } + + /// + /// Initializes a new instance of the class with a set of options. + /// + /// The name of the runner. + /// The set of options. + /// A logger. + /// An optional main domain hook. + public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options, ILogger logger, MainDomHook hook = null) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); _options = options; _logPrefix = "[" + name + "] "; _logger = logger; @@ -105,16 +138,8 @@ namespace Umbraco.Web.Scheduling if (options.Hosted) HostingEnvironment.RegisterObject(this); - if (mainDomInstall != null || mainDomRelease != null) - { - var appContext = ApplicationContext.Current; - var mainDom = appContext == null ? null : appContext.MainDom; - var reg = mainDom == null || ApplicationContext.Current.MainDom.Register(mainDomInstall, mainDomRelease); - if (reg == false) - _completed = _terminated = true; - if (reg && mainDom == null && mainDomInstall != null) - mainDomInstall(); - } + if (hook != null) + _completed = _terminated = hook.Register() == false; if (options.AutoStart && _terminated == false) StartUp(); @@ -123,26 +148,17 @@ namespace Umbraco.Web.Scheduling /// /// Gets the number of tasks in the queue. /// - public int TaskCount - { - get { return _tasks.Count; } - } + public int TaskCount => _tasks.Count; /// /// Gets a value indicating whether a threading task is currently running. /// - public bool IsRunning - { - get { return _isRunning; } - } + public bool IsRunning => _isRunning; /// /// Gets a value indicating whether the runner has completed and cannot accept tasks anymore. /// - public bool IsCompleted - { - get { return _completed; } - } + public bool IsCompleted => _completed; /// /// Gets the running threading task as an immutable awaitable. @@ -217,7 +233,7 @@ namespace Umbraco.Web.Scheduling throw new InvalidOperationException("The task runner has completed."); // add task - _logger.Debug(_logPrefix + "Task added {0}", () => task.GetType().FullName); + _logger.Debug(() => _logPrefix + "Task added " + task.GetType().FullName); _tasks.Post(task); // start @@ -237,12 +253,12 @@ namespace Umbraco.Web.Scheduling { if (_completed) { - _logger.Debug(_logPrefix + "Task cannot be added {0}, the task runner has already shutdown", () => task.GetType().FullName); + _logger.Debug(() => _logPrefix + $"Task cannot be added {task.GetType().FullName}, the task runner has already shutdown"); return false; } // add task - _logger.Debug(_logPrefix + "Task added {0}", () => task.GetType().FullName); + _logger.Debug(() => _logPrefix + "Task added " + task.GetType().FullName); _tasks.Post(task); // start @@ -262,8 +278,7 @@ namespace Umbraco.Web.Scheduling { if (_completed) throw new InvalidOperationException("The task runner has completed."); - if (_cancelTokenSource != null) - _cancelTokenSource.Cancel(); + _cancelTokenSource?.Cancel(); } } @@ -340,8 +355,7 @@ namespace Umbraco.Web.Scheduling // tasks in the queue will be executed... if (wait == false) return; - if (_runningTask != null) - _runningTask.Wait(); // wait for whatever is running to end... + _runningTask?.Wait(); // wait for whatever is running to end... } private async Task Pump() diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs index 28c814db24..5813799f05 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunnerOptions.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Web.Scheduling +namespace Umbraco.Web.Scheduling { /// /// Provides options to the class. @@ -50,4 +50,4 @@ namespace Umbraco.Web.Scheduling /// public bool Hosted { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs b/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs index c10c1c5315..68e306462f 100644 --- a/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs +++ b/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs @@ -1,10 +1,8 @@ -using System.Configuration; -using System.Linq; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Logging; using Umbraco.Core.Sync; using Umbraco.Web.HealthCheck; @@ -13,39 +11,48 @@ namespace Umbraco.Web.Scheduling { internal class HealthCheckNotifier : RecurringTaskBase { - private readonly ApplicationContext _appContext; - private readonly IHealthCheckResolver _healthCheckResolver; + private readonly IRuntimeState _runtimeState; + private readonly HealthCheckCollection _healthChecks; + private readonly HealthCheckNotificationMethodCollection _notifications; + private readonly ILogger _logger; + private readonly ProfilingLogger _proflog; - public HealthCheckNotifier(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - ApplicationContext appContext) + public HealthCheckNotifier(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, + IRuntimeState runtimeState, + ILogger logger, ProfilingLogger proflog) : base(runner, delayMilliseconds, periodMilliseconds) { - _appContext = appContext; - _healthCheckResolver = HealthCheckResolver.Current; + _healthChecks = healthChecks; + _notifications = notifications; + _runtimeState = runtimeState; + _logger = logger; + _proflog = proflog; } public override async Task PerformRunAsync(CancellationToken token) { - if (_appContext == null) return true; // repeat... + if (_runtimeState.Level != RuntimeLevel.Run) + return true; // repeat... - switch (_appContext.GetCurrentServerRole()) + switch (_runtimeState.ServerRole) { case ServerRole.Slave: - LogHelper.Debug("Does not run on slave servers."); + _logger.Debug("Does not run on slave servers."); return true; // DO repeat, server role can change case ServerRole.Unknown: - LogHelper.Debug("Does not run on servers with unknown role."); + _logger.Debug("Does not run on servers with unknown role."); return true; // DO repeat, server role can change } // ensure we do not run if not main domain, but do NOT lock it - if (_appContext.MainDom.IsMainDom == false) + if (_runtimeState.IsMainDom == false) { - LogHelper.Debug("Does not run if not MainDom."); + _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down } - using (_appContext.ProfilingLogger.DebugDuration("Health checks executing", "Health checks complete")) + using (_proflog.DebugDuration("Health checks executing", "Health checks complete")) { var healthCheckConfig = UmbracoConfig.For.HealthCheck(); @@ -58,26 +65,20 @@ namespace Umbraco.Web.Scheduling .Distinct() .ToArray(); - var checks = _healthCheckResolver.HealthChecks + var checks = _healthChecks .Where(x => disabledCheckIds.Contains(x.Id) == false); var results = new HealthCheckResults(checks); results.LogResults(); // Send using registered notification methods that are enabled - var registeredNotificationMethods = HealthCheckNotificationMethodResolver.Current.NotificationMethods.Where(x => x.Enabled); - foreach (var notificationMethod in registeredNotificationMethods) - { - await notificationMethod.SendAsync(results); - } + foreach (var notificationMethod in _notifications.Where(x => x.Enabled)) + await notificationMethod.SendAsync(results, token); } return true; // repeat } - public override bool IsAsync - { - get { return true; } - } + public override bool IsAsync => true; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Scheduling/IBackgroundTask.cs b/src/Umbraco.Web/Scheduling/IBackgroundTask.cs index 2e67ed1790..b1285d0080 100644 --- a/src/Umbraco.Web/Scheduling/IBackgroundTask.cs +++ b/src/Umbraco.Web/Scheduling/IBackgroundTask.cs @@ -27,4 +27,4 @@ namespace Umbraco.Web.Scheduling /// bool IsAsync { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Scheduling/IBackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/IBackgroundTaskRunner.cs index 8c0117541b..e8fd754622 100644 --- a/src/Umbraco.Web/Scheduling/IBackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/IBackgroundTaskRunner.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web.Hosting; namespace Umbraco.Web.Scheduling @@ -17,4 +17,4 @@ namespace Umbraco.Web.Scheduling // fixme - complete the interface? } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index f4beb9d5b8..74c9f79d42 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -1,10 +1,8 @@ -using System; -using System.Net; +using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Umbraco.Core; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Sync; @@ -12,48 +10,49 @@ namespace Umbraco.Web.Scheduling { internal class KeepAlive : RecurringTaskBase { - private readonly ApplicationContext _appContext; + private readonly IRuntimeState _runtime; + private readonly ILogger _logger; + private readonly ProfilingLogger _proflog; - public KeepAlive(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - ApplicationContext appContext) + public KeepAlive(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + IRuntimeState runtime, ILogger logger, ProfilingLogger proflog) : base(runner, delayMilliseconds, periodMilliseconds) { - _appContext = appContext; + _runtime = runtime; + _logger = logger; + _proflog = proflog; } - private ILogger Logger { get { return _appContext.ProfilingLogger.Logger; } } - public override async Task PerformRunAsync(CancellationToken token) { - if (_appContext == null) return true; // repeat... - - switch (_appContext.GetCurrentServerRole()) + // not on slaves nor unknown role servers + switch (_runtime.ServerRole) { case ServerRole.Slave: - Logger.Debug("Does not run on slave servers."); - return true; // DO repeat, server role can change + _logger.Debug("Does not run on slave servers."); + return true; // role may change! case ServerRole.Unknown: - Logger.Debug("Does not run on servers with unknown role."); - return true; // DO repeat, server role can change + _logger.Debug("Does not run on servers with unknown role."); + return true; // role may change! } // ensure we do not run if not main domain, but do NOT lock it - if (_appContext.MainDom.IsMainDom == false) + if (_runtime.IsMainDom == false) { - LogHelper.Debug("Does not run if not MainDom."); + _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down } - using (_appContext.ProfilingLogger.DebugDuration("Keep alive executing", "Keep alive complete")) + using (_proflog.DebugDuration("Keep alive executing", "Keep alive complete")) { string umbracoAppUrl = null; try { - umbracoAppUrl = _appContext.UmbracoApplicationUrl; + umbracoAppUrl = _runtime.ApplicationUrl.ToString(); if (umbracoAppUrl.IsNullOrWhiteSpace()) { - LogHelper.Warn("No url for service (yet), skip."); + _logger.Warn("No url for service (yet), skip."); return true; // repeat } @@ -66,16 +65,13 @@ namespace Umbraco.Web.Scheduling } catch (Exception e) { - LogHelper.Error(string.Format("Failed (at \"{0}\").", umbracoAppUrl), e); + _logger.Error(string.Format("Failed (at \"{0}\").", umbracoAppUrl), e); } } return true; // repeat } - public override bool IsAsync - { - get { return true; } - } + public override bool IsAsync => true; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs b/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs index b3a23d8081..1c86b873bb 100644 --- a/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs +++ b/src/Umbraco.Web/Scheduling/LatchedBackgroundTaskBase.cs @@ -5,13 +5,6 @@ using Umbraco.Core; namespace Umbraco.Web.Scheduling { - /// - /// Provides a base class for latched background tasks. - /// - /// Implement by overriding Run or RunAsync and then IsAsync accordingly, - /// depending on whether the task is implemented as a sync or async method, and then - /// optionnally overriding RunsOnShutdown, to indicate whether the latched task should run - /// immediately on shutdown, or just be abandonned (default). public abstract class LatchedBackgroundTaskBase : DisposableObjectSlim, ILatchedBackgroundTask { private TaskCompletionSource _latch; @@ -42,15 +35,9 @@ namespace Umbraco.Web.Scheduling /// public abstract bool IsAsync { get; } - public Task Latch - { - get { return _latch.Task; } - } + public Task Latch => _latch.Task; - public bool IsLatched - { - get { return _latch.Task.IsCompleted == false; } - } + public bool IsLatched => _latch.Task.IsCompleted == false; protected void Release() { @@ -62,7 +49,7 @@ namespace Umbraco.Web.Scheduling _latch = new TaskCompletionSource(); } - public virtual bool RunsOnShutdown { get { return false; } } + public virtual bool RunsOnShutdown => false; // the task is going to be disposed after execution, // unless it is latched again, thus indicating it wants to diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs index 9000fc72cf..9edd203856 100644 --- a/src/Umbraco.Web/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -1,31 +1,39 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; -using System.Web; -using System.Web.Caching; -using umbraco.BusinessLogic; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; using Umbraco.Core.Sync; namespace Umbraco.Web.Scheduling { internal class LogScrubber : RecurringTaskBase { - private readonly ApplicationContext _appContext; + private readonly IRuntimeState _runtime; + private readonly IAuditService _auditService; private readonly IUmbracoSettingsSection _settings; + private readonly ILogger _logger; + private readonly ProfilingLogger _proflog; + private readonly IScopeProvider _scopeProvider; public LogScrubber(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - ApplicationContext appContext, IUmbracoSettingsSection settings) + IRuntimeState runtime, IAuditService auditService, IUmbracoSettingsSection settings, IScopeProvider scopeProvider, ILogger logger, ProfilingLogger proflog) : base(runner, delayMilliseconds, periodMilliseconds) { - _appContext = appContext; + _runtime = runtime; + _auditService = auditService; _settings = settings; + _scopeProvider = scopeProvider; + _logger = logger; + _proflog = proflog; } // maximum age, in minutes - private static int GetLogScrubbingMaximumAge(IUmbracoSettingsSection settings) + private int GetLogScrubbingMaximumAge(IUmbracoSettingsSection settings) { var maximumAge = 24 * 60; // 24 hours, in minutes try @@ -35,13 +43,13 @@ namespace Umbraco.Web.Scheduling } catch (Exception e) { - LogHelper.Error("Unable to locate a log scrubbing maximum age. Defaulting to 24 hours.", e); + _logger.Error("Unable to locate a log scrubbing maximum age. Defaulting to 24 hours.", e); } return maximumAge; } - public static int GetLogScrubbingInterval(IUmbracoSettingsSection settings) + public static int GetLogScrubbingInterval(IUmbracoSettingsSection settings, ILogger logger) { var interval = 4 * 60 * 60 * 1000; // 4 hours, in milliseconds try @@ -51,47 +59,42 @@ namespace Umbraco.Web.Scheduling } catch (Exception e) { - LogHelper.Error("Unable to locate a log scrubbing interval. Defaulting to 4 hours.", e); + logger.Error("Unable to locate a log scrubbing interval. Defaulting to 4 hours.", e); } return interval; } public override bool PerformRun() { - if (_appContext == null) return true; // repeat... - - switch (_appContext.GetCurrentServerRole()) + switch (_runtime.ServerRole) { case ServerRole.Slave: - LogHelper.Debug("Does not run on slave servers."); + _logger.Debug("Does not run on slave servers."); return true; // DO repeat, server role can change case ServerRole.Unknown: - LogHelper.Debug("Does not run on servers with unknown role."); + _logger.Debug("Does not run on servers with unknown role."); return true; // DO repeat, server role can change } // ensure we do not run if not main domain, but do NOT lock it - if (_appContext.MainDom.IsMainDom == false) + if (_runtime.IsMainDom == false) { - LogHelper.Debug("Does not run if not MainDom."); + _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down } // running on a background task, and Log.CleanLogs uses the old SqlHelper, // better wrap in a scope and ensure it's all cleaned up and nothing leaks - using (var scope = ApplicationContext.Current.ScopeProvider.CreateScope()) - using (DisposableTimer.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) + using (var scope = _scopeProvider.CreateScope()) + using (_proflog.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) { - Log.CleanLogs(GetLogScrubbingMaximumAge(_settings)); + _auditService.CleanLogs(GetLogScrubbingMaximumAge(_settings)); scope.Complete(); } return true; // repeat } - public override bool IsAsync - { - get { return false; } - } + public override bool IsAsync => false; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs b/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs index 447a98527e..7a51890c12 100644 --- a/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs +++ b/src/Umbraco.Web/Scheduling/RecurringTaskBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; @@ -104,4 +104,4 @@ namespace Umbraco.Web.Scheduling _timer.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 52cca003b7..5b0ca2ee1b 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -1,109 +1,73 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using System.Web; -using System.Web.Hosting; -using umbraco; +using System; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Publishing; +using Umbraco.Core.Services; using Umbraco.Core.Sync; -using Umbraco.Web.Routing; -using Umbraco.Web.Security; namespace Umbraco.Web.Scheduling { internal class ScheduledPublishing : RecurringTaskBase { - private readonly ApplicationContext _appContext; - private readonly IUmbracoSettingsSection _settings; + private readonly IRuntimeState _runtime; + private readonly IContentService _contentService; + private readonly ILogger _logger; public ScheduledPublishing(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - ApplicationContext appContext, IUmbracoSettingsSection settings) + IRuntimeState runtime, IContentService contentService, ILogger logger) : base(runner, delayMilliseconds, periodMilliseconds) { - _appContext = appContext; - _settings = settings; + _runtime = runtime; + _contentService = contentService; + _logger = logger; } - private ILogger Logger { get { return _appContext.ProfilingLogger.Logger; } } - public override bool PerformRun() { - if (_appContext == null) return true; // repeat... - if (Suspendable.ScheduledPublishing.CanRun == false) return true; // repeat, later - switch (_appContext.GetCurrentServerRole()) + switch (_runtime.ServerRole) { case ServerRole.Slave: - Logger.Debug("Does not run on slave servers."); + _logger.Debug("Does not run on slave servers."); return true; // DO repeat, server role can change case ServerRole.Unknown: - Logger.Debug("Does not run on servers with unknown role."); + _logger.Debug("Does not run on servers with unknown role."); return true; // DO repeat, server role can change } // ensure we do not run if not main domain, but do NOT lock it - if (_appContext.MainDom.IsMainDom == false) + if (_runtime.IsMainDom == false) { - LogHelper.Debug("Does not run if not MainDom."); + _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down } - UmbracoContext tempContext = null; + // do NOT run publishing if not properly running + if (_runtime.Level != RuntimeLevel.Run) + { + _logger.Debug("Does not run if run level is not Run."); + return true; // repeat/wait + } + try { - // DO not run publishing if content is re-loading - if (content.Instance.isInitializing == false) - { - //TODO: We should remove this in v8, this is a backwards compat hack - // see notes in CacheRefresherEventHandler - // because notifications will not be sent if there is no UmbracoContext - // see NotificationServiceExtensions - var httpContext = new HttpContextWrapper(new HttpContext(new SimpleWorkerRequest("temp.aspx", "", new StringWriter()))); - tempContext = UmbracoContext.EnsureContext( - httpContext, - _appContext, - new WebSecurity(httpContext, _appContext), - _settings, - UrlProviderResolver.Current.Providers, - true); - - var publisher = new ScheduledPublisher(_appContext.Services.ContentService); - var count = publisher.CheckPendingAndProcess(); - Logger.Debug(() => string.Format("Processed {0} items", count)); - } + // run + // fixme context & events during scheduled publishing? + // in v7 we create an UmbracoContext and an HttpContext, and cache instructions + // are batched, and we have to explicitely flush them, how is it going to work here? + var publisher = new ScheduledPublisher(_contentService, _logger); + var count = publisher.CheckPendingAndProcess(); } catch (Exception e) { - Logger.Error("Failed (see exception).", e); - } - finally - { - if (tempContext != null) - { - // because we created an http context and assigned it to UmbracoContext, - // the batched messenger does batch instructions, but since there is no - // request, we need to explicitely tell it to flush the batch of instrs. - var batchedMessenger = ServerMessengerResolver.Current.Messenger as BatchedDatabaseServerMessenger; - if (batchedMessenger != null) - batchedMessenger.FlushBatch(); - - tempContext.Dispose(); // nulls the ThreadStatic context - } + _logger.Error("Failed.", e); } return true; // repeat } - public override bool IsAsync - { - get { return false; } - } + public override bool IsAsync => false; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs index c69d42bd27..dc0f77e0fc 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledTasks.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledTasks.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Net; using System.Net.Http; @@ -17,16 +17,20 @@ namespace Umbraco.Web.Scheduling internal class ScheduledTasks : RecurringTaskBase { - private readonly ApplicationContext _appContext; + private readonly IRuntimeState _runtime; private readonly IUmbracoSettingsSection _settings; + private readonly ILogger _logger; + private readonly ProfilingLogger _proflog; private static readonly Hashtable ScheduledTaskTimes = new Hashtable(); - public ScheduledTasks(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - ApplicationContext appContext, IUmbracoSettingsSection settings) + public ScheduledTasks(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + IRuntimeState runtime, IUmbracoSettingsSection settings, ILogger logger, ProfilingLogger proflog) : base(runner, delayMilliseconds, periodMilliseconds) { - _appContext = appContext; + _runtime = runtime; _settings = settings; + _logger = logger; + _proflog = proflog; } private async Task ProcessTasksAsync(CancellationToken token) @@ -54,7 +58,7 @@ namespace Umbraco.Web.Scheduling { var taskResult = await GetTaskByHttpAync(t.Url, token); if (t.Log) - LogHelper.Info(string.Format("{0} has been called with response: {1}", t.Alias, taskResult)); + _logger.Info(string.Format("{0} has been called with response: {1}", t.Alias, taskResult)); } } } @@ -63,6 +67,9 @@ namespace Umbraco.Web.Scheduling { using (var wc = new HttpClient()) { + // url could be relative, so better set a base url for the http client + wc.BaseAddress = _runtime.ApplicationUrl; + var request = new HttpRequestMessage(HttpMethod.Get, url); //TODO: pass custom the authorization header, currently these aren't really secured! @@ -75,7 +82,7 @@ namespace Umbraco.Web.Scheduling } catch (Exception ex) { - LogHelper.Error("An error occurred calling web task for url: " + url, ex); + _logger.Error("An error occurred calling web task for url: " + url, ex); } return false; } @@ -83,26 +90,24 @@ namespace Umbraco.Web.Scheduling public override async Task PerformRunAsync(CancellationToken token) { - if (_appContext == null) return true; // repeat... - - switch (_appContext.GetCurrentServerRole()) + switch (_runtime.ServerRole) { case ServerRole.Slave: - LogHelper.Debug("Does not run on slave servers."); + _logger.Debug("Does not run on slave servers."); return true; // DO repeat, server role can change case ServerRole.Unknown: - LogHelper.Debug("Does not run on servers with unknown role."); + _logger.Debug("Does not run on servers with unknown role."); return true; // DO repeat, server role can change } // ensure we do not run if not main domain, but do NOT lock it - if (_appContext.MainDom.IsMainDom == false) + if (_runtime.IsMainDom == false) { - LogHelper.Debug("Does not run if not MainDom."); + _logger.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down } - using (DisposableTimer.DebugDuration(() => "Scheduled tasks executing", () => "Scheduled tasks complete")) + using (_proflog.DebugDuration("Scheduled tasks executing", "Scheduled tasks complete")) { try { @@ -110,16 +115,13 @@ namespace Umbraco.Web.Scheduling } catch (Exception ee) { - LogHelper.Error("Error executing scheduled task", ee); + _logger.Error("Error executing scheduled task", ee); } } return true; // repeat } - public override bool IsAsync - { - get { return true; } - } + public override bool IsAsync => true; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs deleted file mode 100644 index 5a8d409a8c..0000000000 --- a/src/Umbraco.Web/Scheduling/Scheduler.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Threading; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.HealthChecks; -using Umbraco.Core.Logging; -using Umbraco.Web.HealthCheck; -using Umbraco.Web.Routing; - -namespace Umbraco.Web.Scheduling -{ - /// - /// Used to do the scheduling for tasks, publishing, etc... - /// - /// - /// All tasks are run in a background task runner which is web aware and will wind down the task correctly instead of killing it completely when - /// the app domain shuts down. - /// - internal sealed class Scheduler : ApplicationEventHandler - { - private BackgroundTaskRunner _keepAliveRunner; - private BackgroundTaskRunner _publishingRunner; - private BackgroundTaskRunner _tasksRunner; - private BackgroundTaskRunner _scrubberRunner; - private BackgroundTaskRunner _healthCheckRunner; - private bool _started = false; - private object _locker = new object(); - private IBackgroundTask[] _tasks; - - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - if (umbracoApplication.Context == null) - return; - - // backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly - _keepAliveRunner = new BackgroundTaskRunner("KeepAlive", applicationContext.ProfilingLogger.Logger); - _publishingRunner = new BackgroundTaskRunner("ScheduledPublishing", applicationContext.ProfilingLogger.Logger); - _tasksRunner = new BackgroundTaskRunner("ScheduledTasks", applicationContext.ProfilingLogger.Logger); - _scrubberRunner = new BackgroundTaskRunner("LogScrubber", applicationContext.ProfilingLogger.Logger); - _healthCheckRunner = new BackgroundTaskRunner("HealthCheckNotifier", applicationContext.ProfilingLogger.Logger); - - //We will start the whole process when a successful request is made - UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt; - } - - private void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e) - { - switch (e.Outcome) - { - case EnsureRoutableOutcome.IsRoutable: - case EnsureRoutableOutcome.NotDocumentRequest: - RegisterBackgroundTasks(e); - break; - } - } - - private void RegisterBackgroundTasks(UmbracoRequestEventArgs e) - { - //remove handler, we're done - UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt; - - LazyInitializer.EnsureInitialized(ref _tasks, ref _started, ref _locker, () => - { - LogHelper.Debug(() => "Initializing the scheduler"); - var settings = UmbracoConfig.For.UmbracoSettings(); - - var healthCheckConfig = UmbracoConfig.For.HealthCheck(); - - const int delayMilliseconds = 60000; - var tasks = new List - { - new KeepAlive(_keepAliveRunner, delayMilliseconds, 300000, e.UmbracoContext.Application), - new ScheduledPublishing(_publishingRunner, delayMilliseconds, 60000, e.UmbracoContext.Application, settings), - new ScheduledTasks(_tasksRunner, delayMilliseconds, 60000, e.UmbracoContext.Application, settings), - new LogScrubber(_scrubberRunner, delayMilliseconds, LogScrubber.GetLogScrubbingInterval(settings), e.UmbracoContext.Application, settings) - }; - - if (healthCheckConfig.NotificationSettings.Enabled) - { - // If first run time not set, start with just small delay after application start - int delayInMilliseconds; - if (string.IsNullOrEmpty(healthCheckConfig.NotificationSettings.FirstRunTime)) - { - delayInMilliseconds = delayMilliseconds; - } - else - { - // Otherwise start at scheduled time - delayInMilliseconds = DateTime.Now.PeriodicMinutesFrom(healthCheckConfig.NotificationSettings.FirstRunTime) * 60 * 1000; - if (delayInMilliseconds < delayMilliseconds) - { - delayInMilliseconds = delayMilliseconds; - } - } - - var periodInMilliseconds = healthCheckConfig.NotificationSettings.PeriodInHours * 60 * 60 * 1000; - tasks.Add(new HealthCheckNotifier(_healthCheckRunner, delayInMilliseconds, periodInMilliseconds, e.UmbracoContext.Application)); - } - - // ping/keepalive - // on all servers - _keepAliveRunner.TryAdd(tasks[0]); - - // scheduled publishing/unpublishing - // install on all, will only run on non-slaves servers - _publishingRunner.TryAdd(tasks[1]); - - _tasksRunner.TryAdd(tasks[2]); - - // log scrubbing - // install on all, will only run on non-slaves servers - _scrubberRunner.TryAdd(tasks[3]); - - if (healthCheckConfig.NotificationSettings.Enabled) - { - _healthCheckRunner.TryAdd(tasks[4]); - } - - OnInitializing(tasks); - - return tasks.ToArray(); - }); - } - - public static event EventHandler> Initializing; - - private static void OnInitializing(List e) - { - var handler = Initializing; - if (handler != null) handler(null, e); - } - } -} diff --git a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs new file mode 100644 index 0000000000..3cadb1d43e --- /dev/null +++ b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.HealthChecks; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Web.HealthCheck; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Scheduling +{ + /// + /// Used to do the scheduling for tasks, publishing, etc... + /// + /// + /// All tasks are run in a background task runner which is web aware and will wind down + /// the task correctly instead of killing it completely when the app domain shuts down. + /// + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + internal sealed class SchedulerComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + private IRuntimeState _runtime; + private IContentService _contentService; + private IAuditService _auditService; + private ILogger _logger; + private ProfilingLogger _proflog; + private IScopeProvider _scopeProvider; + private HealthCheckCollection _healthChecks; + private HealthCheckNotificationMethodCollection _notifications; + + private BackgroundTaskRunner _keepAliveRunner; + private BackgroundTaskRunner _publishingRunner; + private BackgroundTaskRunner _tasksRunner; + private BackgroundTaskRunner _scrubberRunner; + private BackgroundTaskRunner _healthCheckRunner; + + private bool _started; + private object _locker = new object(); + private IBackgroundTask[] _tasks; + + public void Initialize(IRuntimeState runtime, + IContentService contentService, IAuditService auditService, + HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, + IScopeProvider scopeProvider, ILogger logger, ProfilingLogger proflog) + { + _runtime = runtime; + _contentService = contentService; + _auditService = auditService; + _scopeProvider = scopeProvider; + _logger = logger; + _proflog = proflog; + + _healthChecks = healthChecks; + _notifications = notifications; + + // backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly + _keepAliveRunner = new BackgroundTaskRunner("KeepAlive", logger); + _publishingRunner = new BackgroundTaskRunner("ScheduledPublishing", logger); + _tasksRunner = new BackgroundTaskRunner("ScheduledTasks", logger); + _scrubberRunner = new BackgroundTaskRunner("LogScrubber", logger); + _healthCheckRunner = new BackgroundTaskRunner("HealthCheckNotifier", logger); + + // we will start the whole process when a successful request is made + UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce; + } + + private void RegisterBackgroundTasksOnce(object sender, RoutableAttemptEventArgs e) + { + switch (e.Outcome) + { + case EnsureRoutableOutcome.IsRoutable: + case EnsureRoutableOutcome.NotDocumentRequest: + UmbracoModule.RouteAttempt -= RegisterBackgroundTasksOnce; + RegisterBackgroundTasks(); + break; + } + } + + private void RegisterBackgroundTasks() + { + LazyInitializer.EnsureInitialized(ref _tasks, ref _started, ref _locker, () => + { + _logger.Debug(() => "Initializing the scheduler"); + var settings = UmbracoConfig.For.UmbracoSettings(); + + var tasks = new List(); + + tasks.Add(RegisterKeepAlive()); + tasks.Add(RegisterScheduledPublishing()); + tasks.Add(RegisterTaskRunner(settings)); + tasks.Add(RegisterLogScrubber(settings)); + + var healthCheckConfig = UmbracoConfig.For.HealthCheck(); + if (healthCheckConfig.NotificationSettings.Enabled) + tasks.Add(RegisterHealthCheckNotifier(healthCheckConfig, _healthChecks, _notifications, _logger, _proflog)); + + return tasks.ToArray(); + }); + } + + private IBackgroundTask RegisterKeepAlive() + { + // ping/keepalive + // on all servers + var task = new KeepAlive(_keepAliveRunner, 60000, 300000, _runtime, _logger, _proflog); + _keepAliveRunner.TryAdd(task); + return task; + } + + private IBackgroundTask RegisterScheduledPublishing() + { + // scheduled publishing/unpublishing + // install on all, will only run on non-slaves servers + var task = new ScheduledPublishing(_publishingRunner, 60000, 60000, _runtime, _contentService, _logger); + _publishingRunner.TryAdd(task); + return task; + } + + private IBackgroundTask RegisterTaskRunner(IUmbracoSettingsSection settings) + { + var task = new ScheduledTasks(_tasksRunner, 60000, 60000, _runtime, settings, _logger, _proflog); + _tasksRunner.TryAdd(task); + return task; + } + + private IBackgroundTask RegisterHealthCheckNotifier(IHealthChecks healthCheckConfig, + HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, + ILogger logger, ProfilingLogger proflog) + { + // If first run time not set, start with just small delay after application start + int delayInMilliseconds; + if (string.IsNullOrEmpty(healthCheckConfig.NotificationSettings.FirstRunTime)) + { + delayInMilliseconds = 60000; + } + else + { + // Otherwise start at scheduled time + delayInMilliseconds = DateTime.Now.PeriodicMinutesFrom(healthCheckConfig.NotificationSettings.FirstRunTime) * 60 * 1000; + if (delayInMilliseconds < 60000) + { + delayInMilliseconds = 60000; + } + } + + var periodInMilliseconds = healthCheckConfig.NotificationSettings.PeriodInHours * 60 * 60 * 1000; + var task = new HealthCheckNotifier(_healthCheckRunner, delayInMilliseconds, periodInMilliseconds, healthChecks, notifications, _runtime, logger, proflog); + return task; + } + + private IBackgroundTask RegisterLogScrubber(IUmbracoSettingsSection settings) + { + // log scrubbing + // install on all, will only run on non-slaves servers + var task = new LogScrubber(_scrubberRunner, 60000, LogScrubber.GetLogScrubbingInterval(settings, _logger), _runtime, _auditService, settings, _scopeProvider, _logger, _proflog); + _scrubberRunner.TryAdd(task); + return task; + } + } +} diff --git a/src/Umbraco.Web/Scheduling/TaskAndFactoryExtensions.cs b/src/Umbraco.Web/Scheduling/TaskAndFactoryExtensions.cs index a57ea904b0..7220e77e0c 100644 --- a/src/Umbraco.Web/Scheduling/TaskAndFactoryExtensions.cs +++ b/src/Umbraco.Web/Scheduling/TaskAndFactoryExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; @@ -59,4 +59,4 @@ namespace Umbraco.Web.Scheduling #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Scheduling/TaskEventArgs.cs b/src/Umbraco.Web/Scheduling/TaskEventArgs.cs index 5e83b14231..972bc12901 100644 --- a/src/Umbraco.Web/Scheduling/TaskEventArgs.cs +++ b/src/Umbraco.Web/Scheduling/TaskEventArgs.cs @@ -39,4 +39,4 @@ namespace Umbraco.Web.Scheduling ///
public Exception Exception { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Scheduling/ThreadingTaskImmutable.cs b/src/Umbraco.Web/Scheduling/ThreadingTaskImmutable.cs index 1bb5fcbf41..b1ea0d7f71 100644 --- a/src/Umbraco.Web/Scheduling/ThreadingTaskImmutable.cs +++ b/src/Umbraco.Web/Scheduling/ThreadingTaskImmutable.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -41,4 +41,4 @@ namespace Umbraco.Web.Scheduling get { return _task.Status; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs new file mode 100644 index 0000000000..278f124344 --- /dev/null +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -0,0 +1,620 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Xml.Linq; +using Examine; +using Examine.LuceneEngine; +using Examine.LuceneEngine.Providers; +using Lucene.Net.Documents; +using Lucene.Net.Index; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; +using Umbraco.Core.Services.Implement; +using Umbraco.Core.Strings; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; +using Umbraco.Web.Composing; +using Umbraco.Web.PropertyEditors; +using Umbraco.Examine; + +namespace Umbraco.Web.Search +{ + /// + /// Configures and installs Examine. + /// + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public sealed class ExamineComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + private IExamineManager _examineManager; + private static bool _disableExamineIndexing = false; + private static volatile bool _isConfigured = false; + private static readonly object IsConfiguredLocker = new object(); + private IScopeProvider _scopeProvider; + private UrlSegmentProviderCollection _urlSegmentProviders; + private ServiceContext _services; + + // the default enlist priority is 100 + // enlist with a lower priority to ensure that anything "default" runs after us + // but greater that SafeXmlReaderWriter priority which is 60 + private const int EnlistPriority = 80; + + internal void Initialize(IRuntimeState runtime, MainDom mainDom, PropertyEditorCollection propertyEditors, IExamineManager examineManager, ProfilingLogger profilingLogger, IScopeProvider scopeProvider, UrlSegmentProviderCollection urlSegmentProviderCollection, ServiceContext services) + { + _services = services; + _urlSegmentProviders = urlSegmentProviderCollection; + _scopeProvider = scopeProvider; + _examineManager = examineManager; + + //We want to manage Examine's appdomain shutdown sequence ourselves so first we'll disable Examine's default behavior + //and then we'll use MainDom to control Examine's shutdown + ExamineManager.DisableDefaultHostingEnvironmentRegistration(); + + //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain + //terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock + //which simply checks the existence of the lock file + DirectoryTracker.DefaultLockFactory = d => + { + var simpleFsLockFactory = new NoPrefixSimpleFsLockFactory(d); + return simpleFsLockFactory; + }; + + //let's deal with shutting down Examine with MainDom + var examineShutdownRegistered = mainDom.Register(() => + { + using (profilingLogger.TraceDuration("Examine shutting down")) + { + ExamineManager.Instance.Dispose(); + } + }); + + if (!examineShutdownRegistered) + { + profilingLogger.Logger.Debug("Examine shutdown not registered, this appdomain is not the MainDom, Examine will be disabled"); + + //if we could not register the shutdown examine ourselves, it means we are not maindom! in this case all of examine should be disabled! + Suspendable.ExamineEvents.SuspendIndexers(); + _disableExamineIndexing = true; + return; //exit, do not continue + } + + profilingLogger.Logger.Debug("Examine shutdown registered with MainDom"); + + var registeredIndexers = examineManager.IndexProviders.Values.OfType().Count(x => x.EnableDefaultEventHandler); + + profilingLogger.Logger.Info($"Adding examine event handlers for {registeredIndexers} index providers."); + + // don't bind event handlers if we're not suppose to listen + if (registeredIndexers == 0) + return; + + BindGridToExamine(profilingLogger.Logger, examineManager, propertyEditors); + + // bind to distributed cache events - this ensures that this logic occurs on ALL servers + // that are taking part in a load balanced environment. + ContentCacheRefresher.CacheUpdated += ContentCacheRefresherUpdated; + MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated; + MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated; + + // fixme - content type? + // events handling removed in ef013f9d3b945d0a48a306ff1afbd49c10c3fff8 + // because, could not make sense of it? + + EnsureUnlocked(profilingLogger.Logger, examineManager); + + RebuildIndexesOnStartup(profilingLogger.Logger); + } + + /// + /// Called to rebuild empty indexes on startup + /// + /// + private void RebuildIndexesOnStartup(ILogger logger) + { + //TODO: need a way to disable rebuilding on startup + + logger.Info("Starting initialize async background thread."); + + // make it async in order not to slow down the boot + // fixme - should be a proper background task else we cannot stop it! + var bg = new Thread(() => + { + try + { + // rebuilds any empty indexes + RebuildIndexes(true, _examineManager, logger); + } + catch (Exception e) + { + logger.Error("Failed to rebuild empty indexes.", e); + } + }); + bg.Start(); + } + + /// + /// Used to rebuild indexes on startup or cold boot + /// + /// + /// + /// + internal static void RebuildIndexes(bool onlyEmptyIndexes, IExamineManager examineManager, ILogger logger) + { + //do not attempt to do this if this has been disabled since we are not the main dom. + //this can be called during a cold boot + if (_disableExamineIndexing) return; + + EnsureUnlocked(logger, examineManager); + + if (onlyEmptyIndexes) + { + foreach (var indexer in examineManager.IndexProviders.Values.Where(x => x.IsIndexNew())) + { + indexer.RebuildIndex(); + } + } + else + { + //do all of them + ExamineManager.Instance.RebuildIndexes(); + } + } + + /// + /// Must be called to each index is unlocked before any indexing occurs + /// + /// + /// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before + /// either of these happens, we need to configure the indexes. + /// + private static void EnsureUnlocked(ILogger logger, IExamineManager examineManager) + { + if (_disableExamineIndexing) return; + if (_isConfigured) return; + + lock (IsConfiguredLocker) + { + //double chekc + if (_isConfigured) return; + + _isConfigured = true; + + foreach (var luceneIndexer in examineManager.IndexProviders.Values.OfType()) + { + //We now need to disable waiting for indexing for Examine so that the appdomain is shutdown immediately and doesn't wait for pending + //indexing operations. We used to wait for indexing operations to complete but this can cause more problems than that is worth because + //that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems. + luceneIndexer.WaitForIndexQueueOnShutdown = false; + + //we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that + //the indexes are not operational unless MainDom is true + var dir = luceneIndexer.GetLuceneDirectory(); + if (IndexWriter.IsLocked(dir)) + { + logger.Info("Forcing index " + luceneIndexer.Name + " to be unlocked since it was left in a locked state"); + IndexWriter.Unlock(dir); + } + } + } + } + + private static void BindGridToExamine(ILogger logger, IExamineManager examineManager, IEnumerable propertyEditors) + { + //bind the grid property editors - this is a hack until http://issues.umbraco.org/issue/U4-8437 + try + { + var grid = propertyEditors.OfType().FirstOrDefault(); + if (grid != null) + { + foreach (var i in examineManager.IndexProviders.Values.OfType()) + i.DocumentWriting += grid.DocumentWriting; + } + } + catch (Exception e) + { + logger.Error("Failed to bind grid property editor.", e); + } + } + + private void MemberCacheRefresherUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs args) + { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + + switch (args.MessageType) + { + case MessageType.RefreshById: + var c1 = _services.MemberService.GetById((int)args.MessageObject); + if (c1 != null) + { + ReIndexForMember(c1); + } + break; + case MessageType.RemoveById: + + // This is triggered when the item is permanently deleted + + DeleteIndexForEntity((int)args.MessageObject, false); + break; + case MessageType.RefreshByInstance: + if (args.MessageObject is IMember c3) + { + ReIndexForMember(c3); + } + break; + case MessageType.RemoveByInstance: + + // This is triggered when the item is permanently deleted + + if (args.MessageObject is IMember c4) + { + DeleteIndexForEntity(c4.Id, false); + } + break; + case MessageType.RefreshAll: + case MessageType.RefreshByJson: + default: + //We don't support these, these message types will not fire for unpublished content + break; + } + } + + private void MediaCacheRefresherUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs args) + { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + + if (args.MessageType != MessageType.RefreshByPayload) + throw new NotSupportedException(); + + var mediaService = _services.MediaService; + + foreach (var payload in (MediaCacheRefresher.JsonPayload[]) args.MessageObject) + { + if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) + { + // remove from *all* indexes + DeleteIndexForEntity(payload.Id, false); + } + else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) + { + // ExamineEvents does not support RefreshAll + // just ignore that payload + // so what?! + } + else // RefreshNode or RefreshBranch (maybe trashed) + { + var media = mediaService.GetById(payload.Id); + if (media == null || media.Trashed) + { + // gone fishing, remove entirely + DeleteIndexForEntity(payload.Id, false); + continue; + } + + // just that media + ReIndexForMedia(media, media.Trashed == false); + + // branch + if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) + { + var descendants = mediaService.GetDescendants(media); + foreach (var descendant in descendants) + { + ReIndexForMedia(descendant, descendant.Trashed == false); + } + } + } + } + } + + private void ContentCacheRefresherUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) + { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + + if (args.MessageType != MessageType.RefreshByPayload) + throw new NotSupportedException(); + + var contentService = _services.ContentService; + + foreach (var payload in (ContentCacheRefresher.JsonPayload[]) args.MessageObject) + { + if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) + { + // delete content entirely (with descendants) + // false: remove entirely from all indexes + DeleteIndexForEntity(payload.Id, false); + } + else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) + { + // ExamineEvents does not support RefreshAll + // just ignore that payload + // so what?! + } + else // RefreshNode or RefreshBranch (maybe trashed) + { + // don't try to be too clever - refresh entirely + // there has to be race conds in there ;-( + + var content = contentService.GetById(payload.Id); + if (content == null || content.Trashed) + { + // gone fishing, remove entirely from all indexes (with descendants) + DeleteIndexForEntity(payload.Id, false); + continue; + } + + IContent published = null; + if (content.Published && ((ContentService)contentService).IsPathPublished(content)) + published = content; + + // just that content + ReIndexForContent(content, published); + + // branch + if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) + { + var masked = published == null ? null : new List(); + var descendants = contentService.GetDescendants(content); + foreach (var descendant in descendants) + { + published = null; + if (masked != null) // else everything is masked + { + if (masked.Contains(descendant.ParentId) || !descendant.Published) + masked.Add(descendant.Id); + else + published = descendant; + } + + ReIndexForContent(descendant, published); + } + } + } + + // NOTE + // + // DeleteIndexForEntity is handled by UmbracoContentIndexer.DeleteFromIndex() which takes + // care of also deleting the descendants + // + // ReIndexForContent is NOT taking care of descendants so we have to reload everything + // again in order to process the branch - we COULD improve that by just reloading the + // XML from database instead of reloading content & re-serializing! + // + // BUT ... pretty sure it is! see test "Index_Delete_Index_Item_Ensure_Heirarchy_Removed" + } + } + + private void ReIndexForContent(IContent content, IContent published) + { + if (published != null && content.VersionId == published.VersionId) + { + ReIndexForContent(content); // same = both + } + else + { + if (published == null) + { + // remove 'published' - keep 'draft' + DeleteIndexForEntity(content.Id, true); + } + else + { + // index 'published' - don't overwrite 'draft' + ReIndexForContent(published, false); + } + ReIndexForContent(content, true); // index 'draft' + } + } + + private void ReIndexForContent(IContent sender, bool? supportUnpublished = null) + { + var actions = DeferedActions.Get(_scopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForContent(this, sender, supportUnpublished)); + else + DeferedReIndexForContent.Execute(this, sender, supportUnpublished); + } + + private void ReIndexForMember(IMember member) + { + var actions = DeferedActions.Get(_scopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForMember(this, member)); + else + DeferedReIndexForMember.Execute(this, member); + } + + private void ReIndexForMedia(IMedia sender, bool isMediaPublished) + { + var actions = DeferedActions.Get(_scopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForMedia(this, sender, isMediaPublished)); + else + DeferedReIndexForMedia.Execute(this, sender, isMediaPublished); + } + + /// + /// Remove items from any index that doesn't support unpublished content + /// + /// + /// + /// If true, indicates that we will only delete this item from indexes that don't support unpublished content. + /// If false it will delete this from all indexes regardless. + /// + private void DeleteIndexForEntity(int entityId, bool keepIfUnpublished) + { + var actions = DeferedActions.Get(_scopeProvider); + if (actions != null) + actions.Add(new DeferedDeleteIndex(this, entityId, keepIfUnpublished)); + else + DeferedDeleteIndex.Execute(this, entityId, keepIfUnpublished); + } + + private class DeferedActions + { + private readonly List _actions = new List(); + + public static DeferedActions Get(IScopeProvider scopeProvider) + { + var scopeContext = scopeProvider.Context; + + return scopeContext?.Enlist("examineEvents", + () => new DeferedActions(), // creator + (completed, actions) => // action + { + if (completed) actions.Execute(); + }, EnlistPriority); + } + + public void Add(DeferedAction action) + { + _actions.Add(action); + } + + private void Execute() + { + foreach (var action in _actions) + action.Execute(); + } + } + + private abstract class DeferedAction + { + public virtual void Execute() + { } + } + + private class DeferedReIndexForContent : DeferedAction + { + private readonly ExamineComponent _examineComponent; + private readonly IContent _content; + private readonly bool? _supportUnpublished; + + public DeferedReIndexForContent(ExamineComponent examineComponent, IContent content, bool? supportUnpublished) + { + _examineComponent = examineComponent; + _content = content; + _supportUnpublished = supportUnpublished; + } + + public override void Execute() + { + Execute(_examineComponent, _content, _supportUnpublished); + } + + public static void Execute(ExamineComponent examineComponent, IContent content, bool? supportUnpublished) + { + var valueSet = UmbracoContentIndexer.GetValueSets(examineComponent._urlSegmentProviders, examineComponent._services.UserService, content); + + ExamineManager.Instance.IndexItems( + valueSet.ToArray(), + examineComponent._examineManager.IndexProviders.Values.OfType() + // only for the specified indexers + .Where(x => supportUnpublished.HasValue == false || supportUnpublished.Value == x.SupportUnpublishedContent) + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedReIndexForMedia : DeferedAction + { + private readonly ExamineComponent _examineComponent; + private readonly IMedia _media; + private readonly bool _isPublished; + + public DeferedReIndexForMedia(ExamineComponent examineComponent, IMedia media, bool isPublished) + { + _examineComponent = examineComponent; + _media = media; + _isPublished = isPublished; + } + + public override void Execute() + { + Execute(_examineComponent, _media, _isPublished); + } + + public static void Execute(ExamineComponent examineComponent, IMedia media, bool isPublished) + { + var valueSet = UmbracoContentIndexer.GetValueSets(examineComponent._urlSegmentProviders, examineComponent._services.UserService, media); + + ExamineManager.Instance.IndexItems( + valueSet.ToArray(), + examineComponent._examineManager.IndexProviders.Values.OfType() + // index this item for all indexers if the media is not trashed, otherwise if the item is trashed + // then only index this for indexers supporting unpublished media + .Where(x => isPublished || (x.SupportUnpublishedContent)) + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedReIndexForMember : DeferedAction + { + private readonly ExamineComponent _examineComponent; + private readonly IMember _member; + + public DeferedReIndexForMember(ExamineComponent examineComponent, IMember member) + { + _examineComponent = examineComponent; + _member = member; + } + + public override void Execute() + { + Execute(_examineComponent, _member); + } + + public static void Execute(ExamineComponent examineComponent, IMember member) + { + var valueSet = UmbracoMemberIndexer.GetValueSets(member); + + ExamineManager.Instance.IndexItems( + valueSet.ToArray(), + examineComponent._examineManager.IndexProviders.Values.OfType() + //ensure that only the providers are flagged to listen execute + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedDeleteIndex : DeferedAction + { + private readonly ExamineComponent _examineComponent; + private readonly int _id; + private readonly bool _keepIfUnpublished; + + public DeferedDeleteIndex(ExamineComponent examineComponent, int id, bool keepIfUnpublished) + { + _examineComponent = examineComponent; + _id = id; + _keepIfUnpublished = keepIfUnpublished; + } + + public override void Execute() + { + Execute(_examineComponent, _id, _keepIfUnpublished); + } + + public static void Execute(ExamineComponent examineComponent, int id, bool keepIfUnpublished) + { + ExamineManager.Instance.DeleteFromIndexes( + id.ToString(CultureInfo.InvariantCulture), + examineComponent._examineManager.IndexProviders.Values.OfType() + // if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, + // otherwise if keepIfUnpublished == false then remove from all indexes + .Where(x => keepIfUnpublished == false || (x is UmbracoContentIndexer && ((UmbracoContentIndexer)x).SupportUnpublishedContent == false)) + .Where(x => x.EnableDefaultEventHandler)); + } + } + + } +} diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs deleted file mode 100644 index cf4d6e33d4..0000000000 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ /dev/null @@ -1,729 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Xml; -using System.Xml.Linq; -using Examine; -using Examine.LuceneEngine; -using Lucene.Net.Documents; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Scoping; -using Umbraco.Core.Sync; -using Umbraco.Web.Cache; -using UmbracoExamine; -using Content = umbraco.cms.businesslogic.Content; -using Document = umbraco.cms.businesslogic.web.Document; - -namespace Umbraco.Web.Search -{ - /// - /// Used to wire up events for Examine - /// - public sealed class ExamineEvents : ApplicationEventHandler - { - // the default enlist priority is 100 - // enlist with a lower priority to ensure that anything "default" runs after us - // but greater that SafeXmlReaderWriter priority which is 60 - private const int EnlistPriority = 80; - - /// - /// Once the application has started we should bind to all events and initialize the providers. - /// - /// - /// - /// - /// We need to do this on the Started event as to guarantee that all resolvers are setup properly. - /// - protected override void ApplicationStarted(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext) - { - LogHelper.Info("Initializing Examine and binding to business logic events"); - - var registeredProviders = ExamineManager.Instance.IndexProviderCollection - .OfType().Count(x => x.EnableDefaultEventHandler); - - LogHelper.Info("Adding examine event handlers for index providers: {0}", () => registeredProviders); - - //don't bind event handlers if we're not suppose to listen - if (registeredProviders == 0) - return; - - //Bind to distributed cache events - this ensures that this logic occurs on ALL servers that are taking part - // in a load balanced environment. - CacheRefresherBase.CacheUpdated += UnpublishedPageCacheRefresherCacheUpdated; - CacheRefresherBase.CacheUpdated += PublishedPageCacheRefresherCacheUpdated; - CacheRefresherBase.CacheUpdated += MediaCacheRefresherCacheUpdated; - CacheRefresherBase.CacheUpdated += MemberCacheRefresherCacheUpdated; - CacheRefresherBase.CacheUpdated += ContentTypeCacheRefresherCacheUpdated; - - var contentIndexer = ExamineManager.Instance.IndexProviderCollection[Constants.Examine.InternalIndexer] as UmbracoContentIndexer; - if (contentIndexer != null) - { - contentIndexer.DocumentWriting += IndexerDocumentWriting; - } - var memberIndexer = ExamineManager.Instance.IndexProviderCollection[Constants.Examine.InternalMemberIndexer] as UmbracoMemberIndexer; - if (memberIndexer != null) - { - memberIndexer.DocumentWriting += IndexerDocumentWriting; - } - } - - /// - /// This is used to refresh content indexers IndexData based on the DataService whenever a content type is changed since - /// properties may have been added/removed, then we need to re-index any required data if aliases have been changed - /// - /// - /// - /// - /// See: http://issues.umbraco.org/issue/U4-4798, http://issues.umbraco.org/issue/U4-7833 - /// - static void ContentTypeCacheRefresherCacheUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs e) - { - if (Suspendable.ExamineEvents.CanIndex == false) - return; - - var indexersToUpdated = ExamineManager.Instance.IndexProviderCollection.OfType(); - foreach (var provider in indexersToUpdated) - { - provider.RefreshIndexerDataFromDataService(); - } - - if (e.MessageType == MessageType.RefreshByJson) - { - var contentTypesChanged = new HashSet(); - var mediaTypesChanged = new HashSet(); - var memberTypesChanged = new HashSet(); - - var payloads = ContentTypeCacheRefresher.DeserializeFromJsonPayload(e.MessageObject.ToString()); - foreach (var payload in payloads) - { - if (payload.IsNew == false - && (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved || payload.PropertyTypeAliasChanged)) - { - //if we get here it means that some aliases have changed and the indexes for those particular doc types will need to be updated - if (payload.Type == typeof(IContentType).Name) - { - //if it is content - contentTypesChanged.Add(payload.Alias); - } - else if (payload.Type == typeof(IMediaType).Name) - { - //if it is media - mediaTypesChanged.Add(payload.Alias); - } - else if (payload.Type == typeof(IMemberType).Name) - { - //if it is members - memberTypesChanged.Add(payload.Alias); - } - } - } - - //TODO: We need to update Examine to support re-indexing multiple items at once instead of one by one which will speed up - // the re-indexing process, we don't want to revert to rebuilding the whole thing! - - if (contentTypesChanged.Count > 0) - { - foreach (var alias in contentTypesChanged) - { - var ctType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias); - if (ctType != null) - { - var contentItems = ApplicationContext.Current.Services.ContentService.GetContentOfContentType(ctType.Id); - foreach (var contentItem in contentItems) - { - ReIndexForContent(contentItem, contentItem.HasPublishedVersion && contentItem.Trashed == false); - } - } - } - } - if (mediaTypesChanged.Count > 0) - { - foreach (var alias in mediaTypesChanged) - { - var ctType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias); - if (ctType != null) - { - var mediaItems = ApplicationContext.Current.Services.MediaService.GetMediaOfMediaType(ctType.Id); - foreach (var mediaItem in mediaItems) - { - ReIndexForMedia(mediaItem, mediaItem.Trashed == false); - } - } - } - } - if (memberTypesChanged.Count > 0) - { - foreach (var alias in memberTypesChanged) - { - var ctType = ApplicationContext.Current.Services.MemberTypeService.Get(alias); - if (ctType != null) - { - var memberItems = ApplicationContext.Current.Services.MemberService.GetMembersByMemberType(ctType.Id); - foreach (var memberItem in memberItems) - { - ReIndexForMember(memberItem); - } - } - } - } - } - - } - - static void MemberCacheRefresherCacheUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs e) - { - if (Suspendable.ExamineEvents.CanIndex == false) - return; - - switch (e.MessageType) - { - case MessageType.RefreshById: - var c1 = ApplicationContext.Current.Services.MemberService.GetById((int)e.MessageObject); - if (c1 != null) - { - ReIndexForMember(c1); - } - break; - case MessageType.RemoveById: - - // This is triggered when the item is permanently deleted - - DeleteIndexForEntity((int)e.MessageObject, false); - break; - case MessageType.RefreshByInstance: - var c3 = e.MessageObject as IMember; - if (c3 != null) - { - ReIndexForMember(c3); - } - break; - case MessageType.RemoveByInstance: - - // This is triggered when the item is permanently deleted - - var c4 = e.MessageObject as IMember; - if (c4 != null) - { - DeleteIndexForEntity(c4.Id, false); - } - break; - case MessageType.RefreshAll: - case MessageType.RefreshByJson: - default: - //We don't support these, these message types will not fire for unpublished content - break; - } - } - - /// - /// Handles index management for all media events - basically handling saving/copying/trashing/deleting - /// - /// - /// - static void MediaCacheRefresherCacheUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs e) - { - if (Suspendable.ExamineEvents.CanIndex == false) - return; - - switch (e.MessageType) - { - case MessageType.RefreshById: - var c1 = ApplicationContext.Current.Services.MediaService.GetById((int)e.MessageObject); - if (c1 != null) - { - ReIndexForMedia(c1, c1.Trashed == false); - } - break; - case MessageType.RemoveById: - var c2 = ApplicationContext.Current.Services.MediaService.GetById((int)e.MessageObject); - if (c2 != null) - { - //This is triggered when the item has trashed. - // So we need to delete the index from all indexes not supporting unpublished content. - - DeleteIndexForEntity(c2.Id, true); - - //We then need to re-index this item for all indexes supporting unpublished content - - ReIndexForMedia(c2, false); - } - break; - case MessageType.RefreshByJson: - - var jsonPayloads = MediaCacheRefresher.DeserializeFromJsonPayload((string)e.MessageObject); - if (jsonPayloads.Any()) - { - foreach (var payload in jsonPayloads) - { - switch (payload.Operation) - { - case MediaCacheRefresher.OperationType.Saved: - var media1 = ApplicationContext.Current.Services.MediaService.GetById(payload.Id); - if (media1 != null) - { - ReIndexForMedia(media1, media1.Trashed == false); - } - break; - case MediaCacheRefresher.OperationType.Trashed: - - //keep if trashed for indexes supporting unpublished - //(delete the index from all indexes not supporting unpublished content) - - DeleteIndexForEntity(payload.Id, true); - - //We then need to re-index this item for all indexes supporting unpublished content - var media2 = ApplicationContext.Current.Services.MediaService.GetById(payload.Id); - if (media2 != null) - { - ReIndexForMedia(media2, false); - } - - break; - case MediaCacheRefresher.OperationType.Deleted: - - //permanently remove from all indexes - - DeleteIndexForEntity(payload.Id, false); - - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - } - - break; - case MessageType.RefreshByInstance: - case MessageType.RemoveByInstance: - case MessageType.RefreshAll: - default: - //We don't support these, these message types will not fire for media - break; - } - } - - /// - /// Handles index management for all published content events - basically handling published/unpublished - /// - /// - /// - /// - /// This will execute on all servers taking part in load balancing - /// - static void PublishedPageCacheRefresherCacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs e) - { - if (Suspendable.ExamineEvents.CanIndex == false) - return; - - switch (e.MessageType) - { - case MessageType.RefreshById: - var c1 = ApplicationContext.Current.Services.ContentService.GetById((int)e.MessageObject); - if (c1 != null) - { - ReIndexForContent(c1, true); - } - break; - case MessageType.RemoveById: - - //This is triggered when the item has been unpublished or trashed (which also performs an unpublish). - - var c2 = ApplicationContext.Current.Services.ContentService.GetById((int)e.MessageObject); - if (c2 != null) - { - // So we need to delete the index from all indexes not supporting unpublished content. - - DeleteIndexForEntity(c2.Id, true); - - // We then need to re-index this item for all indexes supporting unpublished content - - ReIndexForContent(c2, false); - } - break; - case MessageType.RefreshByInstance: - var c3 = e.MessageObject as IContent; - if (c3 != null) - { - ReIndexForContent(c3, true); - } - break; - case MessageType.RemoveByInstance: - - //This is triggered when the item has been unpublished or trashed (which also performs an unpublish). - - var c4 = e.MessageObject as IContent; - if (c4 != null) - { - // So we need to delete the index from all indexes not supporting unpublished content. - - DeleteIndexForEntity(c4.Id, true); - - // We then need to re-index this item for all indexes supporting unpublished content - - ReIndexForContent(c4, false); - } - break; - case MessageType.RefreshAll: - case MessageType.RefreshByJson: - default: - //We don't support these for examine indexing - break; - } - } - - /// - /// Handles index management for all unpublished content events - basically handling saving/copying/deleting - /// - /// - /// - /// - /// This will execute on all servers taking part in load balancing - /// - static void UnpublishedPageCacheRefresherCacheUpdated(UnpublishedPageCacheRefresher sender, CacheRefresherEventArgs e) - { - if (Suspendable.ExamineEvents.CanIndex == false) - return; - - switch (e.MessageType) - { - case MessageType.RefreshById: - var c1 = ApplicationContext.Current.Services.ContentService.GetById((int) e.MessageObject); - if (c1 != null) - { - ReIndexForContent(c1, false); - } - break; - case MessageType.RemoveById: - - // This is triggered when the item is permanently deleted - - DeleteIndexForEntity((int)e.MessageObject, false); - break; - case MessageType.RefreshByInstance: - var c3 = e.MessageObject as IContent; - if (c3 != null) - { - ReIndexForContent(c3, false); - } - break; - case MessageType.RemoveByInstance: - - // This is triggered when the item is permanently deleted - - var c4 = e.MessageObject as IContent; - if (c4 != null) - { - DeleteIndexForEntity(c4.Id, false); - } - break; - case MessageType.RefreshByJson: - - var jsonPayloads = UnpublishedPageCacheRefresher.DeserializeFromJsonPayload((string)e.MessageObject); - if (jsonPayloads.Any()) - { - foreach (var payload in jsonPayloads) - { - switch (payload.Operation) - { - case UnpublishedPageCacheRefresher.OperationType.Deleted: - - //permanently remove from all indexes - - DeleteIndexForEntity(payload.Id, false); - - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - } - - break; - - case MessageType.RefreshAll: - default: - //We don't support these, these message types will not fire for unpublished content - break; - } - } - - private static void ReIndexForMember(IMember member) - { - var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); - if (actions != null) - actions.Add(new DeferedReIndexForMember(member)); - else - DeferedReIndexForMember.Execute(member); - } - - /// - /// Event handler to create a lower cased version of the node name, this is so we can support case-insensitive searching and still - /// use the Whitespace Analyzer - /// - /// - /// - - private static void IndexerDocumentWriting(object sender, DocumentWritingEventArgs e) - { - if (e.Fields.Keys.Contains("nodeName")) - { - //TODO: This logic should really be put into the content indexer instead of hidden here!! - - //add the lower cased version - e.Document.Add(new Field("__nodeName", - e.Fields["nodeName"].ToLower(), - Field.Store.YES, - Field.Index.ANALYZED, - Field.TermVector.NO - )); - } - } - - private static void ReIndexForMedia(IMedia sender, bool isMediaPublished) - { - var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); - if (actions != null) - actions.Add(new DeferedReIndexForMedia(sender, isMediaPublished)); - else - DeferedReIndexForMedia.Execute(sender, isMediaPublished); - } - - /// - /// Remove items from any index that doesn't support unpublished content - /// - /// - /// - /// If true, indicates that we will only delete this item from indexes that don't support unpublished content. - /// If false it will delete this from all indexes regardless. - /// - private static void DeleteIndexForEntity(int entityId, bool keepIfUnpublished) - { - var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); - if (actions != null) - actions.Add(new DeferedDeleteIndex(entityId, keepIfUnpublished)); - else - DeferedDeleteIndex.Execute(entityId, keepIfUnpublished); - } - - /// - /// Re-indexes a content item whether published or not but only indexes them for indexes supporting unpublished content - /// - /// - /// - /// Value indicating whether the item is published or not - /// - private static void ReIndexForContent(IContent sender, bool isContentPublished) - { - var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); - if (actions != null) - actions.Add(new DeferedReIndexForContent(sender, isContentPublished)); - else - DeferedReIndexForContent.Execute(sender, isContentPublished); - } - - private class DeferedActions - { - private readonly List _actions = new List(); - - public static DeferedActions Get(IScopeProvider scopeProvider) - { - var scopeContext = scopeProvider.Context; - if (scopeContext == null) return null; - - return scopeContext.Enlist("examineEvents", - () => new DeferedActions(), // creator - (completed, actions) => // action - { - if (completed) actions.Execute(); - }, EnlistPriority); - } - - public void Add(DeferedAction action) - { - _actions.Add(action); - } - - private void Execute() - { - foreach (var action in _actions) - action.Execute(); - } - } - - private abstract class DeferedAction - { - public virtual void Execute() - { } - } - - private class DeferedReIndexForContent : DeferedAction - { - private readonly IContent _content; - private readonly bool _isPublished; - - public DeferedReIndexForContent(IContent content, bool isPublished) - { - _content = content; - _isPublished = isPublished; - } - - public override void Execute() - { - Execute(_content, _isPublished); - } - - public static void Execute(IContent content, bool isPublished) - { - var xml = content.ToXml(); - //add an icon attribute to get indexed - xml.Add(new XAttribute("icon", content.ContentType.Icon)); - - ExamineManager.Instance.ReIndexNode( - xml, IndexTypes.Content, - ExamineManager.Instance.IndexProviderCollection.OfType() - - //Index this item for all indexers if the content is published, otherwise if the item is not published - // then only index this for indexers supporting unpublished content - - .Where(x => isPublished || (x.SupportUnpublishedContent)) - .Where(x => x.EnableDefaultEventHandler)); - } - } - - private class DeferedReIndexForMedia : DeferedAction - { - private readonly IMedia _media; - private readonly bool _isPublished; - - public DeferedReIndexForMedia(IMedia media, bool isPublished) - { - _media = media; - _isPublished = isPublished; - } - - public override void Execute() - { - Execute(_media, _isPublished); - } - - public static void Execute(IMedia media, bool isPublished) - { - var xml = media.ToXml(); - //add an icon attribute to get indexed - xml.Add(new XAttribute("icon", media.ContentType.Icon)); - - ExamineManager.Instance.ReIndexNode( - xml, IndexTypes.Media, - ExamineManager.Instance.IndexProviderCollection.OfType() - - //Index this item for all indexers if the media is not trashed, otherwise if the item is trashed - // then only index this for indexers supporting unpublished media - - .Where(x => isPublished || (x.SupportUnpublishedContent)) - .Where(x => x.EnableDefaultEventHandler)); - } - } - - private class DeferedReIndexForMember : DeferedAction - { - private readonly IMember _member; - - public DeferedReIndexForMember(IMember member) - { - _member = member; - } - - public override void Execute() - { - Execute(_member); - } - - public static void Execute(IMember member) - { - ExamineManager.Instance.ReIndexNode( - member.ToXml(), IndexTypes.Member, - ExamineManager.Instance.IndexProviderCollection.OfType() - //ensure that only the providers are flagged to listen execute - .Where(x => x.EnableDefaultEventHandler)); - } - } - - private class DeferedDeleteIndex : DeferedAction - { - private readonly int _id; - private readonly bool _keepIfUnpublished; - - public DeferedDeleteIndex(int id, bool keepIfUnpublished) - { - _id = id; - _keepIfUnpublished = keepIfUnpublished; - } - - public override void Execute() - { - Execute(_id, _keepIfUnpublished); - } - - public static void Execute(int id, bool keepIfUnpublished) - { - ExamineManager.Instance.DeleteFromIndex( - id.ToString(CultureInfo.InvariantCulture), - ExamineManager.Instance.IndexProviderCollection.OfType() - - //if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, - // otherwise if keepIfUnpublished == false then remove from all indexes - - .Where(x => keepIfUnpublished == false || x.SupportUnpublishedContent == false) - .Where(x => x.EnableDefaultEventHandler)); - } - } - - /// - /// Converts a content node to XDocument - /// - /// - /// true if data is going to be returned from cache - /// - [Obsolete("This method is no longer used and will be removed from the core in future versions, the cacheOnly parameter has no effect. Use the other ToXDocument overload instead")] - public static XDocument ToXDocument(Content node, bool cacheOnly) - { - return ToXDocument(node); - } - - /// - /// Converts a content node to Xml - /// - /// - /// - private static XDocument ToXDocument(Content node) - { - if (TypeHelper.IsTypeAssignableFrom(node)) - { - return new XDocument(((Document) node).ContentEntity.ToXml()); - } - - if (TypeHelper.IsTypeAssignableFrom(node)) - { - return new XDocument(((global::umbraco.cms.businesslogic.media.Media) node).MediaItem.ToXml()); - } - - var xDoc = new XmlDocument(); - var xNode = xDoc.CreateNode(XmlNodeType.Element, "node", ""); - node.XmlPopulate(xDoc, ref xNode, false); - - if (xNode.Attributes["nodeTypeAlias"] == null) - { - //we'll add the nodeTypeAlias ourselves - XmlAttribute d = xDoc.CreateAttribute("nodeTypeAlias"); - d.Value = node.ContentType.Alias; - xNode.Attributes.Append(d); - } - - return new XDocument(ExamineXmlExtensions.ToXElement(xNode)); - } - } -} diff --git a/src/Umbraco.Web/Search/ExamineIndexerModel.cs b/src/Umbraco.Web/Search/ExamineIndexerModel.cs index 5f4a4b8cb2..372f50764a 100644 --- a/src/Umbraco.Web/Search/ExamineIndexerModel.cs +++ b/src/Umbraco.Web/Search/ExamineIndexerModel.cs @@ -1,17 +1,16 @@ using System; +using System.Collections.Generic; using System.Runtime.Serialization; using Examine; namespace Umbraco.Web.Search { - [DataContract(Name = "indexer", Namespace = "")] public class ExamineIndexerModel : ExamineSearcherModel { - - [DataMember(Name = "indexCriteria")] - public IIndexCriteria IndexCriteria { get; set; } - + [DataMember(Name = "fieldDefinitions")] + public IEnumerable FieldDefinitions { get; set; } + /// /// The number of docs in the index /// @@ -23,24 +22,11 @@ namespace Umbraco.Web.Search ///
[DataMember(Name = "fieldCount")] public int FieldCount { get; set; } - - /// - /// The number of documents flagged for deletion in the index - /// - [DataMember(Name = "deletionCount")] - public int DeletionCount { get; set; } - - /// - /// Whether or not the indexed is optimized - /// - [DataMember(Name = "isOptimized")] - public bool IsOptimized{ get; set; } - + /// /// Generally will always be true unless someone has created a new non-lucene index /// [DataMember(Name = "isLuceneIndex")] public bool IsLuceneIndex { get; set; } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs b/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs index 8178778a32..58f230b2cd 100644 --- a/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs +++ b/src/Umbraco.Web/Search/LuceneIndexerExtensions.cs @@ -7,6 +7,7 @@ using Lucene.Net.Index; using Lucene.Net.Search; using Lucene.Net.Store; using Umbraco.Core.Logging; +using Umbraco.Web.Composing; namespace Umbraco.Web.Search { @@ -47,14 +48,18 @@ namespace Umbraco.Web.Search { try { - using (var reader = indexer.GetIndexWriter().GetReader()) + if (!((indexer.GetSearcher() as LuceneSearcher)?.GetLuceneSearcher() is IndexSearcher searcher)) + return 0; + + using (searcher) + using (var reader = searcher.IndexReader) { return reader.NumDocs(); } } catch (AlreadyClosedException) { - LogHelper.Warn(typeof(ExamineExtensions), "Cannot get GetIndexDocumentCount, the writer is already closed"); + Current.Logger.Warn(typeof(ExamineExtensions), "Cannot get GetIndexDocumentCount, the writer is already closed"); return 0; } } @@ -66,43 +71,24 @@ namespace Umbraco.Web.Search /// public static int GetIndexFieldCount(this LuceneIndexer indexer) { - //TODO: check for closing! and AlreadyClosedException - try { - using (var reader = indexer.GetIndexWriter().GetReader()) + if (!((indexer.GetSearcher() as LuceneSearcher)?.GetLuceneSearcher() is IndexSearcher searcher)) + return 0; + + using (searcher) + using (var reader = searcher.IndexReader) { return reader.GetFieldNames(IndexReader.FieldOption.ALL).Count; } } catch (AlreadyClosedException) { - LogHelper.Warn(typeof(ExamineExtensions), "Cannot get GetIndexFieldCount, the writer is already closed"); + Current.Logger.Warn(typeof(ExamineExtensions), "Cannot get GetIndexFieldCount, the writer is already closed"); return 0; } } - /// - /// Returns true if the index is optimized or not - /// - /// - /// - public static bool IsIndexOptimized(this LuceneIndexer indexer) - { - try - { - using (var reader = indexer.GetIndexWriter().GetReader()) - { - return reader.IsOptimized(); - } - } - catch (AlreadyClosedException) - { - LogHelper.Warn(typeof(ExamineExtensions), "Cannot get IsIndexOptimized, the writer is already closed"); - return false; - } - } - /// /// Check if the index is locked /// @@ -112,7 +98,7 @@ namespace Umbraco.Web.Search /// If the index does not exist we'll consider it locked /// public static bool IsIndexLocked(this LuceneIndexer indexer) - { + { return indexer.IndexExists() == false || IndexWriter.IsLocked(indexer.GetLuceneDirectory()); } @@ -126,16 +112,20 @@ namespace Umbraco.Web.Search { try { - using (var reader = indexer.GetIndexWriter().GetReader()) + if (!((indexer.GetSearcher() as LuceneSearcher)?.GetLuceneSearcher() is IndexSearcher searcher)) + return 0; + + using (searcher) + using (var reader = searcher.IndexReader) { - return reader.NumDeletedDocs(); + return reader.NumDeletedDocs; } } catch (AlreadyClosedException) { - LogHelper.Warn(typeof(ExamineExtensions), "Cannot get GetDeletedDocumentsCount, the writer is already closed"); + Current.Logger.Warn(typeof(ExamineExtensions), "Cannot get GetDeletedDocumentsCount, the writer is already closed"); return 0; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Search/SearchableApplicationTree.cs b/src/Umbraco.Web/Search/SearchableApplicationTree.cs index 8ce0fd52fc..997d8ce8e8 100644 --- a/src/Umbraco.Web/Search/SearchableApplicationTree.cs +++ b/src/Umbraco.Web/Search/SearchableApplicationTree.cs @@ -1,3 +1,5 @@ +using Umbraco.Web.Trees; + namespace Umbraco.Web.Search { public class SearchableApplicationTree @@ -9,9 +11,8 @@ namespace Umbraco.Web.Search SearchableTree = searchableTree; } - public string AppAlias { get; private set; } - public string TreeAlias { get; private set; } - public ISearchableTree SearchableTree { get; private set; } - + public string AppAlias { get; } + public string TreeAlias { get; } + public ISearchableTree SearchableTree { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Search/SearchableTreeAttribute.cs b/src/Umbraco.Web/Search/SearchableTreeAttribute.cs index c24f494d62..6c44b8946d 100644 --- a/src/Umbraco.Web/Search/SearchableTreeAttribute.cs +++ b/src/Umbraco.Web/Search/SearchableTreeAttribute.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Umbraco.Core.Exceptions; namespace Umbraco.Web.Search { @@ -12,8 +13,8 @@ namespace Umbraco.Web.Search /// public SearchableTreeAttribute(string serviceName, string methodName) { - if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentException("Value cannot be null or whitespace.", "serviceName"); - if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentException("Value cannot be null or whitespace.", "methodName"); + if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName)); + if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentNullOrEmptyException(nameof(methodName)); MethodName = methodName; ServiceName = serviceName; } @@ -24,12 +25,12 @@ namespace Umbraco.Web.Search /// public SearchableTreeAttribute(string serviceName) { - if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentException("Value cannot be null or whitespace.", "serviceName"); + if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName)); MethodName = ""; ServiceName = serviceName; } - public string MethodName { get; private set; } - public string ServiceName { get; private set; } + public string MethodName { get; } + public string ServiceName { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Search/SearchableTreeCollection.cs b/src/Umbraco.Web/Search/SearchableTreeCollection.cs index 463ae54f1d..d9368c973e 100644 --- a/src/Umbraco.Web/Search/SearchableTreeCollection.cs +++ b/src/Umbraco.Web/Search/SearchableTreeCollection.cs @@ -1,18 +1,44 @@ using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Services; +using Umbraco.Web.Trees; namespace Umbraco.Web.Search { - internal class SearchableTreeCollection : KeyedCollection + public class SearchableTreeCollection : BuilderCollectionBase { - protected override string GetKeyForItem(SearchableApplicationTree item) + private readonly Dictionary _dictionary; + + public SearchableTreeCollection(IEnumerable items, IApplicationTreeService treeService) + : base(items) { - return item.TreeAlias; + _dictionary = CreateDictionary(treeService); } + private Dictionary CreateDictionary(IApplicationTreeService treeService) + { + var appTrees = treeService.GetAll().ToArray(); + var dictionary = new Dictionary(); + var searchableTrees = this.ToArray(); + foreach (var searchableTree in searchableTrees) + { + var found = appTrees.FirstOrDefault(x => x.Alias == searchableTree.TreeAlias); + if (found != null) + { + dictionary[searchableTree.TreeAlias] = new SearchableApplicationTree(found.ApplicationAlias, found.Alias, searchableTree); + } + } + return dictionary; + } + + // fixme - oh why?! public IReadOnlyDictionary AsReadOnlyDictionary() { - return new ReadOnlyDictionary(Dictionary); + return new ReadOnlyDictionary(_dictionary); } + + public SearchableApplicationTree this[string key] => _dictionary[key]; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Search/SearchableTreeCollectionBuilder.cs b/src/Umbraco.Web/Search/SearchableTreeCollectionBuilder.cs new file mode 100644 index 0000000000..ae83cc5eab --- /dev/null +++ b/src/Umbraco.Web/Search/SearchableTreeCollectionBuilder.cs @@ -0,0 +1,25 @@ +using LightInject; +using Umbraco.Core.Composing; +using Umbraco.Core.Services; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Search +{ + internal class SearchableTreeCollectionBuilder : LazyCollectionBuilderBase + { + private readonly IApplicationTreeService _treeService; + + public SearchableTreeCollectionBuilder(IServiceContainer container, IApplicationTreeService treeService) + : base(container) + { + _treeService = treeService; + } + + protected override SearchableTreeCollectionBuilder This => this; + + public override SearchableTreeCollection CreateCollection() + { + return new SearchableTreeCollection(CreateItems(), _treeService); + } + } +} diff --git a/src/Umbraco.Web/Search/SearchableTreeResolver.cs b/src/Umbraco.Web/Search/SearchableTreeResolver.cs deleted file mode 100644 index 4831595072..0000000000 --- a/src/Umbraco.Web/Search/SearchableTreeResolver.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Configuration; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Services; -using Umbraco.Web.Editors; -using Umbraco.Web.Trees; - -namespace Umbraco.Web.Search -{ - /// - /// A resolver to return the collection of searchable trees - /// - /// - /// This collection has a request scoped lifetime therefore any instance of ISearchableTree needs to support being a request scoped lifetime - /// - public class SearchableTreeResolver : LazyManyObjectsResolverBase - { - private readonly IApplicationTreeService _treeService; - - public SearchableTreeResolver(IServiceProvider serviceProvider, ILogger logger, IApplicationTreeService treeService, Func> searchableTrees) - : base(serviceProvider, logger, searchableTrees, ObjectLifetimeScope.HttpRequest) - { - _treeService = treeService; - } - - /// - /// Returns the a dictionary of tree alias with it's affiliated - /// - public IReadOnlyDictionary GetSearchableTrees() - { - var appTrees = _treeService.GetAll().ToArray(); - var collection = new SearchableTreeCollection(); - var searchableTrees = Values.ToArray(); - foreach (var searchableTree in searchableTrees) - { - var found = appTrees.FirstOrDefault(x => x.Alias == searchableTree.TreeAlias); - if (found != null) - { - collection.Add(new SearchableApplicationTree(found.ApplicationAlias, found.Alias, searchableTree)); - } - } - return collection.AsReadOnlyDictionary(); - } - } -} diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 0dac79590f..66a7115dc7 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using AutoMapper; using Examine; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -38,17 +39,16 @@ namespace Umbraco.Web.Search var sb = new StringBuilder(); string type; - var searcher = Constants.Examine.InternalSearcher; + var indexer = Constants.Examine.InternalIndexer; var fields = new[] { "id", "__NodeId" }; var umbracoContext = umbracoHelper.UmbracoContext; - var appContext = umbracoContext.Application; //TODO: WE should really just allow passing in a lucene raw query switch (entityType) { case UmbracoEntityTypes.Member: - searcher = Constants.Examine.InternalMemberSearcher; + indexer = Constants.Examine.InternalMemberIndexer; type = "member"; fields = new[] { "id", "__NodeId", "email", "loginName" }; if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && searchFrom.Trim() != "-1") @@ -60,19 +60,19 @@ namespace Umbraco.Web.Search break; case UmbracoEntityTypes.Media: type = "media"; - var allMediaStartNodes = umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(appContext.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, appContext.Services.EntityService); + var allMediaStartNodes = umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(Current.Services.EntityService); + AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, Current.Services.EntityService); break; case UmbracoEntityTypes.Document: type = "content"; - var allContentStartNodes = umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(appContext.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, appContext.Services.EntityService); + var allContentStartNodes = umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(Current.Services.EntityService); + AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, Current.Services.EntityService); break; default: throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); } - var internalSearcher = ExamineManager.Instance.SearchProviderCollection[searcher]; + var internalSearcher = ExamineManager.Instance.GetSearcher(indexer); //build a lucene query: // the __nodeName will be boosted 10x without wildcards @@ -180,7 +180,7 @@ namespace Umbraco.Web.Search sb.Append("+__IndexType:"); sb.Append(type); - var raw = internalSearcher.CreateSearchCriteria().RawQuery(sb.ToString()); + var raw = internalSearcher.CreateCriteria().RawQuery(sb.ToString()); var result = internalSearcher //only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested @@ -208,6 +208,10 @@ namespace Umbraco.Web.Search if (sb == null) throw new ArgumentNullException("sb"); if (entityService == null) throw new ArgumentNullException("entityService"); + Udi udi; + Udi.TryParse(searchFrom, true, out udi); + searchFrom = udi == null ? searchFrom : entityService.GetId(udi).Result.ToString(); + int searchFromId; var entityPath = int.TryParse(searchFrom, out searchFromId) && searchFromId > 0 ? entityService.GetAllPaths(objectType, searchFromId).FirstOrDefault() @@ -273,10 +277,10 @@ namespace Umbraco.Web.Search m.Icon = "icon-user"; } - var searchResult = results.First(x => x.Id.ToInvariantString() == m.Id.ToString()); + var searchResult = results.First(x => x.Id == m.Id.ToString()); if (searchResult.Fields.ContainsKey("email") && searchResult.Fields["email"] != null) { - m.AdditionalData["Email"] = results.First(x => x.Id.ToInvariantString() == m.Id.ToString()).Fields["email"]; + m.AdditionalData["Email"] = results.First(x => x.Id == m.Id.ToString()).Fields["email"]; } if (searchResult.Fields.ContainsKey("__key") && searchResult.Fields["__key"] != null) { @@ -332,4 +336,4 @@ namespace Umbraco.Web.Search } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/AuthenticationExtensions.cs b/src/Umbraco.Web/Security/AuthenticationExtensions.cs new file mode 100644 index 0000000000..691d577d51 --- /dev/null +++ b/src/Umbraco.Web/Security/AuthenticationExtensions.cs @@ -0,0 +1,349 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading; +using System.Web; +using Microsoft.AspNet.Identity; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Newtonsoft.Json; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Security; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Security +{ + /// + /// Extensions to create and renew and remove authentication tickets for the Umbraco back office + /// + public static class AuthenticationExtensions + { + /// + /// This will check the ticket to see if it is valid, if it is it will set the current thread's user and culture + /// + /// + /// + /// If true will attempt to renew the ticket + public static bool AuthenticateCurrentRequest(this HttpContextBase http, AuthenticationTicket ticket, bool renewTicket) + { + if (http == null) throw new ArgumentNullException(nameof(http)); + + //if there was a ticket, it's not expired, - it should not be renewed or its renewable + if (ticket?.Properties.ExpiresUtc != null && ticket.Properties.ExpiresUtc.Value > DateTimeOffset.UtcNow && (renewTicket == false || http.RenewUmbracoAuthTicket())) + { + try + { + //get the Umbraco user identity + if (!(ticket.Identity is UmbracoBackOfficeIdentity identity)) + throw new InvalidOperationException("The AuthenticationTicket specified does not contain the correct Identity type"); + + //set the principal object + var principal = new ClaimsPrincipal(identity); + + //It is actually not good enough to set this on the current app Context and the thread, it also needs + // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually + // an underlying fault of asp.net not propogating the User correctly. + if (HttpContext.Current != null) + { + HttpContext.Current.User = principal; + } + http.User = principal; + Thread.CurrentPrincipal = principal; + + //This is a back office request, we will also set the culture/ui culture + Thread.CurrentThread.CurrentCulture = + Thread.CurrentThread.CurrentUICulture = + new System.Globalization.CultureInfo(identity.Culture); + + return true; + } + catch (Exception ex) + { + if (ex is FormatException || ex is JsonReaderException) + { + //this will occur if the cookie data is invalid + http.UmbracoLogout(); + } + else + { + throw; + } + + } + } + + return false; + } + + + /// + /// This will return the current back office identity. + /// + /// + /// + /// If set to true and a back office identity is not found and not authenticated, this will attempt to authenticate the + /// request just as is done in the Umbraco module and then set the current identity if it is valid. + /// Just like in the UmbracoModule, if this is true then the user's culture will be assigned to the request. + /// + /// + /// Returns the current back office identity if an admin is authenticated otherwise null + /// + public static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContextBase http, bool authenticateRequestIfNotFound) + { + if (http == null) throw new ArgumentNullException(nameof(http)); + if (http.User == null) return null; //there's no user at all so no identity + + //If it's already a UmbracoBackOfficeIdentity + var backOfficeIdentity = http.User.GetUmbracoIdentity(); + if (backOfficeIdentity != null) return backOfficeIdentity; + + if (authenticateRequestIfNotFound == false) return null; + + //even if authenticateRequestIfNotFound is true we cannot continue if the request is actually authenticated + // which would mean something strange is going on that it is not an umbraco identity. + if (http.User.Identity.IsAuthenticated) return null; + + //So the user is not authed but we've been asked to do the auth if authenticateRequestIfNotFound = true, + // which might occur in old webforms style things or for routes that aren't included as a back office request. + // in this case, we are just reverting to authing using the cookie. + + // TODO: Even though this is in theory legacy, we have legacy bits laying around and we'd need to do the auth based on + // how the Module will eventually do it (by calling in to any registered authenticators). + + var ticket = http.GetUmbracoAuthTicket(); + if (http.AuthenticateCurrentRequest(ticket, true)) + { + //now we 'should have an umbraco identity + return http.User.Identity as UmbracoBackOfficeIdentity; + } + return null; + } + + /// + /// This will return the current back office identity. + /// + /// + /// + /// If set to true and a back office identity is not found and not authenticated, this will attempt to authenticate the + /// request just as is done in the Umbraco module and then set the current identity if it is valid + /// + /// + /// Returns the current back office identity if an admin is authenticated otherwise null + /// + internal static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http, bool authenticateRequestIfNotFound) + { + if (http == null) throw new ArgumentNullException("http"); + return new HttpContextWrapper(http).GetCurrentIdentity(authenticateRequestIfNotFound); + } + + public static void UmbracoLogout(this HttpContextBase http) + { + if (http == null) throw new ArgumentNullException("http"); + Logout(http, UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName); + } + + /// + /// This clears the forms authentication cookie + /// + /// + internal static void UmbracoLogout(this HttpContext http) + { + if (http == null) throw new ArgumentNullException("http"); + new HttpContextWrapper(http).UmbracoLogout(); + } + + /// + /// This will force ticket renewal in the OWIN pipeline + /// + /// + /// + public static bool RenewUmbracoAuthTicket(this HttpContextBase http) + { + if (http == null) throw new ArgumentNullException("http"); + http.Items[Constants.Security.ForceReAuthFlag] = true; + return true; + } + + /// + /// This will force ticket renewal in the OWIN pipeline + /// + /// + /// + internal static bool RenewUmbracoAuthTicket(this HttpContext http) + { + if (http == null) throw new ArgumentNullException("http"); + http.Items[Constants.Security.ForceReAuthFlag] = true; + return true; + } + + /// + /// returns the number of seconds the user has until their auth session times out + /// + /// + /// + public static double GetRemainingAuthSeconds(this HttpContextBase http) + { + if (http == null) throw new ArgumentNullException(nameof(http)); + var ticket = http.GetUmbracoAuthTicket(); + return ticket.GetRemainingAuthSeconds(); + } + + /// + /// returns the number of seconds the user has until their auth session times out + /// + /// + /// + public static double GetRemainingAuthSeconds(this AuthenticationTicket ticket) + { + var utcExpired = ticket?.Properties.ExpiresUtc; + if (utcExpired == null) return 0; + var secondsRemaining = utcExpired.Value.Subtract(DateTimeOffset.UtcNow).TotalSeconds; + return secondsRemaining; + } + + /// + /// Gets the umbraco auth ticket + /// + /// + /// + public static AuthenticationTicket GetUmbracoAuthTicket(this HttpContextBase http) + { + if (http == null) throw new ArgumentNullException(nameof(http)); + return GetAuthTicket(http, UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName); + } + + internal static AuthenticationTicket GetUmbracoAuthTicket(this HttpContext http) + { + if (http == null) throw new ArgumentNullException(nameof(http)); + return new HttpContextWrapper(http).GetUmbracoAuthTicket(); + } + + public static AuthenticationTicket GetUmbracoAuthTicket(this IOwinContext ctx) + { + if (ctx == null) throw new ArgumentNullException(nameof(ctx)); + return GetAuthTicket(ctx, UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName); + } + + /// + /// This clears the forms authentication cookie + /// + /// + /// + private static void Logout(this HttpContextBase http, string cookieName) + { + //We need to clear the sessionId from the database. This is legacy code to do any logging out and shouldn't really be used at all but in any case + //we need to make sure the session is cleared. Due to the legacy nature of this it means we need to use singletons + if (http.User != null) + { + var claimsIdentity = http.User.Identity as ClaimsIdentity; + if (claimsIdentity != null) + { + var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType); + Guid guidSession; + if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out guidSession)) + { + Current.Services.UserService.ClearLoginSession(guidSession); + } + } + } + + if (http == null) throw new ArgumentNullException("http"); + //clear the preview cookie and external login + var cookies = new[] { cookieName, Constants.Web.PreviewCookieName, Constants.Security.BackOfficeExternalCookieName }; + foreach (var c in cookies) + { + //remove from the request + http.Request.Cookies.Remove(c); + + //expire from the response + var formsCookie = http.Response.Cookies[c]; + if (formsCookie != null) + { + //this will expire immediately and be removed from the browser + formsCookie.Expires = DateTime.Now.AddYears(-1); + } + else + { + //ensure there's def an expired cookie + http.Response.Cookies.Add(new HttpCookie(c) { Expires = DateTime.Now.AddYears(-1) }); + } + } + } + + private static AuthenticationTicket GetAuthTicket(this IOwinContext owinCtx, string cookieName) + { + var asDictionary = new Dictionary(); + foreach (var requestCookie in owinCtx.Request.Cookies) + { + var key = requestCookie.Key; + asDictionary[key] = requestCookie.Value; + } + + var secureFormat = owinCtx.GetUmbracoAuthTicketDataProtector(); + + //get the ticket + try + { + + return GetAuthTicket(secureFormat, asDictionary, cookieName); + } + catch (Exception) + { + owinCtx.Authentication.SignOut( + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeExternalAuthenticationType); + return null; + } + } + + private static AuthenticationTicket GetAuthTicket(this HttpContextBase http, string cookieName) + { + var asDictionary = new Dictionary(); + for (var i = 0; i < http.Request.Cookies.Keys.Count; i++) + { + var key = http.Request.Cookies.Keys.Get(i); + asDictionary[key] = http.Request.Cookies[key].Value; + } + + var owinCtx = http.GetOwinContext(); + var secureFormat = owinCtx.GetUmbracoAuthTicketDataProtector(); + + //will only happen in tests + if (secureFormat == null) return null; + + //get the ticket + try + { + return GetAuthTicket(secureFormat, asDictionary, cookieName); + } + catch (Exception) + { + //occurs when decryption fails + http.Logout(cookieName); + return null; + } + } + + private static AuthenticationTicket GetAuthTicket(ISecureDataFormat secureDataFormat, IDictionary cookies, string cookieName) + { + if (cookies == null) throw new ArgumentNullException(nameof(cookies)); + + if (cookies.ContainsKey(cookieName) == false) return null; + + var formsCookie = cookies[cookieName]; + if (formsCookie == null) + { + return null; + } + //get the ticket + + return secureDataFormat.Unprotect(formsCookie); + } + } +} diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index 9953eea664..48b505c80c 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; using System.Threading; -using System.Web; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.AspNet.SignalR; @@ -12,108 +8,79 @@ using Microsoft.Owin.Extensions; using Microsoft.Owin.Logging; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; +using Microsoft.Owin.Security.DataHandler; +using Microsoft.Owin.Security.DataProtection; using Owin; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Security.Identity { + /// + /// Provides security/identity extension methods to IAppBuilder. + /// public static class AppBuilderExtensions { - /// - /// Called at the end of configuring middleware - /// - /// - /// - /// This could be used for something else in the future - maybe to inform Umbraco that middleware is done/ready, but for - /// now this is used to raise the custom event - /// - /// This is an extension method in case developer entirely replace the UmbracoDefaultOwinStartup class, in which case they will - /// need to ensure they call this extension method in their startup class. - /// - /// TODO: Move this method in v8, it doesn't belong in this namespace/extension class - /// - public static void FinalizeMiddlewareConfiguration(this IAppBuilder app) - { - UmbracoDefaultOwinStartup.OnMiddlewareConfigured(new OwinMiddlewareConfiguredEventArgs(app)); - } - - /// - /// Sets the OWIN logger to use Umbraco's logging system - /// - /// - /// - /// TODO: Move this method in v8, it doesn't belong in this namespace/extension class - /// - public static void SetUmbracoLoggerFactory(this IAppBuilder app) - { - app.SetLoggerFactory(new OwinLoggerFactory()); - } - - /// - /// This maps a Signal path/hub - /// - /// - /// - public static IAppBuilder UseSignalR(this IAppBuilder app) - { - - // TODO: Move this method in v8, it doesn't belong in this namespace/extension class - var umbracoPath = GlobalSettings.UmbracoMvcArea; - - return app.MapSignalR(HttpRuntime.AppDomainAppVirtualPath + - umbracoPath + "/BackOffice/signalr", new HubConfiguration { EnableDetailedErrors = true }); - } - /// /// Configure Default Identity User Manager for Umbraco /// /// - /// + /// + /// + /// /// public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app, - ApplicationContext appContext, + ServiceContext services, + IContentSection contentSettings, + IGlobalSettings globalSettings, MembershipProviderBase userMembershipProvider) { - if (appContext == null) throw new ArgumentNullException("appContext"); - if (userMembershipProvider == null) throw new ArgumentNullException("userMembershipProvider"); + if (services == null) throw new ArgumentNullException(nameof(services)); + if (userMembershipProvider == null) throw new ArgumentNullException(nameof(userMembershipProvider)); //Configure Umbraco user manager to be created per request app.CreatePerOwinContext( (options, owinContext) => BackOfficeUserManager.Create( options, - appContext.Services.UserService, - appContext.Services.EntityService, - appContext.Services.ExternalLoginService, + services.UserService, + services.MemberTypeService, + services.EntityService, + services.ExternalLoginService, userMembershipProvider, - UmbracoConfig.For.UmbracoSettings().Content)); - + contentSettings, + globalSettings)); + app.SetBackOfficeUserManagerType(); //Create a sign in manager per request - app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger())); + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, globalSettings, app.CreateLogger())); } /// /// Configure a custom UserStore with the Identity User Manager for Umbraco /// /// - /// + /// + /// /// /// + /// public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app, - ApplicationContext appContext, + IRuntimeState runtimeState, + IContentSection contentSettings, + IGlobalSettings globalSettings, MembershipProviderBase userMembershipProvider, BackOfficeUserStore customUserStore) { - if (appContext == null) throw new ArgumentNullException("appContext"); - if (userMembershipProvider == null) throw new ArgumentNullException("userMembershipProvider"); - if (customUserStore == null) throw new ArgumentNullException("customUserStore"); + if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); + if (userMembershipProvider == null) throw new ArgumentNullException(nameof(userMembershipProvider)); + if (customUserStore == null) throw new ArgumentNullException(nameof(customUserStore)); //Configure Umbraco user manager to be created per request app.CreatePerOwinContext( @@ -121,28 +88,30 @@ namespace Umbraco.Web.Security.Identity options, customUserStore, userMembershipProvider, - UmbracoConfig.For.UmbracoSettings().Content)); + contentSettings)); app.SetBackOfficeUserManagerType(); //Create a sign in manager per request - app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, globalSettings, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); } /// /// Configure a custom BackOfficeUserManager for Umbraco /// /// - /// + /// + /// /// public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app, - ApplicationContext appContext, + IRuntimeState runtimeState, + IGlobalSettings globalSettings, Func, IOwinContext, TManager> userManager) where TManager : BackOfficeUserManager where TUser : BackOfficeIdentityUser { - if (appContext == null) throw new ArgumentNullException("appContext"); - if (userManager == null) throw new ArgumentNullException("userManager"); + if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); + if (userManager == null) throw new ArgumentNullException(nameof(userManager)); //Configure Umbraco user manager to be created per request app.CreatePerOwinContext(userManager); @@ -151,42 +120,50 @@ namespace Umbraco.Web.Security.Identity //Create a sign in manager per request app.CreatePerOwinContext( - (options, context) => BackOfficeSignInManager.Create(options, context, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); + (options, context) => BackOfficeSignInManager.Create(options, context, globalSettings, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); } /// /// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline /// /// - /// + /// + /// + /// + /// + /// /// /// /// By default this will be configured to execute on PipelineStage.Authenticate /// - public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, ApplicationContext appContext) + public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState,IUserService userService, IGlobalSettings globalSettings, ISecuritySection securitySection) { - return app.UseUmbracoBackOfficeCookieAuthentication(appContext, PipelineStage.Authenticate); + return app.UseUmbracoBackOfficeCookieAuthentication(umbracoContextAccessor, runtimeState, userService, globalSettings, securitySection, PipelineStage.Authenticate); } /// /// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline /// /// - /// + /// + /// + /// + /// + /// /// /// Configurable pipeline stage /// /// - public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, ApplicationContext appContext, PipelineStage stage) + public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IUserService userService, IGlobalSettings globalSettings, ISecuritySection securitySection, PipelineStage stage) { //Create the default options and provider - var authOptions = app.CreateUmbracoCookieAuthOptions(); + var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySection); - authOptions.Provider = new BackOfficeCookieAuthenticationProvider + authOptions.Provider = new BackOfficeCookieAuthenticationProvider(userService, runtimeState, globalSettings) { - // Enables the application to validate the security stamp when the user - // logs in. This is a security feature which is used when you - // change a password or add an external login to your account. + // Enables the application to validate the security stamp when the user + // logs in. This is a security feature which is used when you + // change a password or add an external login to your account. OnValidateIdentity = SecurityStampValidator .OnValidateIdentity( TimeSpan.FromMinutes(30), @@ -195,43 +172,53 @@ namespace Umbraco.Web.Security.Identity }; - return app.UseUmbracoBackOfficeCookieAuthentication(appContext, authOptions, stage); + return app.UseUmbracoBackOfficeCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, securitySection, authOptions, stage); } /// /// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline /// /// - /// + /// + /// + /// + /// /// Custom auth cookie options can be specified to have more control over the cookie authentication logic /// /// Configurable pipeline stage /// /// - public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, ApplicationContext appContext, CookieAuthenticationOptions cookieOptions, PipelineStage stage) + public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings, ISecuritySection securitySection, CookieAuthenticationOptions cookieOptions, PipelineStage stage) { - if (app == null) throw new ArgumentNullException("app"); - if (appContext == null) throw new ArgumentNullException("appContext"); - if (cookieOptions == null) throw new ArgumentNullException("cookieOptions"); - if (cookieOptions.Provider == null) throw new ArgumentNullException("cookieOptions.Provider"); - if ((cookieOptions.Provider is BackOfficeCookieAuthenticationProvider) == false) throw new ArgumentException("The cookieOptions.Provider must be of type " + typeof(BackOfficeCookieAuthenticationProvider)); - - app.UseUmbracoBackOfficeCookieAuthenticationInternal(cookieOptions, appContext, stage); + if (app == null) throw new ArgumentNullException(nameof(app)); + if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); + if (cookieOptions == null) throw new ArgumentNullException(nameof(cookieOptions)); + if (cookieOptions.Provider == null) + throw new ArgumentNullException("cookieOptions.Provider cannot be null.", nameof(cookieOptions)); + if (cookieOptions.Provider is BackOfficeCookieAuthenticationProvider == false) + throw new ArgumentException($"cookieOptions.Provider must be of type {typeof(BackOfficeCookieAuthenticationProvider)}.", nameof(cookieOptions)); + + app.UseUmbracoBackOfficeCookieAuthenticationInternal(cookieOptions, runtimeState, stage); //don't apply if app is not ready - if (appContext.IsUpgrading || appContext.IsConfigured) - { - var getSecondsOptions = app.CreateUmbracoCookieAuthOptions( - //This defines the explicit path read cookies from for this middleware - new[] {string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path)}); - getSecondsOptions.Provider = cookieOptions.Provider; + if (runtimeState.Level != RuntimeLevel.Upgrade && runtimeState.Level != RuntimeLevel.Run) return app; - //This is a custom middleware, we need to return the user's remaining logged in seconds - app.Use( - getSecondsOptions, - UmbracoConfig.For.UmbracoSettings().Security, - app.CreateLogger()); - } + var cookieAuthOptions = app.CreateUmbracoCookieAuthOptions( + umbracoContextAccessor, globalSettings, runtimeState, securitySection, + //This defines the explicit path read cookies from for this middleware + new[] {$"{globalSettings.Path}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds"}); + cookieAuthOptions.Provider = cookieOptions.Provider; + + //This is a custom middleware, we need to return the user's remaining logged in seconds + app.Use( + cookieAuthOptions, + UmbracoConfig.For.GlobalSettings(), + UmbracoConfig.For.UmbracoSettings().Security, + app.CreateLogger()); + + //This is required so that we can read the auth ticket format outside of this pipeline + app.CreatePerOwinContext( + (options, context) => new UmbracoAuthTicketDataProtector(cookieOptions.TicketDataFormat)); return app; } @@ -248,42 +235,40 @@ namespace Umbraco.Web.Security.Identity /// /// This is required because a developer can specify a custom user manager and due to generic types the key name will registered /// differently in the owin context - /// + /// private static void SetBackOfficeUserManagerType(this IAppBuilder app) where TManager : BackOfficeUserManager where TUser : BackOfficeIdentityUser { if (_markerSet) throw new InvalidOperationException("The back office user manager marker has already been set, only one back office user manager can be configured"); - //on each request set the user manager getter - + //on each request set the user manager getter - // this is required purely because Microsoft.Owin.IOwinContext is super inflexible with it's Get since it can only be // a generic strongly typed instance app.Use((context, func) => { context.Set(BackOfficeUserManager.OwinMarkerKey, new BackOfficeUserManagerMarker()); return func(); - }); + }); } - private static void UseUmbracoBackOfficeCookieAuthenticationInternal(this IAppBuilder app, CookieAuthenticationOptions options, ApplicationContext appContext, PipelineStage stage) + private static void UseUmbracoBackOfficeCookieAuthenticationInternal(this IAppBuilder app, CookieAuthenticationOptions options, IRuntimeState runtimeState, PipelineStage stage) { - if (app == null) - { - throw new ArgumentNullException("app"); - } + if (app == null) throw new ArgumentNullException(nameof(app)); + if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); //First the normal cookie middleware app.Use(typeof(CookieAuthenticationMiddleware), app, options); - //don't apply if app isnot ready - if (appContext.IsUpgrading || appContext.IsConfigured) + //don't apply if app is not ready + if (runtimeState.Level == RuntimeLevel.Upgrade || runtimeState.Level == RuntimeLevel.Run) { //Then our custom middlewares - app.Use(typeof(ForceRenewalCookieAuthenticationMiddleware), app, options, new SingletonUmbracoContextAccessor()); - app.Use(typeof(FixWindowsAuthMiddlware)); + app.Use(typeof(ForceRenewalCookieAuthenticationMiddleware), app, options, Current.UmbracoContextAccessor); + app.Use(typeof(FixWindowsAuthMiddlware)); } //Marks all of the above middlewares to execute on Authenticate - app.UseStageMarker(stage); + app.UseStageMarker(stage); } @@ -292,14 +277,16 @@ namespace Umbraco.Web.Security.Identity /// Umbraco back office configuration ///
/// - /// + /// + /// + /// /// /// /// By default this will be configured to execute on PipelineStage.Authenticate /// - public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, ApplicationContext appContext) + public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState,IGlobalSettings globalSettings) { - return app.UseUmbracoBackOfficeExternalCookieAuthentication(appContext, PipelineStage.Authenticate); + return app.UseUmbracoBackOfficeExternalCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, PipelineStage.Authenticate); } /// @@ -307,13 +294,17 @@ namespace Umbraco.Web.Security.Identity /// Umbraco back office configuration /// /// - /// + /// + /// + /// /// /// - public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, ApplicationContext appContext, PipelineStage stage) + public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app, + IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, + IGlobalSettings globalSettings, PipelineStage stage) { - if (app == null) throw new ArgumentNullException("app"); - if (appContext == null) throw new ArgumentNullException("appContext"); + if (app == null) throw new ArgumentNullException(nameof(app)); + if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); app.UseCookieAuthentication(new CookieAuthenticationOptions { @@ -322,9 +313,9 @@ namespace Umbraco.Web.Security.Identity CookieName = Constants.Security.BackOfficeExternalCookieName, ExpireTimeSpan = TimeSpan.FromMinutes(5), //Custom cookie manager so we can filter requests - CookieManager = new BackOfficeCookieManager(new SingletonUmbracoContextAccessor()), + CookieManager = new BackOfficeCookieManager(umbracoContextAccessor, runtimeState, globalSettings), CookiePath = "/", - CookieSecure = GlobalSettings.UseSSL ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest, + CookieSecure = globalSettings.UseHttps ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest, CookieHttpOnly = true, CookieDomain = UmbracoConfig.For.UmbracoSettings().Security.AuthCookieDomain }, stage); @@ -336,7 +327,10 @@ namespace Umbraco.Web.Security.Identity /// In order for preview to work this needs to be called ///
/// - /// + /// + /// + /// + /// /// /// /// This ensures that during a preview request that the back office use is also Authenticated and that the back office Identity @@ -345,43 +339,40 @@ namespace Umbraco.Web.Security.Identity /// /// By default this will be configured to execute on PipelineStage.PostAuthenticate /// - public static IAppBuilder UseUmbracoPreviewAuthentication(this IAppBuilder app, ApplicationContext appContext) + public static IAppBuilder UseUmbracoPreviewAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings, ISecuritySection securitySettings) { - return app.UseUmbracoPreviewAuthentication(appContext, PipelineStage.PostAuthenticate); + return app.UseUmbracoPreviewAuthentication(umbracoContextAccessor, runtimeState, globalSettings, securitySettings, PipelineStage.PostAuthenticate); } /// /// In order for preview to work this needs to be called /// /// - /// + /// + /// + /// + /// /// /// /// /// This ensures that during a preview request that the back office use is also Authenticated and that the back office Identity /// is added as a secondary identity to the current IPrincipal so it can be used to Authorize the previewed document. /// - public static IAppBuilder UseUmbracoPreviewAuthentication(this IAppBuilder app, ApplicationContext appContext, PipelineStage stage) + public static IAppBuilder UseUmbracoPreviewAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings, ISecuritySection securitySettings, PipelineStage stage) { - //don't apply if app isnot ready - if (appContext.IsConfigured) - { - var authOptions = app.CreateUmbracoCookieAuthOptions(); - app.Use(typeof(PreviewAuthenticationMiddleware), authOptions); + if (runtimeState.Level != RuntimeLevel.Run) return app; - //This middleware must execute at least on PostAuthentication, by default it is on Authorize - // The middleware needs to execute after the RoleManagerModule executes which is during PostAuthenticate, - // currently I've had 100% success with ensuring this fires after RoleManagerModule even if this is set - // to PostAuthenticate though not sure if that's always a guarantee so by default it's Authorize. - if (stage < PipelineStage.PostAuthenticate) - { - throw new InvalidOperationException("The stage specified for UseUmbracoPreviewAuthentication must be greater than or equal to " + PipelineStage.PostAuthenticate); - } + var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySettings); + app.Use(typeof(PreviewAuthenticationMiddleware), authOptions, UmbracoConfig.For.GlobalSettings()); - - app.UseStageMarker(stage); - } + // This middleware must execute at least on PostAuthentication, by default it is on Authorize + // The middleware needs to execute after the RoleManagerModule executes which is during PostAuthenticate, + // currently I've had 100% success with ensuring this fires after RoleManagerModule even if this is set + // to PostAuthenticate though not sure if that's always a guarantee so by default it's Authorize. + if (stage < PipelineStage.PostAuthenticate) + throw new InvalidOperationException("The stage specified for UseUmbracoPreviewAuthentication must be greater than or equal to " + PipelineStage.PostAuthenticate); + app.UseStageMarker(stage); return app; } @@ -394,16 +385,31 @@ namespace Umbraco.Web.Security.Identity /// Create the default umb cookie auth options ///
/// + /// + /// + /// + /// /// /// - public static UmbracoBackOfficeCookieAuthOptions CreateUmbracoCookieAuthOptions(this IAppBuilder app, string[] explicitPaths = null) + public static UmbracoBackOfficeCookieAuthOptions CreateUmbracoCookieAuthOptions(this IAppBuilder app, + IUmbracoContextAccessor umbracoContextAccessor, + IGlobalSettings globalSettings, IRuntimeState runtimeState, ISecuritySection securitySettings, string[] explicitPaths = null) { + //this is how aspnet wires up the default AuthenticationTicket protector so we'll use the same code + var ticketDataFormat = new TicketDataFormat( + app.CreateDataProtector(typeof (CookieAuthenticationMiddleware).FullName, + Constants.Security.BackOfficeAuthenticationType, + "v1")); + var authOptions = new UmbracoBackOfficeCookieAuthOptions( explicitPaths, - UmbracoConfig.For.UmbracoSettings().Security, - GlobalSettings.TimeOutInMinutes, - GlobalSettings.UseSSL); + umbracoContextAccessor, + securitySettings, + globalSettings, + runtimeState, + ticketDataFormat); + return authOptions; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/AuthenticationManagerExtensions.cs b/src/Umbraco.Web/Security/Identity/AuthenticationManagerExtensions.cs index 0acd002d42..4dfa5a7f5b 100644 --- a/src/Umbraco.Web/Security/Identity/AuthenticationManagerExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AuthenticationManagerExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Identity; @@ -83,4 +83,4 @@ namespace Umbraco.Web.Security.Identity return GetExternalLoginInfo(await manager.AuthenticateAsync(authenticationType)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs b/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs index 76c06fbac2..08e367eeeb 100644 --- a/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AuthenticationOptionsExtensions.cs @@ -1,8 +1,10 @@ -using System; +using System; using Microsoft.Owin; using Microsoft.Owin.Security; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; namespace Umbraco.Web.Security.Identity { @@ -21,10 +23,10 @@ namespace Umbraco.Web.Security.Identity public static void SetSignInChallengeResultCallback( this AuthenticationOptions authOptions, Func authProperties) - { + { authOptions.Description.Properties["ChallengeResultCallback"] = authProperties; } - + public static AuthenticationProperties GetSignInChallengeResult(this AuthenticationDescription authenticationDescription, IOwinContext ctx) { if (authenticationDescription.Properties.ContainsKey("ChallengeResultCallback") == false) return null; @@ -68,18 +70,18 @@ namespace Umbraco.Web.Security.Identity /// This is important if the identity provider is to be able to authenticate when upgrading Umbraco. We will try to extract this from /// any options passed in via reflection since none of the default OWIN providers inherit from a base class but so far all of them have a consistent /// name for the 'CallbackPath' property which is of type PathString. So we'll try to extract it if it's not found or supplied. - /// + /// /// If a value is extracted or supplied, this will be added to an internal list which the UmbracoModule will use to allow the request to pass /// through without redirecting to the installer. /// public static void ForUmbracoBackOffice(this AuthenticationOptions options, string style, string icon, string callbackPath = null) { - Mandate.ParameterNotNullOrEmpty(options.AuthenticationType, "options.AuthenticationType"); + if (string.IsNullOrEmpty(options.AuthenticationType)) throw new ArgumentNullOrEmptyException("options.AuthenticationType"); //Ensure the prefix is set if (options.AuthenticationType.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix) == false) { - options.AuthenticationType = Constants.Security.BackOfficeExternalAuthenticationTypePrefix + options.AuthenticationType; + options.AuthenticationType = Constants.Security.BackOfficeExternalAuthenticationTypePrefix + options.AuthenticationType; } options.Description.Properties["SocialStyle"] = style; @@ -106,7 +108,7 @@ namespace Umbraco.Web.Security.Identity } catch (System.Exception ex) { - LogHelper.Error(typeof (AuthenticationOptionsExtensions), "Could not read AuthenticationOptions properties", ex); + Current.Logger.Error(typeof (AuthenticationOptionsExtensions), "Could not read AuthenticationOptions properties", ex); } } else @@ -115,4 +117,4 @@ namespace Umbraco.Web.Security.Identity } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs index 59382888a7..204cd18072 100644 --- a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs @@ -14,25 +14,28 @@ namespace Umbraco.Web.Security.Identity /// A custom cookie manager that is used to read the cookie from the request. /// /// - /// Umbraco's back office cookie needs to be read on two paths: /umbraco and /install and /base therefore we cannot just set the cookie path to be /umbraco, + /// Umbraco's back office cookie needs to be read on two paths: /umbraco and /install and /base therefore we cannot just set the cookie path to be /umbraco, /// instead we'll specify our own cookie manager and return null if the request isn't for an acceptable path. /// internal class BackOfficeCookieManager : ChunkingCookieManager, ICookieManager { private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IRuntimeState _runtime; + private readonly IGlobalSettings _globalSettings; private readonly string[] _explicitPaths; private readonly string _getRemainingSecondsPath; - public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor) - : this(umbracoContextAccessor, null) - { - - } - public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IEnumerable explicitPaths) + public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IGlobalSettings globalSettings) + : this(umbracoContextAccessor, runtime, globalSettings, null) + { } + + public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IGlobalSettings globalSettings, IEnumerable explicitPaths) { _umbracoContextAccessor = umbracoContextAccessor; - _explicitPaths = explicitPaths == null ? null : explicitPaths.ToArray(); - _getRemainingSecondsPath = string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path); + _runtime = runtime; + _globalSettings = globalSettings; + _explicitPaths = explicitPaths?.ToArray(); + _getRemainingSecondsPath = $"{globalSettings.Path}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds"; } /// @@ -43,24 +46,24 @@ namespace Umbraco.Web.Security.Identity /// string ICookieManager.GetRequestCookie(IOwinContext context, string key) { - if (_umbracoContextAccessor.Value == null || context.Request.Uri.IsClientSideRequest()) + if (_umbracoContextAccessor.UmbracoContext == null || context.Request.Uri.IsClientSideRequest()) { return null; } - + return ShouldAuthenticateRequest( - context, - _umbracoContextAccessor.Value.OriginalRequestUrl) == false - //Don't auth request, don't return a cookie - ? null - //Return the default implementation - : base.GetRequestCookie(context, key); + context, + _umbracoContextAccessor.UmbracoContext.OriginalRequestUrl) == false + //Don't auth request, don't return a cookie + ? null + //Return the default implementation + : GetRequestCookie(context, key); } /// /// Determines if we should authenticate the request /// - /// + /// /// /// /// @@ -71,39 +74,36 @@ namespace Umbraco.Web.Security.Identity /// * it is a /base request /// * it is a preview request /// - internal bool ShouldAuthenticateRequest(IOwinContext ctx, Uri originalRequestUrl, bool checkForceAuthTokens = true) + internal bool ShouldAuthenticateRequest(IOwinContext owinContext, Uri originalRequestUrl, bool checkForceAuthTokens = true) { - if (_umbracoContextAccessor.Value.Application.IsConfigured == false - && _umbracoContextAccessor.Value.Application.DatabaseContext.IsDatabaseConfigured == false) - { - //Do not authenticate the request if we don't have a db and we are not configured - since we will never need - // to know a current user in this scenario - we treat it as a new install. Without this we can have some issues - // when people have older invalid cookies on the same domain since our user managers might attempt to lookup a user - // and we don't even have a db. + // Do not authenticate the request if we are not running (don't have a db, are not configured) - since we will never need + // to know a current user in this scenario - we treat it as a new install. Without this we can have some issues + // when people have older invalid cookies on the same domain since our user managers might attempt to lookup a user + // and we don't even have a db. + // was: app.IsConfigured == false (equiv to !Run) && dbContext.IsDbConfigured == false (equiv to Install) + // so, we handle .Install here and NOT .Upgrade + if (_runtime.Level == RuntimeLevel.Install) return false; - } - var request = ctx.Request; - var httpCtx = ctx.TryGetHttpContext(); - + var request = owinContext.Request; + var httpContext = owinContext.TryGetHttpContext(); + //check the explicit paths if (_explicitPaths != null) { return _explicitPaths.Any(x => x.InvariantEquals(request.Uri.AbsolutePath)); } - + //check user seconds path if (request.Uri.AbsolutePath.InvariantEquals(_getRemainingSecondsPath)) return false; if (//check the explicit flag - (checkForceAuthTokens && ctx.Get(Constants.Security.ForceReAuthFlag) != null) - || (checkForceAuthTokens && httpCtx.Success && httpCtx.Result.Items[Constants.Security.ForceReAuthFlag] != null) + (checkForceAuthTokens && owinContext.Get(Constants.Security.ForceReAuthFlag) != null) + || (checkForceAuthTokens && httpContext.Success && httpContext.Result.Items[Constants.Security.ForceReAuthFlag] != null) //check back office - || request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) + || request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath, _globalSettings) //check installer - || request.Uri.IsInstallerRequest() - //check for base - || BaseRest.BaseRestHandler.IsBaseRestRequest(originalRequestUrl)) + || request.Uri.IsInstallerRequest()) { return true; } @@ -111,4 +111,4 @@ namespace Umbraco.Web.Security.Identity } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs index b00e87e465..1526cfb2a1 100644 --- a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.ComponentModel; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Exceptions; using Umbraco.Core.Models.Identity; namespace Umbraco.Web.Security.Identity @@ -13,21 +14,6 @@ namespace Umbraco.Web.Security.Identity /// public class ExternalSignInAutoLinkOptions { - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use the overload specifying user groups instead and defaultAllowedSections now serves no purpose")] - public ExternalSignInAutoLinkOptions( - bool autoLinkExternalAccount = false, - string defaultUserType = "editor", - string[] defaultAllowedSections = null, - string defaultCulture = null) - { - Mandate.ParameterNotNullOrEmpty(defaultUserType, "defaultUserType"); - - _defaultUserGroups = new[] {defaultUserType}; - _autoLinkExternalAccount = autoLinkExternalAccount; - _defaultCulture = defaultCulture ?? GlobalSettings.DefaultUILanguage; - } - /// /// Creates a new instance /// @@ -41,7 +27,7 @@ namespace Umbraco.Web.Security.Identity { _defaultUserGroups = defaultUserGroups ?? new[] { "editor" }; _autoLinkExternalAccount = autoLinkExternalAccount; - _defaultCulture = defaultCulture ?? GlobalSettings.DefaultUILanguage; + _defaultCulture = defaultCulture ?? UmbracoConfig.For.GlobalSettings().DefaultUILanguage; } private readonly string[] _defaultUserGroups; @@ -87,16 +73,16 @@ namespace Umbraco.Web.Security.Identity /// /// For private external auth providers such as Active Directory, which when set to true will automatically /// create a local user if the external provider login was successful. - /// + /// /// For public auth providers this should always be false!!! /// public bool ShouldAutoLinkExternalAccount(UmbracoContext umbracoContext, ExternalLoginInfo loginInfo) { return _autoLinkExternalAccount; } - + private readonly string _defaultCulture; - + /// /// The default Culture to use for auto-linking users /// @@ -105,4 +91,4 @@ namespace Umbraco.Web.Security.Identity return _defaultCulture; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/FixWindowsAuthMiddlware.cs b/src/Umbraco.Web/Security/Identity/FixWindowsAuthMiddlware.cs index 911a304019..aea1b14ecd 100644 --- a/src/Umbraco.Web/Security/Identity/FixWindowsAuthMiddlware.cs +++ b/src/Umbraco.Web/Security/Identity/FixWindowsAuthMiddlware.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; @@ -14,7 +14,7 @@ namespace Umbraco.Web.Security.Identity /// handler and now two identities have been assigned. Unfortunately, at some stage in the pipeline - I'm pretty sure /// it's the Role Provider Module - it again changes the user's Principal to a RolePrincipal and discards the second /// Identity which is the Back office identity thus preventing a user from accessing the back office... it's very annoying. - /// + /// /// To fix this, we re-set the user Principal to only have a single identity: the back office one, since we know this is /// for a back office request. /// @@ -48,4 +48,4 @@ namespace Umbraco.Web.Security.Identity } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs index c3772cbac5..f3c7013701 100644 --- a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs +++ b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Core; using System.Threading.Tasks; using Umbraco.Core.Security; @@ -54,7 +54,7 @@ namespace Umbraco.Web.Security.Identity /// protected override Task ApplyResponseGrantAsync() { - if (_umbracoContextAccessor.Value == null || Context.Request.Uri.IsClientSideRequest()) + if (_umbracoContextAccessor.UmbracoContext == null || Context.Request.Uri.IsClientSideRequest()) { return Task.FromResult(0); } @@ -63,7 +63,7 @@ namespace Umbraco.Web.Security.Identity // which means that it is not a normal URL that is authenticated. var normalAuthUrl = ((BackOfficeCookieManager) Options.CookieManager) - .ShouldAuthenticateRequest(Context, _umbracoContextAccessor.Value.OriginalRequestUrl, + .ShouldAuthenticateRequest(Context, _umbracoContextAccessor.UmbracoContext.OriginalRequestUrl, //Pass in false, we want to know if this is a normal auth'd page checkForceAuthTokens: false); //This is auth'd normally, so OWIN will naturally take care of the cookie renewal @@ -72,7 +72,7 @@ namespace Umbraco.Web.Security.Identity var httpCtx = Context.TryGetHttpContext(); //check for the special flag in either the owin or http context var shouldRenew = Context.Get(Constants.Security.ForceReAuthFlag) != null || (httpCtx.Success && httpCtx.Result.Items[Constants.Security.ForceReAuthFlag] != null); - + if (shouldRenew) { var signin = Helper.LookupSignIn(Options.AuthenticationType); @@ -107,16 +107,16 @@ namespace Umbraco.Web.Security.Identity //now save back all the required cookie details var cookieValue = Options.TicketDataFormat.Protect(ticket); - var cookieOptions = ((UmbracoBackOfficeCookieAuthOptions)Options).CreateRequestCookieOptions(Context, ticket); + var cookieOptions = ((UmbracoBackOfficeCookieAuthOptions)Options).CreateRequestCookieOptions(Context, ticket); Options.CookieManager.AppendResponseCookie( Context, Options.CookieName, cookieValue, cookieOptions); - } + } + - } } } @@ -125,4 +125,4 @@ namespace Umbraco.Web.Security.Identity return Task.FromResult(0); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationMiddleware.cs b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationMiddleware.cs index 77a7a335f0..02d27eccbb 100644 --- a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationMiddleware.cs +++ b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationMiddleware.cs @@ -1,4 +1,4 @@ -using Microsoft.Owin; +using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.Infrastructure; using Owin; @@ -13,8 +13,8 @@ namespace Umbraco.Web.Security.Identity private readonly IUmbracoContextAccessor _umbracoContextAccessor; public ForceRenewalCookieAuthenticationMiddleware( - OwinMiddleware next, - IAppBuilder app, + OwinMiddleware next, + IAppBuilder app, UmbracoBackOfficeCookieAuthOptions options, IUmbracoContextAccessor umbracoContextAccessor) : base(next, app, options) { @@ -26,4 +26,4 @@ namespace Umbraco.Web.Security.Identity return new ForceRenewalCookieAuthenticationHandler(_umbracoContextAccessor); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs b/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs deleted file mode 100644 index ef858e5264..0000000000 --- a/src/Umbraco.Web/Security/Identity/FormsAuthenticationSecureDataFormat.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Security.Claims; -using System.Web.Security; -using Microsoft.Owin; -using Microsoft.Owin.Security; -using Microsoft.Owin.Security.Cookies; -using Newtonsoft.Json; -using Owin; -using Umbraco.Core.Security; - -namespace Umbraco.Web.Security.Identity -{ - - /// - /// Custom secure format that uses the old FormsAuthentication format - /// - internal class FormsAuthenticationSecureDataFormat : ISecureDataFormat - { - private readonly int _loginTimeoutMinutes; - - public FormsAuthenticationSecureDataFormat(int loginTimeoutMinutes) - { - _loginTimeoutMinutes = loginTimeoutMinutes; - } - - public string Protect(AuthenticationTicket data) - { - var backofficeIdentity = (UmbracoBackOfficeIdentity)data.Identity; - var userDataString = JsonConvert.SerializeObject(backofficeIdentity.UserData); - - var ticket = new FormsAuthenticationTicket( - 5, - data.Identity.Name, - data.Properties.IssuedUtc.HasValue - ? data.Properties.IssuedUtc.Value.LocalDateTime - : DateTime.Now, - data.Properties.ExpiresUtc.HasValue - ? data.Properties.ExpiresUtc.Value.LocalDateTime - : DateTime.Now.AddMinutes(_loginTimeoutMinutes), - data.Properties.IsPersistent, - userDataString, - "/" - ); - - return FormsAuthentication.Encrypt(ticket); - } - - /// - /// Unprotects the cookie - /// - /// - /// - public AuthenticationTicket Unprotect(string protectedText) - { - FormsAuthenticationTicket decrypt; - try - { - decrypt = FormsAuthentication.Decrypt(protectedText); - if (decrypt == null) return null; - } - catch (Exception) - { - return null; - } - - UmbracoBackOfficeIdentity identity; - - try - { - identity = new UmbracoBackOfficeIdentity(decrypt); - } - catch (Exception) - { - //if it cannot be created return null, will be due to serialization errors in user data most likely due to corrupt cookies or cookies - //for previous versions of Umbraco - return null; - } - - var ticket = new AuthenticationTicket(identity, new AuthenticationProperties - { - ExpiresUtc = decrypt.Expiration.ToUniversalTime(), - IssuedUtc = decrypt.IssueDate.ToUniversalTime(), - IsPersistent = decrypt.IsPersistent, - AllowRefresh = true - }); - - return ticket; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs index 04dea56c89..25482f17af 100644 --- a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs +++ b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Globalization; using System.Threading.Tasks; @@ -25,31 +25,32 @@ namespace Umbraco.Web.Security.Identity internal class GetUserSecondsMiddleWare : OwinMiddleware { private readonly UmbracoBackOfficeCookieAuthOptions _authOptions; + private readonly IGlobalSettings _globalSettings; private readonly ISecuritySection _security; private readonly ILogger _logger; public GetUserSecondsMiddleWare( OwinMiddleware next, UmbracoBackOfficeCookieAuthOptions authOptions, + IGlobalSettings globalSettings, ISecuritySection security, ILogger logger) : base(next) { - if (authOptions == null) throw new ArgumentNullException("authOptions"); - if (logger == null) throw new ArgumentNullException("logger"); - _authOptions = authOptions; + _authOptions = authOptions ?? throw new ArgumentNullException(nameof(authOptions)); + _globalSettings = globalSettings; _security = security; - _logger = logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public override async Task Invoke(IOwinContext context) { var request = context.Request; var response = context.Response; - + if (request.Uri.Scheme.InvariantStartsWith("http") && request.Uri.AbsolutePath.InvariantEquals( - string.Format("{0}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds", GlobalSettings.Path))) + $"{_globalSettings.Path}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds")) { var cookie = _authOptions.CookieManager.GetRequestCookie(context, _security.AuthCookieName); if (cookie.IsNullOrWhiteSpace() == false) @@ -68,7 +69,7 @@ namespace Umbraco.Web.Security.Identity response.Headers.Add("Expires", new[] { "-1" }); response.Headers.Add("Date", new[] { _authOptions.SystemClock.UtcNow.ToString("R") }); - //Ok, so here we need to check if we want to process/renew the auth ticket for each + //Ok, so here we need to check if we want to process/renew the auth ticket for each // of these requests. If that is the case, the user will really never be logged out until they // close their browser (there will be edge cases of that, especially when debugging) if (_security.KeepUserLoggedIn) @@ -107,7 +108,7 @@ namespace Umbraco.Web.Security.Identity } //We also need to re-validate the user's session if we are relying on this ping to keep their session alive - await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context, _authOptions.CookieManager, _authOptions.SystemClock, issuedUtc, ticket.Identity); + await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context, _authOptions.CookieManager, _authOptions.SystemClock, issuedUtc, ticket.Identity, _globalSettings); } else if (remainingSeconds <= 30) { @@ -138,4 +139,4 @@ namespace Umbraco.Web.Security.Identity } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs index 54d1d6bdce..8a1ffc4614 100644 --- a/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs +++ b/src/Umbraco.Web/Security/Identity/IUmbracoBackOfficeTwoFactorOptions.cs @@ -1,4 +1,4 @@ -using Microsoft.Owin; +using Microsoft.Owin; namespace Umbraco.Web.Security.Identity { @@ -9,4 +9,4 @@ namespace Umbraco.Web.Security.Identity { string GetTwoFactorView(IOwinContext owinContext, UmbracoContext umbracoContext, string username); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web/Security/Identity/PreviewAuthenticationMiddleware.cs index 7ab071ecea..da3e74ab82 100644 --- a/src/Umbraco.Web/Security/Identity/PreviewAuthenticationMiddleware.cs +++ b/src/Umbraco.Web/Security/Identity/PreviewAuthenticationMiddleware.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -27,16 +27,19 @@ namespace Umbraco.Web.Security.Identity internal class PreviewAuthenticationMiddleware : OwinMiddleware { private readonly UmbracoBackOfficeCookieAuthOptions _cookieOptions; + private readonly IGlobalSettings _globalSettings; /// /// Instantiates the middleware with an optional pointer to the next component. /// /// /// + /// public PreviewAuthenticationMiddleware(OwinMiddleware next, - UmbracoBackOfficeCookieAuthOptions cookieOptions) : base(next) + UmbracoBackOfficeCookieAuthOptions cookieOptions, IGlobalSettings globalSettings) : base(next) { _cookieOptions = cookieOptions; + _globalSettings = globalSettings; } /// @@ -50,10 +53,10 @@ namespace Umbraco.Web.Security.Identity if (request.Uri.IsClientSideRequest() == false) { var claimsPrincipal = context.Request.User as ClaimsPrincipal; - var isPreview = request.HasPreviewCookie() + var isPreview = request.HasPreviewCookie() && claimsPrincipal != null && request.Uri != null - && request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) == false; + && request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath, _globalSettings) == false; if (isPreview) { //If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing. @@ -62,13 +65,13 @@ namespace Umbraco.Web.Security.Identity var cookie = request.Cookies[_cookieOptions.CookieName]; if (cookie.IsNullOrWhiteSpace() == false) - { + { var unprotected = _cookieOptions.TicketDataFormat.Unprotect(cookie); if (unprotected != null) { - //Ok, we've got a real ticket, now we can add this ticket's identity to the current - // Principal, this means we'll have 2 identities assigned to the principal which we can - // use to authorize the preview and allow for a back office User. + //Ok, we've got a real ticket, now we can add this ticket's identity to the current + // Principal, this means we'll have 2 identities assigned to the principal which we can + // use to authorize the preview and allow for a back office User. claimsPrincipal.AddIdentity(UmbracoBackOfficeIdentity.FromClaimsIdentity(unprotected.Identity)); } } @@ -81,4 +84,4 @@ namespace Umbraco.Web.Security.Identity } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs b/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs index ebe457eeb8..a4d3852948 100644 --- a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs +++ b/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs @@ -1,12 +1,11 @@ using System; -using System.Security.Claims; -using System.Threading.Tasks; using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Web.Composing; namespace Umbraco.Web.Security.Identity { @@ -15,50 +14,34 @@ namespace Umbraco.Web.Security.Identity /// public sealed class UmbracoBackOfficeCookieAuthOptions : CookieAuthenticationOptions { - public int LoginTimeoutMinutes { get; private set; } - - public UmbracoBackOfficeCookieAuthOptions() - : this(UmbracoConfig.For.UmbracoSettings().Security, GlobalSettings.TimeOutInMinutes, GlobalSettings.UseSSL) - { - } + public int LoginTimeoutMinutes { get; } public UmbracoBackOfficeCookieAuthOptions( string[] explicitPaths, + IUmbracoContextAccessor umbracoContextAccessor, ISecuritySection securitySection, - int loginTimeoutMinutes, - bool forceSsl, - bool useLegacyFormsAuthDataFormat = true) + IGlobalSettings globalSettings, + IRuntimeState runtimeState, + ISecureDataFormat secureDataFormat) { - LoginTimeoutMinutes = loginTimeoutMinutes; + var secureDataFormat1 = secureDataFormat ?? throw new ArgumentNullException(nameof(secureDataFormat)); + LoginTimeoutMinutes = globalSettings.TimeOutInMinutes; AuthenticationType = Constants.Security.BackOfficeAuthenticationType; - - if (useLegacyFormsAuthDataFormat) - { - //If this is not explicitly set it will fall back to the default automatically - TicketDataFormat = new FormsAuthenticationSecureDataFormat(LoginTimeoutMinutes); - } - + SlidingExpiration = true; ExpireTimeSpan = TimeSpan.FromMinutes(LoginTimeoutMinutes); CookieDomain = securitySection.AuthCookieDomain; CookieName = securitySection.AuthCookieName; CookieHttpOnly = true; - CookieSecure = forceSsl ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest; - CookiePath = "/"; + CookieSecure = globalSettings.UseHttps ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest; + CookiePath = "/"; + + TicketDataFormat = new UmbracoSecureDataFormat(LoginTimeoutMinutes, secureDataFormat1); //Custom cookie manager so we can filter requests - CookieManager = new BackOfficeCookieManager(new SingletonUmbracoContextAccessor(), explicitPaths); + CookieManager = new BackOfficeCookieManager(umbracoContextAccessor, runtimeState, globalSettings, explicitPaths); } - - public UmbracoBackOfficeCookieAuthOptions( - ISecuritySection securitySection, - int loginTimeoutMinutes, - bool forceSsl, - bool useLegacyFormsAuthDataFormat = true) - : this(null, securitySection, loginTimeoutMinutes, forceSsl, useLegacyFormsAuthDataFormat) - { - } - + /// /// Creates the cookie options for saving the auth cookie /// @@ -67,8 +50,8 @@ namespace Umbraco.Web.Security.Identity /// public CookieOptions CreateRequestCookieOptions(IOwinContext ctx, AuthenticationTicket ticket) { - if (ctx == null) throw new ArgumentNullException("ctx"); - if (ticket == null) throw new ArgumentNullException("ticket"); + if (ctx == null) throw new ArgumentNullException(nameof(ctx)); + if (ticket == null) throw new ArgumentNullException(nameof(ticket)); var issuedUtc = ticket.Properties.IssuedUtc ?? SystemClock.UtcNow; var expiresUtc = ticket.Properties.ExpiresUtc ?? issuedUtc.Add(ExpireTimeSpan); @@ -89,6 +72,6 @@ namespace Umbraco.Web.Security.Identity return cookieOptions; } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Identity/UmbracoSecureDataFormat.cs b/src/Umbraco.Web/Security/Identity/UmbracoSecureDataFormat.cs new file mode 100644 index 0000000000..2676a5ee25 --- /dev/null +++ b/src/Umbraco.Web/Security/Identity/UmbracoSecureDataFormat.cs @@ -0,0 +1,83 @@ +using System; +using System.Security.Claims; +using System.Web.Security; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Cookies; +using Newtonsoft.Json; +using Owin; +using Umbraco.Core.Security; + +namespace Umbraco.Web.Security.Identity +{ + + /// + /// Custom secure format that ensures the Identity in the ticket is and not just a ClaimsIdentity + /// + internal class UmbracoSecureDataFormat : ISecureDataFormat + { + private readonly int _loginTimeoutMinutes; + private readonly ISecureDataFormat _ticketDataFormat; + + public UmbracoSecureDataFormat(int loginTimeoutMinutes, ISecureDataFormat ticketDataFormat) + { + _loginTimeoutMinutes = loginTimeoutMinutes; + _ticketDataFormat = ticketDataFormat ?? throw new ArgumentNullException(nameof(ticketDataFormat)); + } + + public string Protect(AuthenticationTicket data) + { + var backofficeIdentity = (UmbracoBackOfficeIdentity)data.Identity; + + //create a new ticket based on the passed in tickets details, however, we'll adjust the expires utc based on the specified timeout mins + var ticket = new AuthenticationTicket(backofficeIdentity, + new AuthenticationProperties(data.Properties.Dictionary) + { + IssuedUtc = data.Properties.IssuedUtc, + ExpiresUtc = data.Properties.ExpiresUtc ?? DateTimeOffset.UtcNow.AddMinutes(_loginTimeoutMinutes), + AllowRefresh = data.Properties.AllowRefresh, + IsPersistent = data.Properties.IsPersistent, + RedirectUri = data.Properties.RedirectUri + }); + + return _ticketDataFormat.Protect(ticket); + } + + /// + /// Unprotects the cookie + /// + /// + /// + public AuthenticationTicket Unprotect(string protectedText) + { + AuthenticationTicket decrypt; + try + { + decrypt = _ticketDataFormat.Unprotect(protectedText); + if (decrypt == null) return null; + } + catch (Exception) + { + return null; + } + + UmbracoBackOfficeIdentity identity; + + try + { + identity = UmbracoBackOfficeIdentity.FromClaimsIdentity(decrypt.Identity); + } + catch (Exception) + { + //if it cannot be created return null, will be due to serialization errors in user data most likely due to corrupt cookies or cookies + //for previous versions of Umbraco + return null; + } + + //return the ticket with a UmbracoBackOfficeIdentity + var ticket = new AuthenticationTicket(identity, decrypt.Properties); + + return ticket; + } + } +} diff --git a/src/Umbraco.Web/Security/LegacyDefaultAppMapping.cs b/src/Umbraco.Web/Security/LegacyDefaultAppMapping.cs deleted file mode 100644 index 99d0b71f5b..0000000000 --- a/src/Umbraco.Web/Security/LegacyDefaultAppMapping.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Umbraco.Web.Security -{ - /// - /// This is used specifically to assign a default 'app' to a particular section in order to validate the - /// currently logged in user's allowed applications - /// - /// - /// This relates to these issues: - /// http://issues.umbraco.org/issue/U4-2021 - /// http://issues.umbraco.org/issue/U4-529 - /// - /// In order to fix these issues we need to pass in an 'app' parameter but since we don't want to break compatibility - /// we will create this mapping to map a 'default application' to a section action (like creating or deleting) - /// - public static class LegacyDefaultAppMapping - { - /// - /// Constructor that assigns all initial known mappings - /// - static LegacyDefaultAppMapping() - { - } - - private static readonly ConcurrentDictionary NodeTypeAliasMapping = new ConcurrentDictionary(); - - /// - /// Adds the default app mapping to the node type - /// - /// The nodeType is the same nodeType found in the UI.xml - /// The default app associated with this nodeType if the 'app' parameter was not detected - public static void AddNodeTypeMappingForCreateDialog(string nodeType, string defaultApp) - { - NodeTypeAliasMapping.AddOrUpdate(nodeType, s => defaultApp, (s, s1) => defaultApp); - } - - internal static string GetDefaultAppForCreateDialog(string nodeTypeAlias) - { - string app; - return NodeTypeAliasMapping.TryGetValue(nodeTypeAlias, out app) ? app : null; - } - - } -} diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index eeb9bad52b..5e9a4efc41 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -6,22 +6,25 @@ using System.Linq; using System.Text; using System.Web; using System.Web.Security; +using LightInject; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Security; using Umbraco.Web.Models; using Umbraco.Web.PublishedCache; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; using Umbraco.Web.Editors; using Umbraco.Web.Security.Providers; using Umbraco.Core.Services; +using Umbraco.Web.Routing; using MPE = global::Umbraco.Core.Security.MembershipProviderExtensions; namespace Umbraco.Web.Security { - /// /// A helper class for handling Members /// @@ -29,51 +32,87 @@ namespace Umbraco.Web.Security { private readonly MembershipProvider _membershipProvider; private readonly RoleProvider _roleProvider; - private readonly ApplicationContext _applicationContext; private readonly HttpContextBase _httpContext; + private readonly IPublishedMemberCache _memberCache; private readonly UmbracoContext _umbracoContext; + [Inject] + private IMemberService MemberService { get; set; } + + [Inject] + private IMemberTypeService MemberTypeService { get; set; } + + [Inject] + private IUserService UserService { get; set; } + + [Inject] + private IPublicAccessService PublicAccessService { get; set; } + + [Inject] + private CacheHelper ApplicationCache { get; set; } + + [Inject] + private ILogger Logger { get; set; } + + [Inject] + private PublishedRouter Router { get; set; } + #region Constructors - [Obsolete("Use the constructor specifying an UmbracoContext")] - [EditorBrowsable(EditorBrowsableState.Never)] - public MembershipHelper(ApplicationContext applicationContext, HttpContextBase httpContext) - : this(applicationContext, httpContext, MPE.GetMembersMembershipProvider(), Roles.Enabled ? Roles.Provider : new MembersRoleProvider(applicationContext.Services.MemberService)) - { - } + // used here and there for IMember operations (not front-end stuff, no need for _memberCache) [Obsolete("Use the constructor specifying an UmbracoContext")] [EditorBrowsable(EditorBrowsableState.Never)] - public MembershipHelper(ApplicationContext applicationContext, HttpContextBase httpContext, MembershipProvider membershipProvider, RoleProvider roleProvider) + public MembershipHelper(HttpContextBase httpContext) { - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); - if (httpContext == null) throw new ArgumentNullException("httpContext"); - if (membershipProvider == null) throw new ArgumentNullException("membershipProvider"); - if (roleProvider == null) throw new ArgumentNullException("roleProvider"); - _applicationContext = applicationContext; + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); _httpContext = httpContext; - _membershipProvider = membershipProvider; - _roleProvider = roleProvider; + _membershipProvider = MPE.GetMembersMembershipProvider(); + _roleProvider = Roles.Enabled ? Roles.Provider : new MembersRoleProvider(MemberService); + + // _memberCache remains null - not supposed to use it + // alternatively we'd need to get if from the 'current' UmbracoContext? + + // helpers are *not* instanciated by the container so we have to + // get our dependencies injected manually, through properties. + Current.Container.InjectProperties(this); } + // used everywhere public MembershipHelper(UmbracoContext umbracoContext) - : this(umbracoContext, MPE.GetMembersMembershipProvider(), Roles.Enabled ? Roles.Provider : new MembersRoleProvider(umbracoContext.Application.Services.MemberService)) - { - } + : this(umbracoContext, MPE.GetMembersMembershipProvider(), Roles.Enabled ? Roles.Provider : new MembersRoleProvider(Current.Services.MemberService)) + { } + // used in tests and (this) public MembershipHelper(UmbracoContext umbracoContext, MembershipProvider membershipProvider, RoleProvider roleProvider) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (membershipProvider == null) throw new ArgumentNullException("membershipProvider"); - if (roleProvider == null) throw new ArgumentNullException("roleProvider"); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider)); + if (roleProvider == null) throw new ArgumentNullException(nameof(roleProvider)); + _httpContext = umbracoContext.HttpContext; - _applicationContext = umbracoContext.Application; + _umbracoContext = umbracoContext; _membershipProvider = membershipProvider; _roleProvider = roleProvider; - _umbracoContext = umbracoContext; + _memberCache = umbracoContext.PublishedShapshot.Members; + + // helpers are *not* instanciated by the container so we have to + // get our dependencies injected manually, through properties. + Current.Container.InjectProperties(this); } + #endregion + protected IPublishedMemberCache MemberCache + { + get + { + if (_memberCache == null) + throw new InvalidOperationException("No MemberCache."); + return _memberCache; + } + } + /// /// Check if a document object is protected by the "Protect Pages" functionality in umbraco /// @@ -82,7 +121,7 @@ namespace Umbraco.Web.Security public virtual bool IsProtected(string path) { //this is a cached call - return _applicationContext.Services.PublicAccessService.IsProtected(path); + return PublicAccessService.IsProtected(path); } /// @@ -93,7 +132,7 @@ namespace Umbraco.Web.Security public virtual bool MemberHasAccess(string path) { //cache this in the request cache - return _applicationContext.ApplicationCache.RequestCache.GetCacheItem(string.Format("{0}.{1}-{2}", typeof(MembershipHelper), "MemberHasAccess", path), () => + return ApplicationCache.RequestCache.GetCacheItem(string.Format("{0}.{1}-{2}", typeof(MembershipHelper), "MemberHasAccess", path), () => { if (IsProtected(path)) { @@ -106,20 +145,20 @@ namespace Umbraco.Web.Security /// /// This will check if the member has access to this path /// - /// + /// /// /// /// - /// This is essentially the same as the PublicAccessServiceExtensions.HasAccess however this will use the PCR cache + /// This is essentially the same as the PublicAccessServiceExtensions.HasAccess however this will use the PCR cache /// of the already looked up roles for the member so this doesn't need to happen more than once. - /// This does a safety check in case of things like unit tests where there is no PCR and if that is the case it will use + /// This does a safety check in case of things like unit tests where there is no PCR and if that is the case it will use /// lookup the roles directly. /// private bool HasAccess(string path, RoleProvider roleProvider) { - return _umbracoContext.PublishedContentRequest == null - ? _applicationContext.Services.PublicAccessService.HasAccess(path, CurrentUserName, roleProvider.GetRolesForUser) - : _applicationContext.Services.PublicAccessService.HasAccess(path, CurrentUserName, _umbracoContext.PublishedContentRequest.GetRolesForLogin); + return _umbracoContext.PublishedRequest == null + ? PublicAccessService.HasAccess(path, CurrentUserName, roleProvider.GetRolesForUser) + : PublicAccessService.HasAccess(path, CurrentUserName, Router.GetRolesForLogin); } /// @@ -170,8 +209,8 @@ namespace Umbraco.Web.Security var member = GetCurrentPersistedMember(); - //NOTE: If changing the username is a requirement, than that needs to be done via the IMember directly since MembershipProvider's natively do - // not support changing a username! + //NOTE: If changing the username is a requirement, than that needs to be done via the IMember directly since MembershipProvider's natively do + // not support changing a username! if (model.Name != null && member.Name != model.Name) { member.Name = model.Name; @@ -186,11 +225,11 @@ namespace Umbraco.Web.Security //needs to be editable .Where(p => member.ContentType.MemberCanEditProperty(p.Alias))) { - member.Properties[property.Alias].Value = property.Value; + member.Properties[property.Alias].SetValue(property.Value); } } - _applicationContext.Services.MemberService.Save(member); + MemberService.Save(member); //reset the FormsAuth cookie since the username might have changed FormsAuthentication.SetAuthCookie(member.Username, true); @@ -213,7 +252,7 @@ namespace Umbraco.Web.Security MembershipUser membershipUser; var provider = _membershipProvider; - //update their real name + //update their real name if (provider.IsUmbracoMembershipProvider()) { membershipUser = ((UmbracoMembershipProviderBase)provider).CreateUser( @@ -225,7 +264,7 @@ namespace Umbraco.Web.Security if (status != MembershipCreateStatus.Success) return null; - var member = _applicationContext.Services.MemberService.GetByUsername(membershipUser.UserName); + var member = MemberService.GetByUsername(membershipUser.UserName); member.Name = model.Name; if (model.MemberProperties != null) @@ -233,11 +272,11 @@ namespace Umbraco.Web.Security foreach (var property in model.MemberProperties.Where(p => p.Value != null) .Where(property => member.Properties.Contains(property.Alias))) { - member.Properties[property.Alias].Value = property.Value; + member.Properties[property.Alias].SetValue(property.Value); } } - _applicationContext.Services.MemberService.Save(member); + MemberService.Save(member); } else { @@ -280,7 +319,7 @@ namespace Umbraco.Web.Security if (member == null) { //this should not happen - LogHelper.Warn("The member validated but then no member was returned with the username " + username); + Current.Logger.Warn("The member validated but then no member was returned with the username " + username); return false; } //Log them in @@ -300,66 +339,43 @@ namespace Umbraco.Web.Security public virtual IPublishedContent GetByProviderKey(object key) { - return _applicationContext.ApplicationCache.RequestCache.GetCacheItem( - GetCacheKey("GetByProviderKey", key), () => - { - var provider = _membershipProvider; - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); - } - - var result = _applicationContext.Services.MemberService.GetByProviderKey(key); - return result == null ? null : new MemberPublishedContent(result).CreateModel(); - }); + return MemberCache.GetByProviderKey(key); } public virtual IPublishedContent GetById(int memberId) { - return _applicationContext.ApplicationCache.RequestCache.GetCacheItem( - GetCacheKey("GetById", memberId), () => - { - var provider = _membershipProvider; - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); - } - - var result = _applicationContext.Services.MemberService.GetById(memberId); - return result == null ? null : new MemberPublishedContent(result).CreateModel(); - }); + return MemberCache.GetById(memberId); } public virtual IPublishedContent GetByUsername(string username) { - return _applicationContext.ApplicationCache.RequestCache.GetCacheItem( - GetCacheKey("GetByUsername", username), () => - { - var provider = _membershipProvider; - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); - } - - var result = _applicationContext.Services.MemberService.GetByUsername(username); - return result == null ? null : new MemberPublishedContent(result).CreateModel(); - }); + return MemberCache.GetByUsername(username); } public virtual IPublishedContent GetByEmail(string email) { - return _applicationContext.ApplicationCache.RequestCache.GetCacheItem( - GetCacheKey("GetByEmail", email), () => - { - var provider = _membershipProvider; - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("Cannot access this method unless the Umbraco membership provider is active"); - } + return MemberCache.GetByEmail(email); + } - var result = _applicationContext.Services.MemberService.GetByEmail(email); - return result == null ? null : new MemberPublishedContent(result).CreateModel(); - }); + public virtual IPublishedContent Get(Udi udi) + { + var guidUdi = udi as GuidUdi; + if (guidUdi == null) return null; + + var umbracoType = Constants.UdiEntityType.ToUmbracoObjectType(udi.EntityType); + + var entityService = Current.Services.EntityService; + switch (umbracoType) + { + case UmbracoObjectTypes.Member: + // fixme - need to implement Get(guid)! + var memberAttempt = entityService.GetId(guidUdi.Guid, umbracoType); + if (memberAttempt.Success) + return GetById(memberAttempt.Result); + break; + } + + return null; } /// @@ -373,7 +389,7 @@ namespace Umbraco.Web.Security return null; } var result = GetCurrentPersistedMember(); - return result == null ? null : new MemberPublishedContent(result).CreateModel(); + return result == null ? null : MemberCache.GetByMember(result); } /// @@ -387,7 +403,7 @@ namespace Umbraco.Web.Security return -1; } var result = GetCurrentMember(); - return result == null ? -1 : result.Id; + return result?.Id ?? -1; } #endregion @@ -463,7 +479,7 @@ namespace Umbraco.Web.Security if (provider.IsUmbracoMembershipProvider()) { memberTypeAlias = memberTypeAlias ?? Constants.Conventions.MemberTypes.DefaultAlias; - var memberType = _applicationContext.Services.MemberTypeService.Get(memberTypeAlias); + var memberType = MemberTypeService.Get(memberTypeAlias); if (memberType == null) throw new InvalidOperationException("Could not find a member type with alias " + memberTypeAlias); @@ -493,9 +509,9 @@ namespace Umbraco.Web.Security if (member != null) { var propValue = member.Properties[prop.Alias]; - if (propValue != null && propValue.Value != null) + if (propValue != null && propValue.GetValue() != null) { - value = propValue.Value.ToString(); + value = propValue.GetValue().ToString(); } } @@ -506,11 +522,11 @@ namespace Umbraco.Web.Security Value = value }; - //TODO: Perhaps one day we'll ship with our own EditorTempates but for now developers + //TODO: Perhaps one day we'll ship with our own EditorTempates but for now developers // can just render their own. ////This is a rudimentary check to see what data template we should render - //// if developers want to change the template they can do so dynamically in their views or controllers + //// if developers want to change the template they can do so dynamically in their views or controllers //// for a given property. ////These are the default built-in MVC template types: “Boolean”, “Decimal”, “EmailAddress”, “HiddenInput”, “Html”, “Object”, “String”, “Text”, and “Url” //// by default we'll render a text box since we've defined that metadata on the UmbracoProperty.Value property directly. @@ -519,7 +535,7 @@ namespace Umbraco.Web.Security // viewProperty.EditorTemplate = "UmbracoBoolean"; //} //else - //{ + //{ // switch (prop.DataTypeDatabaseType) // { // case DataTypeDatabaseType.Integer: @@ -605,10 +621,7 @@ namespace Umbraco.Web.Security /// /// Returns the currently logged in username /// - public string CurrentUserName - { - get { return _httpContext.User.Identity.Name; } - } + public string CurrentUserName => _httpContext.User.Identity.Name; /// /// Returns true or false if the currently logged in member is authorized based on the parameters provided @@ -680,8 +693,6 @@ namespace Umbraco.Web.Security var groups = _roleProvider.GetRolesForUser(username); allowAction = allowGroupsList.Select(s => s.ToLowerInvariant()).Intersect(groups.Select(myGroup => myGroup.ToLowerInvariant())).Any(); } - - } return allowAction; @@ -701,7 +712,7 @@ namespace Umbraco.Web.Security { throw new InvalidOperationException("Could not find provider with name " + membershipProviderName); } - + return ChangePassword(username, passwordModel, provider); } @@ -711,10 +722,10 @@ namespace Umbraco.Web.Security /// /// /// - /// + /// public virtual Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) { - var passwordChanger = new PasswordChanger(_applicationContext.ProfilingLogger.Logger, _applicationContext.Services.UserService, UmbracoContext.Current.HttpContext); + var passwordChanger = new PasswordChanger(Logger, UserService, Web.Composing.Current.UmbracoContext.HttpContext); return passwordChanger.ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider); } @@ -738,43 +749,39 @@ namespace Umbraco.Web.Security DateTime? lastActivityDate = null, string comment = null) { - var needsUpdating = new List(); + var update = false; - //set the writable properties if (email != null) { - needsUpdating.Add(member.Email != email); + if (member.Email != email) update = true; member.Email = email; } if (isApproved.HasValue) { - needsUpdating.Add(member.IsApproved != isApproved.Value); + if (member.IsApproved != isApproved.Value) update = true; member.IsApproved = isApproved.Value; } if (lastLoginDate.HasValue) { - needsUpdating.Add(member.LastLoginDate != lastLoginDate.Value); + if (member.LastLoginDate != lastLoginDate.Value) update = true; member.LastLoginDate = lastLoginDate.Value; } if (lastActivityDate.HasValue) { - needsUpdating.Add(member.LastActivityDate != lastActivityDate.Value); + if (member.LastActivityDate != lastActivityDate.Value) update = true; member.LastActivityDate = lastActivityDate.Value; } if (comment != null) { - needsUpdating.Add(member.Comment != comment); + if (member.Comment != comment) update = true; member.Comment = comment; } - //Don't persist anything if nothing has changed - if (needsUpdating.Any(x => x == true)) - { - provider.UpdateUser(member); - return Attempt.Succeed(member); - } + if (update == false) + return Attempt.Fail(member); - return Attempt.Fail(member); + provider.UpdateUser(member); + return Attempt.Succeed(member); } /// @@ -783,7 +790,7 @@ namespace Umbraco.Web.Security /// private IMember GetCurrentPersistedMember() { - return _applicationContext.ApplicationCache.RequestCache.GetCacheItem( + return ApplicationCache.RequestCache.GetCacheItem( GetCacheKey("GetCurrentPersistedMember"), () => { var provider = _membershipProvider; @@ -793,14 +800,17 @@ namespace Umbraco.Web.Security throw new NotSupportedException("An IMember model can only be retreived when using the built-in Umbraco membership providers"); } var username = provider.GetCurrentUserName(); - var member = _applicationContext.Services.MemberService.GetByUsername(username); + var member = MemberService.GetByUsername(username); return member; }); } - private string GetCacheKey(string key, params object[] additional) + private static string GetCacheKey(string key, params object[] additional) { - var sb = new StringBuilder(string.Format("{0}-{1}", typeof(MembershipHelper).Name, key)); + var sb = new StringBuilder(); + sb.Append(typeof (MembershipHelper).Name); + sb.Append("-"); + sb.Append(key); foreach (var s in additional) { sb.Append("-"); diff --git a/src/Umbraco.Web/Security/OwinExtensions.cs b/src/Umbraco.Web/Security/OwinExtensions.cs index 8217dc7f6c..80ba0acfa4 100644 --- a/src/Umbraco.Web/Security/OwinExtensions.cs +++ b/src/Umbraco.Web/Security/OwinExtensions.cs @@ -1,7 +1,7 @@ -using System; -using System.Web; +using System.Web; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; +using Microsoft.Owin.Security; using Umbraco.Core; using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; @@ -10,18 +10,17 @@ using Umbraco.Web.Security.Identity; namespace Umbraco.Web.Security { internal static class OwinExtensions - { - + { /// - /// Nasty little hack to get httpcontextbase from an owin context + /// Gets the for the Umbraco back office cookie /// /// /// - internal static Attempt TryGetHttpContext(this IOwinContext owinContext) + internal static ISecureDataFormat GetUmbracoAuthTicketDataProtector(this IOwinContext owinContext) { - var ctx = owinContext.Get(typeof(HttpContextBase).FullName); - return ctx == null ? Attempt.Fail() : Attempt.Succeed(ctx); + var found = owinContext.Get(); + return found?.Protector; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 3d762d8d1a..8a9fdf8081 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -1,31 +1,25 @@ using System.Collections.Specialized; -using System.ComponentModel.DataAnnotations; using System.Configuration.Provider; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using System.Web.Hosting; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Models.Membership; +using Umbraco.Web.Composing; namespace Umbraco.Web.Security.Providers { /// - /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) + /// Custom Membership Provider for Umbraco Members (User authentication for Frontend applications NOT umbraco CMS) /// public class MembersMembershipProvider : UmbracoMembershipProvider, IUmbracoMemberTypeMembershipProvider { public MembersMembershipProvider() - : this(ApplicationContext.Current.Services.MemberService) - { - } + : this(Current.Services.MemberService, Current.Services.MemberTypeService) + { } - public MembersMembershipProvider(IMembershipMemberService memberService) + public MembersMembershipProvider(IMembershipMemberService memberService, IMemberTypeService memberTypeService) : base(memberService) { LockPropertyTypeAlias = Constants.Conventions.Member.IsLockedOut; @@ -37,32 +31,31 @@ namespace Umbraco.Web.Security.Providers LastPasswordChangedPropertyTypeAlias = Constants.Conventions.Member.LastPasswordChangeDate; PasswordRetrievalQuestionPropertyTypeAlias = Constants.Conventions.Member.PasswordQuestion; PasswordRetrievalAnswerPropertyTypeAlias = Constants.Conventions.Member.PasswordAnswer; + _memberTypeService = memberTypeService; } + private readonly IMemberTypeService _memberTypeService; private string _defaultMemberTypeAlias = "Member"; - private volatile bool _hasDefaultMember = false; + private volatile bool _hasDefaultMember; private static readonly object Locker = new object(); - private bool _providerKeyAsGuid = false; + private bool _providerKeyAsGuid; + + public override string ProviderName => "MembersMembershipProvider"; - public override string ProviderName - { - get { return "MembersMembershipProvider"; } - } - protected override MembershipUser ConvertToMembershipUser(IMember entity) { return entity.AsConcreteMembershipUser(Name, _providerKeyAsGuid); } - public string LockPropertyTypeAlias { get; private set; } - public string LastLockedOutPropertyTypeAlias { get; private set; } - public string FailedPasswordAttemptsPropertyTypeAlias { get; private set; } - public string ApprovedPropertyTypeAlias { get; private set; } - public string CommentPropertyTypeAlias { get; private set; } - public string LastLoginPropertyTypeAlias { get; private set; } - public string LastPasswordChangedPropertyTypeAlias { get; private set; } - public string PasswordRetrievalQuestionPropertyTypeAlias { get; private set; } - public string PasswordRetrievalAnswerPropertyTypeAlias { get; private set; } + public string LockPropertyTypeAlias { get; } + public string LastLockedOutPropertyTypeAlias { get; } + public string FailedPasswordAttemptsPropertyTypeAlias { get; } + public string ApprovedPropertyTypeAlias { get; } + public string CommentPropertyTypeAlias { get; } + public string LastLoginPropertyTypeAlias { get; } + public string LastPasswordChangedPropertyTypeAlias { get; } + public string PasswordRetrievalQuestionPropertyTypeAlias { get; } + public string PasswordRetrievalAnswerPropertyTypeAlias { get; } public override void Initialize(string name, NameValueCollection config) { @@ -91,7 +84,7 @@ namespace Umbraco.Web.Security.Providers protected override Attempt GetRawPassword(string username) { - var found = MemberService.GetByUsername(username); + var found = MemberService.GetByUsername(username); if (found == null) return Attempt.Fail(); return Attempt.Succeed(found.RawPasswordValue); } @@ -106,7 +99,7 @@ namespace Umbraco.Web.Security.Providers { if (_hasDefaultMember == false) { - _defaultMemberTypeAlias = MemberService.GetDefaultMemberType(); + _defaultMemberTypeAlias = _memberTypeService.GetDefault(); if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) { throw new ProviderException("No default member type alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); diff --git a/src/Umbraco.Web/Security/Providers/MembersRoleProvider.cs b/src/Umbraco.Web/Security/Providers/MembersRoleProvider.cs index 2ddbc0b5a2..89507a6c5b 100644 --- a/src/Umbraco.Web/Security/Providers/MembersRoleProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersRoleProvider.cs @@ -1,4 +1,4 @@ -using System.Collections.Specialized; +using System.Collections.Specialized; using System.Configuration.Provider; using System.Linq; using System.Web.Security; @@ -6,6 +6,7 @@ using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Services; +using Umbraco.Web.Composing; namespace Umbraco.Web.Security.Providers { @@ -19,12 +20,12 @@ namespace Umbraco.Web.Security.Providers } public MembersRoleProvider() - : this(ApplicationContext.Current.Services.MemberService) - { + : this(Current.Services.MemberService) + { } private string _applicationName; - + public override bool IsUserInRole(string username, string roleName) { return GetRolesForUser(username).Any(x => x == roleName); @@ -98,4 +99,4 @@ namespace Umbraco.Web.Security.Providers } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index 099611fbc0..04df63a41f 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Core.Models.Identity; namespace Umbraco.Web.Security.Providers @@ -54,10 +55,10 @@ namespace Umbraco.Web.Security.Providers /// The friendly name of the provider. /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. /// The name of the provider is null. - /// An attempt is made to call - /// on a provider after the provider + /// An attempt is made to call + /// on a provider after the provider /// has already been initialized. - /// The name of the provider has a length of zero. + /// The name of the provider has a length of zero. public override void Initialize(string name, NameValueCollection config) { if (config == null) { throw new ArgumentNullException("config"); } @@ -81,7 +82,7 @@ namespace Umbraco.Web.Security.Providers /// protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) { - //NOTE: due to backwards compatibilty reasons (and UX reasons), this provider doesn't care about the old password and + //NOTE: due to backwards compatibilty reasons (and UX reasons), this provider doesn't care about the old password and // allows simply setting the password manually so we don't really care about the old password. // This is allowed based on the overridden AllowManuallyChangingPassword option. @@ -148,7 +149,7 @@ namespace Umbraco.Web.Security.Providers if (MemberService.Exists(username)) { status = MembershipCreateStatus.DuplicateUserName; - LogHelper.Warn>("Cannot create member as username already exists: " + username); + Current.Logger.Warn>("Cannot create member as username already exists: " + username); return null; } @@ -156,7 +157,7 @@ namespace Umbraco.Web.Security.Providers if (MemberService.GetByEmail(email) != null && RequiresUniqueEmail) { status = MembershipCreateStatus.DuplicateEmail; - LogHelper.Warn>( + Current.Logger.Warn>( "Cannot create member as a member with the same email address exists: " + email); return null; } @@ -213,7 +214,9 @@ namespace Umbraco.Web.Security.Providers /// public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = MemberService.FindByEmail(emailToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + long totalRecords2; + var byEmail = MemberService.FindByEmail(emailToMatch, pageIndex, pageSize, out totalRecords2, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = Convert.ToInt32(totalRecords2); var collection = new MembershipUserCollection(); foreach (var m in byEmail) @@ -235,7 +238,9 @@ namespace Umbraco.Web.Security.Providers /// public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { - var byEmail = MemberService.FindByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords, StringPropertyMatchType.Wildcard).ToArray(); + long totalRecords2; + var byEmail = MemberService.FindByUsername(usernameToMatch, pageIndex, pageSize, out totalRecords2, StringPropertyMatchType.Wildcard).ToArray(); + totalRecords = Convert.ToInt32(totalRecords2); var collection = new MembershipUserCollection(); foreach (var m in byEmail) @@ -258,7 +263,9 @@ namespace Umbraco.Web.Security.Providers { var membersList = new MembershipUserCollection(); - var pagedMembers = MemberService.GetAll(pageIndex, pageSize, out totalRecords); + long totalRecords2; + var pagedMembers = MemberService.GetAll(pageIndex, pageSize, out totalRecords2); + totalRecords = Convert.ToInt32(totalRecords2); foreach (var m in pagedMembers) { @@ -271,7 +278,7 @@ namespace Umbraco.Web.Security.Providers /// Gets the number of users currently accessing the application. /// /// - /// The number of users currently accessing the application. + /// The number of users currently accessing the application. /// /// /// The way this is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members @@ -450,14 +457,14 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException(string.Format("No member with the username '{0}' found", username)); } - // Non need to update + // Non need to update if (member.IsLockedOut == false) return true; member.IsLockedOut = false; member.FailedPasswordAttempts = 0; MemberService.Save(member); - + return true; } @@ -478,7 +485,7 @@ namespace Umbraco.Web.Security.Providers /// /// Updates e-mail approved status, lock status and comment on a user. /// - /// A object that represents the user to update and the updated information for the user. + /// A object that represents the user to update and the updated information for the user. public override void UpdateUser(MembershipUser user) { var m = MemberService.GetByUsername(user.UserName); @@ -490,7 +497,7 @@ namespace Umbraco.Web.Security.Providers if (RequiresUniqueEmail && user.Email.Trim().IsNullOrWhiteSpace() == false) { - int totalRecs; + long totalRecs; var byEmail = MemberService.FindByEmail(user.Email.Trim(), 0, int.MaxValue, out totalRecs, StringPropertyMatchType.Exact); if (byEmail.Count(x => x.Id != m.Id) > 0) { @@ -518,7 +525,7 @@ namespace Umbraco.Web.Security.Providers if (member == null) { - LogHelper.Info( + Current.Logger.Info( string.Format( "Login attempt failed for username {0} from IP address {1}, the user does not exist", username, @@ -532,7 +539,7 @@ namespace Umbraco.Web.Security.Providers if (member.IsApproved == false) { - LogHelper.Info( + Current.Logger.Info( string.Format( "Login attempt failed for username {0} from IP address {1}, the user is not approved", username, @@ -546,7 +553,7 @@ namespace Umbraco.Web.Security.Providers } if (member.IsLockedOut) { - LogHelper.Info( + Current.Logger.Info( string.Format( "Login attempt failed for username {0} from IP address {1}, the user is locked", username, @@ -574,15 +581,15 @@ namespace Umbraco.Web.Security.Providers member.IsLockedOut = true; member.LastLockoutDate = DateTime.Now; - LogHelper.Info( + Current.Logger.Info( string.Format( "Login attempt failed for username {0} from IP address {1}, the user is now locked out, max invalid password attempts exceeded", username, - GetCurrentRequestIpAddress())); + GetCurrentRequestIpAddress())); } else { - LogHelper.Info( + Current.Logger.Info( string.Format( "Login attempt failed for username {0} from IP address {1}", username, @@ -599,11 +606,11 @@ namespace Umbraco.Web.Security.Providers member.LastLoginDate = DateTime.Now; - LogHelper.Info( + Current.Logger.Info( string.Format( "Login attempt succeeded for username {0} from IP address {1}", username, - GetCurrentRequestIpAddress())); + GetCurrentRequestIpAddress())); } //don't raise events for this! It just sets the member dates, if we do raise events this will @@ -643,4 +650,4 @@ namespace Umbraco.Web.Security.Providers public bool Authenticated { get; set; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs index 35d24558f0..56cbb0be2b 100644 --- a/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UsersMembershipProvider.cs @@ -10,25 +10,28 @@ using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Web.Composing; namespace Umbraco.Web.Security.Providers { /// - /// Custom Membership Provider for Umbraco Users (User authentication for Umbraco Backend CMS) + /// Custom Membership Provider for Umbraco Users (User authentication for Umbraco Backend CMS) /// public class UsersMembershipProvider : UmbracoMembershipProvider, IUsersMembershipProvider { public UsersMembershipProvider() - : this(ApplicationContext.Current.Services.UserService) + : this(Current.Services.UserService, Current.Services.MemberTypeService) { } - public UsersMembershipProvider(IMembershipMemberService memberService) + public UsersMembershipProvider(IMembershipMemberService memberService, IMemberTypeService memberTypeService) : base(memberService) { + _memberTypeService = memberTypeService; } + private readonly IMemberTypeService _memberTypeService; private string _defaultMemberTypeAlias = "writer"; private volatile bool _hasDefaultMember = false; private static readonly object Locker = new object(); @@ -70,7 +73,7 @@ namespace Umbraco.Web.Security.Providers base.Initialize(name, config); if (config == null) { throw new ArgumentNullException("config"); } - + _allowManuallyChangingPassword = config.GetValue("allowManuallyChangingPassword", false); _enablePasswordReset = config.GetValue("enablePasswordReset", false); @@ -86,7 +89,7 @@ namespace Umbraco.Web.Security.Providers } } if (_hasDefaultMember == false && config["defaultUserGroupAlias"] != null) - { + { if (config["defaultUserGroupAlias"].IsNullOrWhiteSpace() == false) { _defaultMemberTypeAlias = config["defaultUserGroupAlias"]; @@ -105,7 +108,7 @@ namespace Umbraco.Web.Security.Providers { if (_hasDefaultMember == false) { - _defaultMemberTypeAlias = MemberService.GetDefaultMemberType(); + _defaultMemberTypeAlias = _memberTypeService.GetDefault(); if (_defaultMemberTypeAlias.IsNullOrWhiteSpace()) { throw new ProviderException("No default user group alias is specified in the web.config string. Please add a 'defaultUserTypeAlias' to the add element in the provider declaration in web.config"); @@ -117,7 +120,7 @@ namespace Umbraco.Web.Security.Providers return _defaultMemberTypeAlias; } } - + /// /// Overridden in order to call the BackOfficeUserManager.UnlockUser method in order to raise the user audit events /// @@ -133,7 +136,7 @@ namespace Umbraco.Web.Security.Providers if (userManager != null) { userManager.RaiseAccountUnlockedEvent(member.Id); - } + } } return result; } @@ -166,7 +169,7 @@ namespace Umbraco.Web.Security.Providers { //we have successfully logged in, if the failed password attempts was modified it means it was reset if (result.Member.WasPropertyDirty("FailedPasswordAttempts")) - { + { userManager.RaiseResetAccessFailedCountEvent(result.Member.Id); } } @@ -182,4 +185,4 @@ namespace Umbraco.Web.Security.Providers : HttpContext.Current.GetOwinContext().GetBackOfficeUserManager(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs b/src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs new file mode 100644 index 0000000000..c65c010204 --- /dev/null +++ b/src/Umbraco.Web/Security/UmbracoAuthTicketDataProtector.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.Owin.Security; +using Umbraco.Core; + +namespace Umbraco.Web.Security +{ + /// + /// This is used so that we can retrive the auth ticket protector from an IOwinContext + /// + internal class UmbracoAuthTicketDataProtector : DisposableObjectSlim + { + public UmbracoAuthTicketDataProtector(ISecureDataFormat protector) + { + Protector = protector ?? throw new ArgumentNullException(nameof(protector)); + } + + public ISecureDataFormat Protector { get; } + } +} diff --git a/src/Umbraco.Web/Security/ValidateRequestAttempt.cs b/src/Umbraco.Web/Security/ValidateRequestAttempt.cs index fdd894aa60..207ceb54c5 100644 --- a/src/Umbraco.Web/Security/ValidateRequestAttempt.cs +++ b/src/Umbraco.Web/Security/ValidateRequestAttempt.cs @@ -2,13 +2,13 @@ { public enum ValidateRequestAttempt { - Success = 0, + Success = 0, FailedNoPrivileges = 100, - //FailedTimedOut, + //FailedTimedOut, FailedNoContextId = 101, FailedNoSsl = 102 } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/WebAuthExtensions.cs b/src/Umbraco.Web/Security/WebAuthExtensions.cs index c50e7114f8..d38345e48c 100644 --- a/src/Umbraco.Web/Security/WebAuthExtensions.cs +++ b/src/Umbraco.Web/Security/WebAuthExtensions.cs @@ -1,4 +1,4 @@ -using System.Net.Http; +using System.Net.Http; using System.Security.Claims; using System.Security.Principal; using System.ServiceModel.Channels; @@ -22,7 +22,7 @@ namespace Umbraco.Web.Security internal static IPrincipal SetPrincipalForRequest(this HttpRequestMessage request, IPrincipal principal) { //It is actually not good enough to set this on the current app Context and the thread, it also needs - // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually + // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually // an underlying fault of asp.net not propogating the User correctly. if (HttpContext.Current != null) { @@ -50,7 +50,7 @@ namespace Umbraco.Web.Security internal static IPrincipal SetPrincipalForRequest(this HttpContextBase httpContext, IPrincipal principal) { //It is actually not good enough to set this on the current app Context and the thread, it also needs - // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually + // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually // an underlying fault of asp.net not propogating the User correctly. if (HttpContext.Current != null) { @@ -61,4 +61,4 @@ namespace Umbraco.Web.Security return principal; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index dd7e757c8d..ad0c5781a3 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -2,21 +2,23 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Security; using System.Web; using System.Web.Security; using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Services; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; -using umbraco.businesslogic.Exceptions; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; -using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Composing; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; -using User = umbraco.BusinessLogic.User; namespace Umbraco.Web.Security { @@ -26,14 +28,16 @@ namespace Umbraco.Web.Security public class WebSecurity : DisposableObjectSlim { private HttpContextBase _httpContext; - private ApplicationContext _applicationContext; + private readonly IUserService _userService; + private readonly IGlobalSettings _globalSettings; - public WebSecurity(HttpContextBase httpContext, ApplicationContext applicationContext) + public WebSecurity(HttpContextBase httpContext, IUserService userService, IGlobalSettings globalSettings) { _httpContext = httpContext; - _applicationContext = applicationContext; + _userService = userService; + _globalSettings = globalSettings; } - + /// /// Returns true or false if the currently logged in member is authorized based on the parameters provided /// @@ -49,11 +53,11 @@ namespace Umbraco.Web.Security IEnumerable allowGroups = null, IEnumerable allowMembers = null) { - if (UmbracoContext.Current == null) + if (Current.UmbracoContext == null) { return false; } - var helper = new MembershipHelper(UmbracoContext.Current); + var helper = new MembershipHelper(Current.UmbracoContext); return helper.IsMemberAuthorized(allowAll, allowTypes, allowGroups, allowMembers); } @@ -71,11 +75,7 @@ namespace Umbraco.Web.Security if (_currentUser == null) { var id = GetUserId(); - if (id == -1) - { - return null; - } - _currentUser = _applicationContext.Services.UserService.GetUserById(id); + _currentUser = id ? _userService.GetUserById(id.Result) : null; } return _currentUser; @@ -102,9 +102,7 @@ namespace Umbraco.Web.Security private BackOfficeUserManager _userManager; protected BackOfficeUserManager UserManager - { - get { return _userManager ?? (_userManager = _httpContext.GetOwinContext().GetBackOfficeUserManager()); } - } + => _userManager ?? (_userManager = _httpContext.GetOwinContext().GetBackOfficeUserManager()); /// /// Logs a user in. @@ -116,14 +114,14 @@ namespace Umbraco.Web.Security var owinCtx = _httpContext.GetOwinContext(); //ensure it's done for owin too owinCtx.Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType); - + var user = UserManager.FindByIdAsync(userId).Result; - SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false).Wait(); + SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false).Wait(); _httpContext.SetPrincipalForRequest(owinCtx.Request.User); - return TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes).TotalSeconds; + return TimeSpan.FromMinutes(_globalSettings.TimeOutInMinutes).TotalSeconds; } /// @@ -155,20 +153,12 @@ namespace Umbraco.Web.Security /// This uses ASP.NET Identity to perform the validation /// public virtual bool ValidateBackOfficeCredentials(string username, string password) - { + { //find the user by username var user = UserManager.FindByNameAsync(username).Result; return user != null && UserManager.CheckPasswordAsync(user, password).Result; } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Back office users shouldn't be resolved from the membership provider, they should be resolved usign the BackOfficeUserManager or the IUserService")] - public virtual MembershipUser GetBackOfficeMembershipUser(string username, bool setOnline) - { - var membershipProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); - return membershipProvider != null ? membershipProvider.GetUser(username, setOnline) : null; - } - + /// /// Validates the current user to see if they have access to the specified app /// @@ -183,28 +173,15 @@ namespace Umbraco.Web.Security } return CurrentUser.AllowedSections.Any(uApp => uApp.InvariantEquals(app)); } - - /// - /// Gets the user id. - /// - /// This is not used - /// - [Obsolete("This method is no longer used, use the GetUserId() method without parameters instead")] - public int GetUserId(string umbracoUserContextId) - { - return GetUserId(); - } /// /// Gets the currnet user's id. /// /// - public virtual int GetUserId() + public virtual Attempt GetUserId() { var identity = _httpContext.GetCurrentIdentity(false); - if (identity == null) - return -1; - return Convert.ToInt32(identity.Id); + return identity == null ? Attempt.Fail() : Attempt.Succeed(Convert.ToInt32(identity.Id)); } /// @@ -214,22 +191,9 @@ namespace Umbraco.Web.Security public virtual string GetSessionId() { var identity = _httpContext.GetCurrentIdentity(false); - if (identity == null) - return null; - return identity.SessionId; + return identity?.SessionId; } - - /// - /// Validates the user context ID. - /// - /// This doesn't do anything - /// - [Obsolete("This method is no longer used, use the ValidateCurrentUser() method instead")] - public bool ValidateUserContextId(string currentUmbracoUserContextId) - { - return ValidateCurrentUser(); - } - + /// /// Validates the currently logged in user and ensures they are not timed out /// @@ -237,7 +201,7 @@ namespace Umbraco.Web.Security public virtual bool ValidateCurrentUser() { return ValidateCurrentUser(false, true) == ValidateRequestAttempt.Success; - } + } /// /// Validates the current user assigned to the request and ensures the stored user data is valid @@ -248,10 +212,10 @@ namespace Umbraco.Web.Security public virtual ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true) { //This will first check if the current user is already authenticated - which should be the case in nearly all circumstances - // since the authentication happens in the Module, that authentication also checks the ticket expiry. We don't + // since the authentication happens in the Module, that authentication also checks the ticket expiry. We don't // need to check it a second time because that requires another decryption phase and nothing can tamper with it during the request. - if (IsAuthenticated() == false) + if (IsAuthenticated() == false) { //There is no user if (throwExceptions) throw new InvalidOperationException("The user has no umbraco contextid - try logging in"); @@ -261,7 +225,7 @@ namespace Umbraco.Web.Security var user = CurrentUser; // Check for console access - if (user == null || (requiresApproval && user.IsApproved == false) || (user.IsLockedOut && GlobalSettings.RequestIsInUmbracoApplication(_httpContext))) + if (user == null || (requiresApproval && user.IsApproved == false) || (user.IsLockedOut && RequestIsInUmbracoApplication(_httpContext))) { if (throwExceptions) throw new ArgumentException("You have no priviledges to the umbraco console. Please contact your administrator"); return ValidateRequestAttempt.FailedNoPrivileges; @@ -270,6 +234,11 @@ namespace Umbraco.Web.Security } + private static bool RequestIsInUmbracoApplication(HttpContextBase context) + { + return context.Request.Path.ToLower().IndexOf(IOHelper.ResolveUrl(SystemDirectories.Umbraco).ToLower(), StringComparison.Ordinal) > -1; + } + /// /// Authorizes the full request, checks for SSL and validates the current user /// @@ -278,9 +247,9 @@ namespace Umbraco.Web.Security internal ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false) { // check for secure connection - if (GlobalSettings.UseSSL && _httpContext.Request.IsSecureConnection == false) + if (_globalSettings.UseHttps && _httpContext.Request.IsSecureConnection == false) { - if (throwExceptions) throw new UserAuthorizationException("This installation requires a secure connection (via SSL). Please update the URL to include https://"); + if (throwExceptions) throw new SecurityException("This installation requires a secure connection (via SSL). Please update the URL to include https://"); return ValidateRequestAttempt.FailedNoSsl; } return ValidateCurrentUser(throwExceptions); @@ -297,12 +266,6 @@ namespace Umbraco.Web.Security return user.HasSectionAccess(section); } - [Obsolete("Do not use this method if you don't have to, use the overload with IUser instead")] - internal bool UserHasSectionAccess(string section, User user) - { - return user.Applications.Any(uApp => uApp.alias == section); - } - /// /// Checks if the specified user by username as access to the app /// @@ -311,7 +274,7 @@ namespace Umbraco.Web.Security /// internal bool UserHasSectionAccess(string section, string username) { - var user = _applicationContext.Services.UserService.GetByUsername(username); + var user = _userService.GetByUsername(username); if (user == null) { return false; @@ -319,18 +282,6 @@ namespace Umbraco.Web.Security return user.HasSectionAccess(section); } - [Obsolete("Returns the current user's unique umbraco sesion id - this cannot be set and isn't intended to be used in your code")] - public string UmbracoUserContextId - { - get - { - return _httpContext.GetUmbracoAuthTicket() == null ? "" : GetSessionId(); - } - set - { - } - } - /// /// Ensures that a back office user is logged in /// @@ -344,7 +295,5 @@ namespace Umbraco.Web.Security { _httpContext = null; } - - } } diff --git a/src/Umbraco.Web/Services/ApplicationTreeService.cs b/src/Umbraco.Web/Services/ApplicationTreeService.cs new file mode 100644 index 0000000000..ef717dc16b --- /dev/null +++ b/src/Umbraco.Web/Services/ApplicationTreeService.cs @@ -0,0 +1,450 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Composing; +using Umbraco.Core.Services; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Services +{ + internal class ApplicationTreeService : IApplicationTreeService + { + private readonly ILogger _logger; + private readonly CacheHelper _cache; + private Lazy> _allAvailableTrees; + internal const string TreeConfigFileName = "trees.config"; + private static string _treeConfig; + private static readonly object Locker = new object(); + + public ApplicationTreeService(ILogger logger, CacheHelper cache) + { + _logger = logger; + _cache = cache; + } + + /// + /// gets/sets the trees.config file path + /// + /// + /// The setter is generally only going to be used in unit tests, otherwise it will attempt to resolve it using the IOHelper.MapPath + /// + internal static string TreeConfigFilePath + { + get + { + if (string.IsNullOrWhiteSpace(_treeConfig)) + { + _treeConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + TreeConfigFileName); + } + return _treeConfig; + } + set { _treeConfig = value; } + } + + /// + /// The main entry point to get application trees + /// + /// + /// This lazily on first access will scan for plugin trees and ensure the trees.config is up-to-date with the plugins. If plugins + /// haven't changed on disk then the file will not be saved. The trees are all then loaded from this config file into cache and returned. + /// + private List GetAppTrees() + { + return _cache.RuntimeCache.GetCacheItem>( + CacheKeys.ApplicationTreeCacheKey, + () => + { + var list = ReadFromXmlAndSort(); + + //now we can check the non-volatile flag + if (_allAvailableTrees != null) + { + var hasChanges = false; + + LoadXml(doc => + { + //Now, load in the xml structure and update it with anything that is not declared there and save the file. + + //NOTE: On the first iteration here, it will lazily scan all trees, etc... this is because this ienumerable is lazy + // based on the ApplicationTreeRegistrar - and as noted there this is not an ideal way to do things but were stuck like this + // currently because of the legacy assemblies and types not in the Core. + + //Get all the trees not registered in the config (those not matching by alias casing will be detected as "unregistered") + var unregistered = _allAvailableTrees.Value + .Where(x => list.Any(l => l.Alias == x.Alias) == false) + .ToArray(); + + hasChanges = unregistered.Any(); + + if (hasChanges == false) return false; + + //add or edit the unregistered ones and re-save the file if any changes were found + var count = 0; + foreach (var tree in unregistered) + { + var existingElement = doc.Root.Elements("add").SingleOrDefault(x => + string.Equals(x.Attribute("alias").Value, tree.Alias, + StringComparison.InvariantCultureIgnoreCase) && + string.Equals(x.Attribute("application").Value, tree.ApplicationAlias, + StringComparison.InvariantCultureIgnoreCase)); + if (existingElement != null) + { + existingElement.SetAttributeValue("alias", tree.Alias); + } + else + { + if (tree.Title.IsNullOrWhiteSpace()) + { + doc.Root.Add(new XElement("add", + new XAttribute("initialize", tree.Initialize), + new XAttribute("sortOrder", tree.SortOrder), + new XAttribute("alias", tree.Alias), + new XAttribute("application", tree.ApplicationAlias), + new XAttribute("iconClosed", tree.IconClosed), + new XAttribute("iconOpen", tree.IconOpened), + new XAttribute("type", tree.Type))); + } + else + { + doc.Root.Add(new XElement("add", + new XAttribute("initialize", tree.Initialize), + new XAttribute("sortOrder", tree.SortOrder), + new XAttribute("alias", tree.Alias), + new XAttribute("application", tree.ApplicationAlias), + new XAttribute("title", tree.Title), + new XAttribute("iconClosed", tree.IconClosed), + new XAttribute("iconOpen", tree.IconOpened), + new XAttribute("type", tree.Type))); + } + + } + count++; + } + + //don't save if there's no changes + return count > 0; + }, true); + + if (hasChanges) + { + //If there were changes, we need to re-read the structures from the XML + list = ReadFromXmlAndSort(); + } + } + + return list; + }, new TimeSpan(0, 10, 0)); + } + + /// + /// Creates a new application tree. + /// + /// if set to true [initialize]. + /// The sort order. + /// The application alias. + /// The alias. + /// The title. + /// The icon closed. + /// The icon opened. + /// The type. + public void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type) + { + LoadXml(doc => + { + var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == alias && x.Attribute("application").Value == applicationAlias); + + if (el == null) + { + doc.Root.Add(new XElement("add", + new XAttribute("initialize", initialize), + new XAttribute("sortOrder", sortOrder), + new XAttribute("alias", alias), + new XAttribute("application", applicationAlias), + new XAttribute("title", title), + new XAttribute("iconClosed", iconClosed), + new XAttribute("iconOpen", iconOpened), + new XAttribute("type", type))); + } + + return true; + + }, true); + + OnNew(new ApplicationTree(initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, type), new EventArgs()); + } + + /// + /// Saves this instance. + /// + public void SaveTree(ApplicationTree tree) + { + LoadXml(doc => + { + var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == tree.Alias && x.Attribute("application").Value == tree.ApplicationAlias); + + if (el != null) + { + el.RemoveAttributes(); + + el.Add(new XAttribute("initialize", tree.Initialize)); + el.Add(new XAttribute("sortOrder", tree.SortOrder)); + el.Add(new XAttribute("alias", tree.Alias)); + el.Add(new XAttribute("application", tree.ApplicationAlias)); + el.Add(new XAttribute("title", tree.Title)); + el.Add(new XAttribute("iconClosed", tree.IconClosed)); + el.Add(new XAttribute("iconOpen", tree.IconOpened)); + el.Add(new XAttribute("type", tree.Type)); + } + + return true; + + }, true); + + OnUpdated(tree, new EventArgs()); + } + + /// + /// Deletes this instance. + /// + public void DeleteTree(ApplicationTree tree) + { + LoadXml(doc => + { + doc.Root.Elements("add") + .Where(x => x.Attribute("application") != null + && x.Attribute("application").Value == tree.ApplicationAlias + && x.Attribute("alias") != null && x.Attribute("alias").Value == tree.Alias).Remove(); + + return true; + + }, true); + + OnDeleted(tree, new EventArgs()); + } + + /// + /// Gets an ApplicationTree by it's tree alias. + /// + /// The tree alias. + /// An ApplicationTree instance + public ApplicationTree GetByAlias(string treeAlias) + { + return GetAppTrees().Find(t => (t.Alias == treeAlias)); + + } + + /// + /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. + /// + /// Returns a ApplicationTree Array + public IEnumerable GetAll() + { + return GetAppTrees().OrderBy(x => x.SortOrder); + } + + /// + /// Gets the application tree for the applcation with the specified alias + /// + /// The application alias. + /// Returns a ApplicationTree Array + public IEnumerable GetApplicationTrees(string applicationAlias) + { + return GetApplicationTrees(applicationAlias, false); + } + + /// + /// Gets the application tree for the applcation with the specified alias + /// + /// The application alias. + /// + /// Returns a ApplicationTree Array + public IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized) + { + var list = GetAppTrees().FindAll( + t => + { + if (onlyInitialized) + return (t.ApplicationAlias == applicationAlias && t.Initialize); + return (t.ApplicationAlias == applicationAlias); + } + ); + + return list.OrderBy(x => x.SortOrder).ToArray(); + } + + /// + /// Loads in the xml structure from disk if one is found, otherwise loads in an empty xml structure, calls the + /// callback with the xml document and saves the structure back to disk if saveAfterCallback is true. + /// + /// + /// + internal void LoadXml(Func callback, bool saveAfterCallbackIfChanges) + { + lock (Locker) + { + var doc = System.IO.File.Exists(TreeConfigFilePath) + ? XDocument.Load(TreeConfigFilePath) + : XDocument.Parse(""); + + if (doc.Root != null) + { + var hasChanges = callback.Invoke(doc); + + if (saveAfterCallbackIfChanges && hasChanges + //Don't save it if it is empty, in some very rare cases if the app domain get's killed in the middle of this process + // in some insane way the file saved will be empty. I'm pretty sure it's not actually anything to do with the xml doc and + // more about the IO trying to save the XML doc, but it doesn't hurt to check. + && doc.Root != null && doc.Root.Elements().Any()) + { + //ensures the folder exists + Directory.CreateDirectory(Path.GetDirectoryName(TreeConfigFilePath)); + + //saves it + doc.Save(TreeConfigFilePath); + + //remove the cache now that it has changed SD: I'm leaving this here even though it + // is taken care of by events as well, I think unit tests may rely on it being cleared here. + _cache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); + } + } + } + } + + private List ReadFromXmlAndSort() + { + var list = new List(); + + //read in the xml file containing trees and convert them all to ApplicationTree instances + LoadXml(doc => + { + foreach (var addElement in doc.Root.Elements("add").OrderBy(x => + { + var sortOrderAttr = x.Attribute("sortOrder"); + return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0; + })) + { + var applicationAlias = (string)addElement.Attribute("application"); + var type = (string)addElement.Attribute("type"); + var assembly = (string)addElement.Attribute("assembly"); + + var clrType = Type.GetType(type); + if (clrType == null) + { + _logger.Warn("The tree definition: " + addElement.ToString() + " could not be resolved to a .Net object type"); + continue; + } + + //check if the tree definition (applicationAlias + type + assembly) is already in the list + + if (list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias) && tree.GetRuntimeType() == clrType) == false) + { + list.Add(new ApplicationTree( + addElement.Attribute("initialize") == null || Convert.ToBoolean(addElement.Attribute("initialize").Value), + addElement.Attribute("sortOrder") != null + ? Convert.ToByte(addElement.Attribute("sortOrder").Value) + : (byte)0, + (string)addElement.Attribute("application"), + (string)addElement.Attribute("alias"), + (string)addElement.Attribute("title"), + (string)addElement.Attribute("iconClosed"), + (string)addElement.Attribute("iconOpen"), + (string)addElement.Attribute("type"))); + } + } + + return false; + + }, false); + + return list; + } + + + internal static event TypedEventHandler Deleted; + private static void OnDeleted(ApplicationTree app, EventArgs args) + { + if (Deleted != null) + { + Deleted(app, args); + } + } + + internal static event TypedEventHandler New; + private static void OnNew(ApplicationTree app, EventArgs args) + { + if (New != null) + { + New(app, args); + } + } + + internal static event TypedEventHandler Updated; + private static void OnUpdated(ApplicationTree app, EventArgs args) + { + if (Updated != null) + { + Updated(app, args); + } + } + + /// + /// This class is here so that we can provide lazy access to tree scanning for when it is needed + /// + private class LazyEnumerableTrees : IEnumerable + { + public LazyEnumerableTrees() + { + _lazyTrees = new Lazy>(() => + { + var added = new List(); + + // Load all Controller Trees by attribute + var types = Current.TypeLoader.GetTypesWithAttribute(); // fixme inject + //convert them to ApplicationTree instances + var items = types + .Select(x => + new Tuple(x, x.GetCustomAttributes(false).Single())) + .Select(x => new ApplicationTree(x.Item2.Initialize, x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen, x.Item1.GetFullNameWithAssembly())) + .ToArray(); + + added.AddRange(items.Select(x => x.Alias)); + + return items.ToArray(); + }); + } + + private readonly Lazy> _lazyTrees; + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + public IEnumerator GetEnumerator() + { + return _lazyTrees.Value.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + } +} diff --git a/src/Umbraco.Web/Services/SectionService.cs b/src/Umbraco.Web/Services/SectionService.cs new file mode 100644 index 0000000000..411d122d49 --- /dev/null +++ b/src/Umbraco.Web/Services/SectionService.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Composing; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Web.Models.Trees; +using File = System.IO.File; + +namespace Umbraco.Web.Services +{ + internal class SectionService : ISectionService + { + private readonly IUserService _userService; + private readonly Lazy> _allAvailableSections; + private readonly IApplicationTreeService _applicationTreeService; + private readonly IScopeProvider _scopeProvider; + private readonly CacheHelper _cache; + internal const string AppConfigFileName = "applications.config"; + private static string _appConfig; + private static readonly object Locker = new object(); + + public SectionService( + IUserService userService, + IApplicationTreeService applicationTreeService, + IScopeProvider scopeProvider, + CacheHelper cache) + { + _applicationTreeService = applicationTreeService ?? throw new ArgumentNullException(nameof(applicationTreeService)); + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + _userService = userService; + _scopeProvider = scopeProvider; + _allAvailableSections = new Lazy>(() => new LazyEnumerableSections()); + } + + + /// + /// gets/sets the application.config file path + /// + /// + /// The setter is generally only going to be used in unit tests, otherwise it will attempt to resolve it using the IOHelper.MapPath + /// + internal static string AppConfigFilePath + { + get + { + if (string.IsNullOrWhiteSpace(_appConfig)) + { + _appConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + AppConfigFileName); + } + return _appConfig; + } + set => _appConfig = value; + } + + /// + /// The cache storage for all applications + /// + public IEnumerable
GetSections() + { + return _cache.RuntimeCache.GetCacheItem>( + CacheKeys.ApplicationsCacheKey, + () => + { + var list = ReadFromXmlAndSort(); + var hasChanges = false; + var localCopyList = list; + + LoadXml(doc => + { + //Now, load in the xml structure and update it with anything that is not declared there and save the file. + //NOTE: On the first iteration here, it will lazily scan all apps, etc... this is because this ienumerable is lazy + //Get all the trees not registered in the config + + var unregistered = _allAvailableSections.Value + .Where(x => localCopyList.Any(l => l.Alias == x.Alias) == false) + .ToArray(); + + hasChanges = unregistered.Any(); + + var count = 0; + foreach (var attr in unregistered) + { + doc.Root.Add(new XElement("add", + new XAttribute("alias", attr.Alias), + new XAttribute("name", attr.Name), + new XAttribute("icon", attr.Icon), + new XAttribute("sortOrder", attr.SortOrder))); + count++; + } + + //don't save if there's no changes + return count > 0; + }, true); + + if (hasChanges) + { + //If there were changes, we need to re-read the structures from the XML + list = ReadFromXmlAndSort(); + } + + return list; + + }, new TimeSpan(0, 10, 0)); + } + + internal void LoadXml(Func callback, bool saveAfterCallbackIfChanged) + { + lock (Locker) + { + var doc = File.Exists(AppConfigFilePath) + ? XDocument.Load(AppConfigFilePath) + : XDocument.Parse(""); + + if (doc.Root != null) + { + var changed = callback.Invoke(doc); + + if (saveAfterCallbackIfChanged && changed) + { + //ensure the folder is created! + Directory.CreateDirectory(Path.GetDirectoryName(AppConfigFilePath)); + + doc.Save(AppConfigFilePath); + + //remove the cache so it gets re-read ... SD: I'm leaving this here even though it + // is taken care of by events as well, I think unit tests may rely on it being cleared here. + _cache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); + } + } + } + } + + /// + /// Get the user's allowed sections + /// + /// + /// + public IEnumerable
GetAllowedSections(int userId) + { + + var user = _userService.GetUserById(userId); + if (user == null) + { + throw new InvalidOperationException("No user found with id " + userId); + } + + return GetSections().Where(x => user.AllowedSections.Contains(x.Alias)); + } + + /// + /// Gets the application by its alias. + /// + /// The application alias. + /// + public Section GetByAlias(string appAlias) + { + return GetSections().FirstOrDefault(t => t.Alias == appAlias); + } + + /// + /// Creates a new applcation if no application with the specified alias is found. + /// + /// The application name. + /// The application alias. + /// The application icon, which has to be located in umbraco/images/tray folder. + public void MakeNew(string name, string alias, string icon) + { + var sections = GetSections(); + var nextSortOrder = sections.Any() ? sections.Max(x => x.SortOrder) + 1 : 1; + MakeNew(name, alias, icon, nextSortOrder); + } + + /// + /// Makes the new. + /// + /// The name. + /// The alias. + /// The icon. + /// The sort order. + public void MakeNew(string name, string alias, string icon, int sortOrder) + { + if (GetSections().All(x => x.Alias != alias)) + { + LoadXml(doc => + { + doc.Root.Add(new XElement("add", + new XAttribute("alias", alias), + new XAttribute("name", name), + new XAttribute("icon", icon), + new XAttribute("sortOrder", sortOrder))); + return true; + }, true); + + //raise event + OnNew(new Section(name, alias, icon, sortOrder), new EventArgs()); + } + } + + /// + /// Deletes the section + /// + public void DeleteSection(Section section) + { + lock (Locker) + { + //delete the assigned applications + using (var scope = _scopeProvider.CreateScope()) + { + scope.Database.Execute("delete from umbracoUserGroup2App where app = @appAlias", + new { appAlias = section.Alias }); + scope.Complete(); + } + + //delete the assigned trees + var trees = _applicationTreeService.GetApplicationTrees(section.Alias); + foreach (var t in trees) + { + _applicationTreeService.DeleteTree(t); + } + + LoadXml(doc => + { + doc.Root.Elements("add").Where(x => x.Attribute("alias") != null && x.Attribute("alias").Value == section.Alias) + .Remove(); + + return true; + }, true); + + //raise event + OnDeleted(section, new EventArgs()); + } + } + + private List
ReadFromXmlAndSort() + { + var tmp = new List
(); + + LoadXml(doc => + { + foreach (var addElement in doc.Root.Elements("add").OrderBy(x => + { + var sortOrderAttr = x.Attribute("sortOrder"); + return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0; + })) + { + var sortOrderAttr = addElement.Attribute("sortOrder"); + tmp.Add(new Section(addElement.Attribute("name").Value, + addElement.Attribute("alias").Value, + addElement.Attribute("icon").Value, + sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0)); + } + return false; + }, false); + + return tmp; + } + + internal static event TypedEventHandler Deleted; + private static void OnDeleted(Section app, EventArgs args) + { + if (Deleted != null) + { + Deleted(app, args); + } + } + + internal static event TypedEventHandler New; + private static void OnNew(Section app, EventArgs args) + { + if (New != null) + { + New(app, args); + } + } + + /// + /// This class is here so that we can provide lazy access to tree scanning for when it is needed + /// + private class LazyEnumerableSections : IEnumerable
+ { + public LazyEnumerableSections() + { + _lazySections = new Lazy>(() => + { + // Load all Applications by attribute and add them to the XML config + + //don't cache the result of this because it is only used once during app startup, caching will just add a bit more mem overhead for no reason + var types = Current.TypeLoader.GetTypesWithAttribute(cache: false); // fixme - inject + + //since applications don't populate their metadata from the attribute and because it is an interface, + //we need to interrogate the attributes for the data. Would be better to have a base class that contains + //metadata populated by the attribute. Oh well i guess. + var attrs = types.Select(x => x.GetCustomAttributes(false).Single()); + return Enumerable.ToArray
(attrs.Select(x => new Section(x.Name, x.Alias, x.Icon, x.SortOrder))); + }); + } + + private readonly Lazy> _lazySections; + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + public IEnumerator
GetEnumerator() + { + return _lazySections.Value.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + } +} diff --git a/src/Umbraco.Web/SignalR/IPreviewHub.cs b/src/Umbraco.Web/SignalR/IPreviewHub.cs new file mode 100644 index 0000000000..2eb722c40d --- /dev/null +++ b/src/Umbraco.Web/SignalR/IPreviewHub.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Web.SignalR +{ + public interface IPreviewHub + { + // define methods implemented by client + // ReSharper disable InconsistentNaming + + void refreshed(int id); + + // ReSharper restore InconsistentNaming + } +} diff --git a/src/Umbraco.Web/SignalR/PreviewHub.cs b/src/Umbraco.Web/SignalR/PreviewHub.cs new file mode 100644 index 0000000000..3fc3225fb8 --- /dev/null +++ b/src/Umbraco.Web/SignalR/PreviewHub.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNet.SignalR; + +namespace Umbraco.Web.SignalR +{ + public class PreviewHub : Hub + { } +} diff --git a/src/Umbraco.Web/SignalR/PreviewHubComponent.cs b/src/Umbraco.Web/SignalR/PreviewHubComponent.cs new file mode 100644 index 0000000000..07467d3bfa --- /dev/null +++ b/src/Umbraco.Web/SignalR/PreviewHubComponent.cs @@ -0,0 +1,47 @@ +using System; +using LightInject; +using Microsoft.AspNet.SignalR; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; + +namespace Umbraco.Web.SignalR +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public class PreviewHubComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + public override void Compose(Composition composition) + { + base.Compose(composition); + composition.Container.Register(_ => GlobalHost.ConnectionManager.GetHubContext(), new PerContainerLifetime()); + } + + // using a lazy arg here means that we won't create the hub until necessary + // and therefore we won't have too bad an impact on boot time + public void Initialize(Lazy> hubContext) + { + // ContentService.Saved is too soon - the content cache is not ready yet + // try using the content cache refresher event, because when it triggers + // the cache has already been notified of the changes + //ContentService.Saved += (sender, args) => + //{ + // var entity = args.SavedEntities.FirstOrDefault(); + // if (entity != null) + // _previewHub.Clients.All.refreshed(entity.Id); + //}; + + ContentCacheRefresher.CacheUpdated += (sender, args) => + { + if (args.MessageType != MessageType.RefreshByPayload) return; + var payloads = (ContentCacheRefresher.JsonPayload[])args.MessageObject; + var hubContextInstance = hubContext.Value; + foreach (var payload in payloads) + { + var id = payload.Id; // keep it simple for now, ignore ChangeTypes + hubContextInstance.Clients.All.refreshed(id); + } + }; + } + } +} diff --git a/src/Umbraco.Web/SingletonHttpContextAccessor.cs b/src/Umbraco.Web/SingletonHttpContextAccessor.cs deleted file mode 100644 index 3d6ac159fb..0000000000 --- a/src/Umbraco.Web/SingletonHttpContextAccessor.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Web; - -namespace Umbraco.Web -{ - internal class SingletonHttpContextAccessor : IHttpContextAccessor - { - public HttpContextBase Value - { - get - { - var httpContext = HttpContext.Current; - return httpContext == null ? null : new HttpContextWrapper(httpContext); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/SingletonUmbracoContextAccessor.cs b/src/Umbraco.Web/SingletonUmbracoContextAccessor.cs deleted file mode 100644 index ef2df5b6ea..0000000000 --- a/src/Umbraco.Web/SingletonUmbracoContextAccessor.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Web -{ - internal class SingletonUmbracoContextAccessor : IUmbracoContextAccessor - { - public UmbracoContext Value - { - get { return UmbracoContext.Current; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/DataTypes/LegacyUploadFieldWorkaround.cs b/src/Umbraco.Web/Strategies/DataTypes/LegacyUploadFieldWorkaround.cs deleted file mode 100644 index f7168b7e66..0000000000 --- a/src/Umbraco.Web/Strategies/DataTypes/LegacyUploadFieldWorkaround.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Drawing; -using System.Globalization; -using System.Linq; -using System.Xml; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using umbraco.interfaces; -using Umbraco.Core; - -namespace Umbraco.Web.Strategies.DataTypes -{ - /// - /// Before Save Content/Media subscriber that checks for Upload fields and updates related fields accordingly. - /// - /// - /// This is an intermediate fix for the legacy DataTypeUploadField and the FileHandlerData, so that properties - /// are saved correctly when using the Upload field on a (legacy) Document or Media class. - /// - public class LegacyUploadFieldWorkaround : ApplicationEventHandler - { - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - global::umbraco.cms.businesslogic.media.Media.BeforeSave += MediaBeforeSave; - global::umbraco.cms.businesslogic.web.Document.BeforeSave += DocumentBeforeSave; - } - - void DocumentBeforeSave(global::umbraco.cms.businesslogic.web.Document sender, global::umbraco.cms.businesslogic.SaveEventArgs e) - { - if (UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties.Any()) - { - var property = sender.GenericProperties.FirstOrDefault(x =>x.PropertyType.DataTypeDefinition.DataType!=null && x.PropertyType.DataTypeDefinition.DataType.Id == new Guid(Constants.PropertyEditors.UploadField)); - if (property == null) - return; - - - FillProperties(sender, property); - } - } - - void MediaBeforeSave(global::umbraco.cms.businesslogic.media.Media sender, global::umbraco.cms.businesslogic.SaveEventArgs e) - { - if (UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties.Any()) - { - var property = sender.GenericProperties.FirstOrDefault(x => x.PropertyType.DataTypeDefinition.DataType.Id == new Guid(Constants.PropertyEditors.UploadField)); - if (property == null) - return; - - - FillProperties(sender, property); - } - } - - private void FillProperties(global::umbraco.cms.businesslogic.Content content, global::umbraco.cms.businesslogic.property.Property property) - { - var uploadFieldConfigNode = UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties - .FirstOrDefault(x => x.Alias == property.PropertyType.Alias); - - if (uploadFieldConfigNode != null) - { - var fileSystem = FileSystemProviderManager.Current.GetFileSystemProvider(); - //Ensure that the Property has a Value before continuing - if(property.Value == null) - return; - - var path = fileSystem.GetRelativePath(property.Value.ToString()); - - if (string.IsNullOrWhiteSpace(path) == false && fileSystem.FileExists(path)) - { - long size; - using (var fileStream = fileSystem.OpenFile(path)) - { - size = fileStream.Length; - } - - var extension = fileSystem.GetExtension(path) != null - ? fileSystem.GetExtension(path).Substring(1).ToLowerInvariant() - : ""; - - var isImageType = UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(extension); - - var dimensions = isImageType ? GetDimensions(path, fileSystem) : null; - - - if (isImageType) - { - // only add dimensions to web images - content.getProperty(uploadFieldConfigNode.WidthFieldAlias).Value = dimensions.Item1.ToString(CultureInfo.InvariantCulture); - content.getProperty(uploadFieldConfigNode.HeightFieldAlias).Value = dimensions.Item2.ToString(CultureInfo.InvariantCulture); - } - - content.getProperty(uploadFieldConfigNode.LengthFieldAlias).Value = size == default(long) ? string.Empty : size.ToString(CultureInfo.InvariantCulture); - content.getProperty(uploadFieldConfigNode.ExtensionFieldAlias).Value = string.IsNullOrEmpty(extension) ? string.Empty : extension; - - } - } - } - - private Tuple GetDimensions(string path, IFileSystem fs) - { - - int fileWidth; - int fileHeight; - using (var stream = fs.OpenFile(path)) - { - using (var image = Image.FromStream(stream)) - { - fileWidth = image.Width; - fileHeight = image.Height; - } - } - - return new Tuple(fileWidth, fileHeight); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/LegacyClasses.cs b/src/Umbraco.Web/Strategies/LegacyClasses.cs deleted file mode 100644 index 434ab8ccbf..0000000000 --- a/src/Umbraco.Web/Strategies/LegacyClasses.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using Umbraco.Core.Publishing; -using Umbraco.Web.Cache; - - -namespace Umbraco.Web.Strategies.Publishing -{ - [Obsolete("This is not used and will be removed from the codebase in future versions")] - public class UpdateCacheAfterPublish : ApplicationEventHandler - { - } - - [Obsolete("This is not used and will be removed from the codebase in future versions")] - public class UpdateCacheAfterUnPublish : ApplicationEventHandler - { - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs deleted file mode 100644 index f1d7f761af..0000000000 --- a/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Linq; -using System.Web; -using Umbraco.Core.Events; -using Umbraco.Core.Persistence.Migrations; -using Umbraco.Web.WebApi.Filters; -using umbraco.interfaces; -using Umbraco.Core; -using Umbraco.Core.Configuration; - -namespace Umbraco.Web.Strategies.Migrations -{ - /// - /// After upgrade we clear out the csrf tokens - /// - public class ClearCsrfCookiesAfterUpgrade : MigrationStartupHander - { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) - { - if (e.ProductName != Constants.System.UmbracoMigrationName) return; - - if (HttpContext.Current == null) return; - - var http = new HttpContextWrapper(HttpContext.Current); - - http.ExpireCookie(AngularAntiForgeryHelper.AngularCookieName); - http.ExpireCookie(AngularAntiForgeryHelper.CsrfValidationCookieName); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs deleted file mode 100644 index 52144ae383..0000000000 --- a/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using Umbraco.Core; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.SqlSyntax; -using umbraco.interfaces; -using Umbraco.Core.Configuration; - -namespace Umbraco.Web.Strategies.Migrations -{ - /// - /// This will execute after upgrading to remove any xml cache for media that are currently in the bin - /// - /// - /// This will execute for specific versions - - /// - /// * If current is less than or equal to 7.0.0 - /// - public class ClearMediaXmlCacheForDeletedItemsAfterUpgrade : MigrationStartupHander - { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) - { - if (e.ProductName != Constants.System.UmbracoMigrationName) return; - - var target70 = new Version(7, 0, 0); - - if (e.ConfiguredVersion <= target70) - { - //This query is structured to work with MySql, SQLCE and SqlServer: - // http://issues.umbraco.org/issue/U4-3876 - - var sql = @"DELETE FROM cmsContentXml WHERE nodeId IN - (SELECT nodeId FROM (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml - INNER JOIN umbracoNode ON cmsContentXml.nodeId = umbracoNode.id - WHERE nodeObjectType = '" + Constants.ObjectTypes.Media + "' AND " + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("path") + " LIKE '%-21%') x)"; - - var count = e.MigrationContext.Database.Execute(sql); - - LogHelper.Info("Cleared " + count + " items from the media xml cache that were trashed and not meant to be there"); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/EnsureAppsTreesUpdatedOnUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/EnsureAppsTreesUpdatedOnUpgrade.cs deleted file mode 100644 index 96373f60fd..0000000000 --- a/src/Umbraco.Web/Strategies/Migrations/EnsureAppsTreesUpdatedOnUpgrade.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix; -using umbraco.BusinessLogic; -using umbraco.interfaces; - -namespace Umbraco.Web.Strategies.Migrations -{ - /// - /// This is kind of a hack to ensure that Apps and Trees that might still reside in the db is - /// written to the 'new' applications.config and trees.config files upon upgrade to version 6.0 - /// - public class EnsureAppsTreesUpdatedOnUpgrade : ApplicationEventHandler - { - /// - /// Ensure this is run when not configured - /// - protected override bool ExecuteWhenApplicationNotConfigured - { - get { return true; } - } - - /// - /// Ensure this is run when not configured - /// - protected override bool ExecuteWhenDatabaseNotConfigured - { - get { return true; } - } - - protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - //This event will be triggered once the EnsureAppsTreesUpdated Up() method is run during upgrades - EnsureAppsTreesUpdated.Upgrading += EnsureAppsTreesUpdated_Upgrading; - } - - void EnsureAppsTreesUpdated_Upgrading(object sender, EnsureAppsTreesUpdated.UpgradingEventArgs e) - { - var treeRegistrar = new ApplicationTreeRegistrar(); - var appRegistrar = new ApplicationRegistrar(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs deleted file mode 100644 index 45eb0a92b3..0000000000 --- a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; -using umbraco.interfaces; -using Umbraco.Core.Configuration; - -namespace Umbraco.Web.Strategies.Migrations -{ - /// - /// Creates the built in list view data types - /// - public class EnsureDefaultListViewDataTypesCreated : MigrationStartupHander - { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) - { - if (e.ProductName != Constants.System.UmbracoMigrationName) return; - - var target720 = new Version(7, 2, 0); - - if (e.ConfiguredVersion <= target720) - { - EnsureListViewDataTypeCreated(e); - - } - } - - private void EnsureListViewDataTypeCreated(MigrationEventArgs e) - { - using (var transaction = e.MigrationContext.Database.GetTransaction()) - { - try - { - //Turn on identity insert if db provider is not mysql - if (SqlSyntaxContext.SqlSyntaxProvider.SupportsIdentityInsert()) - e.MigrationContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("umbracoNode")))); - - if (e.MigrationContext.Database.Exists(Constants.System.DefaultContentListViewDataTypeId)) - { - //If this already exists then just exit - return; - } - - e.MigrationContext.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultContentListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - e.MigrationContext.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - e.MigrationContext.Database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMembersListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - } - finally - { - //Turn off identity insert if db provider is not mysql - if (SqlSyntaxContext.SqlSyntaxProvider.SupportsIdentityInsert()) - e.MigrationContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF;", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("umbracoNode")))); - } - - - try - { - //Turn on identity insert if db provider is not mysql - if (SqlSyntaxContext.SqlSyntaxProvider.SupportsIdentityInsert()) - e.MigrationContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsDataType")))); - - e.MigrationContext.Database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -26, DataTypeId = Constants.System.DefaultContentListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - e.MigrationContext.Database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -27, DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - e.MigrationContext.Database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -28, DataTypeId = Constants.System.DefaultMembersListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - } - finally - { - //Turn off identity insert if db provider is not mysql - if (SqlSyntaxContext.SqlSyntaxProvider.SupportsIdentityInsert()) - e.MigrationContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF;", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsDataType")))); - } - - - - try - { - //Turn on identity insert if db provider is not mysql - if (SqlSyntaxContext.SqlSyntaxProvider.SupportsIdentityInsert()) - e.MigrationContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} ON ", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsDataTypePreValues")))); - - //defaults for the member list - e.MigrationContext.Database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -1, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "10" }); - e.MigrationContext.Database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "Name" }); - e.MigrationContext.Database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -3, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "asc" }); - e.MigrationContext.Database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); - } - finally - { - //Turn off identity insert if db provider is not mysql - if (SqlSyntaxContext.SqlSyntaxProvider.SupportsIdentityInsert()) - e.MigrationContext.Database.Execute(new Sql(string.Format("SET IDENTITY_INSERT {0} OFF;", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName("cmsDataTypePreValues")))); - } - - - - transaction.Complete(); - } - } - - } - -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHander.cs b/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHander.cs deleted file mode 100644 index a350911580..0000000000 --- a/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHander.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Persistence.Migrations; - -namespace Umbraco.Web.Strategies.Migrations -{ - /// - /// Base class that can be used to run code after the migration runner has executed - /// - public abstract class MigrationStartupHander : ApplicationEventHandler - { - /// - /// Ensure this is run when not configured - /// - protected override bool ExecuteWhenApplicationNotConfigured - { - get { return true; } - } - - /// - /// Ensure this is run when not configured - /// - protected override bool ExecuteWhenDatabaseNotConfigured - { - get { return true; } - } - - public void Unsubscribe() - { - MigrationRunner.Migrated -= MigrationRunner_Migrated; - } - - /// - /// Attach to event on starting - /// - /// - /// - protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - MigrationRunner.Migrated += MigrationRunner_Migrated; - } - - private void MigrationRunner_Migrated(MigrationRunner sender, Core.Events.MigrationEventArgs e) - { - if (TargetProductNames.Length == 0 || TargetProductNames.Contains(e.ProductName)) - { - AfterMigration(sender, e); - } - } - - protected abstract void AfterMigration(MigrationRunner sender, Core.Events.MigrationEventArgs e); - - /// - /// Override to specify explicit target product names - /// - /// - /// Leaving empty will run for all migration products - /// - public virtual string[] TargetProductNames { get { return new string[] {}; } } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs b/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs deleted file mode 100644 index a4e4349372..0000000000 --- a/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Events; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Migrations; - -namespace Umbraco.Web.Strategies.Migrations -{ - - /// - /// When upgrading version 7.3 the migration MigrateStylesheetDataToFile will execute but we don't want to overwrite the developers - /// files during the migration since other parts of the migration might fail. So once the migration is complete, we'll then copy over the temp - /// files that this migration created over top of the developer's files. We'll also create a backup of their files. - /// - public sealed class OverwriteStylesheetFilesFromTempFiles : MigrationStartupHander - { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) - { - if (e.ProductName != Constants.System.UmbracoMigrationName) return; - - var target73 = new Version(7, 3, 0); - - if (e.ConfiguredVersion <= target73) - { - var tempCssFolder = IOHelper.MapPath("~/App_Data/TEMP/CssMigration/"); - var cssFolder = IOHelper.MapPath("~/css"); - if (Directory.Exists(tempCssFolder)) - { - var files = Directory.GetFiles(tempCssFolder, "*.css", SearchOption.AllDirectories); - foreach (var file in files) - { - var relativePath = file.TrimStart(tempCssFolder).TrimStart("\\"); - var cssFilePath = Path.Combine(cssFolder, relativePath); - if (File.Exists(cssFilePath)) - { - //backup - var targetPath = Path.Combine(tempCssFolder, relativePath.EnsureEndsWith(".bak")); - e.MigrationContext.Logger.Info("CSS file is being backed up from {0}, to {1} before being migrated to new format", () => cssFilePath, () => targetPath); - File.Copy(cssFilePath, targetPath, true); - } - - //ensure the sub folder eixts - Directory.CreateDirectory(Path.GetDirectoryName(cssFilePath)); - File.Copy(file, cssFilePath, true); - } - } - } - } - } -} diff --git a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs deleted file mode 100644 index 70f0b58f1a..0000000000 --- a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Persistence.UnitOfWork; -using umbraco.interfaces; -using Umbraco.Core; -using Umbraco.Core.Configuration; - -namespace Umbraco.Web.Strategies.Migrations -{ - /// - /// This event ensures that upgrades from (configured) versions lower then 6.0.0 - /// have their publish state updated after the database schema has been migrated. - /// - public class PublishAfterUpgradeToVersionSixth : MigrationStartupHander - { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) - { - if (e.ProductName != Constants.System.UmbracoMigrationName) return; - - var target = new Version(6, 0, 0); - if (e.ConfiguredVersion < target) - { - var sql = new Sql(); - sql.Select("*") - .From() - .InnerJoin() - .On(left => left.VersionId, right => right.VersionId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document)) - .Where(x => x.Path.StartsWith("-1")); - - var dtos = e.MigrationContext.Database.Fetch(sql); - var toUpdate = new List(); - var versionGroup = dtos.GroupBy(x => x.NodeId); - foreach (var grp in versionGroup) - { - var published = grp.FirstOrDefault(x => x.Published); - var newest = grp.FirstOrDefault(x => x.Newest); - - if (newest != null) - { - double timeDiff = new TimeSpan(newest.UpdateDate.Ticks - newest.ContentVersionDto.VersionDate.Ticks).TotalMilliseconds; - var hasPendingChanges = timeDiff > 2000; - - if (hasPendingChanges == false && published != null) - { - published.Published = false; - toUpdate.Add(published); - newest.Published = true; - toUpdate.Add(newest); - } - } - } - - //Commit the updated entries for the cmsDocument table - using (var transaction = e.MigrationContext.Database.GetTransaction()) - { - //Loop through the toUpdate - foreach (var dto in toUpdate) - { - e.MigrationContext.Database.Update(dto); - } - - transaction.Complete(); - } - } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/RebuildXmlCachesAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/RebuildXmlCachesAfterUpgrade.cs deleted file mode 100644 index 5f8f8b8593..0000000000 --- a/src/Umbraco.Web/Strategies/Migrations/RebuildXmlCachesAfterUpgrade.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using umbraco; -using Umbraco.Core; -using Umbraco.Core.Events; -using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Services; -using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; - -namespace Umbraco.Web.Strategies.Migrations -{ - /// - /// Rebuilds the Xml caches after upgrading. - /// This will execute after upgrading to rebuild the xml cache - /// - /// - /// This cannot execute as part of a DB migration since it needs access to services and repositories. - /// Executes for: - /// - Media Xml : if current is less than, or equal to, 7.0.0 (superceeded by the next rule) - /// - Media & Content Xml : if current is less than, or equal to, 7.3.0 - because 7.3.0 adds .Key to cached items - /// - /// - public class RebuildXmlCachesAfterUpgrade : MigrationStartupHander - { - protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) - { - if (e.ProductName != Constants.System.UmbracoMigrationName) return; - - var v730 = new Semver.SemVersion(new Version(7, 3, 0)); - - var doMedia = e.ConfiguredSemVersion < v730; - var doContent = e.ConfiguredSemVersion < v730; - - if (doMedia) - { - var mediaService = (MediaService) ApplicationContext.Current.Services.MediaService; - mediaService.RebuildXmlStructures(); - - // note: not re-indexing medias? - } - - if (doContent) - { - // rebuild Xml in database - var contentService = (ContentService) ApplicationContext.Current.Services.ContentService; - contentService.RebuildXmlStructures(); - - // refresh the Xml cache - content.Instance.RefreshContentFromDatabase(); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/NotificationsHandler.cs b/src/Umbraco.Web/Strategies/NotificationsHandler.cs deleted file mode 100644 index a085bd463a..0000000000 --- a/src/Umbraco.Web/Strategies/NotificationsHandler.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Services; -using umbraco; -using umbraco.BusinessLogic.Actions; -using Umbraco.Core.Models; - -namespace Umbraco.Web.Strategies -{ - /// - /// Subscribes to the relavent events in order to send out notifications - /// - public sealed class NotificationsHandler : ApplicationEventHandler - { - - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - base.ApplicationStarted(umbracoApplication, applicationContext); - - ContentService.SentToPublish += (sender, args) => - applicationContext.Services.NotificationService.SendNotification( - args.Entity, ActionToPublish.Instance, applicationContext); - - //Send notifications for the published action - ContentService.Published += (sender, args) => - args.PublishedEntities.ForEach( - content => - applicationContext.Services.NotificationService.SendNotification( - content, ActionPublish.Instance, applicationContext)); - - //Send notifications for the update and created actions - ContentService.Saved += (sender, args) => - { - var newEntities = new List(); - var updatedEntities = new List(); - - //need to determine if this is updating or if it is new - foreach (var entity in args.SavedEntities) - { - var dirty = (IRememberBeingDirty) entity; - if (dirty.WasPropertyDirty("Id")) - { - //it's new - newEntities.Add(entity); - } - else - { - //it's updating - updatedEntities.Add(entity); - } - } - applicationContext.Services.NotificationService.SendNotification(newEntities, ActionNew.Instance, applicationContext); - applicationContext.Services.NotificationService.SendNotification(updatedEntities, ActionUpdate.Instance, applicationContext); - }; - - //Send notifications for the delete action - ContentService.Deleted += (sender, args) => - args.DeletedEntities.ForEach( - content => - applicationContext.Services.NotificationService.SendNotification( - content, ActionDelete.Instance, applicationContext)); - - //Send notifications for the unpublish action - ContentService.UnPublished += (sender, args) => - args.PublishedEntities.ForEach( - content => - applicationContext.Services.NotificationService.SendNotification( - content, ActionUnPublish.Instance, applicationContext)); - - } - - } -} diff --git a/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs b/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs deleted file mode 100644 index d543567ac3..0000000000 --- a/src/Umbraco.Web/Strategies/PublicAccessEventHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using umbraco.cms.businesslogic.web; -using Umbraco.Core; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Services; - -namespace Umbraco.Web.Strategies -{ - /// - /// Used to ensure that the public access data file is kept up to date properly - /// - public sealed class PublicAccessEventHandler : ApplicationEventHandler - { - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - base.ApplicationStarted(umbracoApplication, applicationContext); - - MemberGroupService.Saved += MemberGroupService_Saved; - } - - static void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs e) - { - foreach (var grp in e.SavedEntities) - { - //check if the name has changed - if (grp.AdditionalData.ContainsKey("previousName") - && grp.AdditionalData["previousName"] != null - && grp.AdditionalData["previousName"].ToString().IsNullOrWhiteSpace() == false - && grp.AdditionalData["previousName"].ToString() != grp.Name) - { - ApplicationContext.Current.Services.PublicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"].ToString(), grp.Name); - } - } - } - } -} diff --git a/src/Umbraco.Web/Strategies/RelateOnCopyHandler.cs b/src/Umbraco.Web/Strategies/RelateOnCopyHandler.cs deleted file mode 100644 index ae72000edc..0000000000 --- a/src/Umbraco.Web/Strategies/RelateOnCopyHandler.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Auditing; -using Umbraco.Core.Models; -using Umbraco.Core.Services; - -namespace Umbraco.Web.Strategies -{ - [Obsolete("This class is no longer used and will be removed from the codebase in future versions")] - public sealed class RelateOnCopyHandler : ApplicationEventHandler - { - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - } - } -} diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs deleted file mode 100644 index dff5c6730c..0000000000 --- a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Web; -using Newtonsoft.Json; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Services; -using Umbraco.Core.Sync; -using Umbraco.Web.Routing; -using Umbraco.Web.Scheduling; - -namespace Umbraco.Web.Strategies -{ - /// - /// Ensures that servers are automatically registered in the database, when using the database server registrar. - /// - /// - /// At the moment servers are automatically registered upon first request and then on every - /// request but not more than once per (configurable) period. This really is "for information & debug" purposes so - /// we can look at the table and see what servers are registered - but the info is not used anywhere. - /// Should we actually want to use this, we would need a better and more deterministic way of figuring - /// out the "server address" ie the address to which server-to-server requests should be sent - because it - /// probably is not the "current request address" - especially in multi-domains configurations. - /// - public sealed class ServerRegistrationEventHandler : ApplicationEventHandler - { - private DatabaseServerRegistrar _registrar; - private BackgroundTaskRunner _backgroundTaskRunner; - private bool _started = false; - private TouchServerTask _task; - private object _lock = new object(); - - // bind to events - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - _registrar = ServerRegistrarResolver.Current.Registrar as DatabaseServerRegistrar; - - // only for the DatabaseServerRegistrar - if (_registrar == null) return; - - _backgroundTaskRunner = new BackgroundTaskRunner( - new BackgroundTaskRunnerOptions { AutoStart = true }, - applicationContext.ProfilingLogger.Logger); - - //We will start the whole process when a successful request is made - UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt; - } - - /// - /// Handle when a request is made - /// - /// - /// - /// - /// We require this because: - /// - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest - /// - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest - /// we are safe, UmbracoApplicationUrl has been initialized - /// - private void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e) - { - switch (e.Outcome) - { - case EnsureRoutableOutcome.IsRoutable: - case EnsureRoutableOutcome.NotDocumentRequest: - RegisterBackgroundTasks(e); - break; - } - } - - private void RegisterBackgroundTasks(UmbracoRequestEventArgs e) - { - //remove handler, we're done - UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt; - - //only perform this one time ever - LazyInitializer.EnsureInitialized(ref _task, ref _started, ref _lock, () => - { - var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl; - var svc = e.UmbracoContext.Application.Services.ServerRegistrationService; - - var task = new TouchServerTask(_backgroundTaskRunner, - 15000, //delay before first execution - _registrar.Options.RecurringSeconds*1000, //amount of ms between executions - svc, _registrar, serverAddress); - - //Perform the rest async, we don't want to block the startup sequence - // this will just reoccur on a background thread - _backgroundTaskRunner.TryAdd(task); - - return task; - }); - } - - private class TouchServerTask : RecurringTaskBase - { - private readonly IServerRegistrationService _svc; - private readonly DatabaseServerRegistrar _registrar; - private readonly string _serverAddress; - - /// - /// Initializes a new instance of the class. - /// - /// The task runner. - /// The delay. - /// The period. - /// - /// - /// - /// The task will repeat itself periodically. Use this constructor to create a new task. - public TouchServerTask(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - IServerRegistrationService svc, DatabaseServerRegistrar registrar, string serverAddress) - : base(runner, delayMilliseconds, periodMilliseconds) - { - if (svc == null) throw new ArgumentNullException("svc"); - _svc = svc; - _registrar = registrar; - _serverAddress = serverAddress; - } - - public override bool IsAsync - { - get { return false; } - } - - /// - /// Runs the background task. - /// - /// A value indicating whether to repeat the task. - public override bool PerformRun() - { - try - { - // TouchServer uses a proper unit of work etc underneath so even in a - // background task it is safe to call it without dealing with any scope - _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); - - return true; // repeat - } - catch (Exception ex) - { - LogHelper.Error("Failed to update server record in database.", ex); - - return false; // probably stop if we have an error - } - } - } - } -} diff --git a/src/Umbraco.Web/Suspendable.cs b/src/Umbraco.Web/Suspendable.cs index db4ef53485..9e15b2419e 100644 --- a/src/Umbraco.Web/Suspendable.cs +++ b/src/Umbraco.Web/Suspendable.cs @@ -1,9 +1,10 @@ using System; -using System.Diagnostics; using Examine; using Examine.Providers; -using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Examine; using Umbraco.Web.Cache; +using Umbraco.Web.Search; namespace Umbraco.Web { @@ -24,19 +25,13 @@ namespace Umbraco.Web } } - public static bool CanUpdateDocumentCache - { - get - { - // trying a partial update - // ok if not suspended, or if we haven't done a full already - return _suspended == false || _tried == false; - } - } + // trying a partial update + // ok if not suspended, or if we haven't done a full already + public static bool CanUpdateDocumentCache => _suspended == false || _tried == false; public static void SuspendDocumentCache() { - ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (PageCacheRefresher), "Suspend document cache."); + Current.ProfilingLogger.Logger.Info(typeof (PageCacheRefresher), "Suspend document cache."); _suspended = true; } @@ -44,12 +39,12 @@ namespace Umbraco.Web { _suspended = false; - ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (PageCacheRefresher), string.Format("Resume document cache (reload:{0}).", _tried ? "true" : "false")); + Current.ProfilingLogger.Logger.Info(typeof (PageCacheRefresher), $"Resume document cache (reload:{(_tried ? "true" : "false")})."); if (_tried == false) return; _tried = false; - var pageRefresher = CacheRefreshersResolver.Current.GetById(new Guid(DistributedCache.PageCacheRefresherId)); + var pageRefresher = Current.CacheRefreshers[ContentCacheRefresher.UniqueId]; pageRefresher.RefreshAll(); } } @@ -70,7 +65,7 @@ namespace Umbraco.Web public static void SuspendIndexers() { - ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (ExamineEvents), "Suspend indexers."); + Current.ProfilingLogger.Logger.Info(typeof (ExamineEvents), "Suspend indexers."); _suspended = true; } @@ -78,16 +73,15 @@ namespace Umbraco.Web { _suspended = false; - ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (ExamineEvents), string.Format("Resume indexers (rebuild:{0}).", _tried ? "true" : "false")); + Current.ProfilingLogger.Logger.Info(typeof (ExamineEvents), $"Resume indexers (rebuild:{(_tried ? "true" : "false")})."); if (_tried == false) return; _tried = false; // fixme - could we fork this on a background thread? - foreach (BaseIndexProvider indexer in ExamineManager.Instance.IndexProviderCollection) - { - indexer.RebuildIndex(); - } + //TODO: when resuming do we always want a full rebuild of all indexes? + // fixme - can we inject IExamineManager somehow? + ExamineComponent.RebuildIndexes(false, ExamineManager.Instance, Current.Logger); } } @@ -95,20 +89,17 @@ namespace Umbraco.Web { private static bool _suspended; - public static bool CanRun - { - get { return _suspended == false; } - } + public static bool CanRun => _suspended == false; public static void Suspend() { - ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (ScheduledPublishing), "Suspend scheduled publishing."); + Current.ProfilingLogger.Logger.Info(typeof (ScheduledPublishing), "Suspend scheduled publishing."); _suspended = true; } public static void Resume() { - ApplicationContext.Current.ProfilingLogger.Logger.Info(typeof (ScheduledPublishing), "Resume scheduled publishing."); + Current.ProfilingLogger.Logger.Info(typeof (ScheduledPublishing), "Resume scheduled publishing."); _suspended = false; } } diff --git a/src/Umbraco.Web/TagQuery.cs b/src/Umbraco.Web/TagQuery.cs index 807a0699cb..0b6ecda986 100644 --- a/src/Umbraco.Web/TagQuery.cs +++ b/src/Umbraco.Web/TagQuery.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using AutoMapper; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Web.Models; @@ -19,7 +20,7 @@ namespace Umbraco.Web private readonly ITagQuery _wrappedQuery; private readonly ITagService _tagService; - private readonly ITypedPublishedContentQuery _typedContentQuery; + private readonly IPublishedContentQuery _contentQuery; [Obsolete("Use the alternate constructor specifying the contentQuery instead")] public TagQuery(ITagService tagService) @@ -33,7 +34,7 @@ namespace Umbraco.Web if (tagService == null) throw new ArgumentNullException("tagService"); if (contentQuery == null) throw new ArgumentNullException("contentQuery"); _tagService = tagService; - _typedContentQuery = contentQuery; + _contentQuery = contentQuery; } /// @@ -50,15 +51,15 @@ namespace Umbraco.Web /// Constructor /// /// - /// - public TagQuery(ITagService tagService, ITypedPublishedContentQuery typedContentQuery) + /// + public TagQuery(ITagService tagService, IPublishedContentQuery contentQuery) { if (tagService == null) throw new ArgumentNullException("tagService"); - if (typedContentQuery == null) throw new ArgumentNullException("typedContentQuery"); + if (contentQuery == null) throw new ArgumentNullException("contentQuery"); _tagService = tagService; - _typedContentQuery = typedContentQuery; + _contentQuery = contentQuery; } - + /// /// Returns all content that is tagged with the specified tag value and optional tag group /// @@ -72,7 +73,7 @@ namespace Umbraco.Web var ids = _tagService.GetTaggedContentByTag(tag, tagGroup) .Select(x => x.EntityId); - return _typedContentQuery.TypedContent(ids) + return _contentQuery.Content(ids) .Where(x => x != null); } @@ -88,7 +89,7 @@ namespace Umbraco.Web var ids = _tagService.GetTaggedContentByTagGroup(tagGroup) .Select(x => x.EntityId); - return _typedContentQuery.TypedContent(ids) + return _contentQuery.Content(ids) .Where(x => x != null); } @@ -105,7 +106,7 @@ namespace Umbraco.Web var ids = _tagService.GetTaggedMediaByTag(tag, tagGroup) .Select(x => x.EntityId); - return _typedContentQuery.TypedMedia(ids) + return _contentQuery.Media(ids) .Where(x => x != null); } @@ -121,7 +122,7 @@ namespace Umbraco.Web var ids = _tagService.GetTaggedMediaByTagGroup(tagGroup) .Select(x => x.EntityId); - return _typedContentQuery.TypedMedia(ids) + return _contentQuery.Media(ids) .Where(x => x != null); } @@ -214,4 +215,4 @@ namespace Umbraco.Web return Mapper.Map>(_tagService.GetTagsForEntity(contentId, tagGroup)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Templates/TemplateRenderer.cs b/src/Umbraco.Web/Templates/TemplateRenderer.cs index d7d331d887..0fc490d3c8 100644 --- a/src/Umbraco.Web/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web/Templates/TemplateRenderer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.IO; using System.Linq; @@ -10,98 +10,107 @@ using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.Routing; using umbraco; -using umbraco.cms.businesslogic.language; using Umbraco.Core.Configuration; +using Umbraco.Core.Services; +using LightInject; +using Umbraco.Web.Composing; namespace Umbraco.Web.Templates { - /// - /// This is used purely for the RenderTemplate functionality in Umbraco - /// - /// - /// This allows you to render either an MVC or Webforms template based purely off of a node id and an optional alttemplate id as string output. - /// - internal class TemplateRenderer - { - private readonly UmbracoContext _umbracoContext; - private object _oldPageId; - private object _oldPageElements; - private PublishedContentRequest _oldPublishedContentRequest; - private object _oldAltTemplate; + /// + /// This is used purely for the RenderTemplate functionality in Umbraco + /// + /// + /// This allows you to render either an MVC or Webforms template based purely off of a node id and an optional alttemplate id as string output. + /// + internal class TemplateRenderer + { + private readonly IFileService _fileService = Current.Services.FileService; // fixme inject + private readonly UmbracoContext _umbracoContext; + private object _oldPageId; + private object _oldPageElements; + private PublishedRequest _oldPublishedRequest; + private object _oldAltTemplate; - public TemplateRenderer(UmbracoContext umbracoContext, int pageId, int? altTemplateId) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - PageId = pageId; - AltTemplate = altTemplateId; - _umbracoContext = umbracoContext; - } + public TemplateRenderer(UmbracoContext umbracoContext, int pageId, int? altTemplateId) + { + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + PageId = pageId; + AltTemplate = altTemplateId; + _umbracoContext = umbracoContext; + } - /// - /// Gets/sets the page id for the template to render - /// - public int PageId { get; private set; } + // todo - inject! + private PublishedRouter PublishedRouter => Core.Composing.Current.Container.GetInstance(); - /// - /// Gets/sets the alt template to render if there is one - /// - public int? AltTemplate { get; private set; } - public void Render(StringWriter writer) - { - if (writer == null) throw new ArgumentNullException("writer"); - // instanciate a request a process - // important to use CleanedUmbracoUrl - lowercase path-only version of the current url, though this isn't going to matter - // terribly much for this implementation since we are just creating a doc content request to modify it's properties manually. - var contentRequest = new PublishedContentRequest(_umbracoContext.CleanedUmbracoUrl, _umbracoContext.RoutingContext); + /// + /// Gets/sets the page id for the template to render + /// + public int PageId { get; } - var doc = contentRequest.RoutingContext.UmbracoContext.ContentCache.GetById(PageId); + /// + /// Gets/sets the alt template to render if there is one + /// + public int? AltTemplate { get; } - if (doc == null) - { - writer.Write("", PageId); - return; - } + public void Render(StringWriter writer) + { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + // instanciate a request a process + // important to use CleanedUmbracoUrl - lowercase path-only version of the current url, though this isn't going to matter + // terribly much for this implementation since we are just creating a doc content request to modify it's properties manually. + // fixme - previously that would create an aengine... + var contentRequest = PublishedRouter.CreateRequest(_umbracoContext); + + var doc = contentRequest.UmbracoContext.ContentCache.GetById(PageId); + + if (doc == null) + { + writer.Write("", PageId); + return; + } //in some cases the UmbracoContext will not have a PublishedContentRequest assigned to it if we are not in the //execution of a front-end rendered page. In this case set the culture to the default. //set the culture to the same as is currently rendering - if (_umbracoContext.PublishedContentRequest == null) + if (_umbracoContext.PublishedRequest == null) { - var defaultLanguage = Language.GetAllAsList().FirstOrDefault(); - contentRequest.Culture = defaultLanguage == null - ? CultureInfo.CurrentUICulture - : new CultureInfo(defaultLanguage.CultureAlias); + var defaultLanguage = Current.Services.LocalizationService.GetAllLanguages().FirstOrDefault(); + contentRequest.Culture = defaultLanguage == null + ? CultureInfo.CurrentUICulture + : defaultLanguage.CultureInfo; } else { - contentRequest.Culture = _umbracoContext.PublishedContentRequest.Culture; + contentRequest.Culture = _umbracoContext.PublishedRequest.Culture; } - - //set the doc that was found by id - contentRequest.PublishedContent = doc; - //set the template, either based on the AltTemplate found or the standard template of the doc - contentRequest.TemplateModel = UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates || AltTemplate.HasValue == false - ? _umbracoContext.Application.Services.FileService.GetTemplate(doc.TemplateId) - : _umbracoContext.Application.Services.FileService.GetTemplate(AltTemplate.Value); - //if there is not template then exit - if (!contentRequest.HasTemplate) - { - if (!AltTemplate.HasValue) - { - writer.Write("", doc.TemplateId); - } - else - { - writer.Write("", AltTemplate); - } - return; - } + //set the doc that was found by id + contentRequest.PublishedContent = doc; + //set the template, either based on the AltTemplate found or the standard template of the doc + contentRequest.TemplateModel = UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates || AltTemplate.HasValue == false + ? _fileService.GetTemplate(doc.TemplateId) + : _fileService.GetTemplate(AltTemplate.Value); - //First, save all of the items locally that we know are used in the chain of execution, we'll need to restore these - //after this page has rendered. - SaveExistingItems(); + //if there is not template then exit + if (contentRequest.HasTemplate == false) + { + if (AltTemplate.HasValue == false) + { + writer.Write("", doc.TemplateId); + } + else + { + writer.Write("", AltTemplate); + } + return; + } + + //First, save all of the items locally that we know are used in the chain of execution, we'll need to restore these + //after this page has rendered. + SaveExistingItems(); try { @@ -116,50 +125,50 @@ namespace Umbraco.Web.Templates //restore items on context objects to continuing rendering the parent template RestoreItems(); } - - } - private void ExecuteTemplateRendering(TextWriter sw, PublishedContentRequest contentRequest) - { - //NOTE: Before we used to build up the query strings here but this is not necessary because when we do a - // Server.Execute in the TemplateRenderer, we pass in a 'true' to 'preserveForm' which automatically preserves all current - // query strings so there's no need for this. HOWEVER, once we get MVC involved, we might have to do some fun things, - // though this will happen in the TemplateRenderer. + } - //var queryString = _umbracoContext.HttpContext.Request.QueryString.AllKeys - // .ToDictionary(key => key, key => context.Request.QueryString[key]); - - switch (contentRequest.RenderingEngine) - { - case RenderingEngine.Mvc: - var requestContext = new RequestContext(_umbracoContext.HttpContext, new RouteData() - { - Route = RouteTable.Routes["Umbraco_default"] - }); - var routeHandler = new RenderRouteHandler(ControllerBuilder.Current.GetControllerFactory(), _umbracoContext); - var routeDef = routeHandler.GetUmbracoRouteDefinition(requestContext, contentRequest); - var renderModel = new RenderModel(contentRequest.PublishedContent, contentRequest.Culture); - //manually add the action/controller, this is required by mvc - requestContext.RouteData.Values.Add("action", routeDef.ActionName); - requestContext.RouteData.Values.Add("controller", routeDef.ControllerName); - //add the rest of the required route data - routeHandler.SetupRouteDataForRequest(renderModel, requestContext, contentRequest); + private void ExecuteTemplateRendering(TextWriter sw, PublishedRequest request) + { + //NOTE: Before we used to build up the query strings here but this is not necessary because when we do a + // Server.Execute in the TemplateRenderer, we pass in a 'true' to 'preserveForm' which automatically preserves all current + // query strings so there's no need for this. HOWEVER, once we get MVC involved, we might have to do some fun things, + // though this will happen in the TemplateRenderer. + + //var queryString = _umbracoContext.HttpContext.Request.QueryString.AllKeys + // .ToDictionary(key => key, key => context.Request.QueryString[key]); + + switch (request.RenderingEngine) + { + case RenderingEngine.Mvc: + var requestContext = new RequestContext(_umbracoContext.HttpContext, new RouteData() + { + Route = RouteTable.Routes["Umbraco_default"] + }); + var routeHandler = new RenderRouteHandler(_umbracoContext, ControllerBuilder.Current.GetControllerFactory()); + var routeDef = routeHandler.GetUmbracoRouteDefinition(requestContext, request); + var renderModel = new ContentModel(request.PublishedContent); + //manually add the action/controller, this is required by mvc + requestContext.RouteData.Values.Add("action", routeDef.ActionName); + requestContext.RouteData.Values.Add("controller", routeDef.ControllerName); + //add the rest of the required route data + routeHandler.SetupRouteDataForRequest(renderModel, requestContext, request); var stringOutput = RenderUmbracoRequestToString(requestContext); - - sw.Write(stringOutput); - break; - case RenderingEngine.WebForms: - default: - var webFormshandler = (global::umbraco.UmbracoDefault)BuildManager - .CreateInstanceFromVirtualPath("~/default.aspx", typeof(global::umbraco.UmbracoDefault)); - //the 'true' parameter will ensure that the current query strings are carried through, we don't have - // to build up the url again, it will just work. - _umbracoContext.HttpContext.Server.Execute(webFormshandler, sw, true); - break; - } - - } + + sw.Write(stringOutput); + break; + case RenderingEngine.WebForms: + default: + var webFormshandler = (global::umbraco.UmbracoDefault)BuildManager + .CreateInstanceFromVirtualPath("~/default.aspx", typeof(global::umbraco.UmbracoDefault)); + //the 'true' parameter will ensure that the current query strings are carried through, we don't have + // to build up the url again, it will just work. + _umbracoContext.HttpContext.Server.Execute(webFormshandler, sw, true); + break; + } + + } /// /// This will execute the UmbracoMvcHandler for the request specified and get the string output. @@ -188,40 +197,39 @@ namespace Umbraco.Web.Templates return newWriter.ToString(); } - private void SetNewItemsOnContextObjects(PublishedContentRequest contentRequest) - { - // handlers like default.aspx will want it and most macros currently need it - contentRequest.UmbracoPage = new page(contentRequest); - //now, set the new ones for this page execution - _umbracoContext.HttpContext.Items["pageID"] = contentRequest.PublishedContent.Id; - _umbracoContext.HttpContext.Items["pageElements"] = contentRequest.UmbracoPage.Elements; - _umbracoContext.HttpContext.Items[Umbraco.Core.Constants.Conventions.Url.AltTemplate] = null; - _umbracoContext.PublishedContentRequest = contentRequest; - } + private void SetNewItemsOnContextObjects(PublishedRequest request) + { + // handlers like default.aspx will want it and most macros currently need it + request.UmbracoPage = new page(request); + //now, set the new ones for this page execution + _umbracoContext.HttpContext.Items["pageID"] = request.PublishedContent.Id; + _umbracoContext.HttpContext.Items["pageElements"] = request.UmbracoPage.Elements; + _umbracoContext.HttpContext.Items[Core.Constants.Conventions.Url.AltTemplate] = null; + _umbracoContext.PublishedRequest = request; + } - /// - /// Save all items that we know are used for rendering execution to variables so we can restore after rendering - /// - private void SaveExistingItems() - { - //Many objects require that these legacy items are in the http context items... before we render this template we need to first - //save the values in them so that we can re-set them after we render so the rest of the execution works as per normal. - _oldPageId = _umbracoContext.HttpContext.Items["pageID"]; - _oldPageElements = _umbracoContext.HttpContext.Items["pageElements"]; - _oldPublishedContentRequest = _umbracoContext.PublishedContentRequest; - _oldAltTemplate = _umbracoContext.HttpContext.Items[Umbraco.Core.Constants.Conventions.Url.AltTemplate]; - } + /// + /// Save all items that we know are used for rendering execution to variables so we can restore after rendering + /// + private void SaveExistingItems() + { + //Many objects require that these legacy items are in the http context items... before we render this template we need to first + //save the values in them so that we can re-set them after we render so the rest of the execution works as per normal. + _oldPageId = _umbracoContext.HttpContext.Items["pageID"]; + _oldPageElements = _umbracoContext.HttpContext.Items["pageElements"]; + _oldPublishedRequest = _umbracoContext.PublishedRequest; + _oldAltTemplate = _umbracoContext.HttpContext.Items[Umbraco.Core.Constants.Conventions.Url.AltTemplate]; + } - /// - /// Restores all items back to their context's to continue normal page rendering execution - /// - private void RestoreItems() - { - _umbracoContext.PublishedContentRequest = _oldPublishedContentRequest; - _umbracoContext.HttpContext.Items["pageID"] = _oldPageId; - _umbracoContext.HttpContext.Items["pageElements"] = _oldPageElements; - _umbracoContext.HttpContext.Items[Umbraco.Core.Constants.Conventions.Url.AltTemplate] = _oldAltTemplate; - } - - } -} \ No newline at end of file + /// + /// Restores all items back to their context's to continue normal page rendering execution + /// + private void RestoreItems() + { + _umbracoContext.PublishedRequest = _oldPublishedRequest; + _umbracoContext.HttpContext.Items["pageID"] = _oldPageId; + _umbracoContext.HttpContext.Items["pageElements"] = _oldPageElements; + _umbracoContext.HttpContext.Items[Umbraco.Core.Constants.Conventions.Url.AltTemplate] = _oldAltTemplate; + } + } +} diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index a7e6738374..71e0d3e550 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -4,11 +4,12 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Web.Composing; using Umbraco.Web.Routing; namespace Umbraco.Web.Templates { - //NOTE: I realize there is only one class in this namespace but I'm pretty positive that there will be more classes in + //NOTE: I realize there is only one class in this namespace but I'm pretty positive that there will be more classes in //this namespace once we start migrating and cleaning up more code. /// @@ -19,19 +20,10 @@ namespace Umbraco.Web.Templates //TODO: Pass in an Umbraco context!!!!!!!! Don't rely on the singleton so things are more testable internal static string ParseInternalLinks(string text, bool preview) { - // save and set for url provider - var inPreviewMode = UmbracoContext.Current.InPreviewMode; - UmbracoContext.Current.InPreviewMode = preview; - - try + using (UmbracoContext.Current.ForcedPreview(preview)) // force for url provider { text = ParseInternalLinks(text); } - finally - { - // restore - UmbracoContext.Current.InPreviewMode = inPreviewMode; - } return text; } @@ -70,7 +62,7 @@ namespace Umbraco.Web.Templates { var newLink = urlProvider.GetUrl(intId); text = text.Replace(tag.Value, "href=\"" + newLink); - } + } } } @@ -84,17 +76,15 @@ namespace Umbraco.Web.Templates /// [Obsolete("Use the overload specifying all dependencies instead")] public static string ParseInternalLinks(string text) - { + { //don't attempt to proceed without a context as we cannot lookup urls without one - if (UmbracoContext.Current == null || UmbracoContext.Current.RoutingContext == null) - { + if (UmbracoContext.Current == null) return text; - } var urlProvider = UmbracoContext.Current.UrlProvider; return ParseInternalLinks(text, urlProvider); } - + // static compiled regex for faster performance private static readonly Regex LocalLinkPattern = new Regex(@"href=""[/]?(?:\{|\%7B)localLink:([a-zA-Z0-9-://]+)(?:\}|\%7D)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); @@ -115,11 +105,11 @@ namespace Umbraco.Web.Templates { if (UmbracoConfig.For.UmbracoSettings().Content.ResolveUrlsFromTextString == false) return text; - using (var timer = DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) + using (var timer = Current.ProfilingLogger.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) { // find all relative urls (ie. urls that contain ~) var tags = ResolveUrlPattern.Matches(text); - LogHelper.Debug(typeof(IOHelper), "After regex: " + timer.Stopwatch.ElapsedMilliseconds + " matched: " + tags.Count); + Current.Logger.Debug(typeof(IOHelper), "After regex: " + timer.Stopwatch.ElapsedMilliseconds + " matched: " + tags.Count); foreach (Match tag in tags) { var url = ""; diff --git a/src/Umbraco.Web/Tour/BackOfficeTourFilter.cs b/src/Umbraco.Web/Tour/BackOfficeTourFilter.cs new file mode 100644 index 0000000000..bcca2e6673 --- /dev/null +++ b/src/Umbraco.Web/Tour/BackOfficeTourFilter.cs @@ -0,0 +1,63 @@ +using System.Text.RegularExpressions; + +namespace Umbraco.Web.Tour +{ + /// + /// Represents a back-office tour filter. + /// + public class BackOfficeTourFilter + { + /// + /// Initializes a new instance of the class. + /// + /// Value to filter out tours by a plugin, can be null + /// Value to filter out a tour file, can be null + /// Value to filter out a tour alias, can be null + /// + /// Depending on what is null will depend on how the filter is applied. + /// If pluginName is not NULL and it's matched then we check if tourFileName is not NULL and it's matched then we check tour alias is not NULL and then match it, + /// if any steps is NULL then the filters upstream are applied. + /// Example, pluginName = "hello", tourFileName="stuff", tourAlias=NULL = we will filter out the tour file "stuff" from the plugin "hello" but not from other plugins if the same file name exists. + /// Example, tourAlias="test.*" = we will filter out all tour aliases that start with the word "test" regardless of the plugin or file name + /// + public BackOfficeTourFilter(Regex pluginName, Regex tourFileName, Regex tourAlias) + { + PluginName = pluginName; + TourFileName = tourFileName; + TourAlias = tourAlias; + } + + /// + /// Gets the plugin name filtering regex. + /// + public Regex PluginName { get; } + + /// + /// Gets the tour filename filtering regex. + /// + public Regex TourFileName { get; } + + /// + /// Gets the tour alias filtering regex. + /// + public Regex TourAlias { get; } + + /// + /// Creates a filter to filter on the plugin name. + /// + public static BackOfficeTourFilter FilterPlugin(Regex pluginName) + => new BackOfficeTourFilter(pluginName, null, null); + + /// + /// Creates a filter to filter on the tour filename. + /// + public static BackOfficeTourFilter FilterFile(Regex tourFileName) + => new BackOfficeTourFilter(null, tourFileName, null); + + /// + /// Creates a filter to filter on the tour alias. + /// + public static BackOfficeTourFilter FilterAlias(Regex tourAlias) + => new BackOfficeTourFilter(null, null, tourAlias); + } +} diff --git a/src/Umbraco.Web/Tour/TourFilterCollection.cs b/src/Umbraco.Web/Tour/TourFilterCollection.cs new file mode 100644 index 0000000000..e221c17170 --- /dev/null +++ b/src/Umbraco.Web/Tour/TourFilterCollection.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Tour +{ + /// + /// Represents a collection of items. + /// + public class TourFilterCollection : BuilderCollectionBase + { + /// + /// Initializes a new instance of the class. + /// + public TourFilterCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Web/Tour/TourFilterCollectionBuilder.cs b/src/Umbraco.Web/Tour/TourFilterCollectionBuilder.cs new file mode 100644 index 0000000000..5084975bbd --- /dev/null +++ b/src/Umbraco.Web/Tour/TourFilterCollectionBuilder.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using LightInject; +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Tour +{ + /// + /// Builds a collection of items. + /// + public class TourFilterCollectionBuilder : CollectionBuilderBase + { + private readonly HashSet _instances = new HashSet(); + + /// + /// Initializes a new instance of the class. + /// + public TourFilterCollectionBuilder(IServiceContainer container) + : base(container) + { } + + /// + protected override IEnumerable CreateItems(params object[] args) + { + return base.CreateItems(args).Concat(_instances); + } + + /// + /// Adds a filter instance. + /// + public void AddFilter(BackOfficeTourFilter filter) + { + _instances.Add(filter); + } + + /// + /// Removes a filter instance. + /// + public void RemoveFilter(BackOfficeTourFilter filter) + { + _instances.Remove(filter); + } + + /// + /// Removes all filter instances. + /// + public void RemoveAllFilters() + { + _instances.Clear(); + } + + /// + /// Removes filters matching a condition. + /// + public void RemoveFilter(Func predicate) + { + _instances.RemoveWhere(new Predicate(predicate)); + } + + /// + /// Creates and adds a filter instance filtering by plugin name. + /// + public void AddFilterByPlugin(string pluginName) + { + pluginName = pluginName.EnsureStartsWith("^").EnsureEndsWith("$"); + _instances.Add(BackOfficeTourFilter.FilterPlugin(new Regex(pluginName, RegexOptions.IgnoreCase))); + } + + /// + /// Creates and adds a filter instance filtering by tour filename. + /// + public void AddFilterByFile(string filename) + { + filename = filename.EnsureStartsWith("^").EnsureEndsWith("$"); + _instances.Add(BackOfficeTourFilter.FilterFile(new Regex(filename, RegexOptions.IgnoreCase))); + } + } +} diff --git a/src/Umbraco.Web/TourFilterResolver.cs b/src/Umbraco.Web/TourFilterResolver.cs deleted file mode 100644 index 79112f7097..0000000000 --- a/src/Umbraco.Web/TourFilterResolver.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using Umbraco.Web.Models; - -namespace Umbraco.Web -{ - /// - /// Allows for adding filters for tours during startup - /// - public class TourFilterResolver : ManyObjectsResolverBase - { - public TourFilterResolver(IServiceProvider serviceProvider, ILogger logger) : base(serviceProvider, logger) - { - } - - private readonly HashSet _instances = new HashSet(); - - public IEnumerable Filters - { - get { return Values; } - } - - /// - /// Adds a filter instance - /// - /// - public void AddFilter(BackOfficeTourFilter filter) - { - using (Resolution.Configuration) - _instances.Add(filter); - } - - /// - /// Helper method for adding a filter by exact plugin name - /// - /// Regex string used for matching - public void AddFilterByPlugin(string pluginName) - { - pluginName = pluginName.EnsureStartsWith("^").EnsureEndsWith("$"); - using (Resolution.Configuration) - _instances.Add(BackOfficeTourFilter.FilterPlugin(new Regex(pluginName, RegexOptions.IgnoreCase))); - } - - /// - /// Helper method for adding a filter by exact file name - /// - /// - public void AddFilterByFile(string file) - { - file = file.EnsureStartsWith("^").EnsureEndsWith("$"); - using (Resolution.Configuration) - _instances.Add(BackOfficeTourFilter.FilterFile(new Regex(file, RegexOptions.IgnoreCase))); - } - - /// - /// Helper method for adding a filter by exact tour alias - /// - /// - public void AddFilterByAlias(string alias) - { - alias = alias.EnsureStartsWith("^").EnsureEndsWith("$"); - using (Resolution.Configuration) - _instances.Add(BackOfficeTourFilter.FilterAlias(new Regex(alias, RegexOptions.IgnoreCase))); - } - - /// - /// Removes a filter instance - /// - /// - public void RemoveFilter(BackOfficeTourFilter filter) - { - using (Resolution.Configuration) - _instances.Remove(filter); - } - - /// - /// Removes a filter instance based on callback - /// - /// - public void RemoveFilterWhere(Func filter) - { - using (Resolution.Configuration) - _instances.RemoveWhere(new Predicate(filter)); - } - - /// - /// - /// Overridden to return the combined created instances based on the resolved Types and the Concrete values added with AddFilter - /// - /// - protected override IEnumerable CreateInstances() - { - var createdInstances = base.CreateInstances(); - return createdInstances.Concat(_instances); - } - - public override void Clear() - { - base.Clear(); - _instances.Clear(); - } - - internal override void ResetCollections() - { - base.ResetCollections(); - _instances.Clear(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ActionUrlMethod.cs b/src/Umbraco.Web/Trees/ActionUrlMethod.cs index 1b2ab635bc..e178f085ec 100644 --- a/src/Umbraco.Web/Trees/ActionUrlMethod.cs +++ b/src/Umbraco.Web/Trees/ActionUrlMethod.cs @@ -8,4 +8,4 @@ Dialog, BlankWindow } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 37d880f262..a37bd08c8b 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -5,16 +5,14 @@ using System.Net; using System.Net.Http.Formatting; using System.Threading.Tasks; using System.Web.Http; -using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; -using umbraco; - namespace Umbraco.Web.Trees { [AngularJsonOnlyConfiguration] @@ -39,9 +37,9 @@ namespace Umbraco.Web.Trees //find all tree definitions that have the current application alias var appTrees = Services.ApplicationTreeService.GetApplicationTrees(application, onlyInitialized).ToArray(); - if (string.IsNullOrEmpty(tree) == false || appTrees.Length == 1) + if (appTrees.Length == 1 || string.IsNullOrEmpty(tree) == false ) { - var apptree = string.IsNullOrEmpty(tree) == false + var apptree = string.IsNullOrEmpty(tree) == false ? appTrees.SingleOrDefault(x => x.Alias == tree) : appTrees.SingleOrDefault(); @@ -50,7 +48,7 @@ namespace Umbraco.Web.Trees var result = await GetRootForSingleAppTree( apptree, Constants.System.Root.ToString(CultureInfo.InvariantCulture), - queryStrings, + queryStrings, application); //this will be null if it cannot convert to ta single root section @@ -66,12 +64,12 @@ namespace Umbraco.Web.Trees //This could be null if the tree decides not to return it's root (i.e. the member type tree does this when not in umbraco membership mode) if (rootNode != null) { - collection.Add(rootNode); + collection.Add(rootNode); } } var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); - multiTree.Name = ui.Text("sections", application); + multiTree.Name = Services.TextService.Localize("sections/"+ application); return multiTree; } @@ -83,18 +81,18 @@ namespace Umbraco.Web.Trees /// private async Task GetRootForMultipleAppTree(ApplicationTree configTree, FormDataCollection queryStrings) { - if (configTree == null) throw new ArgumentNullException("configTree"); - try - { - var byControllerAttempt = await configTree.TryGetRootNodeFromControllerTree(queryStrings, ControllerContext); - if (byControllerAttempt.Success) - { - return byControllerAttempt.Result; - } - } - catch (HttpResponseException) - { - //if this occurs its because the user isn't authorized to view that tree, in this case since we are loading multiple trees we + if (configTree == null) throw new ArgumentNullException(nameof(configTree)); + try + { + var byControllerAttempt = await configTree.TryGetRootNodeFromControllerTree(queryStrings, ControllerContext); + if (byControllerAttempt.Success) + { + return byControllerAttempt.Result; + } + } + catch (HttpResponseException) + { + //if this occurs its because the user isn't authorized to view that tree, in this case since we are loading multiple trees we //will just return null so that it's not added to the list. return null; } @@ -119,7 +117,7 @@ namespace Umbraco.Web.Trees private async Task GetRootForSingleAppTree(ApplicationTree configTree, string id, FormDataCollection queryStrings, string application) { var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); - if (configTree == null) throw new ArgumentNullException("configTree"); + if (configTree == null) throw new ArgumentNullException(nameof(configTree)); var byControllerAttempt = configTree.TryLoadFromControllerTree(id, queryStrings, ControllerContext); if (byControllerAttempt.Success) { @@ -132,8 +130,8 @@ namespace Umbraco.Web.Trees //if the root node has a route path, we cannot create a single root section because by specifying the route path this would //override the dashboard route and that means there can be no dashboard for that section which is a breaking change. - if (rootNode.Result.RoutePath.IsNullOrWhiteSpace() == false - && rootNode.Result.RoutePath != "#" + if (string.IsNullOrWhiteSpace(rootNode.Result.RoutePath) == false + && rootNode.Result.RoutePath != "#" && rootNode.Result.RoutePath != application) { //null indicates this cannot be converted @@ -141,9 +139,9 @@ namespace Umbraco.Web.Trees } var sectionRoot = SectionRootNode.CreateSingleTreeSectionRoot( - rootId, - rootNode.Result.ChildNodesUrl, - rootNode.Result.MenuUrl, + rootId, + rootNode.Result.ChildNodesUrl, + rootNode.Result.MenuUrl, rootNode.Result.Name, byControllerAttempt.Result); @@ -171,16 +169,12 @@ namespace Umbraco.Web.Trees "", //TODO: I think we'll need this in this situation! legacyAttempt.Result); - + sectionRoot.AdditionalData.Add("treeAlias", configTree.Alias); return sectionRoot; } throw new ApplicationException("Could not render a tree for type " + configTree.Alias); } - - } - - } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs index c312452a39..e628ad77d9 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs @@ -14,7 +14,9 @@ using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using umbraco.cms.presentation.Trees; +using Umbraco.Core.Composing; using Umbraco.Core.Services; +using Current = Umbraco.Web.Composing.Current; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; using UrlHelper = System.Web.Http.Routing.UrlHelper; @@ -22,7 +24,6 @@ namespace Umbraco.Web.Trees { internal static class ApplicationTreeExtensions { - private static readonly ConcurrentDictionary TreeAttributeCache = new ConcurrentDictionary(); internal static TreeAttribute GetTreeAttribute(this Type treeControllerType) @@ -68,9 +69,9 @@ namespace Umbraco.Web.Trees internal static Attempt TryGetControllerTree(this ApplicationTree appTree) { //get reference to all TreeApiControllers - var controllerTrees = UmbracoApiControllerResolver.Current.RegisteredUmbracoApiControllers - .Where(TypeHelper.IsTypeAssignableFrom) - .ToArray(); + var controllerTrees = Current.UmbracoApiControllerTypes + .Where(TypeHelper.IsTypeAssignableFrom) + .ToArray(); //find the one we're looking for var foundControllerTree = controllerTrees.FirstOrDefault(x => x == appTree.GetRuntimeType()); @@ -89,7 +90,7 @@ namespace Umbraco.Web.Trees /// /// /// - /// This ensures that authorization filters are applied to the sub request + /// This ensures that authorization filters are applied to the sub request /// internal static async Task> TryGetRootNodeFromControllerTree(this ApplicationTree appTree, FormDataCollection formCollection, HttpControllerContext controllerContext) { @@ -98,17 +99,17 @@ namespace Umbraco.Web.Trees { return Attempt.Fail(foundControllerTreeAttempt.Exception); } - + var foundControllerTree = foundControllerTreeAttempt.Result; //instantiate it, since we are proxying, we need to setup the instance with our current context var instance = (TreeController)DependencyResolver.Current.GetService(foundControllerTree); - //NOTE: This is all required in order to execute the auth-filters for the sub request, we + //NOTE: This is all required in order to execute the auth-filters for the sub request, we // need to "trick" web-api into thinking that it is actually executing the proxied controller. var urlHelper = controllerContext.Request.GetUrlHelper(); //create the proxied URL for the controller action - var proxiedUrl = controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Authority) + + var proxiedUrl = controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Authority) + urlHelper.GetUmbracoApiService("GetRootNode", instance.GetType()); //add the query strings to it proxiedUrl += "?" + formCollection.ToQueryString(); @@ -128,12 +129,12 @@ namespace Umbraco.Web.Trees if (WebApiVersionCheck.WebApiVersion >= Version.Parse("5.0.0")) { - //In WebApi2, this is required to be set: + //In WebApi2, this is required to be set: // proxiedControllerContext.RequestContext = controllerContext.RequestContext // but we need to do this with reflection because of codebase changes between version 4/5 //NOTE: Use TypeHelper here since the reflection is cached var controllerContextRequestContext = TypeHelper.GetProperty(controllerContext.GetType(), "RequestContext").GetValue(controllerContext); - TypeHelper.GetProperty(proxiedControllerContext.GetType(), "RequestContext").SetValue(proxiedControllerContext, controllerContextRequestContext); + TypeHelper.GetProperty(proxiedControllerContext.GetType(), "RequestContext").SetValue(proxiedControllerContext, controllerContextRequestContext); } instance.ControllerContext = proxiedControllerContext; @@ -157,28 +158,30 @@ namespace Umbraco.Web.Trees { throw new HttpResponseException(result); } - + //return the root var node = instance.GetRootNode(formCollection); return node == null - ? Attempt.Fail(new InvalidOperationException("Could not return a root node for tree " + appTree.Type)) + ? Attempt.Fail(new InvalidOperationException("Could not return a root node for tree " + appTree.Type)) : Attempt.Succeed(node); } - internal static Attempt TryLoadFromControllerTree(this ApplicationTree appTree, string id, FormDataCollection formCollection, HttpControllerContext controllerContext) + internal static Attempt TryLoadFromControllerTree(this ApplicationTree appTree, string id, FormDataCollection formCollection, HttpControllerContext controllerContext) { var foundControllerTreeAttempt = appTree.TryGetControllerTree(); if (foundControllerTreeAttempt.Success == false) - { return Attempt.Fail(foundControllerTreeAttempt.Exception); - } - var foundControllerTree = foundControllerTreeAttempt.Result; - //instantiate it, since we are proxying, we need to setup the instance with our current context - var instance = (TreeController)DependencyResolver.Current.GetService(foundControllerTree); + // instantiate it, since we are proxying, we need to setup the instance with our current context + var foundControllerTree = foundControllerTreeAttempt.Result; + var instance = (TreeController) DependencyResolver.Current.GetService(foundControllerTree); + if (instance == null) + throw new Exception("Failed to get tree " + foundControllerTree.FullName + "."); + instance.ControllerContext = controllerContext; instance.Request = controllerContext.Request; - //return it's data + + // return its data return Attempt.Succeed(instance.GetNodes(id, formCollection)); } @@ -196,11 +199,12 @@ namespace Umbraco.Web.Trees return Attempt.Succeed(null); } - var legacyController = new LegacyTreeController(xmlTreeNodeAttempt.Result, appTree.Alias, currentSection, urlHelper); - var newRoot = legacyController.GetRootNode(formCollection); + //var temp = new LegacyTreeController(xmlTreeNodeAttempt.Result, appTree.Alias, currentSection, urlHelper); + var temp = new TreeControllerBaseStuffForLegacy(appTree.Alias, xmlTreeNodeAttempt.Result.Text, urlHelper); + var newRoot = temp.GetRootNode(formCollection); return Attempt.Succeed(newRoot); - + } internal static Attempt TryGetRootXmlNodeFromLegacyTree(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper) @@ -216,7 +220,7 @@ namespace Umbraco.Web.Trees bTree.SetTreeParameters(treeParams); var xmlRoot = bTree.RootNode; - + return Attempt.Succeed(xmlRoot); } @@ -224,8 +228,8 @@ namespace Umbraco.Web.Trees { //This is how the legacy trees worked.... var treeDef = TreeDefinitionCollection.Instance.FindTree(appTree.Alias); - return treeDef == null - ? Attempt.Fail(new InstanceNotFoundException("Could not find tree of type " + appTree.Alias)) + return treeDef == null + ? Attempt.Fail(new InstanceNotFoundException("Could not find tree of type " + appTree.Alias)) : Attempt.Succeed(treeDef); } @@ -249,7 +253,7 @@ namespace Umbraco.Web.Trees var currentSection = formCollection.GetRequiredString("section"); - var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(rootAttempt.Result, currentSection); + var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(rootAttempt.Result, currentSection); return Attempt.Succeed(result); } @@ -301,5 +305,5 @@ namespace Umbraco.Web.Trees } } - + } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs b/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs deleted file mode 100644 index 60ea0aabd8..0000000000 --- a/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; -using umbraco.businesslogic; -using Umbraco.Core.Services; - -namespace Umbraco.Web.Trees -{ - /// - /// A startup handler for putting the tree config in the config file based on attributes found - /// - /// - /// TODO: This is really not a very ideal process but the code is found here because tree plugins are in the Web project or the legacy business logic project. - /// Moving forward we can put the base tree plugin classes in the core and then this can all just be taken care of normally within the service. - /// - public sealed class ApplicationTreeRegistrar : ApplicationEventHandler - { - protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - //Call initialize on the tree service with the lazy enumerable class below, when the tree service needs to resolve, - // it will lazily do the scanning and comparing so it's not actually done on app start. - applicationContext.Services.ApplicationTreeService.Intitialize(new LazyEnumerableTrees()); - } - - /// - /// This class is here so that we can provide lazy access to tree scanning for when it is needed - /// - private class LazyEnumerableTrees : IEnumerable - { - public LazyEnumerableTrees() - { - _lazyTrees = new Lazy>(() => - { - var added = new List(); - - // Load all Controller Trees by attribute - var types = PluginManager.Current.ResolveAttributedTreeControllers(); - //convert them to ApplicationTree instances - var items = types - .Select(x => - new Tuple(x, x.GetCustomAttributes(false).Single())) - .Select(x => new ApplicationTree(x.Item2.Initialize, x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen, x.Item1.GetFullNameWithAssembly())) - .ToArray(); - - added.AddRange(items.Select(x => x.Alias)); - - //find the legacy trees - var legacyTreeTypes = PluginManager.Current.ResolveAttributedTrees(); - //convert them to ApplicationTree instances - var legacyItems = legacyTreeTypes - .Select(x => - new Tuple( - x, - x.GetCustomAttributes(false).SingleOrDefault(), - x.GetCustomAttributes(false).SingleOrDefault())) - //ensure that the legacy tree attribute exists - .Where(x => x.Item2 != null) - //ensure that it's not obsoleted, any obsoleted tree will not be auto added to the config - .Where(x => x.Item3 == null) - //make sure the legacy tree isn't added on top of the controller tree! - .Where(x => added.InvariantContains(x.Item2.Alias) == false) - .Select(x => new ApplicationTree(x.Item2.Initialize, x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen, x.Item1.GetFullNameWithAssembly())); - - return items.Concat(legacyItems).ToArray(); - }); - } - - private readonly Lazy> _lazyTrees; - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return _lazyTrees.Value.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - } -} diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs index 0f9da51e63..93d7746f8f 100644 --- a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs @@ -1,128 +1,118 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Globalization; -using System.Net; -using System.Net.Http.Formatting; -using System.Web.Http; -using umbraco; -using umbraco.businesslogic.Actions; -using umbraco.BusinessLogic.Actions; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Trees -{ - /// - /// The content blueprint tree controller - /// - /// - /// This authorizes based on access to the content section even though it exists in the settings - /// - [UmbracoApplicationAuthorize(Constants.Applications.Content)] - [Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, null, sortOrder: 8)] - [PluginController("UmbracoTrees")] - [CoreTree] - public class ContentBlueprintTreeController : TreeController - { - - protected override TreeNode CreateRootNode(FormDataCollection queryStrings) - { - var root = base.CreateRootNode(queryStrings); - - //this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Settings, Constants.Trees.ContentBlueprints, "intro"); - - return root; - } - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) - { - var nodes = new TreeNodeCollection(); - - //get all blueprints - var entities = Services.EntityService.GetChildren(Constants.System.Root, UmbracoObjectTypes.DocumentBlueprint).ToArray(); - - //check if we're rendering the root in which case we'll render the content types that have blueprints - if (id == Constants.System.Root.ToInvariantString()) - { - //get all blueprint content types - var contentTypeAliases = entities.Select(x => ((UmbracoEntity) x).ContentTypeAlias).Distinct(); - //get the ids - var contentTypeIds = Services.ContentTypeService.GetAllContentTypeIds(contentTypeAliases.ToArray()).ToArray(); - - //now get the entities ... it's a bit round about but still smaller queries than getting all document types - var docTypeEntities = contentTypeIds.Length == 0 - ? new IUmbracoEntity[0] - : Services.EntityService.GetAll(UmbracoObjectTypes.DocumentType, contentTypeIds).ToArray(); - - nodes.AddRange(docTypeEntities - .Select(entity => - { - var treeNode = CreateTreeNode(entity, Constants.ObjectTypes.DocumentBlueprintGuid, id, queryStrings, "icon-item-arrangement", true); - treeNode.Path = string.Format("-1,{0}", entity.Id); - treeNode.NodeType = "document-type-blueprints"; - //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. - treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; - return treeNode; - })); - - return nodes; - } - else - { - var intId = id.TryConvertTo(); - //Get the content type - var ct = Services.ContentTypeService.GetContentType(intId.Result); - if (ct == null) return nodes; - - var blueprintsForDocType = entities.Where(x => ct.Alias == ((UmbracoEntity) x).ContentTypeAlias); - nodes.AddRange(blueprintsForDocType - .Select(entity => - { - var treeNode = CreateTreeNode(entity, Constants.ObjectTypes.DocumentBlueprintGuid, id, queryStrings, "icon-blueprint", false); - treeNode.Path = string.Format("-1,{0},{1}", ct.Id, entity.Id); - return treeNode; - })); - } - - return nodes; - } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Core.Services; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.Actions; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + /// + /// The content blueprint tree controller + /// + /// + /// This authorizes based on access to the content section even though it exists in the settings + /// + [UmbracoApplicationAuthorize(Constants.Applications.Content)] + [Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, null, sortOrder: 8)] + [PluginController("UmbracoTrees")] + [CoreTree] + public class ContentBlueprintTreeController : TreeController + { + + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + + //this will load in a custom UI instead of the dashboard for the root node + root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Settings, Constants.Trees.ContentBlueprints, "intro"); + + return root; + } + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + + //get all blueprints + var entities = Services.EntityService.GetChildren(Constants.System.Root, UmbracoObjectTypes.DocumentBlueprint).ToArray(); + + //check if we're rendering the root in which case we'll render the content types that have blueprints + if (id == Constants.System.Root.ToInvariantString()) + { + //get all blueprint content types + var contentTypeAliases = entities.Select(x => ((ContentEntitySlim) x).ContentTypeAlias).Distinct(); + //get the ids + var contentTypeIds = Services.ContentTypeService.GetAllContentTypeIds(contentTypeAliases.ToArray()).ToArray(); + + //now get the entities ... it's a bit round about but still smaller queries than getting all document types + var docTypeEntities = contentTypeIds.Length == 0 + ? new IUmbracoEntity[0] + : Services.EntityService.GetAll(UmbracoObjectTypes.DocumentType, contentTypeIds).ToArray(); + + nodes.AddRange(docTypeEntities + .Select(entity => + { + var treeNode = CreateTreeNode(entity, Constants.ObjectTypes.DocumentBlueprint, id, queryStrings, "icon-item-arrangement", true); + treeNode.Path = string.Format("-1,{0}", entity.Id); + treeNode.NodeType = "document-type-blueprints"; + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + return treeNode; + })); + + return nodes; + } + + var intId = id.TryConvertTo(); + //Get the content type + var ct = Services.ContentTypeService.Get(intId.Result); + if (ct == null) return nodes; + + var blueprintsForDocType = entities.Where(x => ct.Alias == ((ContentEntitySlim) x).ContentTypeAlias); + nodes.AddRange(blueprintsForDocType + .Select(entity => + { + var treeNode = CreateTreeNode(entity, Constants.ObjectTypes.DocumentBlueprint, id, queryStrings, "icon-blueprint", false); + treeNode.Path = $"-1,{ct.Id},{entity.Id}"; + return treeNode; + })); + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) { // root actions - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - return menu; - } - var cte = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.DocumentType); - //only refresh & create if it's a content type - if (cte != null) - { - var ct = Services.ContentTypeService.GetContentType(cte.Id); - var createItem = menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionCreateBlueprintFromContent.Instance.Alias))); - createItem.NavigateToRoute("/settings/contentBlueprints/edit/-1?create=true&doctype=" + ct.Alias); - - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - - return menu; - } - - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - - return menu; - } - - } -} \ No newline at end of file + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + return menu; + } + var cte = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.DocumentType); + //only refresh & create if it's a content type + if (cte != null) + { + var ct = Services.ContentTypeService.Get(cte.Id); + var createItem = menu.Items.Add(Services.TextService.Localize($"actions/{ActionCreateBlueprintFromContent.Instance.Alias}")); + createItem.NavigateToRoute("/settings/contentBlueprints/edit/-1?create=true&doctype=" + ct.Alias); + + menu.Items.Add(Services.TextService.Localize($"actions/{ActionRefresh.Instance.Alias}"), true); + + return menu; + } + + menu.Items.Add(Services.TextService.Localize($"actions/{ActionDelete.Instance.Alias}")); + + return menu; + } + + } +} diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index ea644f5dc8..60ef3cf529 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -1,24 +1,18 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Net; -using System.Net.Http; using System.Net.Http.Formatting; using System.Web.Http; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; -using umbraco; -using umbraco.BusinessLogic.Actions; -using umbraco.businesslogic; -using umbraco.businesslogic.Actions; -using umbraco.cms.businesslogic.web; -using umbraco.interfaces; +using Umbraco.Web._Legacy.Actions; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Search; using Constants = Umbraco.Core.Constants; @@ -34,7 +28,6 @@ namespace Umbraco.Web.Trees Constants.Applications.Settings, Constants.Applications.Developer, Constants.Applications.Members)] - [LegacyBaseTree(typeof(loadContent))] [Tree(Constants.Applications.Content, Constants.Trees.Content)] [PluginController("UmbracoTrees")] [CoreTree] @@ -42,64 +35,49 @@ namespace Umbraco.Web.Trees public class ContentTreeController : ContentTreeControllerBase, ISearchableTree { private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); - - protected override int RecycleBinId - { - get { return Constants.System.RecycleBinContent; } - } - protected override bool RecycleBinSmells - { - get { return Services.ContentService.RecycleBinSmells(); } - } + protected override int RecycleBinId => Constants.System.RecycleBinContent; + + protected override bool RecycleBinSmells => Services.ContentService.RecycleBinSmells(); private int[] _userStartNodes; protected override int[] UserStartNodes - { - get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService)); } - } + => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService)); - /// - /// Creates a tree node for a content item based on an UmbracoEntity - /// - /// - /// - /// - /// - protected override TreeNode GetSingleTreeNode(IUmbracoEntity e, string parentId, FormDataCollection queryStrings) + /// + protected override TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormDataCollection queryStrings) { - var entity = (UmbracoEntity)e; - - var allowedUserOptions = GetAllowedUserMenuItemsForNode(e); - if (CanUserAccessNode(e, allowedUserOptions)) + var allowedUserOptions = GetAllowedUserMenuItemsForNode(entity); + if (CanUserAccessNode(entity, allowedUserOptions)) { //Special check to see if it ia a container, if so then we'll hide children. - var isContainer = e.IsContainer(); // && (queryStrings.Get("isDialog") != "true"); + var isContainer = entity.IsContainer; // && (queryStrings.Get("isDialog") != "true"); var node = CreateTreeNode( entity, - Constants.ObjectTypes.DocumentGuid, + Constants.ObjectTypes.Document, parentId, queryStrings, - entity.HasChildren && (isContainer == false)); - - node.AdditionalData.Add("contentType", entity.ContentTypeAlias); + entity.HasChildren && !isContainer); + // entity is either a container, or a document if (isContainer) { node.AdditionalData.Add("isContainer", true); node.SetContainerStyle(); } + else + { + var documentEntity = (IDocumentEntitySlim) entity; + if (!documentEntity.Published) + node.SetNotPublishedStyle(); + if (documentEntity.Edited) + node.SetHasUnpublishedVersionStyle(); + node.AdditionalData.Add("contentType", documentEntity.ContentTypeAlias); + } - - if (entity.IsPublished == false) - node.SetNotPublishedStyle(); - - if (entity.HasPendingChanges) - node.SetHasUnpublishedVersionStyle(); - - if (Services.PublicAccessService.IsProtected(e.Path)) + if (Services.PublicAccessService.IsProtected(entity.Path)) node.SetProtectedStyle(); return node; @@ -109,11 +87,11 @@ namespace Umbraco.Web.Trees } protected override MenuItemCollection PerformGetMenuForNode(string id, FormDataCollection queryStrings) - { + { if (id == Constants.System.Root.ToInvariantString()) { var menu = new MenuItemCollection(); - + // if the user's start node is not the root then the only menu item to display is refresh if (UserStartNodes.Contains(Constants.System.Root) == false) { @@ -127,14 +105,13 @@ namespace Umbraco.Web.Trees menu.DefaultMenuAlias = ActionNew.Instance.Alias; // we need to get the default permissions as you can't set permissions on the very root node - //TODO: Use the new services to get permissions - var nodeActions = global::umbraco.BusinessLogic.Actions.Action.FromString( - UmbracoUser.GetPermissions(Constants.System.Root.ToInvariantString())) - .Select(x => new MenuItem(x)); + var permission = Services.UserService.GetPermissions(Security.CurrentUser, Constants.System.Root).First(); + var nodeActions = global::Umbraco.Web._Legacy.Actions.Action.FromEntityPermission(permission) + .Select(x => new MenuItem(x)); //these two are the standard items - menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)); - menu.Items.Add(ui.Text("actions", ActionSort.Instance.Alias), true).ConvertLegacyMenuItem(null, "content", "content"); + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)); + menu.Items.Add(Services.TextService.Localize("actions", ActionSort.Instance.Alias), true).ConvertLegacyMenuItem(null, "content", "content"); //filter the standard items FilterUserAllowedMenuItems(menu, nodeActions); @@ -145,8 +122,8 @@ namespace Umbraco.Web.Trees } // add default actions for *all* users - menu.Items.Add(ui.Text("actions", ActionRePublish.Instance.Alias)).ConvertLegacyMenuItem(null, "content", "content"); - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + menu.Items.Add(Services.TextService.Localize("actions", ActionRePublish.Instance.Alias)).ConvertLegacyMenuItem(null, "content", "content"); + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); return menu; } @@ -163,7 +140,7 @@ namespace Umbraco.Web.Trees { throw new HttpResponseException(HttpStatusCode.NotFound); } - + //if the user has no path access for this node, all they can do is refresh if (Security.CurrentUser.HasPathAccess(item, Services.EntityService, RecycleBinId) == false) { @@ -183,7 +160,7 @@ namespace Umbraco.Web.Trees if (item.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString())) { nodeMenu.DefaultMenuAlias = null; - nodeMenu.Items.Insert(2, new MenuItem(ActionRestore.Instance, ui.Text("actions", ActionRestore.Instance.Alias))); + nodeMenu.Items.Insert(2, new MenuItem(ActionRestore.Instance, Services.TextService.Localize("actions", ActionRestore.Instance.Alias))); } else { @@ -220,37 +197,52 @@ namespace Umbraco.Web.Trees protected MenuItemCollection GetAllNodeMenuItems(IUmbracoEntity item) { var menu = new MenuItemCollection(); - menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)); - menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); + AddActionNode(item, menu); + AddActionNode(item, menu); - menu.Items.Add(ui.Text("actions", ActionCreateBlueprintFromContent.Instance.Alias)); + AddActionNode(item, menu); //need to ensure some of these are converted to the legacy system - until we upgrade them all to be angularized. - menu.Items.Add(ui.Text("actions", ActionMove.Instance.Alias), true); - menu.Items.Add(ui.Text("actions", ActionCopy.Instance.Alias)); - menu.Items.Add(ui.Text("actions", ActionChangeDocType.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); + AddActionNode(item, menu, true); + AddActionNode(item, menu); + AddActionNode(item, menu, convert: true); - menu.Items.Add(ui.Text("actions", ActionSort.Instance.Alias), true).ConvertLegacyMenuItem(item, "content", "content"); + AddActionNode(item, menu, true, true); - menu.Items.Add(ui.Text("actions", ActionRollback.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(ui.Text("actions", ActionAudit.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(ui.Text("actions", ActionPublish.Instance.Alias), true).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(ui.Text("actions", ActionToPublish.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(ui.Text("actions", ActionAssignDomain.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(ui.Text("actions", ActionRights.Instance.Alias), true); - menu.Items.Add(ui.Text("actions", ActionProtect.Instance.Alias), true).ConvertLegacyMenuItem(item, "content", "content"); + AddActionNode(item, menu, convert: true); + AddActionNode(item, menu, convert: true); + AddActionNode(item, menu, true, true); + AddActionNode(item, menu, convert: true); + AddActionNode(item, menu, convert: true); + AddActionNode(item, menu, convert: true); + AddActionNode(item, menu, true, true); - menu.Items.Add(ui.Text("actions", ActionNotify.Instance.Alias), true).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(ui.Text("actions", ActionSendToTranslate.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); + AddActionNode(item, menu, true, true); + AddActionNode(item, menu, convert: true); - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + AddActionNode(item, menu, true); return menu; } + private void AddActionNode(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool convert = false) + where TAction : IAction + { + var menuItem = menu.Items.Add(Services.TextService.Localize("actions", Current.Actions.GetAction().Alias), hasSeparator); + if (convert) menuItem.ConvertLegacyMenuItem(item, "content", "content"); + } + + private void AddActionNode(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool convert = false) + where TItem : MenuItem, new() + where TAction : IAction + { + var menuItem = menu.Items.Add(Services.TextService.Localize("actions", Current.Actions.GetAction().Alias), hasSeparator); + if (convert) menuItem.ConvertLegacyMenuItem(item, "content", "content"); + } + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) { return _treeSearcher.ExamineSearch(Umbraco, query, UmbracoEntityTypes.Document, pageSize, pageIndex, out totalFound, searchFrom); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 668e7c310a..dd27c80382 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -1,28 +1,25 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Formatting; using System.Web.Http; using Umbraco.Core; +using Umbraco.Core.Services; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence; using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi.Filters; -using umbraco; -using umbraco.BusinessLogic.Actions; using System.Globalization; +using Umbraco.Core.Models.Entities; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.Trees { public abstract class ContentTreeControllerBase : TreeController { - #region Actions /// @@ -35,12 +32,18 @@ namespace Umbraco.Web.Trees public TreeNode GetTreeNode(string id, FormDataCollection queryStrings) { int asInt; + Guid asGuid = Guid.Empty; if (int.TryParse(id, out asInt) == false) { - throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + if (Guid.TryParse(id, out asGuid) == false) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + } } - var entity = Services.EntityService.Get(asInt, UmbracoObjectType); + var entity = asGuid == Guid.Empty + ? Services.EntityService.Get(asInt, UmbracoObjectType) + : Services.EntityService.Get(asGuid, UmbracoObjectType); if (entity == null) { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); @@ -72,7 +75,7 @@ namespace Umbraco.Web.Trees return node; } - protected abstract TreeNode GetSingleTreeNode(IUmbracoEntity e, string parentId, FormDataCollection queryStrings); + protected abstract TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormDataCollection queryStrings); /// /// Returns a for the and @@ -82,10 +85,9 @@ namespace Umbraco.Web.Trees /// /// /// - internal TreeNode GetSingleTreeNodeWithAccessCheck(IUmbracoEntity e, string parentId, FormDataCollection queryStrings) + internal TreeNode GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormDataCollection queryStrings) { - bool hasPathAccess; - var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess); + var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out var hasPathAccess); if (entityIsAncestorOfStartNodes == false) return null; @@ -132,7 +134,7 @@ namespace Umbraco.Web.Trees // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access if (HasPathAccess(id, queryStrings) == false) { - LogHelper.Warn("User " + Security.CurrentUser.Username + " does not have access to node with id " + id); + Logger.Warn("User " + Security.CurrentUser.Username + " does not have access to node with id " + id); return nodes; } @@ -179,16 +181,16 @@ namespace Umbraco.Web.Trees protected abstract UmbracoObjectTypes UmbracoObjectType { get; } - protected IEnumerable GetChildEntities(string id) + protected IEnumerable GetChildEntities(string id) { // try to parse id as an integer else use GetEntityFromId // which will grok Guids, Udis, etc and let use obtain the id - int entityId; - if (int.TryParse(id, out entityId) == false) + if (int.TryParse(id, out var entityId) == false) { var entity = GetEntityFromId(id); if (entity == null) throw new HttpResponseException(HttpStatusCode.NotFound); + entityId = entity.Id; } @@ -198,7 +200,7 @@ namespace Umbraco.Web.Trees { return UserStartNodes.Length > 0 ? Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes) - : Enumerable.Empty(); + : Enumerable.Empty(); } return Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToArray(); @@ -259,7 +261,7 @@ namespace Umbraco.Web.Trees RecycleBinId.ToInvariantString(), id, queryStrings, - ui.GetText("general", "recycleBin"), + Services.TextService.Localize("general/recycleBin"), "icon-trash", RecycleBinSmells, queryStrings.GetValue("application") + TreeAlias.EnsureStartsWith('/') + "/recyclebin")); @@ -288,7 +290,7 @@ namespace Umbraco.Web.Trees //before we get the children we need to see if this is a container node //test if the parent is a listview / container - if (current != null && current.IsContainer()) + if (current != null && current.IsContainer) { //no children! return new TreeNodeCollection(); @@ -308,8 +310,8 @@ namespace Umbraco.Web.Trees if (RecycleBinId.ToInvariantString() == id) { var menu = new MenuItemCollection(); - menu.Items.Add(ui.Text("actions", "emptyTrashcan")); - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + menu.Items.Add(Services.TextService.Localize("actions/emptyTrashcan")); + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); return menu; } @@ -341,11 +343,13 @@ namespace Umbraco.Web.Trees internal IEnumerable GetAllowedUserMenuItemsForNode(IUmbracoEntity dd) { - var actions = ActionsResolver.Current.FromActionSymbols(Security.CurrentUser.GetPermissions(dd.Path, Services.UserService)) + var permission = Services.UserService.GetPermissions(Security.CurrentUser, dd.Path); + var actions = global::Umbraco.Web._Legacy.Actions.Action.FromEntityPermission(permission) .ToList(); // A user is allowed to delete their own stuff - if (dd.CreatorId == Security.GetUserId() && actions.Contains(ActionDelete.Instance) == false) + var tryGetCurrentUserId = Security.GetUserId(); + if (tryGetCurrentUserId && dd.CreatorId == tryGetCurrentUserId.Result && actions.Contains(ActionDelete.Instance) == false) actions.Add(ActionDelete.Instance); return actions.Select(x => new MenuItem(x)); @@ -363,6 +367,7 @@ namespace Umbraco.Web.Trees return allowedUserOptions.Select(x => x.Action).OfType().Any(); } + /// /// this will parse the string into either a GUID or INT /// @@ -400,40 +405,34 @@ namespace Umbraco.Web.Trees /// /// This object has it's own contextual cache for these lookups /// - internal IUmbracoEntity GetEntityFromId(string id) + internal IEntitySlim GetEntityFromId(string id) { return _entityCache.GetOrAdd(id, s => { - IUmbracoEntity entity; + IEntitySlim entity; - Guid idGuid; - int idInt; - Udi idUdi; - - if (Guid.TryParse(s, out idGuid)) + if (Guid.TryParse(s, out var idGuid)) { - entity = Services.EntityService.GetByKey(idGuid, UmbracoObjectType); + entity = Services.EntityService.Get(idGuid, UmbracoObjectType); } - else if (int.TryParse(s, out idInt)) + else if (int.TryParse(s, out var idInt)) { entity = Services.EntityService.Get(idInt, UmbracoObjectType); } - else if (Udi.TryParse(s, out idUdi)) + else if (Udi.TryParse(s, out var idUdi)) { var guidUdi = idUdi as GuidUdi; - entity = guidUdi != null ? Services.EntityService.GetByKey(guidUdi.Guid, UmbracoObjectType) : null; + entity = guidUdi != null ? Services.EntityService.Get(guidUdi.Guid, UmbracoObjectType) : null; } else { - return null; + entity = null; } return entity; }); - - } - private readonly ConcurrentDictionary _entityCache = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _entityCache = new ConcurrentDictionary(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 33ae50dbbf..4bdef0246a 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -3,24 +3,22 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; using AutoMapper; -using umbraco; -using umbraco.BusinessLogic.Actions; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Search; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] - [Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, null, sortOrder: 6)] + [Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, null, sortOrder: 0)] [Mvc.PluginController("UmbracoTrees")] [CoreTree] - [LegacyBaseTree(typeof(loadNodeTypes))] public class ContentTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) @@ -35,7 +33,7 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-folder", dt.HasChildren(), ""); + var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-folder", dt.HasChildren, ""); node.Path = dt.Path; node.NodeType = "container"; //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. @@ -51,10 +49,10 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt, Constants.ObjectTypes.DocumentTypeGuid, id, queryStrings, "icon-item-arrangement", - //NOTE: Since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. - //We need this check to keep supporting sites where childs have already been created. - dt.HasChildren()); + // since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. + // need this check to keep supporting sites where childs have already been created. + var hasChildren = dt.HasChildren; + var node = CreateTreeNode(dt, Constants.ObjectTypes.DocumentType, id, queryStrings, "icon-item-arrangement", hasChildren); node.Path = dt.Path; return node; @@ -74,9 +72,9 @@ namespace Umbraco.Web.Trees //set the default to create menu.DefaultMenuAlias = ActionNew.Instance.Alias; - // root actions + // root actions menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionImport.Instance.Alias)), true).ConvertLegacyMenuItem(new UmbracoEntity + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionImport.Instance.Alias)), true).ConvertLegacyMenuItem(new EntitySlim { Id = int.Parse(id), Level = 1, @@ -100,19 +98,19 @@ namespace Umbraco.Web.Trees Icon = "icon icon-edit" }); - if (container.HasChildren() == false) + if (container.HasChildren == false) { //can delete doc type menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)), true); } menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - + } else { - var ct = Services.ContentTypeService.GetContentType(int.Parse(id)); - var parent = ct == null ? null : Services.ContentTypeService.GetContentType(ct.ParentId); + var ct = Services.ContentTypeService.Get(int.Parse(id)); + var parent = ct == null ? null : Services.ContentTypeService.Get(ct.ParentId); if (enableInheritedDocumentTypes) { @@ -134,7 +132,7 @@ namespace Umbraco.Web.Trees } } menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionCopy.Instance.Alias))); - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionExport.Instance.Alias)), true).ConvertLegacyMenuItem(new UmbracoEntity + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionExport.Instance.Alias)), true).ConvertLegacyMenuItem(new EntitySlim { Id = int.Parse(id), Level = 1, @@ -151,7 +149,7 @@ namespace Umbraco.Web.Trees public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) { - var results = Services.EntityService.GetPagedDescendantsFromRoot(UmbracoObjectTypes.DocumentType, pageIndex, pageSize, out totalFound, filter: query); + var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.DocumentType, pageIndex, pageSize, out totalFound, filter: query); return Mapper.Map>(results); } } diff --git a/src/Umbraco.Web/Trees/CoreTreeAttribute.cs b/src/Umbraco.Web/Trees/CoreTreeAttribute.cs index 70aa87fb5e..3b317a547a 100644 --- a/src/Umbraco.Web/Trees/CoreTreeAttribute.cs +++ b/src/Umbraco.Web/Trees/CoreTreeAttribute.cs @@ -11,6 +11,6 @@ namespace Umbraco.Web.Trees [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] internal class CoreTreeAttribute : Attribute { - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 70da27c1bf..991f87d196 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -1,28 +1,23 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; -using System.Net; using System.Net.Http.Formatting; -using System.Web.Http; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; -using umbraco; -using umbraco.BusinessLogic.Actions; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Search; +using Umbraco.Web._Legacy.Actions; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] - [Tree(Constants.Applications.Developer, Constants.Trees.DataTypes, null, sortOrder:1)] + [Tree(Constants.Applications.Settings, Constants.Trees.DataTypes, null, sortOrder:1)] [PluginController("UmbracoTrees")] [CoreTree] public class DataTypeTreeController : TreeController, ISearchableTree @@ -30,8 +25,8 @@ namespace Umbraco.Web.Trees protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { var intId = id.TryConvertTo(); - if (intId == false) throw new InvalidOperationException("Id must be an integer"); - + if (intId == false) throw new InvalidOperationException("Id must be an integer"); + var nodes = new TreeNodeCollection(); //Folders first @@ -40,7 +35,7 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt, Constants.ObjectTypes.DataTypeGuid, id, queryStrings, "icon-folder", dt.HasChildren()); + var node = CreateTreeNode(dt, Constants.ObjectTypes.DataType, id, queryStrings, "icon-folder", dt.HasChildren); node.Path = dt.Path; node.NodeType = "container"; //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. @@ -75,13 +70,13 @@ namespace Umbraco.Web.Trees { var systemIds = new[] { - Constants.System.DefaultContentListViewDataTypeId, - Constants.System.DefaultMediaListViewDataTypeId, - Constants.System.DefaultMembersListViewDataTypeId + Constants.DataTypes.DefaultContentListView, + Constants.DataTypes.DefaultMediaListView, + Constants.DataTypes.DefaultMembersListView }; return systemIds; - } - + } + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) { var menu = new MenuItemCollection(); @@ -91,9 +86,9 @@ namespace Umbraco.Web.Trees //set the default to create menu.DefaultMenuAlias = ActionNew.Instance.Alias; - // root actions + // root actions menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); return menu; } @@ -108,15 +103,14 @@ namespace Umbraco.Web.Trees menu.Items.Add(new MenuItem("rename", Services.TextService.Localize(String.Format("actions/{0}", "rename"))) { Icon = "icon icon-edit" - }); + }); - if (container.HasChildren() == false) + if (container.HasChildren == false) { //can delete data type menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - } - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); - + } + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); } else { @@ -129,14 +123,14 @@ namespace Umbraco.Web.Trees menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), hasSeparator: true); } - + return menu; } public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) { - var results = Services.EntityService.GetPagedDescendantsFromRoot(UmbracoObjectTypes.DataType, pageIndex, pageSize, out totalFound, filter: query); + var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.DataType, pageIndex, pageSize, out totalFound, filter: query); return Mapper.Map>(results); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/DictionaryTreeController.cs b/src/Umbraco.Web/Trees/DictionaryTreeController.cs new file mode 100644 index 0000000000..a70eba29e2 --- /dev/null +++ b/src/Umbraco.Web/Trees/DictionaryTreeController.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Services; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.Actions; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Dictionary)] + [Tree(Constants.Applications.Settings, Constants.Trees.Dictionary, null, sortOrder: 3)] + [Mvc.PluginController("UmbracoTrees")] + [CoreTree] + public class DictionaryTreeController : TreeController + { + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var node = base.CreateRootNode(queryStrings); + + // For now, this is using the legacy webforms view but will need refactoring + // when the dictionary has been converted to Angular. + node.RoutePath = String.Format("{0}/framed/{1}", queryStrings.GetValue("application"), + Uri.EscapeDataString("settings/DictionaryItemList.aspx")); + + return node; + } + + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var intId = ValidateId(id); + + var nodes = new TreeNodeCollection(); + nodes.AddRange(GetDictionaryItems(intId) + .OrderBy(dictionaryItem => dictionaryItem.ItemKey) + .Select(dictionaryItem => CreateTreeNode(id, queryStrings, dictionaryItem))); + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var intId = ValidateId(id); + + var menu = new MenuItemCollection(); + + if (intId == Constants.System.Root) + { + // Again, menu actions will need to use legacy views as this section hasn't been converted to Angular (yet!) + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + .ConvertLegacyMenuItem(null, "dictionary", queryStrings.GetValue("application")); + + menu.Items.Add( + Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + } + else + { + + var dictionaryItem = Services.LocalizationService.GetDictionaryItemById(intId); + var entity = new EntitySlim + { + Id = dictionaryItem.Id, + Level = 1, + ParentId = -1, + Name = dictionaryItem.ItemKey + }; + + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + .ConvertLegacyMenuItem(entity, "dictionary", queryStrings.GetValue("application")); + + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)) + .ConvertLegacyMenuItem(null, "dictionary", queryStrings.GetValue("application")); + + menu.Items.Add( + Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + } + + return menu; + } + + private IEnumerable GetDictionaryItems(int id) + { + if (id > Constants.System.Root) + { + var dictionaryItem = Services.LocalizationService.GetDictionaryItemById(id); + + if (dictionaryItem != null) + { + return Services.LocalizationService.GetDictionaryItemChildren(dictionaryItem.Key); + } + } + + return Services.LocalizationService.GetRootDictionaryItems(); + } + + private TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, IDictionaryItem dictionaryItem) + { + var hasChildren = Services.LocalizationService.GetDictionaryItemChildren(dictionaryItem.Key).Any(); + + // Again, menu actions will need to use legacy views as this section hasn't been converted to Angular (yet!) + var node = CreateTreeNode(dictionaryItem.Id.ToInvariantString(), id, queryStrings, dictionaryItem.ItemKey, + "icon-book-alt", hasChildren, + String.Format("{0}/framed/{1}", queryStrings.GetValue("application"), + Uri.EscapeDataString("settings/editDictionaryItem.aspx?id=" + + dictionaryItem.Id))); + + return node; + } + + private int ValidateId(string id) + { + var intId = id.TryConvertTo(); + if (intId == false) + { + throw new InvalidOperationException("Id must be an integer"); + } + + return intId.Result; + } + } +} diff --git a/src/Umbraco.Web/Trees/FileSystemTreeController.cs b/src/Umbraco.Web/Trees/FileSystemTreeController.cs index f26e255a41..c31da79724 100644 --- a/src/Umbraco.Web/Trees/FileSystemTreeController.cs +++ b/src/Umbraco.Web/Trees/FileSystemTreeController.cs @@ -1,43 +1,47 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http.Formatting; -using umbraco.BusinessLogic.Actions; +using System.Web; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; -using System.Web; +using Umbraco.Web._Legacy.Actions; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { public abstract class FileSystemTreeController : TreeController { - protected abstract IFileSystem2 FileSystem { get; } + protected abstract IFileSystem FileSystem { get; } protected abstract string[] Extensions { get; } protected abstract string FileIcon { get; } + protected abstract bool EnableCreateOnFolder { get; } /// /// Inheritors can override this method to modify the file node that is created. /// - /// - protected virtual void OnRenderFileNode(ref TreeNode treeNode) { } + protected virtual void OnRenderFileNode(ref TreeNode treeNode) + { } /// /// Inheritors can override this method to modify the folder node that is created. /// - /// - protected virtual void OnRenderFolderNode(ref TreeNode treeNode) { } + protected virtual void OnRenderFolderNode(ref TreeNode treeNode) + { } protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { + var nodes = new TreeNodeCollection(); + var path = string.IsNullOrEmpty(id) == false && id != Constants.System.Root.ToInvariantString() ? HttpUtility.UrlDecode(id).TrimStart("/") : ""; var directories = FileSystem.GetDirectories(path); - var nodes = new TreeNodeCollection(); foreach (var directory in directories) { var hasChildren = FileSystem.GetFiles(directory).Any() || FileSystem.GetDirectories(directory).Any(); @@ -45,26 +49,28 @@ namespace Umbraco.Web.Trees var name = Path.GetFileName(directory); var node = CreateTreeNode(HttpUtility.UrlEncode(directory), path, queryStrings, name, "icon-folder", hasChildren); OnRenderFolderNode(ref node); - if(node != null) + if (node != null) nodes.Add(node); } - //this is a hack to enable file system tree to support multiple file extension look-up - //so the pattern both support *.* *.xml and xml,js,vb for lookups var files = FileSystem.GetFiles(path).Where(x => { var extension = Path.GetExtension(x); return extension != null && Extensions.Contains(extension.Trim('.'), StringComparer.InvariantCultureIgnoreCase); + + // fixme - should we filter out hidden files? but then, FileSystem does not support attributes! }); foreach (var file in files) { var withoutExt = Path.GetFileNameWithoutExtension(file); - if (withoutExt.IsNullOrWhiteSpace()) continue; + if (string.IsNullOrWhiteSpace(withoutExt)) continue; var name = Path.GetFileName(file); var node = CreateTreeNode(HttpUtility.UrlEncode(file), path, queryStrings, name, FileIcon, false); + OnRenderFileNode(ref node); + if (node != null) nodes.Add(node); } @@ -72,77 +78,89 @@ namespace Umbraco.Web.Trees return nodes; } - protected virtual MenuItemCollection GetMenuForRootNode(FormDataCollection queryStrings) + protected virtual TreeNodeCollection GetTreeNodesForFile(string path, string id, FormDataCollection queryStrings) { - var menu = new MenuItemCollection(); - - //set the default to create - menu.DefaultMenuAlias = ActionNew.Instance.Alias; - //create action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - //refresh action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - - return menu; - } - - protected virtual MenuItemCollection GetMenuForFolder(string path, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - //set the default to create - menu.DefaultMenuAlias = ActionNew.Instance.Alias; - //create action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - - var hasChildren = FileSystem.GetFiles(path).Any() || FileSystem.GetDirectories(path).Any(); - - //We can only delete folders if it doesn't have any children (folders or files) - if (hasChildren == false) - { - //delete action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)), true); - } - - //refresh action - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - - return menu; - } - - protected virtual MenuItemCollection GetMenuForFile(string path, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - //if it's not a directory then we only allow to delete the item - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - - return menu; + return new TreeNodeCollection(); } protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) { - //if root node no need to visit the filesystem so lets just create the menu and return it - if (id == Constants.System.Root.ToInvariantString()) - { - return GetMenuForRootNode(queryStrings); - } - var menu = new MenuItemCollection(); - var path = string.IsNullOrEmpty(id) == false && id != Constants.System.Root.ToInvariantString() - ? HttpUtility.UrlDecode(id).TrimStart("/") - : ""; + OnBeforeRenderMenu(menu, id, queryStrings); - var isFile = FileSystem.FileExists(path); - var isDirectory = FileSystem.DirectoryExists(path); - - if (isDirectory) + // if root node no need to visit the filesystem so lets just create the menu and return it + if (id == Constants.System.Root.ToInvariantString()) { - return GetMenuForFolder(path, queryStrings); + GetMenuForRootNode(menu, queryStrings); + } + else + { + var path = string.IsNullOrEmpty(id) == false && id != Constants.System.Root.ToInvariantString() + ? HttpUtility.UrlDecode(id).TrimStart("/") + : ""; + var isFile = FileSystem.FileExists(path); + var isDirectory = FileSystem.DirectoryExists(path); + + if (isDirectory) + GetMenuForFolder(menu, path, id, queryStrings); + else if (isFile) + GetMenuForFile(menu, path, id, queryStrings); } - return isFile ? GetMenuForFile(path, queryStrings) : menu; + OnAfterRenderMenu(menu, id, queryStrings); + + return menu; } + + protected virtual void GetMenuForRootNode(MenuItemCollection menu, FormDataCollection queryStrings) + { + // default create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + + // create action + //Since we haven't implemented anything for file systems in angular, this needs to be converted to + //use the legacy format + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + .ConvertLegacyFileSystemMenuItem("", "init" + TreeAlias, queryStrings.GetValue("application")); + + // refresh action + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + } + + protected virtual void GetMenuForFolder(MenuItemCollection menu, string path, string id, FormDataCollection queryStrings) + { + if (EnableCreateOnFolder) + { + // default create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + + // create action + //Since we haven't implemented anything for file systems in angular, this needs to be converted to + //use the legacy format + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + .ConvertLegacyFileSystemMenuItem(id, TreeAlias + "Folder", queryStrings.GetValue("application")); + } + + // delete action + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias), true) + .ConvertLegacyFileSystemMenuItem(id, TreeAlias + "Folder", queryStrings.GetValue("application")); + + // refresh action + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + } + + protected virtual void GetMenuForFile(MenuItemCollection menu, string path, string id, FormDataCollection queryStrings) + { + // delete action + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias), true) + .ConvertLegacyFileSystemMenuItem(id, TreeAlias, queryStrings.GetValue("application")); + } + + protected virtual void OnBeforeRenderMenu(MenuItemCollection menu, string id, FormDataCollection queryStrings) + { } + + protected virtual void OnAfterRenderMenu(MenuItemCollection menu, string id, FormDataCollection queryStrings) + { } } } diff --git a/src/Umbraco.Web/Search/ISearchableTree.cs b/src/Umbraco.Web/Trees/ISearchableTree.cs similarity index 91% rename from src/Umbraco.Web/Search/ISearchableTree.cs rename to src/Umbraco.Web/Trees/ISearchableTree.cs index 6e25724bfa..6e52b054b4 100644 --- a/src/Umbraco.Web/Search/ISearchableTree.cs +++ b/src/Umbraco.Web/Trees/ISearchableTree.cs @@ -1,8 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.Web.Search +namespace Umbraco.Web.Trees { public interface ISearchableTree { @@ -24,4 +23,4 @@ namespace Umbraco.Web.Search /// IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/LanguageTreeController.cs b/src/Umbraco.Web/Trees/LanguageTreeController.cs index 5481b7d40c..9a9f32128c 100644 --- a/src/Umbraco.Web/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web/Trees/LanguageTreeController.cs @@ -1,13 +1,4 @@ -using System; -using System.Data.SqlClient; -using System.Globalization; using System.Net.Http.Formatting; -using System.Web.Services.Description; -using umbraco; -using umbraco.BusinessLogic.Actions; -using Umbraco.Core; -using Umbraco.Core.Models; -using umbraco.presentation.actions; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; @@ -16,84 +7,38 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Languages)] - [LegacyBaseTree(typeof(loadLanguages))] [Tree(Constants.Applications.Settings, Constants.Trees.Languages, null, sortOrder: 4)] [PluginController("UmbracoTrees")] [CoreTree] public class LanguageTreeController : TreeController { - /// - /// The method called to render the contents of the tree structure - /// - /// - /// - /// All of the query string parameters passed from jsTree - /// - /// - /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end - /// to the back end to be used in the query for model data. - /// protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { - var nodes = new TreeNodeCollection(); + //We don't have any child nodes & only use the root node to load a custom UI + return new TreeNodeCollection(); + } - if (id == Constants.System.Root.ToInvariantString()) - { - var languages = Services.LocalizationService.GetAllLanguages(); - foreach (var language in languages) - { - nodes.Add( - CreateTreeNode( - language.Id.ToString(CultureInfo.InvariantCulture), "-1", queryStrings, language.CultureInfo.DisplayName, "icon-flag-alt", false, - //TODO: Rebuild the language editor in angular, then we dont need to have this at all (which is just a path to the legacy editor) - "/" + queryStrings.GetValue("application") + "/framed/" + - Uri.EscapeDataString("settings/editLanguage.aspx?id=" + language.Id))); - } - } - - return nodes; + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + //We don't have any menu item options (such as create/delete/reload) & only use the root node to load a custom UI + return null; } /// - /// Returns the menu structure for the node + /// Helper method to create a root model for a tree /// - /// - /// /// - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - //Create the normal create action - menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)) - //Since we haven't implemented anything for languages in angular, this needs to be converted to - //use the legacy format - .ConvertLegacyMenuItem(null, "initlanguages", queryStrings.GetValue("application")); - - //refresh action - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + var root = base.CreateRootNode(queryStrings); - return menu; - } + //this will load in a custom UI instead of the dashboard for the root node + root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Settings, Constants.Trees.Languages, "overview"); + root.Icon = "icon-globe"; + root.HasChildren = false; + root.MenuUrl = null; - var lang = Services.LocalizationService.GetLanguageById(int.Parse(id)); - if (lang == null) return new MenuItemCollection(); - - //add delete option for all languages - menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)) - //Since we haven't implemented anything for languages in angular, this needs to be converted to - //use the legacy format - .ConvertLegacyMenuItem(new UmbracoEntity - { - Id = lang.Id, - Level = 1, - ParentId = -1, - Name = lang.CultureInfo.DisplayName - }, "language", queryStrings.GetValue("application")); - - return menu; + return root; } } } diff --git a/src/Umbraco.Web/Trees/LegacyBaseTreeAttribute.cs b/src/Umbraco.Web/Trees/LegacyBaseTreeAttribute.cs deleted file mode 100644 index fcb858e0ad..0000000000 --- a/src/Umbraco.Web/Trees/LegacyBaseTreeAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using Umbraco.Core; -using umbraco.cms.presentation.Trees; - -namespace Umbraco.Web.Trees -{ - /// - /// This attribute is used purely to maintain some compatibility with legacy webform tree pickers - /// - /// - /// This allows us to attribute new trees with their legacy counterparts and when a legacy tree is loaded this will indicate - /// on the new tree which legacy tree to load (it won't actually render using the new tree) - /// - [AttributeUsage(AttributeTargets.Class)] - internal sealed class LegacyBaseTreeAttribute : Attribute - { - public Type BaseTreeType { get; private set; } - - public LegacyBaseTreeAttribute(Type baseTreeType) - { - if (!TypeHelper.IsTypeAssignableFrom(baseTreeType)) - { - throw new InvalidOperationException("The type for baseTreeType must be assignable from " + typeof(BaseTree)); - } - - BaseTreeType = baseTreeType; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/LegacyTreeController.cs b/src/Umbraco.Web/Trees/LegacyTreeController.cs index cf8d90e0c4..eafd3ed83b 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeController.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeController.cs @@ -2,44 +2,24 @@ using System.Globalization; using System.Linq; using System.Net.Http.Formatting; -using System.Web.Http.Routing; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using umbraco.cms.presentation.Trees; namespace Umbraco.Web.Trees { - /// /// This is used to output JSON from legacy trees /// - [PluginController("UmbracoTrees")] - [LegacyTreeAuthorizeAttribute] + [PluginController("UmbracoTrees"), LegacyTreeAuthorizeAttribute] public class LegacyTreeController : TreeControllerBase { private readonly XmlTreeNode _xmlTreeNode; - private readonly string _treeAlias; private readonly string _currentSection; - private readonly string _rootDisplay; - - public LegacyTreeController() - { - - } - - public LegacyTreeController(XmlTreeNode xmlTreeNode, string treeAlias, string currentSection, UrlHelper urlHelper) - { - _xmlTreeNode = xmlTreeNode; - _treeAlias = treeAlias; - _currentSection = currentSection; - _rootDisplay = xmlTreeNode.Text; - Url = urlHelper; - } protected override TreeNode CreateRootNode(FormDataCollection queryStrings) { @@ -47,8 +27,8 @@ namespace Umbraco.Web.Trees _xmlTreeNode.NodeID, _xmlTreeNode, Url, - _currentSection, - queryStrings, + _currentSection, + queryStrings, isRoot: true); } @@ -59,7 +39,7 @@ namespace Umbraco.Web.Trees if (attempt.Success == false) { var msg = "Could not render tree " + queryStrings.GetRequiredString("treeType") + " for node id " + id; - LogHelper.Error(msg, attempt.Exception); + Logger.Error(msg, attempt.Exception); throw new ApplicationException(msg); } @@ -86,13 +66,13 @@ namespace Umbraco.Web.Trees if (attempt.Success == false) { var msg = "Could not render menu for root node for treeType " + queryStrings.GetRequiredString("treeType"); - LogHelper.Error(msg, attempt.Exception); + Logger.Error(msg, attempt.Exception); throw new ApplicationException(msg); } foreach (var menuItem in attempt.Result.Items) { - menuItem.Name = global::umbraco.ui.Text("actions", menuItem.Alias); + menuItem.Name = Services.TextService.Localize("actions", menuItem.Alias); } return attempt.Result; } @@ -102,26 +82,20 @@ namespace Umbraco.Web.Trees if (attempt.Success == false) { var msg = "Could not render menu for treeType " + queryStrings.GetRequiredString("treeType") + " for node id " + parentId; - LogHelper.Error(msg, attempt.Exception); + Logger.Error(msg, attempt.Exception); throw new ApplicationException(msg); } foreach (var menuItem in attempt.Result.Items) { - menuItem.Name = global::umbraco.ui.Text("actions", menuItem.Alias); + menuItem.Name = Services.TextService.Localize("actions", menuItem.Alias); } return attempt.Result; - } + } } - public override string RootNodeDisplayName - { - get { return _rootDisplay; } - } + public override string RootNodeDisplayName { get; } - public override string TreeAlias - { - get { return _treeAlias; } - } + public override string TreeAlias { get; } private ApplicationTree GetTree(FormDataCollection queryStrings) { @@ -135,4 +109,4 @@ namespace Umbraco.Web.Trees } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index c6be75f124..af3bca0e0a 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -11,12 +11,13 @@ using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; using umbraco; -using umbraco.BusinessLogic; -using umbraco.BusinessLogic.Actions; -using umbraco.cms.helpers; + using umbraco.cms.presentation.Trees; using umbraco.controls.Tree; -using umbraco.interfaces; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using Umbraco.Web.Composing; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.Trees { @@ -34,25 +35,28 @@ namespace Umbraco.Web.Trees var controllerAttempt = appTree.TryGetControllerTree(); if (controllerAttempt.Success) { - var legacyAtt = controllerAttempt.Result.GetCustomAttribute(false); - if (legacyAtt == null) - { - LogHelper.Warn("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeController) + " tree type with the legacy web services unless attributed with " + typeof(LegacyBaseTreeAttribute)); - return null; - } + Current.Logger.Warn("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeController) + " tree type with the legacy web services."); + return null; - var treeDef = new TreeDefinition( - legacyAtt.BaseTreeType, - new ApplicationTree(false, true, (byte)appTree.SortOrder, appTree.ApplicationAlias, appTree.Alias, appTree.Title, appTree.IconClosed, appTree.IconOpened, "", legacyAtt.BaseTreeType.GetFullNameWithAssembly(), ""), - new Application(appTree.Alias, appTree.Alias, "", 0)); + //var legacyAtt = controllerAttempt.Result.GetCustomAttribute(false); + //if (legacyAtt == null) + //{ + // Current.Logger.Warn("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeController) + " tree type with the legacy web services unless attributed with " + typeof(LegacyBaseTreeAttribute)); + // return null; + //} - tree = treeDef.CreateInstance(); - tree.TreeAlias = appTree.Alias; + //var treeDef = new TreeDefinition( + // legacyAtt.BaseTreeType, + // new ApplicationTree(true, appTree.SortOrder, appTree.ApplicationAlias, appTree.Alias, appTree.Title, appTree.IconClosed, appTree.IconOpened, legacyAtt.BaseTreeType.GetFullNameWithAssembly()), + // new Section(appTree.Alias, appTree.Alias, "", 0)); + + //tree = treeDef.CreateInstance(); + //tree.TreeAlias = appTree.Alias; } else { - //get the tree that we need to render + //get the tree that we need to render var treeDef = TreeDefinitionCollection.Instance.FindTree(appTree.Alias); if (treeDef == null) { @@ -124,25 +128,35 @@ namespace Umbraco.Web.Trees } else { - var menuItem = collection.Items.Add(t, ui.Text("actions", t.Alias)); + var menuItem = collection.Items.Add(t, Current.Services.TextService.Localize("actions", t.Alias)); var currentAction = t; - //First try to get a URL/title from the legacy action, - // if that doesn't work, try to get the legacy confirm view - // if that doesn't work and there's no jsAction in there already then add the legacy js method call - Attempt - .Try(GetUrlAndTitleFromLegacyAction(currentAction, xmlTreeNode.NodeID, xmlTreeNode.NodeType, xmlTreeNode.Text, currentSection), - action => menuItem.LaunchDialogUrl(action.Url, action.DialogTitle)) - .OnFailure(() => GetLegacyConfirmView(currentAction), - view => menuItem.LaunchDialogView( - view, - ui.GetText("defaultdialogs", "confirmdelete") + " '" + xmlTreeNode.Text + "' ?")) - .OnFailure(() => menuItem.AdditionalData.ContainsKey(MenuItem.JsActionKey) - ? Attempt.Fail(false) - : Attempt.Succeed(true), - b => menuItem.ExecuteLegacyJs(menuItem.Action.JsFunctionName)); - + // try to get a URL/title from the legacy action + var attempt = GetUrlAndTitleFromLegacyAction(currentAction, xmlTreeNode.NodeID, xmlTreeNode.NodeType, xmlTreeNode.Text, currentSection); + if (attempt) + { + var action = attempt.Result; + menuItem.LaunchDialogUrl(action.Url, action.DialogTitle); + } + else + { + // if that doesn't work, try to get the legacy confirm view + var attempt2 = GetLegacyConfirmView(currentAction); + if (attempt2) + { + var view = attempt2.Result; + var textService = Current.Services.TextService; + menuItem.LaunchDialogView(view, textService.Localize("defaultdialogs/confirmdelete") + " '" + xmlTreeNode.Text + "' ?"); + } + else + { + // if that doesn't work and there's no jsAction in there already then add the legacy js method call + if (menuItem.AdditionalData.ContainsKey(MenuItem.JsActionKey) == false) + menuItem.ExecuteLegacyJs(menuItem.Action.JsFunctionName); + } + } + numAdded++; } } @@ -158,7 +172,7 @@ namespace Umbraco.Web.Trees return collection; } - + /// /// This will look at the legacy IAction's JsFunctionName and convert it to a confirmation dialog view if possible @@ -176,7 +190,7 @@ namespace Umbraco.Web.Trees { case "UmbClientMgr.appActions().actionDelete()": return Attempt.Succeed( - Core.Configuration.GlobalSettings.Path.EnsureEndsWith('/') + "views/common/dialogs/legacydelete.html"); + UmbracoConfig.For.GlobalSettings().Path.EnsureEndsWith('/') + "views/common/dialogs/legacydelete.html"); } return Attempt.Fail(); @@ -203,69 +217,69 @@ namespace Umbraco.Web.Trees return Attempt.Succeed( new LegacyUrlAction( "create.aspx?nodeId=" + nodeId + "&nodeType=" + nodeType + "&nodeName=" + nodeName + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "create"))); + Current.Services.TextService.Localize("actions/create"))); case "UmbClientMgr.appActions().actionNewFolder()": return Attempt.Succeed( new LegacyUrlAction( "createFolder.aspx?nodeId=" + nodeId + "&nodeType=" + nodeType + "&nodeName=" + nodeName + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "create"))); + Current.Services.TextService.Localize("actions/create"))); case "UmbClientMgr.appActions().actionSort()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/sort.aspx?id=" + nodeId + "&nodeType=" + nodeType + "&app=" + currentSection + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "sort"))); - + Current.Services.TextService.Localize("actions/sort"))); + case "UmbClientMgr.appActions().actionProtect()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/protectPage.aspx?mode=cut&nodeId=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "protect"))); + Current.Services.TextService.Localize("actions/protect"))); case "UmbClientMgr.appActions().actionRollback()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/rollback.aspx?nodeId=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "rollback"))); + Current.Services.TextService.Localize("actions/rollback"))); case "UmbClientMgr.appActions().actionNotify()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/notifications.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "notify"))); + Current.Services.TextService.Localize("actions/notify"))); case "UmbClientMgr.appActions().actionPublish()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/publish.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "publish"))); + Current.Services.TextService.Localize("actions/publish"))); case "UmbClientMgr.appActions().actionChangeDocType()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/ChangeDocType.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "changeDocType"))); + Current.Services.TextService.Localize("actions/changeDocType"))); case "UmbClientMgr.appActions().actionToPublish()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/SendPublish.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "sendtopublish"))); + Current.Services.TextService.Localize("actions/sendtopublish"))); case "UmbClientMgr.appActions().actionRePublish()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/republish.aspx?rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "republish"))); + Current.Services.TextService.Localize("actions/republish"))); case "UmbClientMgr.appActions().actionAssignDomain()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/assignDomain2.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "assignDomain"))); + Current.Services.TextService.Localize("actions/assignDomain"))); case "UmbClientMgr.appActions().actionSendToTranslate()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/sendToTranslation.aspx?id=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "sendToTranslate"))); - + Current.Services.TextService.Localize("actions/sendToTranslate"))); + case "UmbClientMgr.appActions().actionImport()": return Attempt.Succeed( new LegacyUrlAction( "dialogs/importDocumentType.aspx", - ui.GetText("actions", "importDocumentType"))); + Current.Services.TextService.Localize("actions/importDocumentType"))); case "UmbClientMgr.appActions().actionExport()": return Attempt.Succeed( new LegacyUrlAction( @@ -275,8 +289,8 @@ namespace Umbraco.Web.Trees return Attempt.Succeed( new LegacyUrlAction( "dialogs/viewAuditTrail.aspx?nodeId=" + nodeId + "&rnd=" + DateTime.UtcNow.Ticks, - ui.GetText("actions", "auditTrail"))); - + Current.Services.TextService.Localize("actions/auditTrail"))); + } return Attempt.Fail(); } @@ -309,7 +323,7 @@ namespace Umbraco.Web.Trees childNodesSource = childNodesSource.AppendQueryStringToUrl(childQuery); //for the menu source we need to detect if this is a root node since we'll need to set the parentId and id to -1 - // for which we'll handle correctly on the server side. + // for which we'll handle correctly on the server side. //if there are no menu items, then this will be empty var menuSource = ""; if (xmlTreeNode.Menu != null && xmlTreeNode.Menu.Any()) @@ -328,10 +342,10 @@ namespace Umbraco.Web.Trees { menuQueryStrings.Add(i.Key, i.Value); } - - menuSource = menuSource.AppendQueryStringToUrl(menuQueryStrings.ToQueryString()); + + menuSource = menuSource.AppendQueryStringToUrl(menuQueryStrings.ToQueryString()); } - + //TODO: Might need to add stuff to additional attributes @@ -365,7 +379,7 @@ namespace Umbraco.Web.Trees internal static TreeNodeCollection ConvertFromLegacy(string parentId, XmlTree xmlTree, UrlHelper urlHelper, string currentSection, FormDataCollection currentQueryStrings) { - //TODO: Once we get the editor URL stuff working we'll need to figure out how to convert + //TODO: Once we get the editor URL stuff working we'll need to figure out how to convert // that over to use the old school ui.xml stuff for these old trees and however the old menu items worked. var collection = new TreeNodeCollection(); diff --git a/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs b/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs index ec9951afdb..3d0c754683 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs @@ -5,6 +5,8 @@ using System.Text; using Umbraco.Core; using Umbraco.Core.Logging; using umbraco.cms.presentation.Trees; +using Umbraco.Web.Composing; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.Trees { @@ -32,7 +34,7 @@ namespace Umbraco.Web.Trees } catch (Exception ex) { - LogHelper.Error(typeof(LegacyTreeJavascript), "Could not load the JS from the legacy tree " + bTree.TreeAlias, ex); + Current.Logger.Error(typeof(LegacyTreeJavascript), "Could not load the JS from the legacy tree " + bTree.TreeAlias, ex); } } @@ -46,7 +48,7 @@ namespace Umbraco.Web.Trees public static string GetLegacyIActionJavascript() { var js = new StringBuilder(); - foreach (var a in ActionsResolver.Current.Actions.ToList()) + foreach (var a in Current.Actions) { // NH: Added a try/catch block to this as an error in a 3rd party action can crash the whole menu initialization try @@ -54,17 +56,17 @@ namespace Umbraco.Web.Trees if (string.IsNullOrEmpty(a.Alias) == false && (string.IsNullOrEmpty(a.JsFunctionName) == false || string.IsNullOrEmpty(a.JsSource) == false)) { // if the action is using invalid javascript we need to do something about this - if (global::umbraco.BusinessLogic.Actions.Action.ValidateActionJs(a) == false) + if (global::Umbraco.Web._Legacy.Actions.Action.ValidateActionJs(a) == false) { js.AppendLine("function IActionProxy_" + a.Alias.ToSafeAlias() + "() {"); - js.AppendLine(global::umbraco.BusinessLogic.Actions.Action.ConvertLegacyJs(a.JsFunctionName)); + js.AppendLine(global::Umbraco.Web._Legacy.Actions.Action.ConvertLegacyJs(a.JsFunctionName)); js.AppendLine("}"); } } } catch (Exception ee) { - LogHelper.Error(typeof(LegacyTreeJavascript), "Error initializing tree action", ee); + Current.Logger.Error(typeof(LegacyTreeJavascript), "Error initializing tree action", ee); } } @@ -83,4 +85,4 @@ namespace Umbraco.Web.Trees return js; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/LegacyTreeParams.cs b/src/Umbraco.Web/Trees/LegacyTreeParams.cs index 09124e8689..ddd822e9ef 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeParams.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeParams.cs @@ -34,4 +34,4 @@ namespace Umbraco.Web.Trees public TreeDialogModes DialogMode { get; set; } public string FunctionToCall { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/MacroTreeController.cs b/src/Umbraco.Web/Trees/MacroTreeController.cs deleted file mode 100644 index 5e0a8fa25a..0000000000 --- a/src/Umbraco.Web/Trees/MacroTreeController.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Net.Http.Formatting; -using umbraco; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using System.Linq; -using umbraco.BusinessLogic.Actions; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Services; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Trees -{ - [UmbracoTreeAuthorize(Constants.Trees.Macros)] - [Tree(Constants.Applications.Developer, Constants.Trees.Macros, null, sortOrder: 2)] - [LegacyBaseTree(typeof(loadMacros))] - [PluginController("UmbracoTrees")] - [CoreTree] - public class MacroTreeController : TreeController - { - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) - { - var intId = id.TryConvertTo(); - if (intId == false) throw new InvalidOperationException("Id must be an integer"); - - var nodes = new TreeNodeCollection(); - - nodes.AddRange( - Services.MacroService.GetAll() - .OrderBy(entity => entity.Name) - .Select(macro => - { - var node = CreateTreeNode(macro.Id.ToInvariantString(), id, queryStrings, macro.Name, "icon-settings-alt", false); - node.Path = "-1," + macro.Id; - node.AssignLegacyJsCallback("javascript:UmbClientMgr.contentFrame('developer/macros/editMacro.aspx?macroID=" + macro.Id + "');"); - return node; - })); - - return nodes; - } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - //set the default to create - menu.DefaultMenuAlias = ActionNew.Instance.Alias; - - // root actions - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))) - .ConvertLegacyMenuItem(null, Constants.Trees.Macros, queryStrings.GetValue("application")); - - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); - return menu; - } - - //TODO: This is all hacky ... don't have time to convert the tree, views and dialogs over properly so we'll keep using the legacy dialogs - var menuItem = menu.Items.Add(ActionDelete.Instance, Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - var legacyConfirmView = LegacyTreeDataConverter.GetLegacyConfirmView(ActionDelete.Instance); - if (legacyConfirmView == false) - throw new InvalidOperationException("Could not resolve the confirmation view for the legacy action " + ActionDelete.Instance.Alias); - menuItem.LaunchDialogView( - legacyConfirmView.Result, - Services.TextService.Localize(string.Format("general/{0}", ActionDelete.Instance.Alias))); - - return menu; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs new file mode 100644 index 0000000000..2968408bdb --- /dev/null +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -0,0 +1,82 @@ +using System; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Services; +using Umbraco.Web._Legacy.Actions; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Macros)] + [Tree(Constants.Applications.Developer, Constants.Trees.Macros, "Macros", sortOrder: 2)] + [PluginController("UmbracoTrees")] + [CoreTree] + public class MacrosTreeController : TreeController + { + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + foreach (var macro in Services.MacroService.GetAll()) + { + nodes.Add(CreateTreeNode( + macro.Id.ToString(), + id, + queryStrings, + macro.Name, + "icon-settings-alt", + false, + //TODO: Rebuild the macro editor in angular, then we dont need to have this at all (which is just a path to the legacy editor) + "/" + queryStrings.GetValue("application") + "/framed/" + + Uri.EscapeDataString("/umbraco/developer/macros/editMacro.aspx?macroID=" + macro.Id))); + } + } + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + //Create the normal create action + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + //Since we haven't implemented anything for macros in angular, this needs to be converted to + //use the legacy format + .ConvertLegacyMenuItem(null, "initmacros", queryStrings.GetValue("application")); + + //refresh action + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + + return menu; + } + + + var macro = Services.MacroService.GetById(int.Parse(id)); + if (macro == null) return new MenuItemCollection(); + + //add delete option for all macros + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)) + //Since we haven't implemented anything for macros in angular, this needs to be converted to + //use the legacy format + .ConvertLegacyMenuItem(new EntitySlim + { + Id = macro.Id, + Level = 1, + ParentId = -1, + Name = macro.Name + }, "macros", queryStrings.GetValue("application")); + + return menu; + } + } +} diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 99117b3b04..53185f746b 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -3,17 +3,15 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http.Formatting; -using System.Web; using System.Web.Http; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; -using umbraco; -using umbraco.BusinessLogic.Actions; +using Umbraco.Web._Legacy.Actions; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Search; using Constants = Umbraco.Core.Constants; @@ -28,30 +26,21 @@ namespace Umbraco.Web.Trees Constants.Applications.Settings, Constants.Applications.Developer, Constants.Applications.Members)] - [LegacyBaseTree(typeof(loadMedia))] [Tree(Constants.Applications.Media, Constants.Trees.Media)] [PluginController("UmbracoTrees")] [CoreTree] [SearchableTree("searchResultFormatter", "configureMediaResult")] public class MediaTreeController : ContentTreeControllerBase, ISearchableTree { - private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); - - protected override int RecycleBinId - { - get { return Constants.System.RecycleBinMedia; } - } + private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); - protected override bool RecycleBinSmells - { - get { return Services.MediaService.RecycleBinSmells(); } - } + protected override int RecycleBinId => Constants.System.RecycleBinMedia; + + protected override bool RecycleBinSmells => Services.MediaService.RecycleBinSmells(); private int[] _userStartNodes; protected override int[] UserStartNodes - { - get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); } - } + => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); /// /// Creates a tree node for a content item based on an UmbracoEntity @@ -60,27 +49,29 @@ namespace Umbraco.Web.Trees /// /// /// - protected override TreeNode GetSingleTreeNode(IUmbracoEntity e, string parentId, FormDataCollection queryStrings) + protected override TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormDataCollection queryStrings) { - var entity = (UmbracoEntity)e; - //Special check to see if it ia a container, if so then we'll hide children. - var isContainer = e.IsContainer(); // && (queryStrings.Get("isDialog") != "true"); + var isContainer = entity.IsContainer; // && (queryStrings.Get("isDialog") != "true"); var node = CreateTreeNode( entity, - Constants.ObjectTypes.MediaGuid, + Constants.ObjectTypes.Media, parentId, queryStrings, - entity.HasChildren && (isContainer == false)); - - node.AdditionalData.Add("contentType", entity.ContentTypeAlias); + entity.HasChildren && !isContainer); + // entity is either a container, or a media if (isContainer) { node.SetContainerStyle(); node.AdditionalData.Add("isContainer", true); } + else + { + var contentEntity = (IContentEntitySlim) entity; + node.AdditionalData.Add("contentType", contentEntity.ContentTypeAlias); + } return node; } @@ -97,16 +88,16 @@ namespace Umbraco.Web.Trees // if the user's start node is not the root then the only menu item to display is refresh if (UserStartNodes.Contains(Constants.System.Root) == false) { - menu.Items.Add( - Services.TextService.Localize(string.Concat("actions/", ActionRefresh.Instance.Alias)), + menu.Items.Add( + Services.TextService.Localize(string.Concat("actions/", ActionRefresh.Instance.Alias)), true); return menu; } - // root actions - menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)); - menu.Items.Add(ui.Text("actions", ActionSort.Instance.Alias), true).ConvertLegacyMenuItem(null, "media", "media"); - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + // root actions + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)); + menu.Items.Add(Services.TextService.Localize("actions", ActionSort.Instance.Alias), true).ConvertLegacyMenuItem(null, "media", "media"); + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); return menu; } @@ -128,17 +119,17 @@ namespace Umbraco.Web.Trees Services.TextService.Localize(string.Concat("actions/", ActionRefresh.Instance.Alias)), true); return menu; - } + } //return a normal node menu: - menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)); - menu.Items.Add(ui.Text("actions", ActionMove.Instance.Alias)); - menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); - menu.Items.Add(ui.Text("actions", ActionSort.Instance.Alias)).ConvertLegacyMenuItem(item, "media", "media"); - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)); + menu.Items.Add(Services.TextService.Localize("actions", ActionMove.Instance.Alias)); + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)); + menu.Items.Add(Services.TextService.Localize("actions", ActionSort.Instance.Alias)).ConvertLegacyMenuItem(item, "media", "media"); + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); //if the media item is in the recycle bin, don't have a default menu, just show the regular menu - if (item.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString())) + if (item.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString())) { menu.DefaultMenuAlias = null; } @@ -146,10 +137,7 @@ namespace Umbraco.Web.Trees return menu; } - protected override UmbracoObjectTypes UmbracoObjectType - { - get { return UmbracoObjectTypes.Media; } - } + protected override UmbracoObjectTypes UmbracoObjectType => UmbracoObjectTypes.Media; /// /// Returns true or false if the current user has access to the node based on the user's allowed start node (path) access @@ -160,7 +148,8 @@ namespace Umbraco.Web.Trees protected override bool HasPathAccess(string id, FormDataCollection queryStrings) { var entity = GetEntityFromId(id); - return HasPathAccess(entity, queryStrings); + + return HasPathAccess(entity, queryStrings); } public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) @@ -168,4 +157,4 @@ namespace Umbraco.Web.Trees return _treeSearcher.ExamineSearch(Umbraco, query, UmbracoEntityTypes.Media, pageSize, pageIndex, out totalFound, searchFrom); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index c0fde0db92..f89a405795 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; using AutoMapper; -using umbraco; -using umbraco.BusinessLogic.Actions; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; @@ -12,15 +10,15 @@ using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web._Legacy.Actions; using Umbraco.Web.Search; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] - [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:5)] + [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:8)] [Mvc.PluginController("UmbracoTrees")] [CoreTree] - [LegacyBaseTree(typeof(loadMediaTypes))] public class MediaTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) @@ -35,7 +33,7 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-folder", dt.HasChildren(), ""); + var node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-folder", dt.HasChildren, ""); node.Path = dt.Path; node.NodeType = "container"; //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. @@ -51,10 +49,10 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt, Constants.ObjectTypes.MediaTypeGuid, id, queryStrings, "icon-thumbnails", - //NOTE: Since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. - //We need this check to keep supporting sites where childs have already been created. - dt.HasChildren()); + // since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. + // need this check to keep supporting sites where childs have already been created. + var hasChildren = dt.HasChildren; + var node = CreateTreeNode(dt, Constants.ObjectTypes.MediaType, id, queryStrings, "icon-thumbnails", hasChildren); node.Path = dt.Path; return node; @@ -74,7 +72,7 @@ namespace Umbraco.Web.Trees //set the default to create menu.DefaultMenuAlias = ActionNew.Instance.Alias; - // root actions + // root actions menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias))); return menu; @@ -91,21 +89,19 @@ namespace Umbraco.Web.Trees menu.Items.Add(new MenuItem("rename", Services.TextService.Localize(String.Format("actions/{0}", "rename"))) { Icon = "icon icon-edit" - }); + }); - if (container.HasChildren() == false) + if (container.HasChildren == false) { //can delete doc type menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); - } + } menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), hasSeparator: true); - - } else { - var ct = Services.ContentTypeService.GetMediaType(int.Parse(id)); - var parent = ct == null ? null : Services.ContentTypeService.GetMediaType(ct.ParentId); + var ct = Services.MediaTypeService.Get(int.Parse(id)); + var parent = ct == null ? null : Services.MediaTypeService.Get(ct.ParentId); if (enableInheritedMediaTypes) { @@ -138,7 +134,7 @@ namespace Umbraco.Web.Trees public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) { - var results = Services.EntityService.GetPagedDescendantsFromRoot(UmbracoObjectTypes.MediaType, pageIndex, pageSize, out totalFound, filter: query); + var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.MediaType, pageIndex, pageSize, out totalFound, filter: query); return Mapper.Map>(results); } } diff --git a/src/Umbraco.Web/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs new file mode 100644 index 0000000000..d1a0d2c318 --- /dev/null +++ b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.MemberGroups)] + [Tree(Constants.Applications.Members, Constants.Trees.MemberGroups, null, sortOrder: 2)] + [Mvc.PluginController("UmbracoTrees")] + [CoreTree] + public class MemberGroupTreeController : MemberTypeAndGroupTreeControllerBase + { + protected override IEnumerable GetTreeNodesFromService(string id, FormDataCollection queryStrings) + { + return Services.MemberGroupService.GetAll() + .OrderBy(x => x.Name) + .Select(dt => CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", false)); + } + } +} diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index 68f12ef6ca..a0dbcee233 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; @@ -9,17 +8,16 @@ using System.Web.Http; using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Services; using Umbraco.Core.Security; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; -using umbraco; -using umbraco.BusinessLogic.Actions; +using Umbraco.Web._Legacy.Actions; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Search; using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Services; namespace Umbraco.Web.Trees { @@ -29,7 +27,6 @@ namespace Umbraco.Web.Trees Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members)] - [LegacyBaseTree(typeof (loadMembers))] [Tree(Constants.Applications.Members, Constants.Trees.Members, null, sortOrder: 0)] [PluginController("UmbracoTrees")] [CoreTree] @@ -54,7 +51,7 @@ namespace Umbraco.Web.Trees /// [HttpQueryStringFilter("queryStrings")] public TreeNode GetTreeNode(string id, FormDataCollection queryStrings) - { + { var node = GetSingleTreeNode(id, queryStrings); //add the tree alias to the node since it is standalone (has no root for which this normally belongs) @@ -86,7 +83,7 @@ namespace Umbraco.Web.Trees "icon-user", false, "", - Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(Constants.ObjectTypes.MemberGuid), member.Key)); + Udi.Create(ObjectTypes.GetUdiType(Constants.ObjectTypes.Member), member.Key)); node.AdditionalData.Add("contentType", member.ContentTypeAlias); node.AdditionalData.Add("isContainer", true); @@ -116,10 +113,10 @@ namespace Umbraco.Web.Trees "icon-user", false); - return node; + return node; } - + } protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) @@ -129,7 +126,7 @@ namespace Umbraco.Web.Trees if (id == Constants.System.Root.ToInvariantString()) { nodes.Add( - CreateTreeNode(Constants.Conventions.MemberTypes.AllMembersListId, id, queryStrings, ui.Text("member", "allMembers"), "icon-users", false, + CreateTreeNode(Constants.Conventions.MemberTypes.AllMembersListId, id, queryStrings, Services.TextService.Localize("member/allMembers"), "icon-users", false, queryStrings.GetValue("application") + TreeAlias.EnsureStartsWith('/') + "/list/" + Constants.Conventions.MemberTypes.AllMembersListId)); if (_isUmbracoProvider) @@ -155,14 +152,14 @@ namespace Umbraco.Web.Trees if (id == Constants.System.Root.ToInvariantString()) { - // root actions + // root actions if (_provider.IsUmbracoMembershipProvider()) { //set default menu.DefaultMenuAlias = ActionNew.Instance.Alias; //Create the normal create action - menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)); + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)); } else { @@ -173,13 +170,24 @@ namespace Umbraco.Web.Trees createMenuItem.NavigateToRoute("/member/member/edit/-1?create=true"); menu.Items.Add(createMenuItem); } - - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); return menu; } //add delete option for all members - menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)); + + if (Security.CurrentUser.HasAccessToSensitiveData()) + { + menu.Items.Add(new ExportMember + { + Name = Services.TextService.Localize("actions/export"), + Icon = "download-alt", + Alias = "export" + }); + } + return menu; } @@ -189,4 +197,4 @@ namespace Umbraco.Web.Trees return _treeSearcher.ExamineSearch(Umbraco, query, UmbracoEntityTypes.Member, pageSize, pageIndex, out totalFound, searchFrom); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/MemberTypeAndGroupTreeControllerBase.cs b/src/Umbraco.Web/Trees/MemberTypeAndGroupTreeControllerBase.cs new file mode 100644 index 0000000000..a710b52595 --- /dev/null +++ b/src/Umbraco.Web/Trees/MemberTypeAndGroupTreeControllerBase.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Core.Services; +using Umbraco.Web._Legacy.Actions; +using Umbraco.Web.Models.Trees; + +namespace Umbraco.Web.Trees +{ + [Mvc.PluginController("UmbracoTrees")] + [CoreTree] + public abstract class MemberTypeAndGroupTreeControllerBase : TreeController + { + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + nodes.AddRange(GetTreeNodesFromService(id, queryStrings)); + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + // root actions + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)); + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + return menu; + } + else + { + //delete member type/group + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)); + } + + return menu; + } + + protected abstract IEnumerable GetTreeNodesFromService(string id, FormDataCollection queryStrings); + } +} diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index 1e0c32a4b4..10b5d35b6e 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; -using System.Text; -using System.Threading.Tasks; -using umbraco; -using umbraco.BusinessLogic.Actions; using Umbraco.Core; using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi.Filters; @@ -13,41 +8,14 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] - [Tree(Constants.Applications.Members, Constants.Trees.MemberTypes, null, sortOrder:2 )] - [Mvc.PluginController("UmbracoTrees")] - [CoreTree] - [LegacyBaseTree(typeof(loadMemberTypes))] - public class MemberTypeTreeController : TreeController + [Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, null, sortOrder: 1)] + public class MemberTypeTreeController : MemberTypeAndGroupTreeControllerBase { - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + protected override IEnumerable GetTreeNodesFromService(string id, FormDataCollection queryStrings) { - var nodes = new TreeNodeCollection(); - nodes.AddRange( - Services.MemberTypeService.GetAll() - .OrderBy(x => x.Name) - .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberTypeGuid, id, queryStrings, "icon-item-arrangement", false))); - - return nodes; - } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - // root actions - menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)); - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); - return menu; - } - else - { - //delete member type - menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); - } - - return menu; + return Services.MemberTypeService.GetAll() + .OrderBy(x => x.Name) + .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, "icon-item-arrangement", false)); } } } diff --git a/src/Umbraco.Web/Trees/MenuRenderingEventArgs.cs b/src/Umbraco.Web/Trees/MenuRenderingEventArgs.cs index fa40d60f91..e07850857f 100644 --- a/src/Umbraco.Web/Trees/MenuRenderingEventArgs.cs +++ b/src/Umbraco.Web/Trees/MenuRenderingEventArgs.cs @@ -22,4 +22,4 @@ namespace Umbraco.Web.Trees Menu = menu; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/PackagesTreeController.cs b/src/Umbraco.Web/Trees/PackagesTreeController.cs index d6cfbbef13..ff23738e92 100644 --- a/src/Umbraco.Web/Trees/PackagesTreeController.cs +++ b/src/Umbraco.Web/Trees/PackagesTreeController.cs @@ -1,22 +1,14 @@ using System; -using System.Collections.Generic; -using System.Globalization; using System.Linq; -using System.Net; using System.Net.Http.Formatting; -using System.Web.Http; -using Umbraco.Core; -using Umbraco.Core.Models; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; using umbraco; -using umbraco.BusinessLogic.Actions; using umbraco.cms.businesslogic.packager; -using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Services; +using Umbraco.Web._Legacy.Actions; using Constants = Umbraco.Core.Constants; -using Umbraco.Core.Services; namespace Umbraco.Web.Trees { @@ -24,7 +16,6 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Developer, Constants.Trees.Packages, null, sortOrder: 0)] [PluginController("UmbracoTrees")] [CoreTree] - [LegacyBaseTree(typeof(loadPackager))] public class PackagesTreeController : TreeController { /// @@ -49,7 +40,7 @@ namespace Umbraco.Web.Trees var nodes = new TreeNodeCollection(); var createdPackages = CreatedPackage.GetAllCreatedPackages(); - + if (id == "created") { nodes.AddRange( @@ -60,7 +51,7 @@ namespace Umbraco.Web.Trees var node = CreateTreeNode(dt.Data.Id.ToString(), id, queryStrings, dt.Data.Name, "icon-inbox", false, string.Format("/{0}/framed/{1}", queryStrings.GetValue("application"), - Uri.EscapeDataString("developer/Packages/EditPackage.aspx?id=" + dt.Data.Id))); + Uri.EscapeDataString("developer/Packages/EditPackage.aspx?id=" + dt.Data.Id))); return node; })); } @@ -76,7 +67,7 @@ namespace Umbraco.Web.Trees createdPackages.Count > 0, string.Empty); - + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; @@ -84,7 +75,7 @@ namespace Umbraco.Web.Trees nodes.Add(node); } - + return nodes; } @@ -110,10 +101,10 @@ namespace Umbraco.Web.Trees else { //it's a package node - menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)); } return menu; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs index 6e77c12fba..db97d5c3a4 100644 --- a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs @@ -1,4 +1,5 @@ using Umbraco.Core.IO; +using Umbraco.Web.Composing; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; @@ -9,28 +10,19 @@ namespace Umbraco.Web.Trees /// /// Tree for displaying partial view macros in the developer app /// - [Tree(Constants.Applications.Developer, "partialViewMacros", null, sortOrder: 6)] + [Tree(Constants.Applications.Developer, Constants.Trees.PartialViewMacros, null, sortOrder: 6)] [UmbracoTreeAuthorize(Constants.Trees.PartialViewMacros)] [PluginController("UmbracoTrees")] [CoreTree] - public class PartialViewMacrosTreeController : FileSystemTreeController + public class PartialViewMacrosTreeController : PartialViewsTreeController { - protected override IFileSystem2 FileSystem - { - get { return FileSystemProviderManager.Current.MacroPartialsFileSystem; } - } + protected override IFileSystem FileSystem => Current.FileSystems.MacroPartialsFileSystem; private static readonly string[] ExtensionsStatic = {"cshtml"}; - protected override string[] Extensions - { - get { return ExtensionsStatic; } - } + protected override string[] Extensions => ExtensionsStatic; - protected override string FileIcon - { - get { return "icon-article"; } - } + protected override string FileIcon => "icon-article"; protected override void OnRenderFolderNode(ref TreeNode treeNode) { diff --git a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs index f301845fe1..2aa52a563e 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -1,5 +1,6 @@ using umbraco; using Umbraco.Core.IO; +using Umbraco.Web.Composing; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; @@ -10,33 +11,27 @@ namespace Umbraco.Web.Trees /// /// Tree for displaying partial views in the settings app /// - [Tree(Constants.Applications.Settings, "partialViews", null, sortOrder: 2)] + [Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, null, sortOrder: 2)] [UmbracoTreeAuthorize(Constants.Trees.PartialViews)] [PluginController("UmbracoTrees")] [CoreTree] public class PartialViewsTreeController : FileSystemTreeController { - protected override IFileSystem2 FileSystem - { - get { return FileSystemProviderManager.Current.PartialViewsFileSystem; } - } + protected override IFileSystem FileSystem => Current.FileSystems.PartialViewsFileSystem; private static readonly string[] ExtensionsStatic = {"cshtml"}; - protected override string[] Extensions - { - get { return ExtensionsStatic; } - } + protected override string[] Extensions => ExtensionsStatic; - protected override string FileIcon - { - get { return "icon-article"; } - } + protected override string FileIcon => "icon-article"; protected override void OnRenderFolderNode(ref TreeNode treeNode) { //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + treeNode.Icon = "icon-article"; } + + protected override bool EnableCreateOnFolder => true; } } diff --git a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs new file mode 100644 index 0000000000..14fd817a7c --- /dev/null +++ b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Core; +using Umbraco.Web._Legacy.Actions; +using Umbraco.Core.Services; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.RelationTypes)] + [Tree(Constants.Applications.Developer, Constants.Trees.RelationTypes, null, sortOrder: 4)] + [Mvc.PluginController("UmbracoTrees")] + [CoreTree] + public class RelationTypeTreeController : TreeController + { + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + //Create the normal create action + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + //Since we haven't implemented anything for relationtypes in angular, this needs to be converted to + //use the legacy format + .ConvertLegacyMenuItem(null, "initrelationTypes", queryStrings.GetValue("application")); + //refresh action + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + + return menu; + } + + var relationType = Services.RelationService.GetRelationTypeById(int.Parse(id)); + if (relationType == null) return new MenuItemCollection(); + + //add delete option for all macros + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)) + //Since we haven't implemented anything for relationtypes in angular, this needs to be converted to + //use the legacy format + .ConvertLegacyMenuItem(new EntitySlim + { + Id = relationType.Id, + Level = 1, + ParentId = -1, + Name = relationType.Name + }, "relationTypes", queryStrings.GetValue("application")); + + return menu; + } + + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + nodes.AddRange(Services.RelationService + .GetAllRelationTypes().Select(rt => CreateTreeNode( + rt.Id.ToString(), + id, + queryStrings, + rt.Name, + "icon-trafic", + false, + //TODO: Rebuild the macro editor in angular, then we dont need to have this at all (which is just a path to the legacy editor) + "/" + queryStrings.GetValue("application") + "/framed/" + + Uri.EscapeDataString("/umbraco/developer/RelationTypes/EditRelationType.aspx?id=" + rt.Id) + ))); + } + return nodes; + } + } +} diff --git a/src/Umbraco.Web/Trees/ScriptTreeController.cs b/src/Umbraco.Web/Trees/ScriptTreeController.cs deleted file mode 100644 index c7a3dd5f04..0000000000 --- a/src/Umbraco.Web/Trees/ScriptTreeController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using umbraco; -using Umbraco.Core.IO; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Trees -{ - [UmbracoTreeAuthorize(Constants.Trees.Scripts)] - [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, null, sortOrder: 4)] - [LegacyBaseTree(typeof(loadScripts))] - [PluginController("UmbracoTrees")] - [CoreTree] - public class ScriptTreeController : FileSystemTreeController - { - protected override IFileSystem2 FileSystem - { - get { return FileSystemProviderManager.Current.ScriptsFileSystem; } - } - - private static readonly string[] ExtensionsStatic = { "js" }; - - protected override string[] Extensions - { - get { return ExtensionsStatic; } - } - protected override string FileIcon - { - get { return "icon-script"; } - } - - protected override void OnRenderFolderNode(ref TreeNode treeNode) - { - //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. - treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; - } - } -} diff --git a/src/Umbraco.Web/Trees/ScriptsTreeController.cs b/src/Umbraco.Web/Trees/ScriptsTreeController.cs new file mode 100644 index 0000000000..adab7b5c4c --- /dev/null +++ b/src/Umbraco.Web/Trees/ScriptsTreeController.cs @@ -0,0 +1,28 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Web.Composing; +using Umbraco.Web.Models.Trees; + +namespace Umbraco.Web.Trees +{ + [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, "Scripts", "icon-folder", "icon-folder", sortOrder: 2)] + public class ScriptsTreeController : FileSystemTreeController + { + protected override IFileSystem FileSystem => Current.FileSystems.ScriptsFileSystem; // fixme inject + + private static readonly string[] ExtensionsStatic = { "js" }; + + protected override string[] Extensions => ExtensionsStatic; + + protected override string FileIcon => "icon-script"; + + protected override bool EnableCreateOnFolder => true; + + protected override void OnRenderFolderNode(ref TreeNode treeNode) + { + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + } + } +} diff --git a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs new file mode 100644 index 0000000000..f044d7ea60 --- /dev/null +++ b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs @@ -0,0 +1,20 @@ +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Web.Composing; + +namespace Umbraco.Web.Trees +{ + [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, "Stylesheets", "icon-folder", "icon-folder", sortOrder: 3)] + public class StylesheetsTreeController : FileSystemTreeController + { + protected override IFileSystem FileSystem => Current.FileSystems.StylesheetsFileSystem; // fixme inject + + private static readonly string[] ExtensionsStatic = { "css" }; + + protected override string[] Extensions => ExtensionsStatic; + + protected override string FileIcon => "icon-brackets"; + + protected override bool EnableCreateOnFolder => false; + } +} diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index 33ce91cf6d..d9aa0f21a0 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -1,29 +1,24 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Net.Http.Formatting; -using System.Web.Services.Description; using AutoMapper; -using umbraco; -using umbraco.BusinessLogic.Actions; -using umbraco.cms.businesslogic.template; using Umbraco.Core; -using Umbraco.Core.Configuration; +using Umbraco.Core.Services; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.Search; using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.Actions; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Templates)] - [LegacyBaseTree(typeof (loadTemplates))] [Tree(Constants.Applications.Settings, Constants.Trees.Templates, null, sortOrder:1)] [PluginController("UmbracoTrees")] [CoreTree] @@ -44,8 +39,8 @@ namespace Umbraco.Web.Trees { var nodes = new TreeNodeCollection(); - var found = id == Constants.System.Root.ToInvariantString() - ? Services.FileService.GetTemplates(-1) + var found = id == Constants.System.Root.ToInvariantString() + ? Services.FileService.GetTemplates(-1) : Services.FileService.GetTemplates(int.Parse(id)); nodes.AddRange(found.Select(template => CreateTreeNode( @@ -57,7 +52,7 @@ namespace Umbraco.Web.Trees template.IsMasterTemplate ? "icon-newspaper" : "icon-newspaper-alt", template.IsMasterTemplate, GetEditorPath(template, queryStrings), - Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(Constants.ObjectTypes.TemplateTypeGuid), template.Key) + Udi.Create(ObjectTypes.GetUdiType(Constants.ObjectTypes.TemplateType), template.Key) ))); return nodes; @@ -72,16 +67,15 @@ namespace Umbraco.Web.Trees protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) { var menu = new MenuItemCollection(); - //Create the normal create action - var item = menu.Items.Add(ui.Text("actions", ActionNew.Instance.Alias)); - item.NavigateToRoute(string.Format("{0}/templates/edit/{1}?create=true", queryStrings.GetValue("application"), id)); + //Create the normal create action + var item = menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)); + item.NavigateToRoute(string.Format("{0}/templates/edit/{1}?create=true", queryStrings.GetValue("application"), id)); if (id == Constants.System.Root.ToInvariantString()) { - //refresh action - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); return menu; } @@ -94,25 +88,25 @@ namespace Umbraco.Web.Trees if (template.IsMasterTemplate == false) { //add delete option if it doesn't have children - menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias), true); + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias), true); } //add refresh - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); - + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + return menu; } - private UmbracoEntity FromTemplate(ITemplate template) + private EntitySlim FromTemplate(ITemplate template) { - return new UmbracoEntity + return new EntitySlim { CreateDate = template.CreateDate, Id = template.Id, Key = template.Key, Name = template.Name, - NodeObjectTypeId = new Guid(Constants.ObjectTypes.Template), + NodeObjectType = Constants.ObjectTypes.Template, //TODO: Fix parent/paths on templates ParentId = -1, Path = template.Path, @@ -132,7 +126,7 @@ namespace Umbraco.Web.Trees public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) { - var results = Services.EntityService.GetPagedDescendantsFromRoot(UmbracoObjectTypes.Template, pageIndex, pageSize, out totalFound, filter: query); + var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.Template, pageIndex, pageSize, out totalFound, filter: query); return Mapper.Map>(results); } } diff --git a/src/Umbraco.Web/Trees/TreeController.cs b/src/Umbraco.Web/Trees/TreeController.cs index 47d5454dcd..ab5717235a 100644 --- a/src/Umbraco.Web/Trees/TreeController.cs +++ b/src/Umbraco.Web/Trees/TreeController.cs @@ -1,14 +1,5 @@ using System; -using System.Collections.Concurrent; -using System.Globalization; using System.Linq; -using System.Net.Http.Formatting; -using System.Threading; -using System.Web.Security; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; using Umbraco.Core.Services; namespace Umbraco.Web.Trees @@ -25,16 +16,6 @@ namespace Umbraco.Web.Trees Initialize(); } - protected TreeController(UmbracoContext umbracoContext) : base(umbracoContext) - { - Initialize(); - } - - protected TreeController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) - { - Initialize(); - } - /// /// The name to display on the root node /// diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 4547e5e32d..a860c3770a 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -2,13 +2,14 @@ using System.Globalization; using System.Linq; using System.Net.Http.Formatting; +using System.Web.Http.Routing; using Umbraco.Core; using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; using Umbraco.Web.Search; namespace Umbraco.Web.Trees @@ -20,18 +21,6 @@ namespace Umbraco.Web.Trees [AngularJsonOnlyConfiguration] public abstract class TreeControllerBase : UmbracoAuthorizedApiController { - protected TreeControllerBase() - { - } - - protected TreeControllerBase(UmbracoContext umbracoContext) : base(umbracoContext) - { - } - - protected TreeControllerBase(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) - { - } - /// /// The method called to render the contents of the tree structure /// @@ -85,7 +74,7 @@ namespace Umbraco.Web.Trees node.AdditionalData.Add("searchable", "true"); } - //now update all data based on some of the query strings, like if we are running in dialog mode + //now update all data based on some of the query strings, like if we are running in dialog mode if (IsDialog(queryStrings)) { node.RoutePath = "#"; @@ -103,7 +92,7 @@ namespace Umbraco.Web.Trees /// /// All of the query string parameters passed from jsTree /// - /// JSON markup for jsTree + /// JSON markup for jsTree /// /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. @@ -119,7 +108,7 @@ namespace Umbraco.Web.Trees AddQueryStringsToAdditionalData(node, queryStrings); } - //now update all data based on some of the query strings, like if we are running in dialog mode + //now update all data based on some of the query strings, like if we are running in dialog mode if (IsDialog((queryStrings))) { foreach (var node in nodes) @@ -206,7 +195,7 @@ namespace Umbraco.Web.Trees var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { - Name = title, + Name = title, Icon = icon, NodeType = TreeAlias }; @@ -240,10 +229,11 @@ namespace Umbraco.Web.Trees /// /// /// - public TreeNode CreateTreeNode(UmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, bool hasChildren) + public TreeNode CreateTreeNode(IEntitySlim entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, bool hasChildren) { - var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, entity.ContentTypeIcon); - treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); + var contentTypeIcon = entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; + var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, contentTypeIcon); + treeNode.Udi = Udi.Create(ObjectTypes.GetUdiType(entityObjectType), entity.Key); treeNode.HasChildren = hasChildren; return treeNode; } @@ -261,7 +251,7 @@ namespace Umbraco.Web.Trees public TreeNode CreateTreeNode(IUmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, string icon, bool hasChildren) { var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, icon); - treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); + treeNode.Udi = Udi.Create(ObjectTypes.GetUdiType(entityObjectType), entity.Key); treeNode.HasChildren = hasChildren; return treeNode; } @@ -368,7 +358,8 @@ namespace Umbraco.Web.Trees /// public static event TypedEventHandler RootNodeRendering; - private static void OnRootNodeRendering(TreeControllerBase instance, TreeNodeRenderingEventArgs e) + // internal for temp class below - kill eventually! + internal static void OnRootNodeRendering(TreeControllerBase instance, TreeNodeRenderingEventArgs e) { var handler = RootNodeRendering; if (handler != null) handler(instance, e); @@ -388,4 +379,77 @@ namespace Umbraco.Web.Trees if (handler != null) handler(instance, e); } } -} \ No newline at end of file + + internal class TreeControllerBaseStuffForLegacy + { + private readonly string _treeAlias; + private readonly string _rootNodeDisplayName; + private readonly UrlHelper _url; + + public TreeControllerBaseStuffForLegacy(string treeAlias, string rootNodeDisplayName, UrlHelper url) + { + _treeAlias = treeAlias; + _rootNodeDisplayName = rootNodeDisplayName; + _url = url; + } + + public TreeNode GetRootNode(FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var node = CreateRootNode(queryStrings); + + //add the tree alias to the root + node.AdditionalData["treeAlias"] = _treeAlias; + + AddQueryStringsToAdditionalData(node, queryStrings); + + //check if the tree is searchable and add that to the meta data as well + if (this is ISearchableTree) + { + node.AdditionalData.Add("searchable", "true"); + } + + //now update all data based on some of the query strings, like if we are running in dialog mode + if (IsDialog(queryStrings)) + { + node.RoutePath = "#"; + } + + TreeControllerBase.OnRootNodeRendering(null, new TreeNodeRenderingEventArgs(node, queryStrings)); + + return node; + } + + protected virtual TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var rootNodeAsString = Constants.System.Root.ToString(CultureInfo.InvariantCulture); + var currApp = queryStrings.GetValue(TreeQueryStringParameters.Application); + + var node = new TreeNode( + rootNodeAsString, + null, //this is a root node, there is no parent + _url.GetTreeUrl(GetType(), rootNodeAsString, queryStrings), + _url.GetMenuUrl(GetType(), rootNodeAsString, queryStrings)) + { + HasChildren = true, + RoutePath = currApp, + Name = _rootNodeDisplayName + }; + + return node; + } + + protected void AddQueryStringsToAdditionalData(TreeNode node, FormDataCollection queryStrings) + { + foreach (var q in queryStrings.Where(x => node.AdditionalData.ContainsKey(x.Key) == false)) + { + node.AdditionalData.Add(q.Key, q.Value); + } + } + + protected bool IsDialog(FormDataCollection queryStrings) + { + return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); + } + } +} diff --git a/src/Umbraco.Web/Trees/TreeNodeRenderingEventArgs.cs b/src/Umbraco.Web/Trees/TreeNodeRenderingEventArgs.cs index f978619156..287e6030e3 100644 --- a/src/Umbraco.Web/Trees/TreeNodeRenderingEventArgs.cs +++ b/src/Umbraco.Web/Trees/TreeNodeRenderingEventArgs.cs @@ -13,4 +13,4 @@ namespace Umbraco.Web.Trees Node = node; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/TreeNodesRenderingEventArgs.cs b/src/Umbraco.Web/Trees/TreeNodesRenderingEventArgs.cs index cb7f1fdbde..d05ff6a93f 100644 --- a/src/Umbraco.Web/Trees/TreeNodesRenderingEventArgs.cs +++ b/src/Umbraco.Web/Trees/TreeNodesRenderingEventArgs.cs @@ -13,4 +13,4 @@ namespace Umbraco.Web.Trees Nodes = nodes; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs index 7f6fa28187..a8774bfdb5 100644 --- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs +++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs @@ -11,4 +11,4 @@ //public const string OnNodeClick = "OnNodeClick"; //public const string RenderParent = "RenderParent"; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/TreeRenderingEventArgs.cs b/src/Umbraco.Web/Trees/TreeRenderingEventArgs.cs index a7abb1a01b..140a576b6c 100644 --- a/src/Umbraco.Web/Trees/TreeRenderingEventArgs.cs +++ b/src/Umbraco.Web/Trees/TreeRenderingEventArgs.cs @@ -12,4 +12,4 @@ namespace Umbraco.Web.Trees QueryStrings = queryStrings; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/UrlHelperExtensions.cs b/src/Umbraco.Web/Trees/UrlHelperExtensions.cs index 4b94f548ca..38cd618ae1 100644 --- a/src/Umbraco.Web/Trees/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/Trees/UrlHelperExtensions.cs @@ -14,11 +14,11 @@ namespace Umbraco.Web.Trees { var actionUrl = urlHelper.GetUmbracoApiService("GetNodes", treeType) .EnsureEndsWith('?'); - + //now we need to append the query strings - actionUrl += "id=" + nodeId.EnsureEndsWith('&') + queryStrings.ToQueryString("id", + actionUrl += "id=" + nodeId.EnsureEndsWith('&') + queryStrings.ToQueryString("id", //Always ignore the custom start node id when generating URLs for tree nodes since this is a custom once-only parameter - // that should only ever be used when requesting a tree to render (root), not a tree node + // that should only ever be used when requesting a tree to render (root), not a tree node TreeQueryStringParameters.StartNodeId); return actionUrl; } @@ -33,13 +33,12 @@ namespace Umbraco.Web.Trees return actionUrl; } - internal static string GetTreePathFromFilePath(this UrlHelper urlHelper, string virtualPath, string basePath = "") { //This reuses the Logic from umbraco.cms.helpers.DeepLink class - //to convert a filepath to a tree syncing path string. + //to convert a filepath to a tree syncing path string. - //removes the basepath from the path + //removes the basepath from the path //and normalises paths - / is used consistently between trees and editors basePath = basePath.TrimStart("~"); virtualPath = virtualPath.TrimStart("~"); @@ -48,10 +47,10 @@ namespace Umbraco.Web.Trees //-1 is the default root id for trees var sb = new StringBuilder("-1"); - + //split the virtual path and iterate through it var pathPaths = virtualPath.Split('/'); - + for (var p = 0; p < pathPaths.Length; p++) { var path = HttpUtility.UrlEncode(string.Join("/", pathPaths.Take(p + 1))); @@ -64,4 +63,4 @@ namespace Umbraco.Web.Trees return sb.ToString().TrimEnd(","); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/UserPermissionsTreeController.cs b/src/Umbraco.Web/Trees/UserPermissionsTreeController.cs new file mode 100644 index 0000000000..26caa6ac2f --- /dev/null +++ b/src/Umbraco.Web/Trees/UserPermissionsTreeController.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Core.Services; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.Actions; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.UserPermissions)] + [Tree(Constants.Applications.Users, Constants.Trees.UserPermissions, null, sortOrder: 2)] + [PluginController("UmbracoTrees")] + [CoreTree] + public class UserPermissionsTreeController : TreeController + { + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + long totalUsers; + nodes.AddRange( + Services.UserService.GetAll(0, int.MaxValue, out totalUsers) + .Where(x => x.Id != Constants.Security.SuperId && x.IsApproved) + .Select(x => CreateTreeNode(x.Id.ToString(), + id, + queryStrings, + x.Name, + "icon-user", + false, + "/" + queryStrings.GetValue("application") + "/framed/" + + Uri.EscapeDataString("users/PermissionEditor.aspx?id=" + x.Id)))); + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + // root actions + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + return menu; + } + + return menu; + } + } +} diff --git a/src/Umbraco.Web/Trees/UserTreeController.cs b/src/Umbraco.Web/Trees/UserTreeController.cs index 89963a17e4..2c0a4c6cae 100644 --- a/src/Umbraco.Web/Trees/UserTreeController.cs +++ b/src/Umbraco.Web/Trees/UserTreeController.cs @@ -1,11 +1,10 @@ using System.Net.Http.Formatting; -using umbraco; -using umbraco.BusinessLogic.Actions; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.Actions; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees @@ -13,22 +12,9 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Users)] [Tree(Constants.Applications.Users, Constants.Trees.Users, null, sortOrder: 0)] [PluginController("UmbracoTrees")] - [LegacyBaseTree(typeof(loadUsers))] [CoreTree] public class UserTreeController : TreeController { - public UserTreeController() - { - } - - public UserTreeController(UmbracoContext umbracoContext) : base(umbracoContext) - { - } - - public UserTreeController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) - { - } - /// /// Helper method to create a root model for a tree /// @@ -84,4 +70,4 @@ namespace Umbraco.Web.Trees return menu; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/UsersTreeController.cs b/src/Umbraco.Web/Trees/UsersTreeController.cs new file mode 100644 index 0000000000..2fe08e25fb --- /dev/null +++ b/src/Umbraco.Web/Trees/UsersTreeController.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.Actions; +using Umbraco.Core.Services; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Users)] + [Tree(Constants.Applications.Users, Constants.Trees.Users, null)] + [PluginController("UmbracoTrees")] + [CoreTree] + public class UsersTreeController : TreeController + { + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + + var users = new List(Services.UserService.GetAll(0, int.MaxValue, out _)); + var currentUser = UmbracoContext.Current.Security.CurrentUser; + var hideDisabledUsers = UmbracoConfig.For.UmbracoSettings().Security.HideDisabledUsersInBackoffice; + + foreach (var user in users.OrderBy(x => x.IsApproved == false)) + { + // hide disabled user + if (user.IsApproved == false && hideDisabledUsers) + continue; + + if (user.IsSuper()) + { + // only super can see super + if (!currentUser.IsSuper()) continue; + } + else if (user.IsAdmin()) + { + // only admins can see admins + if (!currentUser.IsAdmin()) continue; + } + + var node = CreateTreeNode( + user.Id.ToString(CultureInfo.InvariantCulture), + "-1", + queryStrings, + user.Name, + "icon-user", + false, + "/" + queryStrings.GetValue("application") + "/framed/" + + Uri.EscapeDataString("users/EditUser.aspx?id=" + user.Id)); + + if (user.IsApproved == false) + node.CssClasses.Add("not-published"); + + nodes.Add(node); + } + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + // Root actions + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + .ConvertLegacyMenuItem(null, "users", queryStrings.GetValue("application")); + + menu.Items.Add( + Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + return menu; + } + + // If administator, don't create a menu + if (id == "0") + return menu; + + menu.Items.Add(new DisableUser() + { + Name = Services.TextService.Localize("actions", "disable") + }); + + return menu; + } + } +} diff --git a/src/Umbraco.Web/Trees/XsltTreeController.cs b/src/Umbraco.Web/Trees/XsltTreeController.cs index f35dc42b3a..f3aa45f8c7 100644 --- a/src/Umbraco.Web/Trees/XsltTreeController.cs +++ b/src/Umbraco.Web/Trees/XsltTreeController.cs @@ -1,19 +1,21 @@ -using System; +using System.Collections.Generic; +using System.IO; +using System; using System.Net.Http.Formatting; -using umbraco; -using umbraco.BusinessLogic.Actions; +using Umbraco.Core; using Umbraco.Core.IO; +using Umbraco.Web.Composing; using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.Actions; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Xslt)] - [Tree(Constants.Applications.Developer, Constants.Trees.Xslt, null, sortOrder: 5)] - [LegacyBaseTree(typeof(loadXslt))] + [Tree(Constants.Applications.Settings, Constants.Trees.Xslt, "XSLT Files", "icon-folder", "icon-folder", sortOrder: 2)] [PluginController("UmbracoTrees")] [CoreTree] public class XsltTreeController : FileSystemTreeController @@ -30,10 +32,8 @@ namespace Umbraco.Web.Trees treeNode.AssignLegacyJsCallback("javascript:void(0);"); } - protected override MenuItemCollection GetMenuForFile(string path, FormDataCollection queryStrings) + protected override void GetMenuForFile(MenuItemCollection menu, string path, string id, FormDataCollection queryStrings) { - var menu = new MenuItemCollection(); - //TODO: This is all hacky ... don't have time to convert the tree, views and dialogs over properly so we'll keep using the legacy dialogs var menuItem = menu.Items.Add(ActionDelete.Instance, Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias))); var legacyConfirmView = LegacyTreeDataConverter.GetLegacyConfirmView(ActionDelete.Instance); @@ -42,14 +42,10 @@ namespace Umbraco.Web.Trees menuItem.LaunchDialogView( legacyConfirmView.Result, Services.TextService.Localize("general/delete")); - - return menu; } - protected override MenuItemCollection GetMenuForRootNode(FormDataCollection queryStrings) + protected override void GetMenuForRootNode(MenuItemCollection menu, FormDataCollection queryStrings) { - var menu = new MenuItemCollection(); - //set the default to create menu.DefaultMenuAlias = ActionNew.Instance.Alias; @@ -57,24 +53,17 @@ namespace Umbraco.Web.Trees menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))) .ConvertLegacyMenuItem(null, Constants.Trees.Xslt, queryStrings.GetValue("application")); - menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); - return menu; + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); } - protected override IFileSystem2 FileSystem - { - get { return FileSystemProviderManager.Current.XsltFileSystem; } - } + protected override IFileSystem FileSystem => Current.FileSystems.XsltFileSystem; // fixme inject private static readonly string[] ExtensionsStatic = { "xslt" }; - protected override string[] Extensions - { - get { return ExtensionsStatic; } - } - protected override string FileIcon - { - get { return "icon-code"; } - } + protected override string[] Extensions => ExtensionsStatic; + + protected override string FileIcon => "icon-code"; + + protected override bool EnableCreateOnFolder => true; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/TypeLoaderExtensions.cs b/src/Umbraco.Web/TypeLoaderExtensions.cs new file mode 100644 index 0000000000..2c7bc430ce --- /dev/null +++ b/src/Umbraco.Web/TypeLoaderExtensions.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Media; +using Umbraco.Web.Mvc; +using Umbraco.Web.Trees; +using Umbraco.Web.WebApi; +using umbraco.cms.presentation.Trees; +using Umbraco.Core.Composing; +using Umbraco.Web._Legacy.Actions; + +namespace Umbraco.Web +{ + /// + /// Extension methods for the PluginTypemgr + /// + public static class TypeLoaderExtensions + { + /// + /// Returns all available IAction in application + /// + /// + internal static IEnumerable GetActions(this TypeLoader mgr) + { + return mgr.GetTypes(); + } + + /// + /// Returns all available TreeApiController's in application that are attribute with TreeAttribute + /// + /// + /// + internal static IEnumerable GetAttributedTreeControllers(this TypeLoader mgr) + { + return mgr.GetTypesWithAttribute(); + } + + internal static IEnumerable GetSurfaceControllers(this TypeLoader mgr) + { + return mgr.GetTypes(); + } + + internal static IEnumerable GetUmbracoApiControllers(this TypeLoader mgr) + { + return mgr.GetTypes(); + } + + /// + /// Returns all available ITrees in application + /// + /// + /// + internal static IEnumerable GetTrees(this TypeLoader mgr) + { + return mgr.GetTypes(); + } + + /// + /// Returns all available ISearchableTrees in application + /// + /// + /// + internal static IEnumerable GetSearchableTrees(this TypeLoader mgr) + { + return mgr.GetTypes(); + } + + /// + /// Returns all classes attributed with XsltExtensionAttribute attribute + /// + /// + /// + internal static IEnumerable GetXsltExtensions(this TypeLoader mgr) + { + return mgr.GetAttributedTypes(); + } + + /// + /// Returns all IThumbnailProvider classes + /// + /// + /// + internal static IEnumerable GetThumbnailProviders(this TypeLoader mgr) + { + return mgr.GetTypes(); + } + + /// + /// Returns all IImageUrlProvider classes + /// + /// + /// + internal static IEnumerable GetImageUrlProviders(this TypeLoader mgr) + { + return mgr.GetTypes(); + } + + } +} diff --git a/src/Umbraco.Web/UI/Bundles/JsJQueryCore.cs b/src/Umbraco.Web/UI/Bundles/JsJQueryCore.cs index 9bd6509ae7..15e85af2cb 100644 --- a/src/Umbraco.Web/UI/Bundles/JsJQueryCore.cs +++ b/src/Umbraco.Web/UI/Bundles/JsJQueryCore.cs @@ -12,4 +12,4 @@ namespace Umbraco.Web.UI.Bundles public class JsJQueryCore : Control { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UI/Bundles/JsJQueryPlugins.cs b/src/Umbraco.Web/UI/Bundles/JsJQueryPlugins.cs index 48ff2f38dc..245abc2420 100644 --- a/src/Umbraco.Web/UI/Bundles/JsJQueryPlugins.cs +++ b/src/Umbraco.Web/UI/Bundles/JsJQueryPlugins.cs @@ -6,8 +6,6 @@ namespace Umbraco.Web.UI.Bundles /// /// The core Jquery libs /// - [ClientDependency(ClientDependencyType.Javascript, "Application/jQuery/jquery.ba-bbq.min.js", "UmbracoClient", Priority = 0, Group = 4)] - [ClientDependency(ClientDependencyType.Javascript, "ui/jQueryWresize.js", "UmbracoClient", Priority = 1, Group = 4)] [ClientDependency(ClientDependencyType.Javascript, "modal/jquery.simplemodal.1.4.1.custom.js", "UmbracoClient", Priority = 2, Group = 4)] [ClientDependency(ClientDependencyType.Javascript, "Application/jQuery/jquery.cookie.js", "UmbracoClient", Priority = 3, Group = 4)] [ClientDependency(ClientDependencyType.Javascript, "Application/JQuery/jquery.metadata.min.js", "UmbracoClient", Priority = 4, Group = 4)] @@ -15,4 +13,4 @@ namespace Umbraco.Web.UI.Bundles public class JsJQueryPlugins : Control { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationCore.cs b/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationCore.cs index 17ae644c81..505f9efdc3 100644 --- a/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationCore.cs +++ b/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationCore.cs @@ -8,9 +8,8 @@ namespace Umbraco.Web.UI.Bundles /// [ClientDependency(ClientDependencyType.Javascript, "Application/Extensions.js", "UmbracoClient", Priority = 0, Group = 2)] [ClientDependency(ClientDependencyType.Javascript, "Application/UmbracoApplicationActions.js", "UmbracoClient", Priority = 1, Group = 2)] - [ClientDependency(ClientDependencyType.Javascript, "Application/HistoryManager.js", "UmbracoClient", Priority = 2, Group = 2)] [ClientDependency(ClientDependencyType.Javascript, "Application/UmbracoClientManager.js", "UmbracoClient", Priority = 3, Group = 3)] public class JsUmbracoApplicationCore : Control { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationUI.cs b/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationUI.cs index c8bce7694a..b0354235c8 100644 --- a/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationUI.cs +++ b/src/Umbraco.Web/UI/Bundles/JsUmbracoApplicationUI.cs @@ -4,7 +4,7 @@ using ClientDependency.Core; namespace Umbraco.Web.UI.Bundles { /// - /// The UI Umbraco libs + /// The UI Umbraco libs /// [ClientDependency(ClientDependencyType.Javascript, "ui/default.js", "UmbracoClient", Priority = 0, Group = 5)] [ClientDependency(ClientDependencyType.Javascript, "js/guiFunctions.js", "UmbracoRoot", Priority = 1, Group = 5)] @@ -14,4 +14,4 @@ namespace Umbraco.Web.UI.Bundles public class JsUmbracoApplicationUI : Control { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UI/Bundles/JsUmbracoTree.cs b/src/Umbraco.Web/UI/Bundles/JsUmbracoTree.cs index 7af2046991..5f393376cf 100644 --- a/src/Umbraco.Web/UI/Bundles/JsUmbracoTree.cs +++ b/src/Umbraco.Web/UI/Bundles/JsUmbracoTree.cs @@ -3,7 +3,7 @@ using ClientDependency.Core; namespace Umbraco.Web.UI.Bundles { - /// + /// /// The umb tree libs /// [ClientDependency(ClientDependencyType.Javascript, "Tree/jquery.tree.js", "UmbracoClient", Priority = 0, Group = 10)] @@ -15,4 +15,4 @@ namespace Umbraco.Web.UI.Bundles public class JsUmbracoTree : Control { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UI/CdfLogger.cs b/src/Umbraco.Web/UI/CdfLogger.cs index 32d12f8293..99b45c8057 100644 --- a/src/Umbraco.Web/UI/CdfLogger.cs +++ b/src/Umbraco.Web/UI/CdfLogger.cs @@ -1,41 +1,47 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ClientDependency.Core.Logging; using Umbraco.Core.Logging; -using ILogger = ClientDependency.Core.Logging.ILogger; +using Umbraco.Web.Composing; +using ICdfLogger = ClientDependency.Core.Logging.ILogger; +using ICoreLogger = Umbraco.Core.Logging.ILogger; namespace Umbraco.Web.UI { /// /// A logger for ClientDependency /// - public class CdfLogger : ILogger + public class CdfLogger : ICdfLogger { + private readonly ICoreLogger _logger; + + // Client Dependency doesn't know how to inject + public CdfLogger(/*ICoreLogger logger*/) + { + _logger = Current.Logger; + } + public void Debug(string msg) { - LogHelper.Debug(msg); + _logger.Debug(msg); } public void Info(string msg) { - LogHelper.Info(msg); + _logger.Info(msg); } public void Warn(string msg) { - LogHelper.Warn(msg); + _logger.Warn(msg); } public void Error(string msg, Exception ex) { - LogHelper.Error(msg, ex); + _logger.Error(msg, ex); } public void Fatal(string msg, Exception ex) { - LogHelper.Error(msg, ex); + _logger.Error(msg, ex); } } } diff --git a/src/Umbraco.Web/UI/Controls/FolderBrowser.cs b/src/Umbraco.Web/UI/Controls/FolderBrowser.cs deleted file mode 100644 index 1497b99d8c..0000000000 --- a/src/Umbraco.Web/UI/Controls/FolderBrowser.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Web.UI; -using System.Web.UI.WebControls; -using ClientDependency.Core; -using Umbraco.Core; -using Umbraco.Web.UI.Bundles; -using umbraco.BasePages; -using Umbraco.Core.IO; - -namespace Umbraco.Web.UI.Controls -{ - [ClientDependency(ClientDependencyType.Css, "ContextMenu/Css/jquery.contextMenu.css", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Css, "FolderBrowser/Css/folderbrowser.css", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "ContextMenu/Js/jquery.contextMenu.js", "UmbracoClient", Priority = 5)] - [ClientDependency(ClientDependencyType.Javascript, "FileUploader/js/jquery.fileUploader.js", "UmbracoClient", Priority = 6)] - [ClientDependency(ClientDependencyType.Javascript, "FolderBrowser/js/folderbrowser.js", "UmbracoClient", Priority = 10)] - [ToolboxData("<{0}:FolderBrowser runat=server>")] - public class FolderBrowser : WebControl - { - protected Panel Panel; - - protected int ParentId - { - get - { - // Try and parse from querystring - if (!string.IsNullOrEmpty(Context.Request.QueryString["id"])) - { - int id; - if (Int32.TryParse(Context.Request.QueryString["id"], out id)) - return id; - } - - // Get users root media folder id - var currentUser = UmbracoEnsuredPage.CurrentUser; - if (currentUser != null) - return currentUser.StartMediaId; - - // Nothing else to check so just return -1 - return -1; - } - } - - protected global::umbraco.cms.businesslogic.media.Media ParentNode - { - get - { - return new global::umbraco.cms.businesslogic.media.Media(ParentId); - } - } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - EnsureChildControls(); - - //disable view state for this control - this.EnableViewState = false; - } - - /// - /// Create the native .net child controls for this control - /// - protected override void CreateChildControls() - { - //Ensure the bundles are added - Controls.Add(new JsApplicationLib()); - Controls.Add(new JsJQueryCore()); - - // Create the panel surround - Panel = new Panel - { - ID = "FolderBrowser", - CssClass = "umbFolderBrowser" - }; - - Panel.Attributes.Add("data-parentid", ParentId.ToString()); - - var sb = new StringBuilder(); - - // Create the breadcrumb - var breadCrumb = new List { ParentNode }; - - var parent = ParentNode; - while (parent.Id != -1) - { - parent = new global::umbraco.cms.businesslogic.media.Media(parent.ParentId); - breadCrumb.Add(parent); - } - - breadCrumb.Reverse(); - - sb.Append(""); - - // Path for tree refresh - Panel.Attributes.Add("data-nodepath", ParentNode.Path); - - // Create size changer - sb.Append("
" + - "" + - "" + - "" + - "" + - "" + - "" + - "
"); - - // Create the filter input - sb.Append("
"); - - // Create throbber to display whilst loading items - sb.Append(""); - - // Create thumbnails container - sb.Append("
    " + - "
  • " + - "
"); - - Panel.Controls.Add(new LiteralControl(sb.ToString())); - - Controls.Add(Panel); - - Page.ClientScript.RegisterStartupScript(typeof(FolderBrowser), - "RegisterFolderBrowsers", - string.Format("$(function () {{ $(\".umbFolderBrowser\").folderBrowser({{ umbracoPath : '{0}', basePath : '{1}', reqver : '{2}' }}); " + - "$(\".umbFolderBrowser #filterTerm\").keypress(function(event) {{ return event.keyCode != 13; }});}});", - IOHelper.ResolveUrl(SystemDirectories.Umbraco), - IOHelper.ResolveUrl(SystemDirectories.Base), - UmbracoEnsuredPage.umbracoUserContextID.EncryptWithMachineKey() ), - true); - } - - protected override void Render(HtmlTextWriter writer) - { - this.RenderContents(writer); - } - } -} diff --git a/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs b/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs index 5c6db74513..59b0b5bdf9 100644 --- a/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs +++ b/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs @@ -1,143 +1,144 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Web.Mvc; using System.Web.UI; -using System.Web.UI.HtmlControls; using ClientDependency.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; -using umbraco.DataLayer; +using Umbraco.Web.Composing; namespace Umbraco.Web.UI.Controls { - /// - /// Represents the 'insert macro' button when editing a template which includes the drop down list selector - /// - /// - /// Though this would be nicer to do in a UserControl, unfortunatley the way that the ScrollingMenu control is designed it seems that - /// we have to insert all items via code and loading a UserControl in dynamically is equally ugly. - /// - [ClientDependency(ClientDependencyType.Css, "splitbutton/splitbutton.css", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "splitbutton/jquery.splitbutton.js", "UmbracoClient", Priority = 100)] - [ClientDependency(ClientDependencyType.Javascript, "splitbutton/InsertMacroSplitButton.js", "UmbracoClient", Priority = 101)] - internal class InsertMacroSplitButton : UmbracoControl - { - protected LiteralControl ListContainer; + /// + /// Represents the 'insert macro' button when editing a template which includes the drop down list selector + /// + /// + /// Though this would be nicer to do in a UserControl, unfortunatley the way that the ScrollingMenu control is designed it seems that + /// we have to insert all items via code and loading a UserControl in dynamically is equally ugly. + /// + [ClientDependency(ClientDependencyType.Css, "splitbutton/splitbutton.css", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Javascript, "splitbutton/jquery.splitbutton.js", "UmbracoClient", Priority = 100)] + [ClientDependency(ClientDependencyType.Javascript, "splitbutton/InsertMacroSplitButton.js", "UmbracoClient", Priority = 101)] + internal class InsertMacroSplitButton : UmbracoControl + { + protected LiteralControl ListContainer; - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - EnsureChildControls(); - } + protected override void OnInit(EventArgs e) + { + base.OnInit(e); + EnsureChildControls(); + } - /// - /// The JS callback to display the dialog modal screen to customize the macro to be inserted into the editor if the - /// macro has parameters. - /// - public string ClientCallbackOpenMacroModel { get; set; } + /// + /// The JS callback to display the dialog modal screen to customize the macro to be inserted into the editor if the + /// macro has parameters. + /// + public string ClientCallbackOpenMacroModel { get; set; } - /// - /// The JS callback method which accepts an 'alias' parameter that is invoked when clicking the macro button - /// to insert a macro that has no parameters. - /// - public string ClientCallbackInsertMacroMarkup { get; set; } + /// + /// The JS callback method which accepts an 'alias' parameter that is invoked when clicking the macro button + /// to insert a macro that has no parameters. + /// + public string ClientCallbackInsertMacroMarkup { get; set; } - protected override void CreateChildControls() - { - base.CreateChildControls(); + protected override void CreateChildControls() + { + base.CreateChildControls(); - //var menuItemsId = ClientID + "menuitems"; - //var placeHolderId = ClientID + "sbPlaceholder"; + //var menuItemsId = ClientID + "menuitems"; + //var placeHolderId = ClientID + "sbPlaceholder"; - /*create the list, similar to this, but swap the repeater for real html: - -
- - -
" - data-has-params="<%# DoesMacroHaveSettings(DataBinder.Eval(Container, "DataItem.id").ToString()) %>"> - <%# DataBinder.Eval(Container, "DataItem.macroName")%> -
-
-
-
- */ + /*create the list, similar to this, but swap the repeater for real html: - //Create the drop down menu list first (it is hidden) - var divMacroItemContainer = new TagBuilder("div"); - divMacroItemContainer.Attributes.Add("style", "width: 285px;display:none;"); - divMacroItemContainer.Attributes.Add("class", "sbMenu"); - var macros = ApplicationContext.DatabaseContext.Database.Fetch("select id, macroAlias, macroName from cmsMacro order by macroName"); - foreach (var macro in macros) - { - var divMacro = new TagBuilder("div"); - divMacro.AddCssClass("macro-item"); - divMacro.Attributes.Add("rel", macro.Alias); - divMacro.Attributes.Add("data-has-params", DoesMacroHaveParameters(macro.Id).ToString().ToLower()); - divMacro.SetInnerText(macro.Name); - divMacroItemContainer.InnerHtml += divMacro.ToString(); - } - - /*create the button itself, similar to this: - -
- - Insert Macro - -
- - */ +
+ + +
" + data-has-params="<%# DoesMacroHaveSettings(DataBinder.Eval(Container, "DataItem.id").ToString()) %>"> + <%# DataBinder.Eval(Container, "DataItem.macroName")%> +
+
+
+
+ */ - var divSplitButtonWrapper = new TagBuilder("div"); - divSplitButtonWrapper.AddCssClass("sbPlaceHolder"); - divSplitButtonWrapper.Attributes.Add("id", ClientID + "sbPlaceholder"); - var divButton = new TagBuilder("div"); - divButton.Attributes.Add("style", "display: inline; height: 23px; vertical-align: top;"); - var aButton = new TagBuilder("a"); - aButton.Attributes.Add("href", "#"); //will be bound with jquery - aButton.AddCssClass("sbLink"); - var imgButton = new TagBuilder("img"); - imgButton.Attributes.Add("alt", "Insert Macro"); - imgButton.Attributes.Add("src", this.ResolveUrl(SystemDirectories.Umbraco + "/images/editor/insMacroSB.png")); - imgButton.Attributes.Add("title", "Insert Macro"); - imgButton.Attributes.Add("style", "vertical-align: top;"); - aButton.InnerHtml = imgButton.ToString(); - divButton.InnerHtml = aButton.ToString(); - divSplitButtonWrapper.InnerHtml = divButton.ToString(); + //Create the drop down menu list first (it is hidden) + var divMacroItemContainer = new TagBuilder("div"); + divMacroItemContainer.Attributes.Add("style", "width: 285px;display:none;"); + divMacroItemContainer.Attributes.Add("class", "sbMenu"); - ListContainer = new LiteralControl(divMacroItemContainer.ToString() + divSplitButtonWrapper.ToString()); - Controls.Add(ListContainer); + var macros = Services.MacroService.GetAll().OrderBy(x => x.Name); + foreach (var macro in macros) + { + var divMacro = new TagBuilder("div"); + divMacro.AddCssClass("macro-item"); + divMacro.Attributes.Add("rel", macro.Alias); + divMacro.Attributes.Add("data-has-params", DoesMacroHaveParameters(macro.Id).ToString().ToLower()); + divMacro.SetInnerText(macro.Name); + divMacroItemContainer.InnerHtml += divMacro.ToString(); + } -// Page.ClientScript.RegisterStartupScript( -// typeof(InsertMacroSplitButton), -// ClientID, -// @"jQuery(document).ready(function() { -// jQuery('#" + placeHolderId + " a.sbLink').splitbutton({menu:'#" + menuItemsId + "'}); " + -// "});", -// true); + /*create the button itself, similar to this: - Page.ClientScript.RegisterStartupScript( - typeof(InsertMacroSplitButton), - typeof(InsertMacroSplitButton).Name, //same key for all instancees, we should only render once - @"jQuery(document).ready(function() { - var splitButton = new Umbraco.Controls.InsertMacroSplitButton({ - openMacroModel: " + ClientCallbackOpenMacroModel + @", - insertMacroMarkup: " + ClientCallbackInsertMacroMarkup + @" - }); - splitButton.init(); - });", - true); - } +
+ + Insert Macro + +
+ + */ + + var divSplitButtonWrapper = new TagBuilder("div"); + divSplitButtonWrapper.AddCssClass("sbPlaceHolder"); + divSplitButtonWrapper.Attributes.Add("id", ClientID + "sbPlaceholder"); + var divButton = new TagBuilder("div"); + divButton.Attributes.Add("style", "display: inline; height: 23px; vertical-align: top;"); + var aButton = new TagBuilder("a"); + aButton.Attributes.Add("href", "#"); //will be bound with jquery + aButton.AddCssClass("sbLink"); + var imgButton = new TagBuilder("img"); + imgButton.Attributes.Add("alt", "Insert Macro"); + imgButton.Attributes.Add("src", this.ResolveUrl(SystemDirectories.Umbraco + "/images/editor/insMacroSB.png")); + imgButton.Attributes.Add("title", "Insert Macro"); + imgButton.Attributes.Add("style", "vertical-align: top;"); + aButton.InnerHtml = imgButton.ToString(); + divButton.InnerHtml = aButton.ToString(); + divSplitButtonWrapper.InnerHtml = divButton.ToString(); + + ListContainer = new LiteralControl(divMacroItemContainer.ToString() + divSplitButtonWrapper.ToString()); + Controls.Add(ListContainer); + +// Page.ClientScript.RegisterStartupScript( +// typeof(InsertMacroSplitButton), +// ClientID, +// @"jQuery(document).ready(function() { +// jQuery('#" + placeHolderId + " a.sbLink').splitbutton({menu:'#" + menuItemsId + "'}); " + +// "});", +// true); + + Page.ClientScript.RegisterStartupScript( + typeof(InsertMacroSplitButton), + typeof(InsertMacroSplitButton).Name, //same key for all instancees, we should only render once + @"jQuery(document).ready(function() { + var splitButton = new Umbraco.Controls.InsertMacroSplitButton({ + openMacroModel: " + ClientCallbackOpenMacroModel + @", + insertMacroMarkup: " + ClientCallbackInsertMacroMarkup + @" + }); + splitButton.init(); + });", + true); + } - private bool DoesMacroHaveParameters(int macroId) - { - return ApplicationContext.DatabaseContext.Database.ExecuteScalar(string.Format("SELECT COUNT(*) from cmsMacroProperty where macro = {0}", macroId)) > 0; - } - } + private bool DoesMacroHaveParameters(int macroId) + { + bool hasParameters; + using (var scope = Current.ScopeProvider.CreateScope()) + { + hasParameters = scope.Database.ExecuteScalar("SELECT COUNT(*) from cmsMacroProperty where macro=@macroId", new { macroId }) > 0; + scope.Complete(); + } + return hasParameters; + } + } } diff --git a/src/Umbraco.Web/UI/Controls/ProgressBar.cs b/src/Umbraco.Web/UI/Controls/ProgressBar.cs index b4cfa55555..698066688a 100644 --- a/src/Umbraco.Web/UI/Controls/ProgressBar.cs +++ b/src/Umbraco.Web/UI/Controls/ProgressBar.cs @@ -3,7 +3,7 @@ namespace Umbraco.Web.UI.Controls { public class ProgressBar : System.Web.UI.WebControls.Image - { + { public string Title { get; set; } protected override void Render(System.Web.UI.HtmlTextWriter writer) diff --git a/src/Umbraco.Web/UI/Controls/UmbracoControl.cs b/src/Umbraco.Web/UI/Controls/UmbracoControl.cs index b84deaa232..2329e85703 100644 --- a/src/Umbraco.Web/UI/Controls/UmbracoControl.cs +++ b/src/Umbraco.Web/UI/Controls/UmbracoControl.cs @@ -1,96 +1,68 @@ using System; -using System.Web; using System.Web.Mvc; -using System.Web.Routing; using System.Web.UI; -using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; using Umbraco.Core.Services; -using umbraco.BusinessLogic; -using umbraco.DataLayer; +using Umbraco.Web.Composing; namespace Umbraco.Web.UI.Controls { /// - /// A control that exposes the helpful Umbraco context objects - /// - public abstract class UmbracoControl : Control - { + /// A control that exposes the helpful Umbraco context objects + ///
+ public abstract class UmbracoControl : Control + { + private UrlHelper _url; - /// - /// Default constructor - /// - /// - protected UmbracoControl(UmbracoContext umbracoContext) + protected UmbracoControl(UmbracoContext umbracoContext, ServiceContext services, CacheHelper appCache) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - UmbracoContext = umbracoContext; - Umbraco = new UmbracoHelper(umbracoContext); + UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); + Umbraco = new UmbracoHelper(umbracoContext, services, appCache); + + // fixme inject somehow + Logger = Current.Logger; + ProfilingLogger = Current.ProfilingLogger; + Services = Current.Services; } /// - /// Empty constructor, uses Singleton to resolve the UmbracoContext + /// Empty constructor, uses Singleton to resolve the UmbracoContext. /// protected UmbracoControl() - : this(UmbracoContext.Current) - { - } + : this(Current.UmbracoContext, Current.Services, Current.ApplicationCache) + { } /// /// Returns an UmbracoHelper object /// - public UmbracoHelper Umbraco { get; private set; } + public UmbracoHelper Umbraco { get; } /// - /// Returns an ILogger + /// Gets the logger. /// - public ILogger Logger - { - get { return ProfilingLogger.Logger; } - } + public ILogger Logger { get; } /// - /// Returns a ProfilingLogger + /// Gets the profiling logger. /// - public ProfilingLogger ProfilingLogger - { - get { return UmbracoContext.Application.ProfilingLogger; } - } - - public UmbracoContext UmbracoContext { get; private set; } - - protected ApplicationContext ApplicationContext - { - get { return UmbracoContext.Application; } - } - protected DatabaseContext DatabaseContext - { - get { return ApplicationContext.DatabaseContext; } - } - protected ServiceContext Services - { - get { return ApplicationContext.Services; } - } - - private UrlHelper _url; - /// - /// Returns a UrlHelper - /// - /// - /// This URL helper is created without any route data and an empty request context - /// - public UrlHelper Url - { - get { return _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); } - } + public ProfilingLogger ProfilingLogger { get; } /// - /// Unused, please do not use + /// Gets the Umbraco context. /// - [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] - protected ISqlHelper SqlHelper - { - get { return Application.SqlHelper; } - } - } -} \ No newline at end of file + public UmbracoContext UmbracoContext { get; } + + /// + /// Gets the services context. + /// + protected ServiceContext Services { get; } + + /// + /// Gets a Url helper. + /// + /// This URL helper is created without any route data and an empty request context. + public UrlHelper Url => _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); + } +} diff --git a/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs b/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs index 246b9d45c2..2875cb82bd 100644 --- a/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs +++ b/src/Umbraco.Web/UI/Controls/UmbracoUserControl.cs @@ -4,10 +4,13 @@ using System.Web.Mvc; using System.Web.Routing; using System.Web.UI; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Security; -using umbraco.DataLayer; +using Umbraco.Web.UI.Pages; namespace Umbraco.Web.UI.Controls { @@ -16,119 +19,89 @@ namespace Umbraco.Web.UI.Controls ///
public abstract class UmbracoUserControl : UserControl { + private ClientTools _clientTools; + private UrlHelper _url; + /// /// Default constructor /// /// - protected UmbracoUserControl(UmbracoContext umbracoContext) + /// + /// + protected UmbracoUserControl(UmbracoContext umbracoContext, ServiceContext services, CacheHelper appCache) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); UmbracoContext = umbracoContext; - InstanceId = Guid.NewGuid(); - Umbraco = new UmbracoHelper(umbracoContext); - _membershipHelper = new MembershipHelper(umbracoContext); + Umbraco = new UmbracoHelper(umbracoContext, services, appCache); + Members = new MembershipHelper(umbracoContext); + + // fixme inject somehow + Logger = Current.Logger; + ProfilingLogger = Current.ProfilingLogger; + Services = Current.Services; } /// /// Empty constructor, uses Singleton to resolve the UmbracoContext /// protected UmbracoUserControl() - : this(UmbracoContext.Current) + : this(Current.UmbracoContext, Current.Services, Current.ApplicationCache) + { } + + // for debugging purposes + internal Guid InstanceId { get; } = Guid.NewGuid(); + + /// + /// Gets the Umbraco helper. + /// + public UmbracoHelper Umbraco { get; } + + /// + /// Gets the membership helper; + /// + public MembershipHelper Members { get; } + + /// + /// Gets the web security helper. + /// + public WebSecurity Security => UmbracoContext.Security; + + /// + /// Gets the logger. + /// + public ILogger Logger { get; } + + /// + /// Gets the ProfilingLogger. + /// + public ProfilingLogger ProfilingLogger { get; } + + /// + /// Gets the Umbraco context. + /// + public UmbracoContext UmbracoContext { get; } + + /// + /// Gets the services context. + /// + public ServiceContext Services { get; } + + /// + /// Gets an instance of ClientTools for access to the pages client API. + /// + public ClientTools ClientTools { - } - - private readonly MembershipHelper _membershipHelper; - - /// - /// Useful for debugging - /// - internal Guid InstanceId { get; private set; } - - /// - /// Returns an UmbracoHelper object - /// - public UmbracoHelper Umbraco { get; private set; } - - /// - /// Returns the MemberHelper instance - /// - public MembershipHelper Members - { - get { return _membershipHelper; } + get + { + var page = Page as BasePage; + return _clientTools ?? (_clientTools = page != null ? page.ClientTools : new ClientTools(Page)); + } } /// - /// Returns the current WebSecurity instance + /// Gets a Url helper. /// - public WebSecurity Security - { - get { return UmbracoContext.Security; } - } - - /// - /// Returns an ILogger - /// - public ILogger Logger - { - get { return ProfilingLogger.Logger; } - } - - /// - /// Returns a ProfilingLogger - /// - public ProfilingLogger ProfilingLogger - { - get { return UmbracoContext.Application.ProfilingLogger; } - } - - /// - /// Returns the current UmbracoContext - /// - public UmbracoContext UmbracoContext { get; private set; } - - /// - /// Returns the current ApplicationContext - /// - public ApplicationContext ApplicationContext - { - get { return UmbracoContext.Application; } - } - - /// - /// Returns a ServiceContext - /// - public ServiceContext Services - { - get { return ApplicationContext.Services; } - } - - /// - /// Returns a DatabaseContext - /// - public DatabaseContext DatabaseContext - { - get { return ApplicationContext.DatabaseContext; } - } - - private UrlHelper _url; - /// - /// Returns a UrlHelper - /// - /// - /// This URL helper is created without any route data and an empty request context - /// - public UrlHelper Url - { - get { return _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); } - } - - /// - /// Unused, please do not use - /// - [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] - protected ISqlHelper SqlHelper - { - get { return global::umbraco.BusinessLogic.Application.SqlHelper; } - } + /// This URL helper is created without any route data and an empty request context. + public UrlHelper Url => _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UI/IAssignedApp.cs b/src/Umbraco.Web/UI/IAssignedApp.cs index 3be16f138f..1184c1ffa2 100644 --- a/src/Umbraco.Web/UI/IAssignedApp.cs +++ b/src/Umbraco.Web/UI/IAssignedApp.cs @@ -14,4 +14,4 @@ ///
string AssignedApp { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs b/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs index 26f933f734..9c5e0f1ec2 100644 --- a/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs @@ -2,100 +2,63 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Text; using System.Web; using ClientDependency.Core; using ClientDependency.Core.Config; -using Newtonsoft.Json.Linq; -using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Composing; using Umbraco.Web.PropertyEditors; namespace Umbraco.Web.UI.JavaScript { internal abstract class AssetInitialization { - /// - /// Get all dependencies declared on property editors - /// - /// - /// - /// - protected JArray ScanPropertyEditors(ClientDependencyType cdfType, HttpContextBase httpContext) + protected IEnumerable ScanPropertyEditors(ClientDependencyType assetType, HttpContextBase httpContext) { - if (httpContext == null) throw new ArgumentNullException("httpContext"); - var cdfAttributes = - PropertyEditorResolver.Current.PropertyEditors - .SelectMany(x => x.GetType().GetCustomAttributes(false)) - .Where(x => x.AssetType == cdfType) - .Select(x => x.DependencyFile) - .ToList(); + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); + var attributes = Current.PropertyEditors + .SelectMany(x => x.GetType().GetCustomAttributes(false)) + .Where(x => x.AssetType == assetType) + .Select(x => x.DependencyFile) + .ToList(); - string jsOut; - string cssOut; var renderer = ClientDependencySettings.Instance.MvcRendererCollection["Umbraco.DependencyPathRenderer"]; - renderer.RegisterDependencies(cdfAttributes, new HashSet(), out jsOut, out cssOut, httpContext); + renderer.RegisterDependencies(attributes, new HashSet(), out var scripts, out var stylesheets, httpContext); - var toParse = cdfType == ClientDependencyType.Javascript ? jsOut : cssOut; - - var result = new JArray(); - //split the result by the delimiter and add to the array - foreach (var u in toParse.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries)) - { - result.Add(u); - } - return result; + var toParse = assetType == ClientDependencyType.Javascript ? scripts : stylesheets; + return toParse.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries); } - /// - /// This will use CDF to optimize the asset file collection - /// - /// - /// - /// - /// - /// Return the asset URLs that should be loaded, if the application is in debug mode then the URLs returned will be the same as the ones - /// passed in with the CDF version query strings appended so cache busting works correctly. - /// - protected JArray OptimizeAssetCollection(JArray fileRefs, ClientDependencyType cdfType, HttpContextBase httpContext) + protected IEnumerable OptimizeAssetCollection(IEnumerable assets, ClientDependencyType assetType, HttpContextBase httpContext) { - if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); - var depenencies = fileRefs.Select(x => + var requestUrl = httpContext.Request.Url; + if (requestUrl == null) throw new ArgumentException("HttpContext.Request.Url is null.", nameof(httpContext)); + + var dependencies = assets.Select(x => { - var asString = x.ToString(); - if (asString.StartsWith("/") == false) + // most declarations with be made relative to the /umbraco folder, so things + // ike lib/blah/blah.js so we need to turn them into absolutes here + if (x.StartsWith("/") == false && Uri.IsWellFormedUriString(x, UriKind.Relative)) { - //most declarations with be made relative to the /umbraco folder, so things like lib/blah/blah.js - // so we need to turn them into absolutes here - if (Uri.IsWellFormedUriString(asString, UriKind.Relative)) - { - var absolute = new Uri(httpContext.Request.Url, asString); - return (IClientDependencyFile)new BasicFile(cdfType) { FilePath = absolute.AbsolutePath }; - } + return (IClientDependencyFile) new BasicFile(assetType) { FilePath = new Uri(requestUrl, x).AbsolutePath }; } - return cdfType == ClientDependencyType.Javascript - ? (IClientDependencyFile)new JavascriptFile(asString) - : (IClientDependencyFile)new CssFile(asString); - }).Where(x => x != null).ToList(); - //Get the output string for these registrations which will be processed by CDF correctly to stagger the output based + return assetType == ClientDependencyType.Javascript + ? (IClientDependencyFile) new JavascriptFile(x) + : (IClientDependencyFile) new CssFile(x); + }).ToList(); + + // get the output string for these registrations which will be processed by CDF correctly to stagger the output based // on internal vs external resources. The output will be delimited based on our custom Umbraco.Web.UI.JavaScript.DependencyPathRenderer - string jsOut; - string cssOut; var renderer = ClientDependencySettings.Instance.MvcRendererCollection["Umbraco.DependencyPathRenderer"]; - renderer.RegisterDependencies(depenencies, new HashSet(), out jsOut, out cssOut, httpContext); + renderer.RegisterDependencies(dependencies, new HashSet(), out var scripts, out var stylesheets, httpContext); - var urls = cdfType == ClientDependencyType.Javascript - ? jsOut.Split(new string[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries) - : cssOut.Split(new string[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries); + var urls = assetType == ClientDependencyType.Javascript + ? scripts.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries) + : stylesheets.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries); - var result = new JArray(); - foreach (var u in urls) - { - result.Add(u); - } - return result; + return urls; } - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs b/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs index 19f932e83d..53f2136150 100644 --- a/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/CssInitialization.cs @@ -1,12 +1,9 @@ using System.Web; using ClientDependency.Core; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.IO; using Umbraco.Core.Manifest; namespace Umbraco.Web.UI.JavaScript @@ -14,50 +11,39 @@ namespace Umbraco.Web.UI.JavaScript internal class CssInitialization : AssetInitialization { private readonly ManifestParser _parser; + public CssInitialization(ManifestParser parser) { _parser = parser; } /// - /// Processes all found manifest files and outputs yepnope.injectcss calls for all css files found in all manifests + /// Processes all found manifest files, and outputs css inject calls for all css files found in all manifests. /// public string GetStylesheetInitialization(HttpContextBase httpContext) { - var result = GetStylesheetInitializationArray(httpContext); - - return ParseMain(result); + var files = GetStylesheetFiles(httpContext); + return WriteScript(files); } - public JArray GetStylesheetInitializationArray(HttpContextBase httpContext) + public IEnumerable GetStylesheetFiles(HttpContextBase httpContext) { - var merged = new JArray(); - foreach (var m in _parser.GetManifests()) - { - ManifestParser.MergeJArrays(merged, m.StylesheetInitialize); - } + var stylesheets = new HashSet(); + var optimizedManifest = OptimizeAssetCollection(_parser.Manifest.Stylesheets, ClientDependencyType.Css, httpContext); + foreach (var stylesheet in optimizedManifest) + stylesheets.Add(stylesheet); - //now we can optimize if in release mode - merged = OptimizeAssetCollection(merged, ClientDependencyType.Css, httpContext); + foreach (var stylesheet in ScanPropertyEditors(ClientDependencyType.Css, httpContext)) + stylesheets.Add(stylesheet); - //now we need to merge in any found cdf declarations on property editors - ManifestParser.MergeJArrays(merged, ScanPropertyEditors(ClientDependencyType.Css, httpContext)); - - return merged; + return stylesheets.ToArray(); } - - /// - /// Parses the CssResources.Main and returns a yepnop.injectCss format - /// - /// - /// - internal static string ParseMain(JArray files) + internal static string WriteScript(IEnumerable files) { var sb = new StringBuilder(); foreach (var file in files) sb.AppendFormat("{0}LazyLoad.css('{1}');", Environment.NewLine, file); - return sb.ToString(); } diff --git a/src/Umbraco.Web/UI/JavaScript/DependencyPathRenderer.cs b/src/Umbraco.Web/UI/JavaScript/DependencyPathRenderer.cs index 221f5f8f29..d0d8f99497 100644 --- a/src/Umbraco.Web/UI/JavaScript/DependencyPathRenderer.cs +++ b/src/Umbraco.Web/UI/JavaScript/DependencyPathRenderer.cs @@ -35,7 +35,7 @@ namespace Umbraco.Web.UI.JavaScript jsOutput = jsOutput.Replace("&", "&"); cssOutput = cssOutput.Replace("&", "&"); } - + protected override string RenderSingleJsFile(string js, IDictionary htmlAttributes) { return js + Delimiter; @@ -47,4 +47,4 @@ namespace Umbraco.Web.UI.JavaScript } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs b/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs index 34d297a86f..dffe92f33c 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs @@ -1,17 +1,13 @@ using System.Collections.Generic; -using System.Globalization; -using System.IO; using System.Text.RegularExpressions; using System.Web; using ClientDependency.Core; -using ClientDependency.Core.Config; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Manifest; using System.Linq; +using System.Text; namespace Umbraco.Web.UI.JavaScript { @@ -20,7 +16,7 @@ namespace Umbraco.Web.UI.JavaScript /// main Umbraco initialization output. ///
internal class JsInitialization : AssetInitialization - { + { private readonly ManifestParser _parser; public JsInitialization(ManifestParser parser) @@ -28,65 +24,65 @@ namespace Umbraco.Web.UI.JavaScript _parser = parser; } - //used to strip comments - internal static readonly Regex Comments = new Regex("(/\\*.*\\*/)", RegexOptions.Compiled); - //used for dealing with js functions inside of json (which is not a supported json syntax) + // deal with javascript functions inside of json (not a supported json syntax) private const string PrefixJavaScriptObject = "@@@@"; - private static readonly Regex JsFunctionParser = new Regex(string.Format("(\"{0}(.*?)\")+", PrefixJavaScriptObject), - RegexOptions.Multiline - | RegexOptions.CultureInvariant - | RegexOptions.Compiled); - //used to replace the tokens in the js main + private static readonly Regex JsFunctionParser = new Regex($"(\"{PrefixJavaScriptObject}(.*?)\")+", + RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled); + + // replace tokens in the js main private static readonly Regex Token = new Regex("(\"##\\w+?##\")", RegexOptions.Compiled); /// /// Processes all found manifest files and outputs the main.js file containing all plugin manifests /// - public string GetJavascriptInitialization(HttpContextBase httpContext, JArray umbracoInit, JArray additionalJsFiles = null) + public string GetJavascriptInitialization(HttpContextBase httpContext, IEnumerable umbracoInit, IEnumerable additionalJsFiles = null) { - var result = GetJavascriptInitializationArray(httpContext, umbracoInit, additionalJsFiles); - - return ParseMain( - result.ToString(), - IOHelper.ResolveUrl(SystemDirectories.Umbraco)); + var files = GetScriptFiles(httpContext, umbracoInit, additionalJsFiles); + + var jarray = new StringBuilder(); + jarray.AppendLine("["); + var first = true; + foreach (var file in files) + { + if (first) first = false; + else jarray.AppendLine(","); + jarray.Append("\""); + jarray.Append(file); + jarray.Append("\""); + + } + jarray.Append("]"); + + return WriteScript(jarray.ToString(), IOHelper.ResolveUrl(SystemDirectories.Umbraco)); } - public JArray GetJavascriptInitializationArray(HttpContextBase httpContext, JArray umbracoInit, JArray additionalJsFiles = null) + public IEnumerable GetScriptFiles(HttpContextBase httpContext, IEnumerable umbracoInit, IEnumerable additionalJsFiles = null) { - foreach (var m in _parser.GetManifests()) - { - ManifestParser.MergeJArrays(umbracoInit, m.JavaScriptInitialize); - } - - //merge in the additional ones specified if there are any + var scripts = new HashSet(); + foreach (var script in umbracoInit) + scripts.Add(script); + foreach (var script in _parser.Manifest.Scripts) + scripts.Add(script); if (additionalJsFiles != null) - { - ManifestParser.MergeJArrays(umbracoInit, additionalJsFiles); - } + foreach (var script in additionalJsFiles) + scripts.Add(script); - //now we can optimize if in release mode - umbracoInit = OptimizeAssetCollection(umbracoInit, ClientDependencyType.Javascript, httpContext); + scripts = new HashSet(OptimizeAssetCollection(scripts, ClientDependencyType.Javascript, httpContext)); - //now we need to merge in any found cdf declarations on property editors - ManifestParser.MergeJArrays(umbracoInit, ScanPropertyEditors(ClientDependencyType.Javascript, httpContext)); + foreach (var script in ScanPropertyEditors(ClientDependencyType.Javascript, httpContext)) + scripts.Add(script); - return umbracoInit; + return scripts.ToArray(); } /// /// Returns the default config as a JArray /// /// - internal static JArray GetDefaultInitialization() + internal static IEnumerable GetDefaultInitialization() { - var init = Resources.JsInitialize; - var deserialized = JsonConvert.DeserializeObject(init); - var result = new JArray(); - foreach (var j in deserialized.Where(j => j.Type == JTokenType.String)) - { - result.Add(j); - } - return result; + var resources = JsonConvert.DeserializeObject(Resources.JsInitialize); + return resources.Where(x => x.Type == JTokenType.String).Select(x => x.ToString()); } /// @@ -94,22 +90,18 @@ namespace Umbraco.Web.UI.JavaScript /// /// /// - internal static string ParseMain(params string[] replacements) + internal static string WriteScript(params string[] replacements) { var count = 0; + // replace, catering for the special syntax when we have + // js function() objects contained in the json + return Token.Replace(Resources.Main, match => { - var replaced = replacements[count]; - - //we need to cater for the special syntax when we have js function() objects contained in the json - var jsFunctionParsed = JsFunctionParser.Replace(replaced, "$2"); - - count++; - - return jsFunctionParsed; + var replacement = replacements[count++]; + return JsFunctionParser.Replace(replacement, "$2"); }); } - } } diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js index 961fb8e54e..39185cff3f 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js @@ -3,7 +3,7 @@ 'lib/angular/1.1.5/angular.min.js', 'lib/underscore/underscore-min.js', - 'lib/moment/moment-with-locales.js', + 'lib/moment/moment.min.js', 'lib/jquery-ui/jquery-ui.min.js', 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js', diff --git a/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs b/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs index 8c7087939d..5a4f733991 100644 --- a/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs +++ b/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.UI.JavaScript { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -66,6 +66,8 @@ namespace Umbraco.Web.UI.JavaScript { /// 'lib/angular/1.1.5/angular.min.js', /// 'lib/underscore/underscore-min.js', /// + /// 'lib/moment/moment.min.js', + /// /// 'lib/jquery-ui/jquery-ui.min.js', /// 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js', /// @@ -76,7 +78,7 @@ namespace Umbraco.Web.UI.JavaScript { /// 'lib/angular/angular-ui-sortable.js', /// /// 'lib/angular-dynamic-locale/tmhDynamicLocale.min.js', - /// 'lib/ng-file-upload/ng-file-upload.min. [rest of string was truncated]";. + /// 'lib [rest of string was truncated]";. ///
internal static string JsInitialize { get { diff --git a/src/Umbraco.Web/UI/JavaScript/ServerVariablesParser.cs b/src/Umbraco.Web/UI/JavaScript/ServerVariablesParser.cs index ac47becb33..76f1dae9e2 100644 --- a/src/Umbraco.Web/UI/JavaScript/ServerVariablesParser.cs +++ b/src/Umbraco.Web/UI/JavaScript/ServerVariablesParser.cs @@ -25,9 +25,9 @@ namespace Umbraco.Web.UI.JavaScript } var json = JObject.FromObject(items); - return vars.Replace(Token, json.ToString()); - + return vars.Replace(Token, json.ToString()); + } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UI/JavaScript/UmbracoClientDependencyLoader.cs b/src/Umbraco.Web/UI/JavaScript/UmbracoClientDependencyLoader.cs new file mode 100644 index 0000000000..7b8397d047 --- /dev/null +++ b/src/Umbraco.Web/UI/JavaScript/UmbracoClientDependencyLoader.cs @@ -0,0 +1,44 @@ +using System.Web.UI; +using ClientDependency.Core.Controls; +using ClientDependency.Core.FileRegistration.Providers; +using Umbraco.Core.IO; + +namespace Umbraco.Web.UI.JavaScript +{ + /// + /// Used to load in all client dependencies for Umbraco. + /// Ensures that both UmbracoClient and UmbracoRoot paths are added to the loader. + /// + public class UmbracoClientDependencyLoader : ClientDependencyLoader + { + /// + /// Set the defaults + /// + public UmbracoClientDependencyLoader() + : base() + { + this.AddPath("UmbracoClient", IOHelper.ResolveUrl(SystemDirectories.UmbracoClient)); + this.AddPath("UmbracoRoot", IOHelper.ResolveUrl(SystemDirectories.Umbraco)); + this.ProviderName = LoaderControlProvider.DefaultName; + + } + + public static ClientDependencyLoader TryCreate(Control parent, out bool isNew) + { + if (ClientDependencyLoader.Instance == null) + { + var loader = new UmbracoClientDependencyLoader(); + parent.Controls.Add(loader); + isNew = true; + return loader; + } + else + { + isNew = false; + return ClientDependencyLoader.Instance; + } + + } + + } +} diff --git a/src/Umbraco.Web/UI/Pages/BasePage.cs b/src/Umbraco.Web/UI/Pages/BasePage.cs index e2c64b7ece..c5e8580339 100644 --- a/src/Umbraco.Web/UI/Pages/BasePage.cs +++ b/src/Umbraco.Web/UI/Pages/BasePage.cs @@ -1,138 +1,71 @@ using System; -using System.Data; using System.Web; -using System.Linq; using System.Web.Mvc; using System.Web.Routing; -using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Profiling; using Umbraco.Core.Services; using Umbraco.Web.Security; -using umbraco.BusinessLogic; -using umbraco.DataLayer; using System.Web.UI; +using Umbraco.Core.Persistence; +using Umbraco.Web.Composing; namespace Umbraco.Web.UI.Pages { /// /// umbraco.BasePages.BasePage is the default page type for the umbraco backend. - /// The basepage keeps track of the current user and the page context. But does not + /// The basepage keeps track of the current user and the page context. But does not /// Restrict access to the page itself. /// The keep the page secure, the umbracoEnsuredPage class should be used instead /// public class BasePage : Page { - - private ClientTools _clientTools; - - - //We won't expose this... people should be using the DatabaseContext for custom queries if they need them. - - ///// - ///// Gets the SQL helper. - ///// - ///// The SQL helper. - //protected ISqlHelper SqlHelper - //{ - // get { return global::umbraco.BusinessLogic.Application.SqlHelper; } - //} - - /// - /// Returns an ILogger - /// - public ILogger Logger - { - get { return ProfilingLogger.Logger; } - } - - /// - /// Returns a ProfilingLogger - /// - public ProfilingLogger ProfilingLogger - { - get { return _logger ?? (_logger = new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler)); } - } - - private ProfilingLogger _logger; - - - private UrlHelper _url; - /// - /// Returns a UrlHelper - /// - /// - /// This URL helper is created without any route data and an empty request context - /// - public UrlHelper Url - { - get { return _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); } - } - + private UrlHelper _url; private HtmlHelper _html; - /// - /// Returns a HtmlHelper - /// - /// - /// This html helper is created with an empty context and page so it may not have all of the functionality expected. - /// - public HtmlHelper Html - { - get { return _html ?? (_html = new HtmlHelper(new ViewContext(), new ViewPage())); } - } + private ClientTools _clientTools; /// - /// Returns the current ApplicationContext + /// Gets the logger. /// - public ApplicationContext ApplicationContext - { - get { return ApplicationContext.Current; } - } + public ILogger Logger => Current.Logger; /// - /// Returns the current UmbracoContext + /// Gets the profiling helper. /// - public UmbracoContext UmbracoContext - { - get { return UmbracoContext.Current; } - } + public ProfilingLogger ProfilingLogger => Current.ProfilingLogger; /// - /// Returns the current WebSecurity instance + /// Gets the Url helper. /// - public WebSecurity Security - { - get { return UmbracoContext.Security; } - } + /// This URL helper is created without any route data and an empty request context. + public UrlHelper Url => _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); /// - /// Returns a ServiceContext + /// Gets the Html helper. /// - public ServiceContext Services - { - get { return ApplicationContext.Services; } - } + /// This html helper is created with an empty context and page so it may not have all of the functionality expected. + public HtmlHelper Html => _html ?? (_html = new HtmlHelper(new ViewContext(), new ViewPage())); /// - /// Returns a DatabaseContext + /// Gets the Umbraco context. /// - public DatabaseContext DatabaseContext - { - get { return ApplicationContext.DatabaseContext; } - } + public UmbracoContext UmbracoContext => Current.UmbracoContext; /// - /// Returns a refernce of an instance of ClientTools for access to the pages client API + /// Gets the web security helper. /// - public ClientTools ClientTools - { - get { return _clientTools ?? (_clientTools = new ClientTools(this)); } - } - - + public WebSecurity Security => UmbracoContext.Security; + + /// + /// Gets the services context. + /// + public ServiceContext Services => Current.Services; + + /// + /// Gets an instance of ClientTools for access to the pages client API. + /// + public ClientTools ClientTools => _clientTools ?? (_clientTools = new ClientTools(this)); /// /// Raises the event. @@ -142,12 +75,10 @@ namespace Umbraco.Web.UI.Pages { base.OnLoad(e); - if (!Request.IsSecureConnection && GlobalSettings.UseSSL) - { - string serverName = HttpUtility.UrlEncode(Request.ServerVariables["SERVER_NAME"]); - Response.Redirect(string.Format("https://{0}{1}", serverName, Request.FilePath)); - } - } + if (Request.IsSecureConnection || UmbracoConfig.For.GlobalSettings().UseHttps == false) return; + var serverName = HttpUtility.UrlEncode(Request.ServerVariables["SERVER_NAME"]); + Response.Redirect($"https://{serverName}{Request.FilePath}"); + } } } diff --git a/src/Umbraco.Web/UI/Pages/ClientTools.cs b/src/Umbraco.Web/UI/Pages/ClientTools.cs index 44464676a4..7b59725101 100644 --- a/src/Umbraco.Web/UI/Pages/ClientTools.cs +++ b/src/Umbraco.Web/UI/Pages/ClientTools.cs @@ -1,65 +1,60 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Web; -using Umbraco.Core.IO; -using umbraco.BasePages; +using Umbraco.Core.IO; using System.Web.UI; -using umbraco.BusinessLogic; using Umbraco.Core; namespace Umbraco.Web.UI.Pages { - /// - /// Renders the client side code necessary to interact with the Umbraco client side API. - /// Each method returns an instance of this class so you can chain calls together. - /// - public sealed class ClientTools - { + /// + /// Renders the client side code necessary to interact with the Umbraco client side API. + /// Each method returns an instance of this class so you can chain calls together. + /// + public sealed class ClientTools + { - public ClientTools(Page page) - { - _page = page; - } + public ClientTools(Page page) + { + _page = page; + } - /// - /// Returns the string markup for the JavaScript that is rendered. - /// If referencing JavaScript scripts in the backend, this class should be used - /// in case future changes to the client code is change, this will remain intact. - /// - public static class Scripts - { - internal const string ClientMgrScript = "UmbClientMgr"; - public static string GetAppActions { get { return string.Format("{0}.appActions()", ClientMgrScript); } } - public static string GetMainWindow { get { return string.Format("{0}.mainWindow()", ClientMgrScript); } } - public static string GetMainTree { get { return string.Format("{0}.mainTree()", ClientMgrScript); } } - public static string GetContentFrame() { return string.Format("{0}.contentFrame()", ClientMgrScript); } - public static string ShiftApp(string appAlias) - { + /// + /// Returns the string markup for the JavaScript that is rendered. + /// If referencing JavaScript scripts in the backend, this class should be used + /// in case future changes to the client code is change, this will remain intact. + /// + public static class Scripts + { + internal const string ClientMgrScript = "UmbClientMgr"; + public static string GetAppActions { get { return string.Format("{0}.appActions()", ClientMgrScript); } } + public static string GetMainWindow { get { return string.Format("{0}.mainWindow()", ClientMgrScript); } } + public static string GetMainTree { get { return string.Format("{0}.mainTree()", ClientMgrScript); } } + public static string GetContentFrame() { return string.Format("{0}.contentFrame()", ClientMgrScript); } + public static string ShiftApp(string appAlias) + { return string.Format(ClientMgrScript + ".historyManager().addHistory('{0}')", appAlias); - } - public static string OpenDashboard(string app) - { - return string.Format(GetAppActions + ".openDashboard('{0}');", app); - } - public static string RefreshAdmin { get { return "setTimeout('" + GetMainWindow + ".location.reload()', {0});"; } } - public static string ShowSpeechBubble { get { return GetMainWindow + ".UmbSpeechBubble.ShowMessage('{0}','{1}', '{2}');"; } } - public static string ChangeContentFrameUrl(string url) { - return string.Format(ClientMgrScript + ".contentFrame('{0}');", url); - } + } + public static string OpenDashboard(string app) + { + return string.Format(GetAppActions + ".openDashboard('{0}');", app); + } + public static string RefreshAdmin { get { return "setTimeout('" + GetMainWindow + ".location.reload()', {0});"; } } + public static string ShowSpeechBubble { get { return GetMainWindow + ".UmbSpeechBubble.ShowMessage('{0}','{1}', '{2}');"; } } + public static string ChangeContentFrameUrl(string url) { + return string.Format(ClientMgrScript + ".contentFrame('{0}');", url); + } public static string ReloadContentFrameUrlIfPathLoaded(string url) { return string.Format(ClientMgrScript + ".reloadContentFrameUrlIfPathLoaded('{0}');", url); } - public static string ReloadLocation { get { return string.Format(ClientMgrScript + ".reloadLocation();"); } } + public static string ReloadLocation { get { return ClientMgrScript + ".reloadLocation();"; } } + public static string ReloadLocationIfMatched { get { return ClientMgrScript + ".reloadLocation('{0}');"; } } public static string ChildNodeCreated = GetMainTree + ".childNodeCreated();"; - public static string SyncTree { get { return GetMainTree + ".syncTree('{0}', {1});"; } } - public static string ClearTreeCache { get { return GetMainTree + ".clearTreeCache();"; } } - public static string CopyNode { get { return GetMainTree + ".copyNode('{0}', '{1}');"; } } - public static string MoveNode { get { return GetMainTree + ".moveNode('{0}', '{1}');"; } } - public static string ReloadActionNode { get { return GetMainTree + ".reloadActionNode({0}, {1}, null);"; } } - public static string SetActiveTreeType { get { return GetMainTree + ".setActiveTreeType('{0}');"; } } + public static string SyncTree { get { return GetMainTree + ".syncTree('{0}', {1});"; } } + public static string ClearTreeCache { get { return GetMainTree + ".clearTreeCache();"; } } + public static string CopyNode { get { return GetMainTree + ".copyNode('{0}', '{1}');"; } } + public static string MoveNode { get { return GetMainTree + ".moveNode('{0}', '{1}');"; } } + public static string ReloadActionNode { get { return GetMainTree + ".reloadActionNode({0}, {1}, null);"; } } + public static string SetActiveTreeType { get { return GetMainTree + ".setActiveTreeType('{0}');"; } } public static string RefreshTree { get { return GetMainTree + ".refreshTree();"; } } public static string RefreshTreeType { get { return GetMainTree + ".refreshTree('{0}');"; } } public static string CloseModalWindow() @@ -79,41 +74,41 @@ namespace Umbraco.Web.UI.Pages return string.Format("{0}.openModalWindow('{1}', '{2}', {3}, {4}, {5}, {6}, {7}, '{8}', '{9}');", new object[] { ClientMgrScript, url, name, showHeader.ToString().ToLower(), width, height, top, leftOffset, closeTriggers, onCloseCallback }); } - } + } - private readonly Page _page; + private readonly Page _page; - /// - /// This removes all tree JSON data cached in the client browser. - /// Useful when you want to ensure that the tree is reloaded from live data. - /// - /// - public ClientTools ClearClientTreeCache() - { - RegisterClientScript(Scripts.ClearTreeCache); - return this; - } + /// + /// This removes all tree JSON data cached in the client browser. + /// Useful when you want to ensure that the tree is reloaded from live data. + /// + /// + public ClientTools ClearClientTreeCache() + { + RegisterClientScript(Scripts.ClearTreeCache); + return this; + } - /// - /// Change applications - /// - /// - public ClientTools ShiftApp(string appAlias) - { - RegisterClientScript(Scripts.ShiftApp(appAlias)); - return this; - } - - /// - /// Refresh the entire administration console after a specified amount of time. - /// - /// - /// - public ClientTools RefreshAdmin(int seconds) - { - RegisterClientScript(string.Format(Scripts.RefreshAdmin, seconds * 1000)); - return this; - } + /// + /// Change applications + /// + /// + public ClientTools ShiftApp(string appAlias) + { + RegisterClientScript(Scripts.ShiftApp(appAlias)); + return this; + } + + /// + /// Refresh the entire administration console after a specified amount of time. + /// + /// + /// + public ClientTools RefreshAdmin(int seconds) + { + RegisterClientScript(string.Format(Scripts.RefreshAdmin, seconds * 1000)); + return this; + } /// /// Refreshes the entire current tree @@ -130,34 +125,34 @@ namespace Umbraco.Web.UI.Pages RegisterClientScript(string.Format(Scripts.RefreshTreeType, treeType)); return this; } - - /// - /// A reference to the umbraco UI component "speechbubble". The speechbubble appears in the lower right corner of the screen, notifying users of events - /// - /// The speechbubble icon. - /// The speechbubble header. - /// The body text - public ClientTools ShowSpeechBubble(SpeechBubbleIcon i, string header, string body) - { - RegisterClientScript(string.Format(Scripts.ShowSpeechBubble, i.ToString(), header.Replace("'", "\\'"), body.Replace("'", "\\'"))); - return this; - } - - /// - /// Changes the content in the content frame to the specified URL - /// - /// - public ClientTools ChangeContentFrameUrl(string url) - { + + /// + /// A reference to the umbraco UI component "speechbubble". The speechbubble appears in the lower right corner of the screen, notifying users of events + /// + /// The speechbubble icon. + /// The speechbubble header. + /// The body text + public ClientTools ShowSpeechBubble(SpeechBubbleIcon i, string header, string body) + { + RegisterClientScript(string.Format(Scripts.ShowSpeechBubble, i.ToString(), header.Replace("'", "\\'"), body.Replace("'", "\\'"))); + return this; + } + + /// + /// Changes the content in the content frame to the specified URL + /// + /// + public ClientTools ChangeContentFrameUrl(string url) + { //don't load if there is no url - if (string.IsNullOrEmpty(url)) return this; + if (string.IsNullOrEmpty(url)) return this; url = EnsureUmbracoUrl(url); RegisterClientScript(Scripts.ChangeContentFrameUrl(url)); - + return this; - } + } /// /// Reloads the content in the content frame if the specified URL is currently loaded @@ -174,16 +169,19 @@ namespace Umbraco.Web.UI.Pages return this; } - /// - /// Reloads location, refreshing what is in the content frame - /// - public ClientTools ReloadLocation() + public ClientTools ReloadLocationIfMatched(string routePath) { - RegisterClientScript(Scripts.ReloadLocation); - + RegisterClientScript(string.Format(Scripts.ReloadLocationIfMatched, routePath)); return this; } + public ClientTools ReloadLocation() + { + RegisterClientScript(Scripts.ReloadLocation); + return this; + } + + private string EnsureUmbracoUrl(string url) { if (url.StartsWith("/") && url.StartsWith(IOHelper.ResolveUrl(SystemDirectories.Umbraco)) == false) @@ -203,117 +201,117 @@ namespace Umbraco.Web.UI.Pages /// /// public ClientTools ShowDashboard(string app) - { + { return ChangeContentFrameUrl(SystemDirectories.Umbraco + string.Format("/dashboard.aspx?app={0}", app)); - } - - /// - /// Reloads the children of the current action node and selects the node that didn't exist there before. - /// If the client side system cannot determine which node is new, then no node is selected. - /// - /// - /// This is used by many create dialogs, however the sync method should be used based on the full path of the - /// node but because the current Umbraco implementation of ITask only returns a url to load, there's no way - /// to determine what the full path of the new child is. - /// - /// - public ClientTools ChildNodeCreated() - { - RegisterClientScript(Scripts.ChildNodeCreated); - return this; - } + } - /// - /// Synchronizes the tree to the path specified. - /// - /// - /// - /// If set to true, will ensure that the node to be synced has it's data - /// reloaded from the server. Otherwise, if the node already exists, the tree will simply sync to the node - /// that is already there. - /// - /// - /// This will work for any tree, however you would need to know the path of the node. Currently, media and content - /// are the only trees that store a path, however, if you were working in the template tree for example, a path to a - /// node could be "init,1090" and this method would still work. - /// - /// Sync tree will works by syncing the active tree type. This can be specified explicitly by calling SetActiveTreeType. - /// This will allow developers to sync many trees in one application at one time if needed. - /// - /// - /// - /// - public ClientTools SyncTree(string path, bool forceReload) - { - RegisterClientScript(string.Format(Scripts.SyncTree, path, forceReload.ToString().ToLower())); - return this; - } + /// + /// Reloads the children of the current action node and selects the node that didn't exist there before. + /// If the client side system cannot determine which node is new, then no node is selected. + /// + /// + /// This is used by many create dialogs, however the sync method should be used based on the full path of the + /// node but because the current Umbraco implementation of ITask only returns a url to load, there's no way + /// to determine what the full path of the new child is. + /// + /// + public ClientTools ChildNodeCreated() + { + RegisterClientScript(Scripts.ChildNodeCreated); + return this; + } - public ClientTools CopyNode(string currNodeId, string newParentPath) - { - RegisterClientScript(string.Format(Scripts.CopyNode, currNodeId, newParentPath)); - return this; - } + /// + /// Synchronizes the tree to the path specified. + /// + /// + /// + /// If set to true, will ensure that the node to be synced has it's data + /// reloaded from the server. Otherwise, if the node already exists, the tree will simply sync to the node + /// that is already there. + /// + /// + /// This will work for any tree, however you would need to know the path of the node. Currently, media and content + /// are the only trees that store a path, however, if you were working in the template tree for example, a path to a + /// node could be "init,1090" and this method would still work. + /// + /// Sync tree will works by syncing the active tree type. This can be specified explicitly by calling SetActiveTreeType. + /// This will allow developers to sync many trees in one application at one time if needed. + /// + /// + /// + /// + public ClientTools SyncTree(string path, bool forceReload) + { + RegisterClientScript(string.Format(Scripts.SyncTree, path, forceReload.ToString().ToLower())); + return this; + } - public ClientTools MoveNode(string currNodeId, string newParentPath) - { - RegisterClientScript(string.Format(Scripts.MoveNode, currNodeId, newParentPath)); - return this; - } + public ClientTools CopyNode(string currNodeId, string newParentPath) + { + RegisterClientScript(string.Format(Scripts.CopyNode, currNodeId, newParentPath)); + return this; + } - /// - /// Reloads only the last node that the user interacted with via the context menu. To reload a specify node, use SyncTree. - /// - /// - /// - /// - /// If for whatever reason the client side system cannot just refresh the one node, the system will use jsTree's built in - /// refresh tool, this however won't allow for reselect or reloadChildren. Most trees will work with the single node - /// refresh but 3rd party tools may have poorly built tree data models. - /// - public ClientTools ReloadActionNode(bool reselect, bool reloadChildren) - { - RegisterClientScript(string.Format(Scripts.ReloadActionNode, (!reselect).ToString().ToLower(), (!reloadChildren).ToString().ToLower())); - return this; - } - - /// - /// When the application searches for a node, it searches for nodes in specific tree types. - /// If SyncTree is used, it will sync the tree nodes with the active tree type, therefore if - /// a developer wants to sync a specific tree, they can call this method to set the type to sync. - /// - /// - /// Each branch of a particular tree should theoretically be the same type, however, developers can - /// override the type of each branch in their BaseTree's but this is not standard practice. If there - /// are multiple types of branches in one tree, then only those branches that have the Active tree type - /// will be searched for syncing. - /// - /// - /// - public ClientTools SetActiveTreeType(string treeType) - { - RegisterClientScript(string.Format(Scripts.SetActiveTreeType, treeType)); - return this; - } + public ClientTools MoveNode(string currNodeId, string newParentPath) + { + RegisterClientScript(string.Format(Scripts.MoveNode, currNodeId, newParentPath)); + return this; + } - /// + /// + /// Reloads only the last node that the user interacted with via the context menu. To reload a specify node, use SyncTree. + /// + /// + /// + /// + /// If for whatever reason the client side system cannot just refresh the one node, the system will use jsTree's built in + /// refresh tool, this however won't allow for reselect or reloadChildren. Most trees will work with the single node + /// refresh but 3rd party tools may have poorly built tree data models. + /// + public ClientTools ReloadActionNode(bool reselect, bool reloadChildren) + { + RegisterClientScript(string.Format(Scripts.ReloadActionNode, (!reselect).ToString().ToLower(), (!reloadChildren).ToString().ToLower())); + return this; + } + + /// + /// When the application searches for a node, it searches for nodes in specific tree types. + /// If SyncTree is used, it will sync the tree nodes with the active tree type, therefore if + /// a developer wants to sync a specific tree, they can call this method to set the type to sync. + /// + /// + /// Each branch of a particular tree should theoretically be the same type, however, developers can + /// override the type of each branch in their BaseTree's but this is not standard practice. If there + /// are multiple types of branches in one tree, then only those branches that have the Active tree type + /// will be searched for syncing. + /// + /// + /// + public ClientTools SetActiveTreeType(string treeType) + { + RegisterClientScript(string.Format(Scripts.SetActiveTreeType, treeType)); + return this; + } + + /// /// Closes the Umbraco dialog window if it is open - /// - /// specify a value to return to add to the onCloseCallback method if one was specified in the OpenModalWindow method - /// - public ClientTools CloseModalWindow(string returnVal) - { - RegisterClientScript(Scripts.CloseModalWindow(returnVal)); - return this; - } + /// + /// specify a value to return to add to the onCloseCallback method if one was specified in the OpenModalWindow method + /// + public ClientTools CloseModalWindow(string returnVal) + { + RegisterClientScript(Scripts.CloseModalWindow(returnVal)); + return this; + } /// /// Closes the umbraco dialog window if it is open /// @@ -324,24 +322,24 @@ namespace Umbraco.Web.UI.Pages } - /// - /// Opens a modal window - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public ClientTools OpenModalWindow(string url, string name, bool showHeader, int width, int height, int top, int leftOffset, string closeTriggers, string onCloseCallback) - { - RegisterClientScript(Scripts.OpenModalWindow(url, name, showHeader, width, height, top, leftOffset, closeTriggers, onCloseCallback)); - return this; - } + /// + /// Opens a modal window + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public ClientTools OpenModalWindow(string url, string name, bool showHeader, int width, int height, int top, int leftOffset, string closeTriggers, string onCloseCallback) + { + RegisterClientScript(Scripts.OpenModalWindow(url, name, showHeader, width, height, top, leftOffset, closeTriggers, onCloseCallback)); + return this; + } /// /// This will use the ScriptManager to register the script if one is available, otherwise will default to the ClientScript @@ -349,9 +347,9 @@ namespace Umbraco.Web.UI.Pages /// /// private void RegisterClientScript(string script) - { - //use the hash code of the script to generate the key, this way, the exact same script won't be - //inserted more than once. + { + //use the hash code of the script to generate the key, this way, the exact same script won't be + //inserted more than once. if (ScriptManager.GetCurrent(_page) != null) { ScriptManager.RegisterStartupScript(_page, _page.GetType(), script.GetHashCode().ToString(), script, true); @@ -360,10 +358,10 @@ namespace Umbraco.Web.UI.Pages { _page.ClientScript.RegisterStartupScript(_page.GetType(), script.GetHashCode().ToString(), script, true); } - } + } - } + } } diff --git a/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs b/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs index c78f66ab97..b373549b99 100644 --- a/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs +++ b/src/Umbraco.Web/UI/Pages/UmbracoEnsuredPage.cs @@ -1,16 +1,19 @@ -using System; +using System; +using System.Globalization; using System.Linq; +using System.Security; using System.Web; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Web.Security; -using umbraco; -using umbraco.BusinessLogic; -using umbraco.businesslogic.Exceptions; -using umbraco.interfaces; using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Exceptions; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Security; +using Umbraco.Web.Composing; +using Umbraco.Web.Security; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.UI.Pages { @@ -25,7 +28,7 @@ namespace Umbraco.Web.UI.Pages var treeAuth = this.GetType().GetCustomAttribute(true); if (treeAuth != null) { - var treeByAlias = ApplicationContext.Current.Services.ApplicationTreeService + var treeByAlias = Current.Services.ApplicationTreeService .GetByAlias(treeAuth.TreeAlias); if (treeByAlias != null) { @@ -42,29 +45,28 @@ namespace Umbraco.Web.UI.Pages /// protected void CheckPathAndPermissions(int entityId, UmbracoObjectTypes objectType, IAction actionToCheck) { - if (objectType == UmbracoObjectTypes.Document || objectType == UmbracoObjectTypes.Media) + if (objectType != UmbracoObjectTypes.Document && objectType != UmbracoObjectTypes.Media) + return; + + //check path access + + var entity = entityId == Constants.System.Root + ? EntitySlim.Root + : Services.EntityService.Get(entityId, objectType); + var hasAccess = Security.CurrentUser.HasPathAccess(entity, Services.EntityService, objectType == UmbracoObjectTypes.Document ? Constants.System.RecycleBinContent : Constants.System.RecycleBinMedia); + if (hasAccess == false) + throw new AuthorizationException($"The current user doesn't have access to the path '{entity.Path}'"); + + //only documents have action permissions + if (objectType == UmbracoObjectTypes.Document) { - //check path access - - var entity = entityId == Constants.System.Root - ? UmbracoEntity.Root - : Services.EntityService.Get( - entityId, - objectType); - var hasAccess = Security.CurrentUser.HasPathAccess( - entity, - Services.EntityService, - objectType == UmbracoObjectTypes.Document ? Constants.System.RecycleBinContent : Constants.System.RecycleBinMedia); - if (hasAccess == false) - throw new UserAuthorizationException(string.Format("The current user doesn't have access to the path '{0}'", entity.Path)); - - //only documents have action permissions - if (objectType == UmbracoObjectTypes.Document) - { - var allowedActions = ActionsResolver.Current.FromActionSymbols(Security.CurrentUser.GetPermissions(entity.Path, Services.UserService)).ToArray(); - if (allowedActions.Contains(actionToCheck) == false) - throw new UserAuthorizationException(string.Format("The current user doesn't have permission to {0} on the path '{1}'", actionToCheck.Alias, entity.Path)); - } + var allActions = Current.Actions; + var perms = Security.CurrentUser.GetPermissions(entity.Path, Services.UserService); + var actions = perms + .Select(x => allActions.FirstOrDefault(y => y.Letter.ToString(CultureInfo.InvariantCulture) == x)) + .WhereNotNull(); + if (actions.Contains(actionToCheck) == false) + throw new AuthorizationException($"The current user doesn't have permission to {actionToCheck.Alias} on the path '{entity.Path}'"); } } @@ -75,7 +77,7 @@ namespace Umbraco.Web.UI.Pages /// /// /// - /// Checks if the page exists outside of the /umbraco route, in which case the request will not have been authenticated for the back office + /// Checks if the page exists outside of the /umbraco route, in which case the request will not have been authenticated for the back office /// so we'll force authentication. /// protected override void OnPreInit(EventArgs e) @@ -85,7 +87,7 @@ namespace Umbraco.Web.UI.Pages //If this is not a back office request, then the module won't have authenticated it, in this case we // need to do the auth manually and since this is an UmbracoEnsuredPage, this is the anticipated behavior // TODO: When we implement Identity, this process might not work anymore, will be an interesting challenge - if (Context.Request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) == false) + if (Context.Request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath, UmbracoConfig.For.GlobalSettings()) == false) { var http = new HttpContextWrapper(Context); var ticket = http.GetUmbracoAuthTicket(); @@ -99,8 +101,8 @@ namespace Umbraco.Web.UI.Pages if (!Security.ValidateUserApp(CurrentApp)) { - var ex = new UserAuthorizationException(String.Format("The current user doesn't have access to the section/app '{0}'", CurrentApp)); - LogHelper.Error(String.Format("Tried to access '{0}'", CurrentApp), ex); + var ex = new SecurityException(String.Format("The current user doesn't have access to the section/app '{0}'", CurrentApp)); + Current.Logger.Error(String.Format("Tried to access '{0}'", CurrentApp), ex); throw ex; } @@ -127,26 +129,7 @@ namespace Umbraco.Web.UI.Pages /// If true then umbraco will force any window/frame to reload umbraco in the main window /// protected bool RedirectToUmbraco { get; set; } - - /// - /// Returns the current user - /// - [Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object")] - protected User UmbracoUser - { - get - { - //throw exceptions if not valid (true) - if (!_hasValidated) - { - Security.ValidateCurrentUser(true); - _hasValidated = true; - } - - return new User(Security.CurrentUser); - } - } - + /// /// Used to assign a webforms page's security to a specific tree which will in turn check to see /// if the current user has access to the specified tree's registered section @@ -162,4 +145,4 @@ namespace Umbraco.Web.UI.Pages } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2a164b6ea1..eb057193e4 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1,349 +1,195 @@  - + - 9.0.30729 - 2.0 + v4.7 + false + false {651E1350-91B6-44B7-BD60-7207006D7003} - Debug - AnyCPU - - - - - umbraco - - - JScript - Grid - IE50 - false Library + Umbraco.Web Umbraco.Web - OnBuildSuccess - - - - - - - - - - - - - - - 4.0 - v4.5 - ..\ true + Off + - bin\Debug\ - false - 285212672 - false - - - DEBUG;TRACE - - true - 4096 - false - - - false - false - false - false - 4 full + false + bin\Debug\ + DEBUG;TRACE prompt - AllRules.ruleset + 4 false - Off + latest - bin\Release\ - false - 285212672 - false - - - TRACE - bin\Release\umbraco.xml - true - 4096 - false - - - true - false - false - false - 4 pdbonly + true + bin\Release\ + TRACE prompt - AllRules.ruleset + 4 + bin\Release\Umbraco.Web.xml false - Off + latest - - {07fbc26b-2927-4a22-8d96-d644c667fecc} - UmbracoExamine - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll - True - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - True - - - ..\packages\ClientDependency.1.9.6\lib\net45\ClientDependency.Core.dll - - - ..\packages\dotless.1.5.2\lib\dotless.Core.dll - - - ..\packages\Examine.0.1.89\lib\net45\Examine.dll - - - ..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll - True - - - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - - ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - - - ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll - True - - - ..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll - True - - - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll - True - - - ..\packages\Microsoft.AspNet.SignalR.Core.2.2.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll - True - - - ..\packages\Microsoft.Owin.3.1.0\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.SystemWeb.3.1.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\packages\Microsoft.Owin.Security.3.1.0\lib\net45\Microsoft.Owin.Security.dll - - - ..\packages\Microsoft.Owin.Security.Cookies.3.1.0\lib\net45\Microsoft.Owin.Security.Cookies.dll - - - ..\packages\Microsoft.Owin.Security.OAuth.3.1.0\lib\net45\Microsoft.Owin.Security.OAuth.dll - - - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - True - - - ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - True - - - ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - ..\packages\semver.1.1.2\lib\net45\Semver.dll - - - System - - + - - System.Data - + - - System.Drawing - + - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - + - - ..\packages\System.Threading.Tasks.Dataflow.4.7.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - + + - - 3.5 - + - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - True - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll - True - - - ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll - True - - - ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll - True - - - System.Web.Services - - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll - True - - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll - True - - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - True - + - - System.XML - + - - {5BA5425F-27A7-4677-865E-82246498AA2E} - SqlCE4Umbraco - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} + {31785bc3-256c-4613-b2f5-a1b0bdded8c1} Umbraco.Core - - {6EDD2061-82F2-461B-BB6E-879245A832DE} - umbraco.controls - - - umbraco.businesslogic - {E469A9CE-1BEC-423F-AC44-713CD72457EA} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {CCD75EC3-63DB-4184-B49D-51C1DD337230} - umbraco.cms - - - {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} - umbraco.datalayer - - - umbraco.interfaces - {511F6D8D-7717-440A-9A57-A507E9A8B27F} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {D7636876-0756-43CB-A192-138C6F0D5E42} - umbraco.providers + + Umbraco.Examine + {07FBC26B-2927-4A22-8D96-D644C667FECC} + + Properties\SolutionInfo.cs - + - - - - - - - - - - - - - - - - - - + + - - - - - - - + + + + + + - - + + + - - + - + + + - - + - + + - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -356,37 +202,296 @@ - - + + + - - - - - - - - + - - + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -399,45 +504,17 @@ - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - @@ -448,46 +525,15 @@ - - - - - + + - - - - - - - - - - - - - - - - - - - - - - + - - + - - - - - - @@ -500,36 +546,25 @@ + - - - - - + - - - - - ASPXCodeBehind - + + - - - - - + @@ -542,7 +577,23 @@ - + + + + + + + + + + + + + + + + + @@ -558,7 +609,6 @@ - @@ -577,20 +627,14 @@ - - - - - - @@ -615,12 +659,10 @@ - - - + @@ -633,13 +675,11 @@ - - - + + - @@ -660,37 +700,28 @@ - - - - - + + + - + - - - - - - - @@ -699,42 +730,37 @@ - - - + + - - - + + + - - - + - - - + + - @@ -748,23 +774,18 @@ - - - - - - - - - + + + + + - @@ -784,8 +805,10 @@ ASPXCodeBehind + + ASPXCodeBehind + - True @@ -795,14 +818,11 @@ - - - @@ -814,7 +834,6 @@ - @@ -830,7 +849,6 @@ - @@ -843,7 +861,6 @@ - @@ -857,22 +874,21 @@ - + - + - - - + + + - + - - + @@ -883,10 +899,9 @@ - - + @@ -897,8 +912,6 @@ - - @@ -917,19 +930,10 @@ - - - - - - - - - @@ -939,28 +943,11 @@ - - - - - - - - - - - - - - - - - @@ -982,8 +969,8 @@ Resources.resx - - + + @@ -991,16 +978,12 @@ - ASPXCodeBehind ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind @@ -1014,7 +997,6 @@ AssignDomain2.aspx - ASPXCodeBehind @@ -1022,7 +1004,6 @@ ASPXCodeBehind - @@ -1047,10 +1028,8 @@ - - @@ -1067,7 +1046,7 @@ ASPXCodeBehind - + @@ -1101,39 +1080,73 @@ - - - - - - - - + - - - - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1152,40 +1165,24 @@ ASPXCodeBehind - ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind @@ -1198,15 +1195,10 @@ ASPXCodeBehind - - - ASPXCodeBehind - ASPXCodeBehind - @@ -1216,75 +1208,49 @@ - - - + + - - - - - - ASPXCodeBehind - - - - - + - - - - - + - - + - - Code - - - Code - Code Code - - Code - Code - Code @@ -1293,37 +1259,12 @@ True Settings.settings - - Code - - - - - Code - - - Code - - - Code - Code - - - ASPXCodeBehind - Code - - Code - - - Code - - delete.aspx ASPXCodeBehind @@ -1347,7 +1288,6 @@ publish.aspx - ASPXCodeBehind publish.aspx @@ -1355,13 +1295,7 @@ - - ASPXCodeBehind - - - - ProgressBar.ascx ASPXCodeBehind @@ -1397,7 +1331,6 @@ EditRelationType.aspx - ASPXCodeBehind EditRelationType.aspx @@ -1409,7 +1342,6 @@ NewRelationType.aspx - RelationTypesWebService.asmx Component @@ -1423,16 +1355,6 @@ Preview.aspx - - MemberSearch.ascx - ASPXCodeBehind - - - MemberSearch.ascx - - - - xsltVisualize.aspx ASPXCodeBehind @@ -1456,7 +1378,6 @@ republish.aspx - ASPXCodeBehind republish.aspx @@ -1530,7 +1451,6 @@ rollBack.aspx - ASPXCodeBehind rollBack.aspx @@ -1549,47 +1469,6 @@ viewAuditTrail.aspx - - EditMemberGroup.aspx - ASPXCodeBehind - - - EditMemberGroup.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - ViewMembers.aspx - ASPXCodeBehind - - - ViewMembers.aspx - - - Code - - - - - - - - - - tinymce3tinymceCompress.aspx - ASPXCodeBehind - - - tinymce3tinymceCompress.aspx - - - QuickSearchHandler.ashx - DictionaryItemList.aspx ASPXCodeBehind @@ -1611,37 +1490,12 @@ editLanguage.aspx - - - Code - - - - - - - - - - - - - MacroContainerService.asmx - Component - - - TagsAutoCompleteHandler.ashx - - - TreeClientService.asmx - Component - @@ -1680,39 +1534,10 @@ xml.aspx - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - TreeDataService.ashx - @@ -1730,10 +1555,6 @@ CheckForUpgrade.asmx Component - - CMSNode.asmx - Component - codeEditorSave.asmx Component @@ -1746,47 +1567,9 @@ nodeSorter.asmx Component - - progressStatus.asmx - Component - - - publication.asmx - Component - - ASPXCodeBehind - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - @@ -1801,7 +1584,6 @@ - @@ -1814,130 +1596,8 @@ - - Mvc\web.config - - - - MSDiscoCodeGenerator - Reference.cs - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - ASPXCodeBehind - - - - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - Reference.map - - - Reference.map - - - SettingsSingleFileGenerator - Settings1.Designer.cs - - - - - - ASPXCodeBehind - - - - - - - - - - Form - - - Designer - - - ASPXCodeBehind - - - - - Form - - - - - - - Form - - - - - - - - - - XmlTree.xsd - - - - - - MSDiscoCodeGenerator - Reference.cs - + + ResXFileCodeGenerator Resources.Designer.cs @@ -1952,26 +1612,124 @@ Resources.Designer.cs Designer - - - - - - package.xsd - - - umbraco.xsd - - - umbraco.xsd - - - umbraco.xsd - + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + ASPXCodeBehind + + + + + Form + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + + + XmlTree.xsd + + + + + MSDiscoCodeGenerator + Reference.cs + + + Mvc\web.config + + + MSDiscoCodeGenerator + Reference.cs + + + + + Reference.map + + + Reference.map + + + SettingsSingleFileGenerator + Settings1.Designer.cs + + + Dynamic @@ -1996,28 +1754,28 @@ umbraco_org_umbraco_update_CheckForUpgrade - - - - - 11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 - - - - - - - - - - - - - - + + + + $(PlatformTargetAsMSBuildArchitecture) + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings b/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings deleted file mode 100644 index 662f95686e..0000000000 --- a/src/Umbraco.Web/Umbraco.Web.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp50 \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index b9650a8230..5b0743676e 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -1,58 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Web; -using System.Web.Hosting; -using System.Web.Mvc; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Manifest; -using Umbraco.Web.Routing; -using umbraco.businesslogic; +using Umbraco.Core; +using Umbraco.Web.Runtime; namespace Umbraco.Web { - /// - /// The Umbraco global.asax class - /// + /// + /// Represents the Umbraco global.asax class. + /// public class UmbracoApplication : UmbracoApplicationBase - { - private ManifestWatcher _mw; - - protected override void OnApplicationStarted(object sender, EventArgs e) - { - base.OnApplicationStarted(sender, e); - - if (ApplicationContext.Current.IsConfigured && GlobalSettings.DebugMode) - { - var appPluginFolder = IOHelper.MapPath("~/App_Plugins/"); - if (Directory.Exists(appPluginFolder)) - { - _mw = new ManifestWatcher(LoggerResolver.Current.Logger); - _mw.Start(Directory.GetDirectories(appPluginFolder)); - } - } - } - - protected override void OnApplicationEnd(object sender, EventArgs e) - { - base.OnApplicationEnd(sender, e); - - if (_mw != null) - { - _mw.Dispose(); - } - } - - protected override IBootManager GetBootManager() - { - return new WebBootManager(this); - } - } + { + protected override IRuntime GetRuntime() + { + return new WebRuntime(this); + } + } } diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs index 9e789ac0c5..b23f38fcde 100644 --- a/src/Umbraco.Web/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs @@ -1,30 +1,19 @@ -using System; +using System; using System.Collections; using System.Globalization; using System.IO; -using System.Linq; -using System.Text; using System.Web; -using System.Web.Security; using System.Web.UI; -using System.Xml.Linq; -using System.Xml.XPath; -using HtmlAgilityPack; using Umbraco.Core; -using Umbraco.Core.Dictionary; -using Umbraco.Core.Dynamics; using Umbraco.Core.Models; -using Umbraco.Core.Security; -using Umbraco.Core.Services; -using Umbraco.Core.Xml; -using Umbraco.Web.Routing; -using Umbraco.Web.Security; using Umbraco.Web.Templates; using umbraco; using System.Collections.Generic; -using umbraco.cms.businesslogic.web; using umbraco.presentation.templateControls; -using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; +using Umbraco.Web.Macros; namespace Umbraco.Web { @@ -97,12 +86,12 @@ namespace Umbraco.Web public IHtmlString RenderMacro(string alias, IDictionary parameters) { - if (_umbracoContext.PublishedContentRequest == null) + if (_umbracoContext.PublishedRequest == null) { throw new InvalidOperationException("Cannot render a macro when there is no current PublishedContentRequest."); } - return RenderMacro(alias, parameters, _umbracoContext.PublishedContentRequest.UmbracoPage); + return RenderMacro(alias, parameters, _umbracoContext.PublishedRequest.UmbracoPage); } /// @@ -117,13 +106,11 @@ namespace Umbraco.Web if (alias == null) throw new ArgumentNullException("alias"); if (umbracoPage == null) throw new ArgumentNullException("umbracoPage"); - var m = macro.GetMacro(alias); + var m = Current.Services.MacroService.GetByAlias(alias); if (m == null) - { throw new KeyNotFoundException("Could not find macro with alias " + alias); - } - return RenderMacro(m, parameters, umbracoPage); + return RenderMacro(new MacroModel(m), parameters, umbracoPage); } /// @@ -133,10 +120,10 @@ namespace Umbraco.Web /// The parameters. /// The legacy umbraco page object that is required for some macros /// - internal IHtmlString RenderMacro(macro m, IDictionary parameters, page umbracoPage) + internal IHtmlString RenderMacro(MacroModel m, IDictionary parameters, page umbracoPage) { - if (umbracoPage == null) throw new ArgumentNullException("umbracoPage"); - if (m == null) throw new ArgumentNullException("m"); + if (umbracoPage == null) throw new ArgumentNullException(nameof(umbracoPage)); + if (m == null) throw new ArgumentNullException(nameof(m)); if (_umbracoContext.PageId == null) { @@ -146,16 +133,15 @@ namespace Umbraco.Web var macroProps = new Hashtable(); foreach (var i in parameters) { - //TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method of macro.cs + //TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method of macro.cs // looks for a lower case match. WTF. the whole macro concept needs to be rewritten. //NOTE: the value could have html encoded values, so we need to deal with that macroProps.Add(i.Key.ToLowerInvariant(), (i.Value is string) ? HttpUtility.HtmlDecode(i.Value.ToString()) : i.Value); } - var macroControl = m.renderMacro(macroProps, - umbracoPage.Elements, - _umbracoContext.PageId.Value); + var renderer = new MacroRenderer(Current.ProfilingLogger); + var macroControl = renderer.Render(m, umbracoPage.Elements, _umbracoContext.PageId.Value, macroProps).GetAsControl(); string html; if (macroControl is LiteralControl) @@ -180,9 +166,9 @@ namespace Umbraco.Web // within Razor since it will never be inserted into the page pipeline (which may even not exist at all // if we're running MVC). // - // I'm sure there's more things that will get lost with this context changing but I guess we'll figure + // I'm sure there's more things that will get lost with this context changing but I guess we'll figure // those out as we go along. One thing we lose is the content type response output. - // http://issues.umbraco.org/issue/U4-1599 if it is setup during the macro execution. So + // http://issues.umbraco.org/issue/U4-1599 if it is setup during the macro execution. So // here we'll save the content type response and reset it after execute is called. var contentType = _umbracoContext.HttpContext.Response.ContentType; @@ -200,121 +186,5 @@ namespace Umbraco.Web return new HtmlString(html); } - - /// - /// Renders an field to the template - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - //// - /// - public IHtmlString Field(IPublishedContent currentPage, string fieldAlias, - string altFieldAlias = "", string altText = "", string insertBefore = "", string insertAfter = "", - bool recursive = false, bool convertLineBreaks = false, bool removeParagraphTags = false, - RenderFieldCaseType casing = RenderFieldCaseType.Unchanged, - RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, - bool formatAsDate = false, - bool formatAsDateWithTime = false, - string formatAsDateWithTimeSeparator = "") - - //TODO: commented out until as it is not implemented by umbraco:item yet - //,string formatString = "") - { - Mandate.ParameterNotNull(currentPage, "currentPage"); - Mandate.ParameterNotNullOrEmpty(fieldAlias, "fieldAlias"); - - //TODO: This is real nasty and we should re-write the 'item' and 'ItemRenderer' class but si fine for now - - var attributes = new Dictionary - { - {"field", fieldAlias}, - {"recursive", recursive.ToString().ToLowerInvariant()}, - {"useifempty", altFieldAlias}, - {"textifempty", altText}, - {"stripparagraph", removeParagraphTags.ToString().ToLowerInvariant()}, - { - "case", casing == RenderFieldCaseType.Lower ? "lower" - : casing == RenderFieldCaseType.Upper ? "upper" - : casing == RenderFieldCaseType.Title ? "title" - : string.Empty - }, - {"inserttextbefore", insertBefore}, - {"inserttextafter", insertAfter}, - {"convertlinebreaks", convertLineBreaks.ToString().ToLowerInvariant()}, - {"formatasdate", formatAsDate.ToString().ToLowerInvariant()}, - {"formatasdatewithtime", formatAsDateWithTime.ToString().ToLowerInvariant()}, - {"formatasdatewithtimeseparator", formatAsDateWithTimeSeparator} - }; - switch (encoding) - { - case RenderFieldEncodingType.Url: - attributes.Add("urlencode", "true"); - break; - case RenderFieldEncodingType.Html: - attributes.Add("htmlencode", "true"); - break; - case RenderFieldEncodingType.Unchanged: - default: - break; - } - - //need to convert our dictionary over to this weird dictionary type - var attributesForItem = new AttributeCollectionAdapter( - new AttributeCollection( - new StateBag())); - foreach (var i in attributes) - { - attributesForItem.Add(i.Key, i.Value); - } - - - - var item = new Item(currentPage) - { - Field = fieldAlias, - TextIfEmpty = altText, - LegacyAttributes = attributesForItem - }; - - //here we are going to check if we are in the context of an Umbraco routed page, if we are we - //will leave the NodeId empty since the underlying ItemRenderer will work ever so slightly faster - //since it already knows about the current page. Otherwise, we'll assign the id based on our - //currently assigned node. The PublishedContentRequest will be null if: - // * we are rendering a partial view or child action - // * we are rendering a view from a custom route - if ((_umbracoContext.PublishedContentRequest == null - || _umbracoContext.PublishedContentRequest.PublishedContent.Id != currentPage.Id) - && currentPage.Id > 0) // in case we're rendering a detached content (id == 0) - { - item.NodeId = currentPage.Id.ToString(CultureInfo.InvariantCulture); - } - - - var containerPage = new FormlessPage(); - containerPage.Controls.Add(item); - - using (var output = new StringWriter()) - using (var htmlWriter = new HtmlTextWriter(output)) - { - ItemRenderer.Instance.Init(item); - ItemRenderer.Instance.Load(item); - ItemRenderer.Instance.Render(item, htmlWriter); - - //because we are rendering the output through the legacy Item (webforms) stuff, the {localLinks} will already be replaced. - return new HtmlString(output.ToString()); - } - } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 3532575f38..cb7a29f608 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -1,404 +1,213 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Web; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; +using Umbraco.Web.Runtime; using Umbraco.Web.Security; -using umbraco.BusinessLogic; -using umbraco.presentation.preview; -using Umbraco.Core.CodeAnnotations; -using GlobalSettings = umbraco.GlobalSettings; -using IOHelper = Umbraco.Core.IO.IOHelper; -using SystemDirectories = Umbraco.Core.IO.SystemDirectories; namespace Umbraco.Web { /// /// Class that encapsulates Umbraco information of a specific HTTP request /// - public class UmbracoContext : DisposableObjectSlim, IDisposeOnRequestEnd + public class UmbracoContext : DisposableObject, IDisposeOnRequestEnd { - internal const string HttpContextItemName = "Umbraco.Web.UmbracoContext"; - private static readonly object Locker = new object(); - - private bool _replacing; + private readonly IGlobalSettings _globalSettings; + private readonly Lazy _publishedSnapshot; + private string _previewToken; private bool? _previewing; - private readonly Lazy _contentCache; - private readonly Lazy _mediaCache; - /// - /// Used if not running in a web application (no real HttpContext) - /// - [ThreadStatic] - private static UmbracoContext _umbracoContext; + #region Ensure Context - #region EnsureContext methods - - #region Obsolete - [Obsolete("Use the method that specifies IUmbracoSettings instead")] - [EditorBrowsable(EditorBrowsableState.Never)] + /// + /// Ensures that there is a "current" UmbracoContext. + /// + /// + /// An http context. + /// A published snapshot service. + /// A security helper. + /// The umbraco settings. + /// Some url providers. + /// + /// A value indicating whether to replace the existing context. + /// The "current" UmbracoContext. + /// + /// fixme - this needs to be clarified + /// + /// If is true then the "current" UmbracoContext is replaced + /// with a new one even if there is one already. See . Has to do with + /// creating a context at startup and not being able to access httpContext.Request at that time, so + /// the OriginalRequestUrl remains unspecified until replaces the context. + /// + /// This *has* to be done differently! + /// + /// See http://issues.umbraco.org/issue/U4-1890, http://issues.umbraco.org/issue/U4-1717 + /// + /// + // used by + // UmbracoModule BeginRequest (since it's a request it has an UmbracoContext) + // in BeginRequest so *late* ie *after* the HttpApplication has started (+ init? check!) + // WebRuntimeComponent (and I'm not quite sure why) + // -> because an UmbracoContext seems to be required by UrlProvider to get the "current" published snapshot? + // note: at startup not sure we have an HttpContext.Current + // at startup not sure we have an httpContext.Request => hard to tell "current" url + // should we have a post-boot event of some sort for ppl that *need* ?! + // can we have issues w/ routing context? + // and tests + // can .ContentRequest be null? of course! public static UmbracoContext EnsureContext( + IUmbracoContextAccessor umbracoContextAccessor, HttpContextBase httpContext, - ApplicationContext applicationContext, - WebSecurity webSecurity) - { - return EnsureContext(httpContext, applicationContext, webSecurity, false); - } - [Obsolete("Use the method that specifies IUmbracoSettings instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static UmbracoContext EnsureContext( - HttpContextBase httpContext, - ApplicationContext applicationContext) - { - return EnsureContext(httpContext, applicationContext, new WebSecurity(httpContext, applicationContext), false); - } - [Obsolete("Use the method that specifies IUmbracoSettings instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static UmbracoContext EnsureContext( - HttpContextBase httpContext, - ApplicationContext applicationContext, - bool replaceContext) - { - return EnsureContext(httpContext, applicationContext, new WebSecurity(httpContext, applicationContext), replaceContext); - } - [Obsolete("Use the method that specifies IUmbracoSettings instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static UmbracoContext EnsureContext( - HttpContextBase httpContext, - ApplicationContext applicationContext, - WebSecurity webSecurity, - bool replaceContext) - { - return EnsureContext(httpContext, applicationContext, new WebSecurity(httpContext, applicationContext), replaceContext, null); - } - [Obsolete("Use the method that specifies IUmbracoSettings instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static UmbracoContext EnsureContext( - HttpContextBase httpContext, - ApplicationContext applicationContext, - WebSecurity webSecurity, - bool replaceContext, - bool? preview) - { - return EnsureContext(httpContext, applicationContext, webSecurity, UmbracoConfig.For.UmbracoSettings(), UrlProviderResolver.Current.Providers, replaceContext, preview); - } - #endregion - - /// - /// This is a helper method which is called to ensure that the singleton context is created - /// - /// - /// - /// - /// - /// - /// - /// if set to true will replace the current singleton with a new one, this is generally only ever used because - /// during application startup the base url domain will not be available so after app startup we'll replace the current - /// context with a new one in which we can access the httpcontext.Request object. - /// - /// - /// - /// The Singleton context object - /// - /// - /// This is created in order to standardize the creation of the singleton. Normally it is created during a request - /// in the UmbracoModule, however this module does not execute during application startup so we need to ensure it - /// during the startup process as well. - /// See: http://issues.umbraco.org/issue/U4-1890, http://issues.umbraco.org/issue/U4-1717 - /// - public static UmbracoContext EnsureContext( - HttpContextBase httpContext, - ApplicationContext applicationContext, + IPublishedSnapshotService publishedSnapshotService, WebSecurity webSecurity, IUmbracoSettingsSection umbracoSettings, - IEnumerable urlProviders, - bool replaceContext, - bool? preview = null) + IEnumerable urlProviders, + IGlobalSettings globalSettings, + bool replace = false) { - if (httpContext == null) throw new ArgumentNullException("httpContext"); - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); - if (webSecurity == null) throw new ArgumentNullException("webSecurity"); - if (umbracoSettings == null) throw new ArgumentNullException("umbracoSettings"); - if (urlProviders == null) throw new ArgumentNullException("urlProviders"); + if (umbracoContextAccessor == null) throw new ArgumentNullException(nameof(umbracoContextAccessor)); + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); + if (publishedSnapshotService == null) throw new ArgumentNullException(nameof(publishedSnapshotService)); + if (webSecurity == null) throw new ArgumentNullException(nameof(webSecurity)); + if (umbracoSettings == null) throw new ArgumentNullException(nameof(umbracoSettings)); + if (urlProviders == null) throw new ArgumentNullException(nameof(urlProviders)); + if (globalSettings == null) throw new ArgumentNullException(nameof(globalSettings)); - //if there's already a singleton, and we're not replacing then there's no need to ensure anything - if (UmbracoContext.Current != null) - { - if (replaceContext == false) - return UmbracoContext.Current; - UmbracoContext.Current._replacing = true; - } + // if there is already a current context, return if not replacing + var current = umbracoContextAccessor.UmbracoContext; + if (current != null && replace == false) + return current; - var umbracoContext = CreateContext(httpContext, applicationContext, webSecurity, umbracoSettings, urlProviders, preview); - - //assign the singleton - UmbracoContext.Current = umbracoContext; - return UmbracoContext.Current; + // create & assign to accessor, dispose existing if any + umbracoContextAccessor.UmbracoContext?.Dispose(); + return umbracoContextAccessor.UmbracoContext = new UmbracoContext(httpContext, publishedSnapshotService, webSecurity, umbracoSettings, urlProviders, globalSettings); } - /// - /// Creates a standalone UmbracoContext instance - /// - /// - /// - /// - /// - /// - /// - /// - /// A new instance of UmbracoContext - /// - public static UmbracoContext CreateContext( - HttpContextBase httpContext, - ApplicationContext applicationContext, + // initializes a new instance of the UmbracoContext class + // internal for unit tests + // otherwise it's used by EnsureContext above + // warn: does *not* manage setting any IUmbracoContextAccessor + internal UmbracoContext(HttpContextBase httpContext, + IPublishedSnapshotService publishedSnapshotService, WebSecurity webSecurity, IUmbracoSettingsSection umbracoSettings, - IEnumerable urlProviders, - bool? preview) + IEnumerable urlProviders, + IGlobalSettings globalSettings) { - if (httpContext == null) throw new ArgumentNullException("httpContext"); - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); - if (webSecurity == null) throw new ArgumentNullException("webSecurity"); - if (umbracoSettings == null) throw new ArgumentNullException("umbracoSettings"); - if (urlProviders == null) throw new ArgumentNullException("urlProviders"); + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); + if (publishedSnapshotService == null) throw new ArgumentNullException(nameof(publishedSnapshotService)); + if (webSecurity == null) throw new ArgumentNullException(nameof(webSecurity)); + if (umbracoSettings == null) throw new ArgumentNullException(nameof(umbracoSettings)); + if (urlProviders == null) throw new ArgumentNullException(nameof(urlProviders)); + _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); - var umbracoContext = new UmbracoContext( - httpContext, - applicationContext, - new Lazy(() => PublishedCachesResolver.Current.Caches, false), - webSecurity, - preview); - - // create the RoutingContext, and assign - var routingContext = new RoutingContext( - umbracoContext, - - //TODO: Until the new cache is done we can't really expose these to override/mock - new Lazy>(() => ContentFinderResolver.Current.Finders), - new Lazy(() => ContentLastChanceFinderResolver.Current.Finder), - - // create the nice urls provider - // there's one per request because there are some behavior parameters that can be changed - new Lazy( - () => new UrlProvider( - umbracoContext, - umbracoSettings.WebRouting, - urlProviders), - false)); - - //assign the routing context back - umbracoContext.RoutingContext = routingContext; - - return umbracoContext; - } - - /// - /// Creates a new Umbraco context. - /// - /// - /// - /// The published caches. - /// - /// An optional value overriding detection of preview mode. - internal UmbracoContext( - HttpContextBase httpContext, - ApplicationContext applicationContext, - IPublishedCaches publishedCaches, - WebSecurity webSecurity, - bool? preview = null) - : this(httpContext, applicationContext, new Lazy(() => publishedCaches), webSecurity, preview) - { - } - - /// - /// Creates a new Umbraco context. - /// - /// - /// - /// The published caches. - /// - /// An optional value overriding detection of preview mode. - internal UmbracoContext( - HttpContextBase httpContext, - ApplicationContext applicationContext, - Lazy publishedCaches, - WebSecurity webSecurity, - bool? preview = null) - { - //This ensures the dispose method is called when the request terminates, though - // we also ensure this happens in the Umbraco module because the UmbracoContext is added to the - // http context items. + // ensure that this instance is disposed when the request terminates, though we *also* ensure + // this happens in the Umbraco module since the UmbracoCOntext is added to the HttpContext items. + // + // also, it *can* be returned by the container with a PerRequest lifetime, meaning that the + // container *could* also try to dispose it. + // + // all in all, this context may be disposed more than once, but DisposableObject ensures that + // it is ok and it will be actually disposed only once. httpContext.DisposeOnPipelineCompleted(this); - if (httpContext == null) throw new ArgumentNullException("httpContext"); - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); - ObjectCreated = DateTime.Now; UmbracoRequestId = Guid.NewGuid(); - HttpContext = httpContext; - Application = applicationContext; Security = webSecurity; - _contentCache = new Lazy(() => publishedCaches.Value.CreateContextualContentCache(this)); - _mediaCache = new Lazy(() => publishedCaches.Value.CreateContextualMediaCache(this)); - _previewing = preview; - + // beware - we cannot expect a current user here, so detecting preview mode must be a lazy thing + _publishedSnapshot = new Lazy(() => publishedSnapshotService.CreatePublishedSnapshot(PreviewToken)); + // set the urls... - //original request url - //NOTE: The request will not be available during app startup so we can only set this to an absolute URL of localhost, this + // NOTE: The request will not be available during app startup so we can only set this to an absolute URL of localhost, this // is a work around to being able to access the UmbracoContext during application startup and this will also ensure that people // 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get // the current domain during application startup. // see: http://issues.umbraco.org/issue/U4-1890 + // + OriginalRequestUrl = GetRequestFromContext()?.Url ?? new Uri("http://localhost"); + CleanedUmbracoUrl = UriUtility.UriToUmbraco(OriginalRequestUrl); + UrlProvider = new UrlProvider(this, umbracoSettings.WebRouting, urlProviders); + } - var requestUrl = new Uri("http://localhost"); - var request = GetRequestFromContext(); - if (request != null && request.Url != null) - { - requestUrl = request.Url; - } - this.OriginalRequestUrl = requestUrl; - //cleaned request url - this.CleanedUmbracoUrl = UriUtility.UriToUmbraco(this.OriginalRequestUrl); - - } #endregion /// /// Gets the current Umbraco Context. /// - public static UmbracoContext Current - { - get - { - //if we have a real context then return the request based object - if (System.Web.HttpContext.Current != null) - { - return (UmbracoContext)System.Web.HttpContext.Current.Items[HttpContextItemName]; - } - - //return the object if not running in a real HttpContext - return _umbracoContext; - } - - internal set - { - lock (Locker) - { - //if running in a real HttpContext, this can only be set once - if (System.Web.HttpContext.Current != null && Current != null && !Current._replacing) - { - throw new ApplicationException("The current UmbracoContext can only be set once during a request."); - } - - //if there is an HttpContext, return the item - if (System.Web.HttpContext.Current != null) - { - System.Web.HttpContext.Current.Items[HttpContextItemName] = value; - } - else - { - _umbracoContext = value; - } - } - } - } - - /// - /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this - /// object is instantiated which in the web site is created during the BeginRequest phase. - /// We can then determine complete rendering time from that. - /// - internal DateTime ObjectCreated { get; private set; } - - /// - /// This is used internally for debugging and also used to define anything required to distinguish this request from another. - /// - internal Guid UmbracoRequestId { get; private set; } + // note: obsolete, use Current.UmbracoContext... then obsolete Current too, and inject! + public static UmbracoContext Current => Composing.Current.UmbracoContext; /// - /// Gets the current ApplicationContext + /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this + /// object is instantiated which in the web site is created during the BeginRequest phase. + /// We can then determine complete rendering time from that. /// - [UmbracoWillObsolete("Do not access the ApplicationContext via the UmbracoContext, either inject the ApplicationContext into the services you need or access it via it's own Singleton accessor ApplicationContext.Current")] - [EditorBrowsable(EditorBrowsableState.Never)] - public ApplicationContext Application { get; private set; } + internal DateTime ObjectCreated { get; } + + /// + /// This is used internally for debugging and also used to define anything required to distinguish this request from another. + /// + internal Guid UmbracoRequestId { get; } /// /// Gets the WebSecurity class /// - public WebSecurity Security { get; private set; } - - /// - /// Gets the uri that is handled by ASP.NET after server-side rewriting took place. - /// - internal Uri OriginalRequestUrl { get; private set; } - - /// - /// Gets the cleaned up url that is handled by Umbraco. - /// - /// That is, lowercase, no trailing slash after path, no .aspx... - internal Uri CleanedUmbracoUrl { get; private set; } + public WebSecurity Security { get; } /// - /// Gets or sets the published content cache. + /// Gets the uri that is handled by ASP.NET after server-side rewriting took place. /// - public ContextualPublishedContentCache ContentCache - { - get { return _contentCache.Value; } - } + internal Uri OriginalRequestUrl { get; } /// - /// Gets or sets the published media cache. + /// Gets the cleaned up url that is handled by Umbraco. /// - public ContextualPublishedMediaCache MediaCache - { - get { return _mediaCache.Value; } - } + /// That is, lowercase, no trailing slash after path, no .aspx... + internal Uri CleanedUmbracoUrl { get; } + + /// + /// Gets the published snapshot. + /// + public IPublishedShapshot PublishedShapshot => _publishedSnapshot.Value; + + // for unit tests + internal bool HasPublishedSnapshot => _publishedSnapshot.IsValueCreated; + + /// + /// Gets the published content cache. + /// + public IPublishedContentCache ContentCache => PublishedShapshot.Content; + + /// + /// Gets the published media cache. + /// + public IPublishedMediaCache MediaCache => PublishedShapshot.Media; /// /// Boolean value indicating whether the current request is a front-end umbraco request /// - public bool IsFrontEndUmbracoRequest - { - get { return PublishedContentRequest != null; } - } + public bool IsFrontEndUmbracoRequest => PublishedRequest != null; - /// - /// A shortcut to the UmbracoContext's RoutingContext's NiceUrlProvider - /// - /// - /// If the RoutingContext is null, this will throw an exception. - /// - public UrlProvider UrlProvider - { - get - { - if (RoutingContext == null) - throw new InvalidOperationException("Cannot access the UrlProvider when the UmbracoContext's RoutingContext is null"); - return RoutingContext.UrlProvider; - } - } + /// + /// Gets the url provider. + /// + public UrlProvider UrlProvider { get; } - /// - /// Gets/sets the RoutingContext object - /// - public RoutingContext RoutingContext { get; internal set; } - - /// - /// Gets/sets the PublishedContentRequest object - /// - public PublishedContentRequest PublishedContentRequest { get; set; } + /// + /// Gets/sets the PublishedContentRequest object + /// + public PublishedRequest PublishedRequest { get; set; } /// /// Exposes the HttpContext for the current request /// - public HttpContextBase HttpContext { get; private set; } + public HttpContextBase HttpContext { get; } /// /// Gets a value indicating whether the request has debugging enabled @@ -410,8 +219,10 @@ namespace Umbraco.Web { var request = GetRequestFromContext(); //NOTE: the request can be null during app startup! - return GlobalSettings.DebugMode && request != null - && (!string.IsNullOrEmpty(request["umbdebugshowtrace"]) || !string.IsNullOrEmpty(request["umbdebug"])); + return GlobalSettings.DebugMode + && request != null + && (string.IsNullOrEmpty(request["umbdebugshowtrace"]) == false + || string.IsNullOrEmpty(request["umbdebug"]) == false); } } @@ -440,61 +251,77 @@ namespace Umbraco.Web } /// - /// Gets the current logged in Umbraco user (editor). + /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) /// - /// The Umbraco user object or null - [Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object")] - public User UmbracoUser + public bool InPreviewMode { get { - var user = Security.CurrentUser; - return user == null ? null : new User(user); + if (_previewing.HasValue == false) DetectPreviewMode(); + return _previewing.Value; } - + private set => _previewing = value; } - /// - /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) - /// - /// Can be internally set by the RTE macro rendering to render macros in the appropriate mode. - public bool InPreviewMode + private string PreviewToken { - get { return _previewing ?? (_previewing = DetectInPreviewModeFromRequest()).Value; } - set { _previewing = value; } + get + { + if (_previewing.HasValue == false) DetectPreviewMode(); + return _previewToken; + } } - private bool DetectInPreviewModeFromRequest() + private void DetectPreviewMode() { var request = GetRequestFromContext(); - if (request == null || request.Url == null) - return false; - - return - HttpContext.Request.HasPreviewCookie() - && request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) == false - && Security.CurrentUser != null; // has user + if (request?.Url != null + && request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath, _globalSettings) == false + && Security.CurrentUser != null) + { + var previewToken = request.GetPreviewCookieValue(); // may be null or empty + _previewToken = previewToken.IsNullOrWhiteSpace() ? null : previewToken; + } + + _previewing = _previewToken.IsNullOrWhiteSpace() == false; } - + + // say we render a macro or RTE in a give 'preview' mode that might not be the 'current' one, + // then due to the way it all works at the moment, the 'current' published snapshot need to be in the proper + // default 'preview' mode - somehow we have to force it. and that could be recursive. + internal IDisposable ForcedPreview(bool preview) + { + InPreviewMode = preview; + return PublishedShapshot.ForcedPreview(preview, orig => InPreviewMode = orig); + } + private HttpRequestBase GetRequestFromContext() { try { return HttpContext.Request; } - catch (System.Web.HttpException) + catch (HttpException) { return null; } } - protected override void DisposeResources() { + // DisposableObject ensures that this runs only once + Security.DisposeIfDisposable(); - //If not running in a web ctx, ensure the thread based instance is nulled - _umbracoContext = null; + // reset - important when running outside of http context + // also takes care of the accessor + Composing.Current.ClearUmbracoContext(); + + // help caches release resources + // (but don't create caches just to dispose them) + // context is not multi-threaded + if (_publishedSnapshot.IsValueCreated) + _publishedSnapshot.Value.DisposeIfDisposable(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UmbracoContextExtensions.cs b/src/Umbraco.Web/UmbracoContextExtensions.cs deleted file mode 100644 index 3e5c9d355e..0000000000 --- a/src/Umbraco.Web/UmbracoContextExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.Events; - -namespace Umbraco.Web -{ - /// - /// Provides extension methods for . - /// - public static class UmbracoContextExtensions - { - /// - /// tries to get the Umbraco context from the HttpContext - /// - /// - /// - /// - /// This is useful when working on async threads since the UmbracoContext is not copied over explicitly - /// - public static UmbracoContext GetUmbracoContext(this HttpContext http) - { - return GetUmbracoContext(new HttpContextWrapper(http)); - } - - /// - /// tries to get the Umbraco context from the HttpContext - /// - /// - /// - /// - /// This is useful when working on async threads since the UmbracoContext is not copied over explicitly - /// - public static UmbracoContext GetUmbracoContext(this HttpContextBase http) - { - if (http == null) throw new ArgumentNullException("http"); - - if (http.Items.Contains(UmbracoContext.HttpContextItemName)) - { - var umbCtx = http.Items[UmbracoContext.HttpContextItemName] as UmbracoContext; - return umbCtx; - } - return null; - } - - /// - /// If there are event messages in the current request this will return them , otherwise it will return null - /// - /// - /// - public static EventMessages GetCurrentEventMessages(this UmbracoContext umbracoContext) - { - var eventMessagesFactory = umbracoContext.Application.Services.EventMessagesFactory as ScopeLifespanMessagesFactory; - return eventMessagesFactory == null ? null : eventMessagesFactory.TryGet(); - } - - } -} diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs index 342b8daa16..412156acbc 100644 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs @@ -2,8 +2,12 @@ using Microsoft.Owin; using Owin; using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Security; +using Umbraco.Core.Services; using Umbraco.Web; +using Umbraco.Web.Composing; using Umbraco.Web.Security.Identity; [assembly: OwinStartup("UmbracoDefaultOwinStartup", typeof(UmbracoDefaultOwinStartup))] @@ -18,6 +22,12 @@ namespace Umbraco.Web /// public class UmbracoDefaultOwinStartup { + protected IUmbracoContextAccessor UmbracoContextAccessor => Current.UmbracoContextAccessor; + protected IGlobalSettings GlobalSettings => UmbracoConfig.For.GlobalSettings(); + protected IUmbracoSettingsSection UmbracoSettings => UmbracoConfig.For.UmbracoSettings(); + protected IRuntimeState RuntimeState => Core.Composing.Current.RuntimeState; + protected ServiceContext Services => Current.Services; + /// /// Main startup method /// @@ -26,7 +36,11 @@ namespace Umbraco.Web { app.SanitizeThreadCulture(); - ConfigureServices(app); + // there's nothing we can do really + if (RuntimeState.Level == RuntimeLevel.BootFailed) + return; + + ConfigureServices(app, Services); ConfigureMiddleware(app); } @@ -34,7 +48,8 @@ namespace Umbraco.Web /// Configures services to be created in the OWIN context (CreatePerOwinContext) /// /// - protected virtual void ConfigureServices(IAppBuilder app) + /// + protected virtual void ConfigureServices(IAppBuilder app, ServiceContext services) { app.SetUmbracoLoggerFactory(); ConfigureUmbracoUserManager(app); @@ -51,7 +66,7 @@ namespace Umbraco.Web ConfigureUmbracoAuthentication(app); app - .UseSignalR() + .UseSignalR(GlobalSettings) .FinalizeMiddlewareConfiguration(); } @@ -63,7 +78,9 @@ namespace Umbraco.Web { // (EXPERT: an overload accepts a custom BackOfficeUserStore implementation) app.ConfigureUserManagerForUmbracoBackOffice( - ApplicationContext, + Services, + UmbracoSettings.Content, + GlobalSettings, Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider()); } @@ -76,25 +93,16 @@ namespace Umbraco.Web // Ensure owin is configured for Umbraco back office authentication. // Front-end OWIN cookie configuration must be declared after this code. app - .UseUmbracoBackOfficeCookieAuthentication(ApplicationContext, PipelineStage.Authenticate) - .UseUmbracoBackOfficeExternalCookieAuthentication(ApplicationContext, PipelineStage.Authenticate) - .UseUmbracoPreviewAuthentication(ApplicationContext, PipelineStage.Authorize); + .UseUmbracoBackOfficeCookieAuthentication(UmbracoContextAccessor, RuntimeState, Services.UserService, GlobalSettings, UmbracoSettings.Security, PipelineStage.Authenticate) + .UseUmbracoBackOfficeExternalCookieAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, PipelineStage.Authenticate) + .UseUmbracoPreviewAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, UmbracoSettings.Security, PipelineStage.Authorize); } - /// - /// Raised when the middleware has been configured - /// public static event EventHandler MiddlewareConfigured; - protected virtual ApplicationContext ApplicationContext - { - get { return ApplicationContext.Current; } - } - internal static void OnMiddlewareConfigured(OwinMiddlewareConfiguredEventArgs args) { - var handler = MiddlewareConfigured; - if (handler != null) handler(null, args); + MiddlewareConfigured?.Invoke(null, args); } } } diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 90bc0a436b..2386e4abef 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -2,12 +2,9 @@ using System.ComponentModel; using System.Web; using System.Web.Security; -using System.Xml.Linq; using System.Xml.XPath; using Umbraco.Core; using Umbraco.Core.Dictionary; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Models; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Xml; @@ -18,222 +15,169 @@ using System.IO; using System.Linq; using System.Web.Mvc; using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; namespace Umbraco.Web { + using Examine = global::Examine; + /// - /// A helper class that provides many useful methods and functionality for using Umbraco in templates - /// - public class UmbracoHelper : IUmbracoComponentRenderer + /// A helper class that provides many useful methods and functionality for using Umbraco in templates + /// + public class UmbracoHelper : IUmbracoComponentRenderer { - private readonly UmbracoContext _umbracoContext; - private readonly IPublishedContent _currentPage; - private readonly ITypedPublishedContentQuery _typedQuery; - private readonly IDynamicPublishedContentQuery _dynamicQuery; - private readonly HtmlStringUtilities _stringUtilities = new HtmlStringUtilities(); + private static readonly HtmlStringUtilities StringUtilities = new HtmlStringUtilities(); + + private readonly UmbracoContext _umbracoContext; + private readonly IPublishedContent _currentPage; + private readonly IPublishedContentQuery _iQuery; + private readonly ServiceContext _services; + private readonly CacheHelper _appCache; private IUmbracoComponentRenderer _componentRenderer; private PublishedContentQuery _query; private MembershipHelper _membershipHelper; - private TagQuery _tag; + private ITagQuery _tag; private IDataTypeService _dataTypeService; - private IEntityService _entityService; - private UrlProvider _urlProvider; private ICultureDictionary _cultureDictionary; - /// - /// Lazy instantiates the tag context - /// - public TagQuery TagQuery - { - //TODO: Unfortunately we cannot change this return value to be ITagQuery - // since it's a breaking change, need to fix it for v8 - // http://issues.umbraco.org/issue/U4-6899 + #region Constructors - get - { - return _tag ?? - (_tag = new TagQuery(UmbracoContext.Application.Services.TagService, - _typedQuery ?? ContentQuery)); - } + /// + /// Initializes a new instance of the class. + /// + /// For tests. + internal UmbracoHelper(UmbracoContext umbracoContext, IPublishedContent content, + IPublishedContentQuery query, + ITagQuery tagQuery, + IDataTypeService dataTypeService, + ICultureDictionary cultureDictionary, + IUmbracoComponentRenderer componentRenderer, + MembershipHelper membershipHelper, + ServiceContext services, + CacheHelper appCache) + { + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + if (content == null) throw new ArgumentNullException(nameof(content)); + if (query == null) throw new ArgumentNullException(nameof(query)); + if (tagQuery == null) throw new ArgumentNullException(nameof(tagQuery)); + if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); + if (cultureDictionary == null) throw new ArgumentNullException(nameof(cultureDictionary)); + if (componentRenderer == null) throw new ArgumentNullException(nameof(componentRenderer)); + if (membershipHelper == null) throw new ArgumentNullException(nameof(membershipHelper)); + if (services == null) throw new ArgumentNullException(nameof(services)); + if (appCache == null) throw new ArgumentNullException(nameof(appCache)); + + _umbracoContext = umbracoContext; + _tag = new TagQuery(tagQuery); + _dataTypeService = dataTypeService; + _cultureDictionary = cultureDictionary; + _componentRenderer = componentRenderer; + _membershipHelper = membershipHelper; + _currentPage = content; + _iQuery = query; + _services = services; + _appCache = appCache; } /// - /// Lazy instantiates the query context if not specified in the constructor + /// Initializes a new instance of the class. /// - public PublishedContentQuery ContentQuery + /// For tests - nothing is initialized. + internal UmbracoHelper() + { } + + + /// + /// Initializes a new instance of the class with an Umbraco context + /// and a specific content item. + /// + /// An Umbraco context. + /// A content item. + /// A services context. + /// An application cache helper. + /// Sets the current page to the supplied content item. + public UmbracoHelper(UmbracoContext umbracoContext, ServiceContext services, CacheHelper appCache, IPublishedContent content) + : this(umbracoContext, services, appCache) { - get - { - //If the content query doesn't exist it will either be created with the ITypedPublishedContentQuery, IDynamicPublishedContentQuery - // used to construct this instance or with the content caches of the UmbracoContext - return _query ?? - (_query = _typedQuery != null - ? new PublishedContentQuery(_typedQuery, _dynamicQuery) - : new PublishedContentQuery(UmbracoContext.ContentCache, UmbracoContext.MediaCache)); - } + if (content == null) throw new ArgumentNullException(nameof(content)); + _currentPage = content; } /// - /// Helper method to ensure an umbraco context is set when it is needed + /// Initializes a new instance of the class with an Umbraco context. + /// + /// An Umbraco context. + /// A services context. + /// An application cache helper. + /// Sets the current page to the context's published content request's content item. + public UmbracoHelper(UmbracoContext umbracoContext, ServiceContext services, CacheHelper appCache) + { + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + if (services == null) throw new ArgumentNullException(nameof(services)); + if (appCache == null) throw new ArgumentNullException(nameof(appCache)); + + _umbracoContext = umbracoContext; + if (_umbracoContext.IsFrontEndUmbracoRequest) + _currentPage = _umbracoContext.PublishedRequest.PublishedContent; + _services = services; + _appCache = appCache; + } + + #endregion + + /// + /// Gets the tag context. + /// + public ITagQuery TagQuery => _tag ?? + (_tag = new TagQuery(_services.TagService, _iQuery ?? ContentQuery)); + + /// + /// Gets the query context. + /// + public PublishedContentQuery ContentQuery => _query ?? + (_query = _iQuery != null + ? new PublishedContentQuery(_iQuery) + : new PublishedContentQuery(UmbracoContext.ContentCache, UmbracoContext.MediaCache)); + + /// + /// Gets the Umbraco context. /// public UmbracoContext UmbracoContext { get { if (_umbracoContext == null) - { - throw new NullReferenceException("No " + typeof(UmbracoContext) + " reference has been set for this " + typeof(UmbracoHelper) + " instance"); - } + throw new NullReferenceException("UmbracoContext has not been set."); return _umbracoContext; } } /// - /// Lazy instantiates the membership helper if not specified in the constructor + /// Gets the membership helper. /// - public MembershipHelper MembershipHelper - { - get { return _membershipHelper ?? (_membershipHelper = new MembershipHelper(UmbracoContext)); } - } + public MembershipHelper MembershipHelper => _membershipHelper + ?? (_membershipHelper = new MembershipHelper(UmbracoContext)); /// - /// Lazy instantiates the UrlProvider if not specified in the constructor + /// Gets the url provider. /// - public UrlProvider UrlProvider - { - get { return _urlProvider ?? (_urlProvider = UmbracoContext.UrlProvider); } - } + public UrlProvider UrlProvider => UmbracoContext.UrlProvider; /// - /// Lazy instantiates the IDataTypeService if not specified in the constructor + /// Gets the datatype service. /// - public IDataTypeService DataTypeService - { - get { return _dataTypeService ?? (_dataTypeService = UmbracoContext.Application.Services.DataTypeService); } - } + private IDataTypeService DataTypeService => _dataTypeService + ?? (_dataTypeService = _services.DataTypeService); /// - /// Lazy instantiates the IEntityService + /// Gets the component renderer. /// - private IEntityService EntityService - { - get { return _entityService ?? (_entityService = UmbracoContext.Application.Services.EntityService); } - } - - /// - /// Lazy instantiates the IUmbracoComponentRenderer if not specified in the constructor - /// - public IUmbracoComponentRenderer UmbracoComponentRenderer - { - get { return _componentRenderer ?? (_componentRenderer = new UmbracoComponentRenderer(UmbracoContext)); } - } - - #region Constructors - /// - /// Empty constructor to create an umbraco helper for access to methods that don't have dependencies - /// - public UmbracoHelper() - { - } - - /// - /// Constructor accepting all dependencies - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// This constructor can be used to create a testable UmbracoHelper - /// - public UmbracoHelper(UmbracoContext umbracoContext, IPublishedContent content, - ITypedPublishedContentQuery typedQuery, - IDynamicPublishedContentQuery dynamicQuery, - ITagQuery tagQuery, - IDataTypeService dataTypeService, - UrlProvider urlProvider, - ICultureDictionary cultureDictionary, - IUmbracoComponentRenderer componentRenderer, - MembershipHelper membershipHelper) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (content == null) throw new ArgumentNullException("content"); - if (typedQuery == null) throw new ArgumentNullException("typedQuery"); - if (dynamicQuery == null) throw new ArgumentNullException("dynamicQuery"); - if (tagQuery == null) throw new ArgumentNullException("tagQuery"); - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - if (urlProvider == null) throw new ArgumentNullException("urlProvider"); - if (cultureDictionary == null) throw new ArgumentNullException("cultureDictionary"); - if (componentRenderer == null) throw new ArgumentNullException("componentRenderer"); - if (membershipHelper == null) throw new ArgumentNullException("membershipHelper"); - - _umbracoContext = umbracoContext; - _tag = new TagQuery(tagQuery); - _dataTypeService = dataTypeService; - _urlProvider = urlProvider; - _cultureDictionary = cultureDictionary; - _componentRenderer = componentRenderer; - _membershipHelper = membershipHelper; - _currentPage = content; - _typedQuery = typedQuery; - _dynamicQuery = dynamicQuery; - } - - [Obsolete("Use the constructor specifying all dependencies")] - [EditorBrowsable(EditorBrowsableState.Never)] - public UmbracoHelper(UmbracoContext umbracoContext, IPublishedContent content, PublishedContentQuery query) - : this(umbracoContext) - { - if (content == null) throw new ArgumentNullException("content"); - if (query == null) throw new ArgumentNullException("query"); - _currentPage = content; - _query = query; - } - - /// - /// Custom constructor setting the current page to the parameter passed in - /// - /// - /// - public UmbracoHelper(UmbracoContext umbracoContext, IPublishedContent content) - : this(umbracoContext) - { - if (content == null) throw new ArgumentNullException("content"); - _currentPage = content; - } - - /// - /// Standard constructor setting the current page to the page that has been routed to - /// - /// - public UmbracoHelper(UmbracoContext umbracoContext) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (umbracoContext.RoutingContext == null) throw new NullReferenceException("The RoutingContext on the UmbracoContext cannot be null"); - - _umbracoContext = umbracoContext; - if (_umbracoContext.IsFrontEndUmbracoRequest) - { - _currentPage = _umbracoContext.PublishedContentRequest.PublishedContent; - } - } - - [Obsolete("Use the constructor specifying all dependencies")] - [EditorBrowsable(EditorBrowsableState.Never)] - public UmbracoHelper(UmbracoContext umbracoContext, PublishedContentQuery query) - : this(umbracoContext) - { - if (query == null) throw new ArgumentNullException("query"); - _query = query; - } - #endregion + public IUmbracoComponentRenderer UmbracoComponentRenderer => _componentRenderer + ?? (_componentRenderer = new UmbracoComponentRenderer(UmbracoContext)); /// /// Returns the current IPublishedContent item assigned to the UmbracoHelper @@ -246,144 +190,76 @@ namespace Umbraco.Web /// this will only ever happen if the Helper is constructed with an UmbracoContext and it is not a front-end request /// /// Thrown if the UmbracoHelper is constructed with an UmbracoContext and it is not a front-end request - public IPublishedContent AssignedContentItem - { - get - { - if (_currentPage == null) + public IPublishedContent AssignedContentItem + { + get + { + if (_currentPage == null) throw new InvalidOperationException("Cannot return the " + typeof(IPublishedContent).Name + " because the " + typeof(UmbracoHelper).Name + " was constructed with an " + typeof(UmbracoContext).Name + " and the current request is not a front-end request."); return _currentPage; - } - } + } + } - /// - /// Renders the template for the specified pageId and an optional altTemplateId - /// - /// - /// If not specified, will use the template assigned to the node - /// - public IHtmlString RenderTemplate(int pageId, int? altTemplateId = null) - { + /// + /// Renders the template for the specified pageId and an optional altTemplateId. + /// + /// + /// If not specified, will use the template assigned to the node + /// + public IHtmlString RenderTemplate(int pageId, int? altTemplateId = null) + { return UmbracoComponentRenderer.RenderTemplate(pageId, altTemplateId); - } + } - #region RenderMacro + #region RenderMacro - /// - /// Renders the macro with the specified alias. - /// - /// The alias. - /// - public IHtmlString RenderMacro(string alias) - { + /// + /// Renders the macro with the specified alias. + /// + /// The alias. + /// + public IHtmlString RenderMacro(string alias) + { return UmbracoComponentRenderer.RenderMacro(alias, new { }); - } + } - /// - /// Renders the macro with the specified alias, passing in the specified parameters. - /// - /// The alias. - /// The parameters. - /// - public IHtmlString RenderMacro(string alias, object parameters) - { + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// The alias. + /// The parameters. + /// + public IHtmlString RenderMacro(string alias, object parameters) + { return UmbracoComponentRenderer.RenderMacro(alias, parameters.ToDictionary()); - } + } - /// - /// Renders the macro with the specified alias, passing in the specified parameters. - /// - /// The alias. - /// The parameters. - /// - public IHtmlString RenderMacro(string alias, IDictionary parameters) - { + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// The alias. + /// The parameters. + /// + public IHtmlString RenderMacro(string alias, IDictionary parameters) + { return UmbracoComponentRenderer.RenderMacro(alias, parameters); - } + } - #endregion + #endregion - #region Field + #region Dictionary - /// - /// Renders an field to the template - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - //// - /// - public IHtmlString Field(string fieldAlias, - string altFieldAlias = "", string altText = "", string insertBefore = "", string insertAfter = "", - bool recursive = false, bool convertLineBreaks = false, bool removeParagraphTags = false, - RenderFieldCaseType casing = RenderFieldCaseType.Unchanged, - RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, - bool formatAsDate = false, - bool formatAsDateWithTime = false, - string formatAsDateWithTimeSeparator = "") - { - return UmbracoComponentRenderer.Field(AssignedContentItem, fieldAlias, altFieldAlias, - altText, insertBefore, insertAfter, recursive, convertLineBreaks, removeParagraphTags, - casing, encoding, formatAsDate, formatAsDateWithTime, formatAsDateWithTimeSeparator); - } - - /// - /// Renders an field to the template - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - //// - /// - public IHtmlString Field(IPublishedContent currentPage, string fieldAlias, - string altFieldAlias = "", string altText = "", string insertBefore = "", string insertAfter = "", - bool recursive = false, bool convertLineBreaks = false, bool removeParagraphTags = false, - RenderFieldCaseType casing = RenderFieldCaseType.Unchanged, - RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, - bool formatAsDate = false, - bool formatAsDateWithTime = false, - string formatAsDateWithTimeSeparator = "") - { - return UmbracoComponentRenderer.Field(currentPage, fieldAlias, altFieldAlias, - altText, insertBefore, insertAfter, recursive, convertLineBreaks, removeParagraphTags, - casing, encoding, formatAsDate, formatAsDateWithTime, formatAsDateWithTimeSeparator); - } - - #endregion - - #region Dictionary - - /// - /// Returns the dictionary value for the key specified - /// - /// - /// - public string GetDictionaryValue(string key) - { + /// + /// Returns the dictionary value for the key specified + /// + /// + /// + public string GetDictionaryValue(string key) + { return CultureDictionary[key]; - } + } + /// /// Returns the dictionary value for the key specified, and if empty returns the specified default fall back value /// @@ -399,32 +275,23 @@ namespace Umbraco.Web } return dictionaryValue; } + /// /// Returns the ICultureDictionary for access to dictionary items /// - public ICultureDictionary CultureDictionary - { - get - { - if (_cultureDictionary == null) - { - var factory = CultureDictionaryFactoryResolver.Current.Factory; - _cultureDictionary = factory.CreateDictionary(); - } - return _cultureDictionary; - } - } + public ICultureDictionary CultureDictionary => _cultureDictionary + ?? (_cultureDictionary = Current.CultureDictionaryFactory.CreateDictionary()); - #endregion + #endregion - #region Membership + #region Membership - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use the IsProtected method that only specifies path")] + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the IsProtected method that only specifies path")] public bool IsProtected(int documentId, string path) - { + { return IsProtected(path.EnsureEndsWith("," + documentId)); - } + } /// /// Check if a document object is protected by the "Protect Pages" functionality in umbraco @@ -453,30 +320,30 @@ namespace Umbraco.Web return MembershipHelper.MemberHasAccess(path); } - /// - /// Whether or not the current member is logged in (based on the membership provider) - /// - /// True is the current user is logged in - public bool MemberIsLoggedOn() - { + /// + /// Whether or not the current member is logged in (based on the membership provider) + /// + /// True is the current user is logged in + public bool MemberIsLoggedOn() + { return MembershipHelper.IsLoggedIn(); - } + } - #endregion + #endregion - #region NiceUrls + #region NiceUrls - /// - /// Returns a string with a friendly url from a node. - /// IE.: Instead of having /482 (id) as an url, you can have - /// /screenshots/developer/macros (spoken url) - /// - /// Identifier for the node that should be returned - /// String with a friendly url from a node - public string NiceUrl(int nodeId) - { - return Url(nodeId); - } + /// + /// Returns a string with a friendly url from a node. + /// IE.: Instead of having /482 (id) as an url, you can have + /// /screenshots/developer/macros (spoken url) + /// + /// Identifier for the node that should be returned + /// String with a friendly url from a node + public string NiceUrl(int nodeId) + { + return Url(nodeId); + } /// /// Gets the url of a content identified by its identifier. @@ -494,20 +361,20 @@ namespace Umbraco.Web /// The content identifier. /// The mode. /// The url for the content. - public string Url(int contentId, UrlProviderMode mode) - { + public string Url(int contentId, UrlProviderMode mode) + { return UrlProvider.GetUrl(contentId, mode); - } + } - /// - /// This method will always add the domain to the path if the hostnames are set up correctly. - /// - /// Identifier for the node that should be returned - /// String with a friendly url with full domain from a node - public string NiceUrlWithDomain(int nodeId) - { - return UrlAbsolute(nodeId); - } + /// + /// This method will always add the domain to the path if the hostnames are set up correctly. + /// + /// Identifier for the node that should be returned + /// String with a friendly url with full domain from a node + public string NiceUrlWithDomain(int nodeId) + { + return UrlAbsolute(nodeId); + } /// /// Gets the absolute url of a content identified by its identifier. @@ -521,58 +388,74 @@ namespace Umbraco.Web #endregion - #region Members + #region Member/Content/Media from Udi - public IPublishedContent TypedMember(Udi id) + public IPublishedContent PublishedContent(Udi udi) { - var guidUdi = id as GuidUdi; + var guidUdi = udi as GuidUdi; if (guidUdi == null) return null; - return TypedMember(guidUdi.Guid); + + var umbracoType = Constants.UdiEntityType.ToUmbracoObjectType(udi.EntityType); + + var entityService = Current.Services.EntityService; + switch (umbracoType) + { + case UmbracoObjectTypes.Document: + return Content(guidUdi.Guid); + case UmbracoObjectTypes.Media: + // fixme - need to implement Media(guid)! + var mediaAttempt = entityService.GetId(guidUdi.Guid, umbracoType); + if (mediaAttempt.Success) + return Media(mediaAttempt.Result); + break; + case UmbracoObjectTypes.Member: + // fixme - need to implement Member(guid)! + var memberAttempt = entityService.GetId(guidUdi.Guid, umbracoType); + if (memberAttempt.Success) + return Member(memberAttempt.Result); + break; + } + + return null; } - public IPublishedContent TypedMember(Guid id) + #endregion + + #region Members + + public IPublishedContent Member(Udi id) + { + var guidUdi = id as GuidUdi; + return guidUdi == null ? null : Member(guidUdi.Guid); + } + + public IPublishedContent Member(Guid id) { return MembershipHelper.GetByProviderKey(id); } - public IPublishedContent TypedMember(object id) + public IPublishedContent Member(object id) { - var asInt = id.TryConvertTo(); - return asInt ? MembershipHelper.GetById(asInt.Result) : MembershipHelper.GetByProviderKey(id); + if (ConvertIdObjectToInt(id, out var intId)) + return Member(intId); + if (ConvertIdObjectToGuid(id, out var guidId)) + return Member(guidId); + if (ConvertIdObjectToUdi(id, out var udiId)) + return Member(udiId); + return null; } - public IPublishedContent TypedMember(int id) + public IPublishedContent Member(int id) { return MembershipHelper.GetById(id); } - public IPublishedContent TypedMember(string id) + public IPublishedContent Member(string id) { var asInt = id.TryConvertTo(); return asInt ? MembershipHelper.GetById(asInt.Result) : MembershipHelper.GetByProviderKey(id); } - public dynamic Member(object id) - { - var asInt = id.TryConvertTo(); - return asInt - ? MembershipHelper.GetById(asInt.Result).AsDynamic() - : MembershipHelper.GetByProviderKey(id).AsDynamic(); - } - - public dynamic Member(int id) - { - return MembershipHelper.GetById(id).AsDynamic(); - } - - public dynamic Member(string id) - { - var asInt = id.TryConvertTo(); - return asInt - ? MembershipHelper.GetById(asInt.Result).AsDynamic() - : MembershipHelper.GetByProviderKey(id).AsDynamic(); - } - #endregion #region Content @@ -582,22 +465,19 @@ namespace Umbraco.Web /// /// The unique identifier, or the key, of the content item. /// The content, or null of the content item is not in the cache. - public IPublishedContent TypedContent(object id) + public IPublishedContent Content(object id) { - return TypedContentForObject(id); - } + return ContentForObject(id); + } - private IPublishedContent TypedContentForObject(object id) + private IPublishedContent ContentForObject(object id) { - int intId; - if (ConvertIdObjectToInt(id, out intId)) - return ContentQuery.TypedContent(intId); - Guid guidId; - if (ConvertIdObjectToGuid(id, out guidId)) - return ContentQuery.TypedContent(guidId); - Udi udiId; - if (ConvertIdObjectToUdi(id, out udiId)) - return ContentQuery.TypedContent(udiId); + if (ConvertIdObjectToInt(id, out var intId)) + return ContentQuery.Content(intId); + if (ConvertIdObjectToGuid(id, out var guidId)) + return ContentQuery.Content(guidId); + if (ConvertIdObjectToUdi(id, out var udiId)) + return ContentQuery.Content(udiId); return null; } @@ -606,29 +486,19 @@ namespace Umbraco.Web /// /// The unique identifier of the content item. /// The content, or null of the content item is not in the cache. - public IPublishedContent TypedContent(int id) - { - return ContentQuery.TypedContent(id); - } + public IPublishedContent Content(int id) + { + return ContentQuery.Content(id); + } /// /// Gets a content item from the cache. /// /// The key of the content item. /// The content, or null of the content item is not in the cache. - public IPublishedContent TypedContent(Guid id) + public IPublishedContent Content(Guid id) { - return ContentQuery.TypedContent(id); - } - - /// - /// Gets a content item from the cache - /// - /// - /// - public IPublishedContent TypedContent(Udi id) - { - return ContentQuery.TypedContent(id); + return ContentQuery.Content(id); } /// @@ -636,165 +506,17 @@ namespace Umbraco.Web /// /// The unique identifier, or the key, of the content item. /// The content, or null of the content item is not in the cache. - public IPublishedContent TypedContent(string id) - { - return TypedContentForObject(id); - } - - public IPublishedContent TypedContentSingleAtXPath(string xpath, params XPathVariable[] vars) + public IPublishedContent Content(string id) { - return ContentQuery.TypedContentSingleAtXPath(xpath, vars); - } - - /// - /// Gets content items from the cache. - /// - /// The unique identifiers, or the keys, of the content items. - /// The content items that were found in the cache. - /// Does not support mixing identifiers and keys. - public IEnumerable TypedContent(params object[] ids) - { - return TypedContentForObjects(ids); - } - - private IEnumerable TypedContentForObjects(IEnumerable ids) - { - var idsA = ids.ToArray(); - IEnumerable intIds; - if (ConvertIdsObjectToInts(idsA, out intIds)) - return ContentQuery.TypedContent(intIds); - IEnumerable guidIds; - if (ConvertIdsObjectToGuids(idsA, out guidIds)) - return ContentQuery.TypedContent(guidIds); - return Enumerable.Empty(); - } - - /// - /// Gets content items from the cache. - /// - /// The unique identifiers of the content items. - /// The content items that were found in the cache. - public IEnumerable TypedContent(params int[] ids) - { - return ContentQuery.TypedContent(ids); - } - - /// - /// Gets content items from the cache. - /// - /// The keys of the content items. - /// The content items that were found in the cache. - public IEnumerable TypedContent(params Guid[] ids) - { - return ContentQuery.TypedContent(ids); - } - - /// - /// Gets content items from the cache. - /// - /// The unique identifiers, or the keys, of the content items. - /// The content items that were found in the cache. - /// Does not support mixing identifiers and keys. - public IEnumerable TypedContent(params string[] ids) - { - return TypedContentForObjects(ids); - } - - /// - /// Gets the contents corresponding to the identifiers. - /// - /// The content identifiers. - /// The existing contents corresponding to the identifiers. - /// If an identifier does not match an existing content, it will be missing in the returned value. - public IEnumerable TypedContent(IEnumerable ids) - { - return TypedContentForObjects(ids); - } - - /// - /// Gets the contents corresponding to the identifiers. - /// - /// The content identifiers. - /// The existing contents corresponding to the identifiers. - /// If an identifier does not match an existing content, it will be missing in the returned value. - public IEnumerable TypedContent(IEnumerable ids) - { - return TypedContentForObjects(ids); - } - - /// - /// Gets the contents corresponding to the identifiers. - /// - /// The content identifiers. - /// The existing contents corresponding to the identifiers. - /// If an identifier does not match an existing content, it will be missing in the returned value. - public IEnumerable TypedContent(IEnumerable ids) - { - return ContentQuery.TypedContent(ids); - } - - public IEnumerable TypedContentAtXPath(string xpath, params XPathVariable[] vars) - { - return ContentQuery.TypedContentAtXPath(xpath, vars); - } - - public IEnumerable TypedContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) - { - return ContentQuery.TypedContentAtXPath(xpath, vars); - } - - public IEnumerable TypedContentAtRoot() - { - return ContentQuery.TypedContentAtRoot(); - } - - /// - /// Gets a content item from the cache. - /// - /// The unique identifier, or the key, of the content item. - /// The content, or DynamicNull of the content item is not in the cache. - public dynamic Content(object id) - { - return ContentForObject(id); - } - - private dynamic ContentForObject(object id) - { - int intId; - if (ConvertIdObjectToInt(id, out intId)) - return ContentQuery.Content(intId); - Guid guidId; - if (ConvertIdObjectToGuid(id, out guidId)) - return ContentQuery.Content(guidId); - return DynamicNull.Null; - } - - /// - /// Gets a content item from the cache. - /// - /// The unique identifier of the content item. - /// The content, or DynamicNull of the content item is not in the cache. - public dynamic Content(int id) - { - return ContentQuery.Content(id); - } - - /// - /// Gets a content item from the cache. - /// - /// The unique identifier, or the key, of the content item. - /// The content, or DynamicNull of the content item is not in the cache. - public dynamic Content(string id) - { return ContentForObject(id); } - public dynamic ContentSingleAtXPath(string xpath, params XPathVariable[] vars) + public IPublishedContent Content(Udi id) { - return ContentQuery.ContentSingleAtXPath(xpath, vars); + return ContentQuery.Content(id); } - public dynamic ContentSingleAtXPath(XPathExpression xpath, params XPathVariable[] vars) + public IPublishedContent ContentSingleAtXPath(string xpath, params XPathVariable[] vars) { return ContentQuery.ContentSingleAtXPath(xpath, vars); } @@ -805,12 +527,12 @@ namespace Umbraco.Web /// The unique identifiers, or the keys, of the content items. /// The content items that were found in the cache. /// Does not support mixing identifiers and keys. - public dynamic Content(params object[] ids) + public IEnumerable Content(params object[] ids) { return ContentForObjects(ids); } - private dynamic ContentForObjects(IEnumerable ids) + private IEnumerable ContentForObjects(IEnumerable ids) { var idsA = ids.ToArray(); IEnumerable intIds; @@ -827,42 +549,20 @@ namespace Umbraco.Web /// /// The unique identifiers of the content items. /// The content items that were found in the cache. - public dynamic Content(params int[] ids) - { + public IEnumerable Content(params int[] ids) + { return ContentQuery.Content(ids); - } - - /// - /// Gets content items from the cache. - /// - /// The unique identifiers, or the keys, of the content items. - /// The content items that were found in the cache. - /// Does not support mixing identifiers and keys. - public dynamic Content(params string[] ids) - { - return ContentForObjects(ids); } /// /// Gets content items from the cache. /// - /// The unique identifiers, or the keys, of the content items. + /// The keys of the content items. /// The content items that were found in the cache. - /// Does not support mixing identifiers and keys. - public dynamic Content(IEnumerable ids) - { - return ContentForObjects(ids); - } - - /// - /// Gets content items from the cache. - /// - /// The unique identifiers of the content items. - /// The content items that were found in the cache. - public dynamic Content(IEnumerable ids) - { + public IEnumerable Content(params Guid[] ids) + { return ContentQuery.Content(ids); - } + } /// /// Gets content items from the cache. @@ -870,22 +570,55 @@ namespace Umbraco.Web /// The unique identifiers, or the keys, of the content items. /// The content items that were found in the cache. /// Does not support mixing identifiers and keys. - public dynamic Content(IEnumerable ids) - { + public IEnumerable Content(params string[] ids) + { return ContentForObjects(ids); } - public dynamic ContentAtXPath(string xpath, params XPathVariable[] vars) + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public IEnumerable Content(IEnumerable ids) + { + return ContentForObjects(ids); + } + + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public IEnumerable Content(IEnumerable ids) + { + return ContentForObjects(ids); + } + + /// + /// Gets the contents corresponding to the identifiers. + /// + /// The content identifiers. + /// The existing contents corresponding to the identifiers. + /// If an identifier does not match an existing content, it will be missing in the returned value. + public IEnumerable Content(IEnumerable ids) + { + return ContentQuery.Content(ids); + } + + public IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars) { return ContentQuery.ContentAtXPath(xpath, vars); } - public dynamic ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) + public IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) { return ContentQuery.ContentAtXPath(xpath, vars); } - public dynamic ContentAtRoot() + public IEnumerable ContentAtRoot() { return ContentQuery.ContentAtRoot(); } @@ -897,6 +630,7 @@ namespace Umbraco.Web { return int.TryParse(s, out intId); } + if (id is int) { intId = (int) id; @@ -922,30 +656,13 @@ namespace Umbraco.Web return false; } - private static bool ConvertIdObjectToUdi(object id, out Udi guidId) - { - var s = id as string; - if (s != null) - { - return Udi.TryParse(s, out guidId); - } - if (id is Udi) - { - guidId = (Udi)id; - return true; - } - guidId = null; - return false; - } - private static bool ConvertIdsObjectToInts(IEnumerable ids, out IEnumerable intIds) { var list = new List(); intIds = null; foreach (var id in ids) { - int intId; - if (ConvertIdObjectToInt(id, out intId)) + if (ConvertIdObjectToInt(id, out var intId)) list.Add(intId); else return false; // if one of them is not an int, fail @@ -970,25 +687,37 @@ namespace Umbraco.Web return true; } + private static bool ConvertIdObjectToUdi(object id, out Udi guidId) + { + if (id is string s) + return Udi.TryParse(s, out guidId); + if (id is Udi) + { + guidId = (Udi) id; + return true; + } + guidId = null; + return false; + } + #endregion #region Media - public IPublishedContent TypedMedia(Udi id) + public IPublishedContent Media(Udi id) { var guidUdi = id as GuidUdi; - if (guidUdi == null) return null; - return TypedMedia(guidUdi.Guid); + return guidUdi == null ? null : Media(guidUdi.Guid); } - public IPublishedContent TypedMedia(Guid id) + public IPublishedContent Media(Guid id) { - //TODO: This is horrible but until the media cache properly supports GUIDs we have no choice here and + //TODO: This is horrible but until the media cache properly supports GUIDs we have no choice here and // currently there won't be any way to add this method correctly to `ITypedPublishedContentQuery` without breaking an interface and adding GUID support for media - var entityService = UmbracoContext.Application.Services.EntityService; - var mediaAttempt = entityService.GetIdForKey(id, UmbracoObjectTypes.Media); - return mediaAttempt.Success ? ContentQuery.TypedMedia(mediaAttempt.Result) : null; + var entityService = Current.Services.EntityService; // fixme inject + var mediaAttempt = entityService.GetId(id, UmbracoObjectTypes.Media); + return mediaAttempt.Success ? ContentQuery.Media(mediaAttempt.Result) : null; } /// @@ -1001,135 +730,31 @@ namespace Umbraco.Web /// this result in to this method. /// This method will throw an exception if the value is not of type int or string. /// - public IPublishedContent TypedMedia(object id) + public IPublishedContent Media(object id) { - return TypedMediaForObject(id); + return MediaForObject(id); } - private IPublishedContent TypedMediaForObject(object id) + private IPublishedContent MediaForObject(object id) { - int intId; - if (ConvertIdObjectToInt(id, out intId)) - return ContentQuery.TypedMedia(intId); - Guid guidId; - if (ConvertIdObjectToGuid(id, out guidId)) - return TypedMedia(guidId); - Udi udiId; - if (ConvertIdObjectToUdi(id, out udiId)) - return TypedMedia(udiId); + if (ConvertIdObjectToInt(id, out var intId)) + return ContentQuery.Media(intId); + if (ConvertIdObjectToGuid(id, out var guidId)) + return ContentQuery.Media(guidId); + if (ConvertIdObjectToUdi(id, out var udiId)) + return ContentQuery.Media(udiId); return null; } - public IPublishedContent TypedMedia(int id) - { - return ContentQuery.TypedMedia(id); - } - - public IPublishedContent TypedMedia(string id) - { - int intId; - return ConvertIdObjectToInt(id, out intId) ? ContentQuery.TypedMedia(intId) : null; - } - - /// - /// Gets the medias corresponding to the identifiers. - /// - /// The media identifiers. - /// The existing medias corresponding to the identifiers. - /// If an identifier does not match an existing media, it will be missing in the returned value. - public IEnumerable TypedMedia(params object[] ids) + public IPublishedContent Media(int id) { - return TypedMediaForObjects(ids); - } - - private IEnumerable TypedMediaForObjects(IEnumerable ids) - { - var idsA = ids.ToArray(); - IEnumerable intIds; - if (ConvertIdsObjectToInts(idsA, out intIds)) - return ContentQuery.TypedMedia(intIds); - //IEnumerable guidIds; - //if (ConvertIdsObjectToGuids(idsA, out guidIds)) - // return ContentQuery.TypedMedia(guidIds); - return Enumerable.Empty(); - } - - /// - /// Gets the medias corresponding to the identifiers. - /// - /// The media identifiers. - /// The existing medias corresponding to the identifiers. - /// If an identifier does not match an existing media, it will be missing in the returned value. - public IEnumerable TypedMedia(params int[] ids) - { - return ContentQuery.TypedMedia(ids); - } - - /// - /// Gets the medias corresponding to the identifiers. - /// - /// The media identifiers. - /// The existing medias corresponding to the identifiers. - /// If an identifier does not match an existing media, it will be missing in the returned value. - public IEnumerable TypedMedia(params string[] ids) - { - return TypedMediaForObjects(ids); - } - - /// - /// Gets the medias corresponding to the identifiers. - /// - /// The media identifiers. - /// The existing medias corresponding to the identifiers. - /// If an identifier does not match an existing media, it will be missing in the returned value. - public IEnumerable TypedMedia(IEnumerable ids) - { - return TypedMediaForObjects(ids); - } - - /// - /// Gets the medias corresponding to the identifiers. - /// - /// The media identifiers. - /// The existing medias corresponding to the identifiers. - /// If an identifier does not match an existing media, it will be missing in the returned value. - public IEnumerable TypedMedia(IEnumerable ids) - { - return ContentQuery.TypedMedia(ids); - } - - /// - /// Gets the medias corresponding to the identifiers. - /// - /// The media identifiers. - /// The existing medias corresponding to the identifiers. - /// If an identifier does not match an existing media, it will be missing in the returned value. - public IEnumerable TypedMedia(IEnumerable ids) - { - return TypedMediaForObjects(ids); - } - - public IEnumerable TypedMediaAtRoot() - { - return ContentQuery.TypedMediaAtRoot(); - } - - public dynamic Media(object id) - { - int intId; - return ConvertIdObjectToInt(id, out intId) ? ContentQuery.Media(intId) : DynamicNull.Null; - } - - public dynamic Media(int id) - { return ContentQuery.Media(id); - } + } - public dynamic Media(string id) - { - int intId; - return ConvertIdObjectToInt(id, out intId) ? ContentQuery.Media(intId) : DynamicNull.Null; - } + public IPublishedContent Media(string id) + { + return MediaForObject(id); + } /// /// Gets the medias corresponding to the identifiers. @@ -1137,12 +762,12 @@ namespace Umbraco.Web /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. - public dynamic Media(params object[] ids) + public IEnumerable Media(params object[] ids) { return MediaForObjects(ids); } - private dynamic MediaForObjects(IEnumerable ids) + private IEnumerable MediaForObjects(IEnumerable ids) { var idsA = ids.ToArray(); IEnumerable intIds; @@ -1160,10 +785,10 @@ namespace Umbraco.Web /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. - public dynamic Media(params int[] ids) - { + public IEnumerable Media(params int[] ids) + { return ContentQuery.Media(ids); - } + } /// /// Gets the medias corresponding to the identifiers. @@ -1171,10 +796,10 @@ namespace Umbraco.Web /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. - public dynamic Media(params string[] ids) + public IEnumerable Media(params string[] ids) { return MediaForObjects(ids); - } + } /// /// Gets the medias corresponding to the identifiers. @@ -1182,8 +807,8 @@ namespace Umbraco.Web /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. - public dynamic Media(IEnumerable ids) - { + public IEnumerable Media(IEnumerable ids) + { return MediaForObjects(ids); } @@ -1193,10 +818,10 @@ namespace Umbraco.Web /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. - public dynamic Media(IEnumerable ids) - { + public IEnumerable Media(IEnumerable ids) + { return ContentQuery.Media(ids); - } + } /// /// Gets the medias corresponding to the identifiers. @@ -1204,135 +829,91 @@ namespace Umbraco.Web /// The media identifiers. /// The existing medias corresponding to the identifiers. /// If an identifier does not match an existing media, it will be missing in the returned value. - public dynamic Media(IEnumerable ids) - { + public IEnumerable Media(IEnumerable ids) + { return MediaForObjects(ids); } - public dynamic MediaAtRoot() + public IEnumerable MediaAtRoot() { return ContentQuery.MediaAtRoot(); } - #endregion + #endregion - #region Search + #region Search - /// - /// Searches content - /// - /// - /// - /// - /// - public dynamic Search(string term, bool useWildCards = true, string searchProvider = null) - { - return ContentQuery.Search(term, useWildCards, searchProvider); - } - - /// - /// Searhes content - /// - /// - /// - /// - public dynamic Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) - { - return ContentQuery.Search(criteria, searchProvider); - } - - /// - /// Searches content - /// - /// - /// - /// - /// - public IEnumerable TypedSearch(string term, bool useWildCards = true, string searchProvider = null) - { - return ContentQuery.TypedSearch(term, useWildCards, searchProvider); - } - /// - /// Searches content - /// - /// - /// - /// - /// - /// - /// - /// - public IEnumerable TypedSearch(int skip, int take, out int totalRecords, string term, bool useWildCards = true, string searchProvider = null) + /// Searches content. + /// + /// + /// + /// + /// + public IEnumerable Search(string term, bool useWildCards = true, string searchProvider = null) { - return ContentQuery.TypedSearch(skip, take, out totalRecords, term, useWildCards, searchProvider); - } - + return ContentQuery.Search(term, useWildCards, searchProvider); + } + /// - /// Searhes content - /// - /// - /// + /// Searches content. + /// + /// + /// + /// + /// + /// + /// + /// + public IEnumerable TypedSearch(int skip, int take, out int totalRecords, string term, bool useWildCards = true, string searchProvider = null) + { + return ContentQuery.Search(skip, take, out totalRecords, term, useWildCards, searchProvider); + } + + /// + /// Searhes content. + /// + /// + /// /// /// /// /// - public IEnumerable TypedSearch(int skip, int take, out int totalRecords, Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) - { - return ContentQuery.TypedSearch(skip, take, out totalRecords, criteria, searchProvider); - } + public IEnumerable TypedSearch(int skip, int take, out int totalRecords, Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) + { + return ContentQuery.Search(skip, take, out totalRecords, criteria, searchProvider); + } /// - /// Searhes content + /// Searhes content. /// /// /// /// - public IEnumerable TypedSearch(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) + public IEnumerable Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) { - return ContentQuery.TypedSearch(criteria, searchProvider); - } - + return ContentQuery.Search(criteria, searchProvider); + } + #endregion - - #region Xml - - public dynamic ToDynamicXml(string xml) - { - if (string.IsNullOrWhiteSpace(xml)) return null; - var xElement = XElement.Parse(xml); - return new DynamicXml(xElement); - } - public dynamic ToDynamicXml(XElement xElement) - { - return new DynamicXml(xElement); - } + #region Strings - public dynamic ToDynamicXml(XPathNodeIterator xpni) - { - return new DynamicXml(xpni); - } - - #endregion - - #region Strings - - /// - /// Replaces text line breaks with html line breaks - /// - /// The text. - /// The text with text line breaks replaced with html linebreaks (
)
- public string ReplaceLineBreaksForHtml(string text) - { - return _stringUtilities.ReplaceLineBreaksForHtml(text); - } + /// + /// Replaces text line breaks with html line breaks + /// + /// The text. + /// The text with text line breaks replaced with html linebreaks (
)
+ public string ReplaceLineBreaksForHtml(string text) + { + return StringUtilities.ReplaceLineBreaksForHtml(text); + } /// /// Returns an MD5 hash of the string specified /// /// The text to create a hash from - /// Md5 hash of the string + /// Md5 has of the string [Obsolete("Please use the CreateHash method instead. This may be removed in future versions")] [EditorBrowsable(EditorBrowsableState.Never)] public string CreateMd5Hash(string text) @@ -1341,7 +922,7 @@ namespace Umbraco.Web } /// - /// Generates a hash based on the text string passed in. This method will detect the + /// Generates a hash based on the text string passed in. This method will detect the /// security requirements (is FIPS enabled) and return an appropriate hash. /// /// The text to create a hash from @@ -1355,121 +936,90 @@ namespace Umbraco.Web /// Strips all html tags from a given string, all contents of the tags will remain. /// public HtmlString StripHtml(IHtmlString html, params string[] tags) - { - return StripHtml(html.ToHtmlString(), tags); - } - - /// - /// Strips all html tags from a given string, all contents of the tags will remain. - /// - public HtmlString StripHtml(DynamicNull html, params string[] tags) - { - return new HtmlString(string.Empty); - } - - /// - /// Strips all html tags from a given string, all contents of the tags will remain. - /// - public HtmlString StripHtml(string html, params string[] tags) - { - return _stringUtilities.StripHtmlTags(html, tags); - } - - /// - /// Will take the first non-null value in the collection and return the value of it. - /// - public string Coalesce(params object[] args) - { - return _stringUtilities.Coalesce(args); - } - - /// - /// Will take the first non-null value in the collection and return the value of it. - /// - public string Concatenate(params object[] args) - { - return _stringUtilities.Concatenate(args); - } - - /// - /// Joins any number of int/string/objects into one string and seperates them with the string seperator parameter. - /// - public string Join(string seperator, params object[] args) - { - return _stringUtilities.Join(seperator, args); - } - - /// - /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them - /// - public IHtmlString Truncate(IHtmlString html, int length) - { - return Truncate(html.ToHtmlString(), length, true, false); - } - - /// - /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them - /// - public IHtmlString Truncate(IHtmlString html, int length, bool addElipsis) - { - return Truncate(html.ToHtmlString(), length, addElipsis, false); - } - - /// - /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them - /// - public IHtmlString Truncate(IHtmlString html, int length, bool addElipsis, bool treatTagsAsContent) - { - return Truncate(html.ToHtmlString(), length, addElipsis, treatTagsAsContent); - } - - /// - /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them - /// - public IHtmlString Truncate(DynamicNull html, int length) - { - return new HtmlString(string.Empty); - } - - /// - /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them - /// - public IHtmlString Truncate(DynamicNull html, int length, bool addElipsis) - { - return new HtmlString(string.Empty); - } - - /// - /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them - /// - public IHtmlString Truncate(DynamicNull html, int length, bool addElipsis, bool treatTagsAsContent) - { - return new HtmlString(string.Empty); - } - - /// - /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them - /// - public IHtmlString Truncate(string html, int length) - { - return Truncate(html, length, true, false); - } - - /// - /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them - /// - public IHtmlString Truncate(string html, int length, bool addElipsis) - { - return Truncate(html, length, addElipsis, false); - } - - /// - /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them - /// - public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) - { - return _stringUtilities.Truncate(html, length, addElipsis, treatTagsAsContent); + { + return StripHtml(html.ToHtmlString(), tags); } + + /// + /// Strips all html tags from a given string, all contents of the tags will remain. + /// + public HtmlString StripHtml(string html, params string[] tags) + { + return StringUtilities.StripHtmlTags(html, tags); + } + + /// + /// Will take the first non-null value in the collection and return the value of it. + /// + public string Coalesce(params object[] args) + { + return StringUtilities.Coalesce(args); + } + + /// + /// Will take the first non-null value in the collection and return the value of it. + /// + public string Concatenate(params object[] args) + { + return StringUtilities.Concatenate(args); + } + + /// + /// Joins any number of int/string/objects into one string and seperates them with the string seperator parameter. + /// + public string Join(string separator, params object[] args) + { + return StringUtilities.Join(separator, args); + } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString Truncate(IHtmlString html, int length) + { + return Truncate(html.ToHtmlString(), length, true, false); + } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString Truncate(IHtmlString html, int length, bool addElipsis) + { + return Truncate(html.ToHtmlString(), length, addElipsis, false); + } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString Truncate(IHtmlString html, int length, bool addElipsis, bool treatTagsAsContent) + { + return Truncate(html.ToHtmlString(), length, addElipsis, treatTagsAsContent); + } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString Truncate(string html, int length) + { + return Truncate(html, length, true, false); + } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString Truncate(string html, int length, bool addElipsis) + { + return Truncate(html, length, addElipsis, false); + } + + /// + /// Truncates a string to a given length, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them + /// + public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) + { + return StringUtilities.Truncate(html, length, addElipsis, treatTagsAsContent); + } + #region Truncate by Words /// @@ -1477,7 +1027,7 @@ namespace Umbraco.Web /// public IHtmlString TruncateByWords(string html, int words) { - int length = _stringUtilities.WordsToLength(html, words); + int length = StringUtilities.WordsToLength(html, words); return Truncate(html, length, true, false); } @@ -1487,7 +1037,7 @@ namespace Umbraco.Web /// public IHtmlString TruncateByWords(string html, int words, bool addElipsis) { - int length = _stringUtilities.WordsToLength(html, words); + int length = StringUtilities.WordsToLength(html, words); return Truncate(html, length, addElipsis, false); } @@ -1497,7 +1047,7 @@ namespace Umbraco.Web /// public IHtmlString TruncateByWords(IHtmlString html, int words) { - int length = _stringUtilities.WordsToLength(html.ToHtmlString(), words); + int length = StringUtilities.WordsToLength(html.ToHtmlString(), words); return Truncate(html, length, true, false); } @@ -1507,11 +1057,13 @@ namespace Umbraco.Web /// public IHtmlString TruncateByWords(IHtmlString html, int words, bool addElipsis) { - int length = _stringUtilities.WordsToLength(html.ToHtmlString(), words); + int length = StringUtilities.WordsToLength(html.ToHtmlString(), words); return Truncate(html, length, addElipsis, false); } + #endregion + #endregion #region If @@ -1520,30 +1072,16 @@ namespace Umbraco.Web /// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned. /// public HtmlString If(bool test, string valueIfTrue, string valueIfFalse) - { - return test ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); - } - - /// - /// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned. - /// - public HtmlString If(bool test, string valueIfTrue) - { - return test ? new HtmlString(valueIfTrue) : new HtmlString(string.Empty); - } - - #endregion - - #region Prevalues + { + return test ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); + } /// - /// Gets a specific PreValue by its Id + /// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned. /// - /// Id of the PreValue to retrieve the value from - /// PreValue as a string - public string GetPreValueAsString(int id) + public HtmlString If(bool test, string valueIfTrue) { - return DataTypeService.GetPreValueAsString(id); + return test ? new HtmlString(valueIfTrue) : new HtmlString(string.Empty); } #endregion @@ -1603,15 +1141,12 @@ namespace Umbraco.Web /// internal static string CreateEncryptedRouteString(string controllerName, string controllerAction, string area, object additionalRouteVals = null) { - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - Mandate.ParameterNotNullOrEmpty(controllerAction, "controllerAction"); - Mandate.ParameterNotNull(area, "area"); + if (string.IsNullOrEmpty(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); + if (string.IsNullOrEmpty(controllerAction)) throw new ArgumentNullOrEmptyException(nameof(controllerAction)); + if (area == null) throw new ArgumentNullException(nameof(area)); //need to create a params string as Base64 to put into our hidden field to use during the routes - var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", - HttpUtility.UrlEncode(controllerName), - HttpUtility.UrlEncode(controllerAction), - area); + var surfaceRouteParams = $"c={HttpUtility.UrlEncode(controllerName)}&a={HttpUtility.UrlEncode(controllerAction)}&ar={area}"; //checking if the additional route values is already a dictionary and convert to querystring string additionalRouteValsAsQuery; @@ -1631,11 +1166,5 @@ namespace Umbraco.Web return surfaceRouteParams.EncryptWithMachineKey(); } - - public int GetIdForUdi(Udi udi) - { - var udiToIdAttempt = EntityService.GetIdForUdi(udi); - return udiToIdAttempt.Success ? udiToIdAttempt.Result : -1; - } - } + } } diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index f2503b88c6..746c23a325 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -1,57 +1,99 @@ using System; using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.IO; -using System.Linq; -using System.Security.Principal; +using System.Text; using System.Threading; -using System.Threading.Tasks; using System.Web; -using System.Web.Mvc; using System.Web.Routing; -using Newtonsoft.Json; +using LightInject; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Security; -using Umbraco.Web.Editors; using Umbraco.Web.Routing; using Umbraco.Web.Security; -using umbraco; -using Umbraco.Core.Sync; +using Umbraco.Core.Collections; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Persistence; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Web.Composing; +using Umbraco.Web.PublishedCache; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; -using ObjectExtensions = Umbraco.Core.ObjectExtensions; -using RenderingEngine = Umbraco.Core.RenderingEngine; namespace Umbraco.Web { - // also look at IOHelper.ResolveUrlsFromTextString - nightmarish?! + // also look at IOHelper.ResolveUrlsFromTextString - nightmarish?! - // context.RewritePath supports ~/ or else must begin with /vdir - // Request.RawUrl is still there - // response.Redirect does?! always remap to /vdir?! + // context.RewritePath supports ~/ or else must begin with /vdir + // Request.RawUrl is still there + // response.Redirect does?! always remap to /vdir?! - public class UmbracoModule : IHttpModule - { - #region HttpModule event handlers + public class UmbracoModule : IHttpModule + { + #region Dependencies - /// - /// Begins to process a request. - /// - /// - static void BeginRequest(HttpContextBase httpContext) - { + // modules are *not* instanciated by the container so we have to + // get our dependencies injected manually, through properties, in + // Init(). works for dependencies that are singletons. + + [Inject] + public IUmbracoSettingsSection UmbracoSettings { get; set; } + + [Inject] + public IGlobalSettings GlobalSettings { get; set; } + + [Inject] + public IUmbracoContextAccessor UmbracoContextAccessor { get; set; } + + [Inject] + public IPublishedSnapshotService PublishedSnapshotService { get; set; } + + [Inject] + public IUserService UserService { get; set; } + + [Inject] + public UrlProviderCollection UrlProviders { get; set; } + + [Inject] + public IRuntimeState Runtime { get; set; } + + [Inject] + public ILogger Logger { get; set; } + + [Inject] + internal PublishedRouter PublishedRouter { get; set; } + + [Inject] + internal IUmbracoDatabaseFactory DatabaseFactory { get; set; } + + #endregion + + public UmbracoModule() + { + _combinedRouteCollection = new Lazy(CreateRouteCollection); + } + + #region HttpModule event handlers + + /// + /// Begins to process a request. + /// + /// + private void BeginRequest(HttpContextBase httpContext) + { // ensure application url is initialized - ApplicationUrlHelper.EnsureApplicationUrl(ApplicationContext.Current, httpContext.Request); + ((RuntimeState) Current.RuntimeState).EnsureApplicationUrl(UmbracoSettings, GlobalSettings, httpContext.Request); // do not process if client-side request - if (httpContext.Request.Url.IsClientSideRequest()) - return; + if (httpContext.Request.Url.IsClientSideRequest()) + return; - //write the trace output for diagnostics at the end of the request - httpContext.Trace.Write("UmbracoModule", "Umbraco request begins"); + // write the trace output for diagnostics at the end of the request + httpContext.Trace.Write("UmbracoModule", "Umbraco request begins"); // ok, process @@ -61,269 +103,257 @@ namespace Umbraco.Web legacyRequestInitializer.InitializeRequest(); // create the UmbracoContext singleton, one per request, and assign - // NOTE: we assign 'true' to ensure the context is replaced if it is already set (i.e. during app startup) + // replace existing if any (eg during app startup, a temp one is created) UmbracoContext.EnsureContext( + UmbracoContextAccessor, httpContext, - ApplicationContext.Current, - new WebSecurity(httpContext, ApplicationContext.Current), + PublishedSnapshotService, + new WebSecurity(httpContext, UserService, GlobalSettings), + UmbracoConfig.For.UmbracoSettings(), + UrlProviders, + GlobalSettings, true); - } + } - /// - /// Processses the Umbraco Request - /// - /// - /// - /// - /// This will check if we are trying to route to the default back office page (i.e. ~/Umbraco/ or ~/Umbraco or ~/Umbraco/Default ) - /// and ensure that the MVC handler executes for that. This is required because the route for /Umbraco will never execute because + /// + /// Processses the Umbraco Request + /// + /// + /// + /// + /// This will check if we are trying to route to the default back office page (i.e. ~/Umbraco/ or ~/Umbraco or ~/Umbraco/Default ) + /// and ensure that the MVC handler executes for that. This is required because the route for /Umbraco will never execute because /// files/folders exist there and we cannot set the RouteCollection.RouteExistingFiles = true since that will muck a lot of other things up. /// So we handle it here and explicitly execute the MVC controller. - /// - /// - void ProcessRequest(HttpContextBase httpContext) - { - // do not process if client-side request - if (httpContext.Request.Url.IsClientSideRequest()) - return; + /// + /// + void ProcessRequest(HttpContextBase httpContext) + { + // do not process if client-side request + if (httpContext.Request.Url.IsClientSideRequest()) + return; - if (UmbracoContext.Current == null) - throw new InvalidOperationException("The UmbracoContext.Current is null, ProcessRequest cannot proceed unless there is a current UmbracoContext"); - if (UmbracoContext.Current.RoutingContext == null) - throw new InvalidOperationException("The UmbracoContext.RoutingContext has not been assigned, ProcessRequest cannot proceed unless there is a RoutingContext assigned to the UmbracoContext"); + if (UmbracoContext.Current == null) + throw new InvalidOperationException("The UmbracoContext.Current is null, ProcessRequest cannot proceed unless there is a current UmbracoContext"); - var umbracoContext = UmbracoContext.Current; + var umbracoContext = UmbracoContext.Current; - //re-write for the default back office path - if (httpContext.Request.Url.IsDefaultBackOfficeRequest()) + // re-write for the default back office path + if (httpContext.Request.Url.IsDefaultBackOfficeRequest(GlobalSettings)) { - if (EnsureIsConfigured(httpContext, umbracoContext.OriginalRequestUrl)) - { + if (EnsureRuntime(httpContext, umbracoContext.OriginalRequestUrl)) RewriteToBackOfficeHandler(httpContext); - } return; } - // do not process but remap to handler if it is a base rest request - if (BaseRest.BaseRestHandler.IsBaseRestRequest(umbracoContext.OriginalRequestUrl)) - { - httpContext.RemapHandler(new BaseRest.BaseRestHandler()); - return; - } + // do not process if this request is not a front-end routable page + var isRoutableAttempt = EnsureUmbracoRoutablePage(umbracoContext, httpContext); - // do not process if this request is not a front-end routable page - var isRoutableAttempt = EnsureUmbracoRoutablePage(umbracoContext, httpContext); - //raise event here + // raise event here OnRouteAttempt(new RoutableAttemptEventArgs(isRoutableAttempt.Result, umbracoContext, httpContext)); - if (!isRoutableAttempt.Success) - { - return; - } + if (isRoutableAttempt.Success == false) return; + httpContext.Trace.Write("UmbracoModule", "Umbraco request confirmed"); - httpContext.Trace.Write("UmbracoModule", "Umbraco request confirmed"); + // ok, process - // ok, process + // note: requestModule.UmbracoRewrite also did some stripping of &umbPage + // from the querystring... that was in v3.x to fix some issues with pre-forms + // auth. Paul Sterling confirmed in jan. 2013 that we can get rid of it. - // note: requestModule.UmbracoRewrite also did some stripping of &umbPage - // from the querystring... that was in v3.x to fix some issues with pre-forms - // auth. Paul Sterling confirmed in jan. 2013 that we can get rid of it. - - // instanciate, prepare and process the published content request - // important to use CleanedUmbracoUrl - lowercase path-only version of the current url - var pcr = new PublishedContentRequest(umbracoContext.CleanedUmbracoUrl, umbracoContext.RoutingContext); - umbracoContext.PublishedContentRequest = pcr; - pcr.Prepare(); + // instanciate, prepare and process the published content request + // important to use CleanedUmbracoUrl - lowercase path-only version of the current url + var request = PublishedRouter.CreateRequest(umbracoContext); + umbracoContext.PublishedRequest = request; + PublishedRouter.PrepareRequest(request); // HandleHttpResponseStatus returns a value indicating that the request should // not be processed any further, eg because it has been redirect. then, exit. - if (HandleHttpResponseStatus(httpContext, pcr)) - return; + if (HandleHttpResponseStatus(httpContext, request, Logger)) + return; - if (!pcr.HasPublishedContent) - httpContext.RemapHandler(new PublishedContentNotFoundHandler()); - else - RewriteToUmbracoHandler(httpContext, pcr); - } + if (request.HasPublishedContent == false) + httpContext.RemapHandler(new PublishedContentNotFoundHandler()); + else + RewriteToUmbracoHandler(httpContext, request); + } - #endregion + #endregion - #region Methods + #region Methods - /// - /// Checks the current request and ensures that it is routable based on the structure of the request and URI - /// - /// - /// - /// + /// + /// Checks the current request and ensures that it is routable based on the structure of the request and URI + /// + /// + /// + /// internal Attempt EnsureUmbracoRoutablePage(UmbracoContext context, HttpContextBase httpContext) - { - var uri = context.OriginalRequestUrl; + { + var uri = context.OriginalRequestUrl; - var reason = EnsureRoutableOutcome.IsRoutable; + var reason = EnsureRoutableOutcome.IsRoutable; - // ensure this is a document request - if (!EnsureDocumentRequest(httpContext, uri)) - { - reason = EnsureRoutableOutcome.NotDocumentRequest; - } - // ensure Umbraco is ready to serve documents - else if (!EnsureIsReady(httpContext, uri)) - { - reason = EnsureRoutableOutcome.NotReady; - } - // ensure Umbraco is properly configured to serve documents - else if (!EnsureIsConfigured(httpContext, uri)) + // ensure this is a document request + if (EnsureDocumentRequest(httpContext, uri) == false) { - reason = EnsureRoutableOutcome.NotConfigured; + reason = EnsureRoutableOutcome.NotDocumentRequest; + } + // ensure the runtime is in the proper state + // and deal with needed redirects, etc + else if (EnsureRuntime(httpContext, uri) == false) + { + reason = EnsureRoutableOutcome.NotReady; } // ensure Umbraco has documents to serve - else if (!EnsureHasContent(context, httpContext)) + else if (EnsureHasContent(context, httpContext) == false) { reason = EnsureRoutableOutcome.NoContent; } return Attempt.If(reason == EnsureRoutableOutcome.IsRoutable, reason); - } + } - /// - /// Ensures that the request is a document request (i.e. one that the module should handle) - /// - /// - /// - /// - bool EnsureDocumentRequest(HttpContextBase httpContext, Uri uri) - { - var maybeDoc = true; - var lpath = uri.AbsolutePath.ToLowerInvariant(); + /// + /// Ensures that the request is a document request (i.e. one that the module should handle) + /// + /// + /// + /// + private bool EnsureDocumentRequest(HttpContextBase httpContext, Uri uri) + { + var maybeDoc = true; + var lpath = uri.AbsolutePath.ToLowerInvariant(); - // handle directory-urls used for asmx - // legacy - what's the point really? - if (/*maybeDoc &&*/ GlobalSettings.UseDirectoryUrls) - { - int asmxPos = lpath.IndexOf(".asmx/", StringComparison.OrdinalIgnoreCase); - if (asmxPos >= 0) - { - // use uri.AbsolutePath, not path, 'cos path has been lowercased - httpContext.RewritePath(uri.AbsolutePath.Substring(0, asmxPos + 5), // filePath - uri.AbsolutePath.Substring(asmxPos + 5), // pathInfo - uri.Query.TrimStart('?')); - maybeDoc = false; - } - } - - // a document request should be - // /foo/bar/nil - // /foo/bar/nil/ - // /foo/bar/nil.aspx - // where /foo is not a reserved path - - // if the path contains an extension that is not .aspx - // then it cannot be a document request - var extension = Path.GetExtension(lpath); - if (maybeDoc && extension.IsNullOrWhiteSpace() == false && extension != ".aspx") - maybeDoc = false; - - // at that point, either we have no extension, or it is .aspx - - // if the path is reserved then it cannot be a document request - if (maybeDoc && GlobalSettings.IsReservedPathOrUrl(lpath, httpContext, _combinedRouteCollection.Value)) - maybeDoc = false; - - //NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :) - //if (!maybeDoc) - //{ - // LogHelper.Warn("Not a document"); - //} - - return maybeDoc; - } - - // ensures Umbraco is ready to handle requests - // if not, set status to 503 and transfer request, and return false - // if yes, return true - static bool EnsureIsReady(HttpContextBase httpContext, Uri uri) - { - var ready = ApplicationContext.Current.IsReady; - - // ensure we are ready - if (!ready) - { - LogHelper.Warn("Umbraco is not ready"); - - if (UmbracoConfig.For.UmbracoSettings().Content.EnableSplashWhileLoading == false) - { - // let requests pile up and wait for 10s then show the splash anyway - ready = ApplicationContext.Current.WaitForReady(10 * 1000); - } - - if (!ready) - { - httpContext.Response.StatusCode = 503; - - var bootUrl = "~/config/splashes/booting.aspx"; - - httpContext.RewritePath(UriUtility.ToAbsolute(bootUrl) + "?url=" + HttpUtility.UrlEncode(uri.ToString())); - - return false; - } - } - - return true; - } - - // ensures Umbraco has at least one published node - // if not, rewrites to splash and return false - // if yes, return true - private static bool EnsureHasContent(UmbracoContext context, HttpContextBase httpContext) - { - if (context.ContentCache.HasContent()) - return true; - - LogHelper.Warn("Umbraco has no content"); - - const string noContentUrl = "~/config/splashes/noNodes.aspx"; - httpContext.RewritePath(UriUtility.ToAbsolute(noContentUrl)); - - return false; - } - - private bool _notConfiguredReported; - - // ensures Umbraco is configured - // if not, redirect to install and return false - // if yes, return true - private bool EnsureIsConfigured(HttpContextBase httpContext, Uri uri) - { - if (ApplicationContext.Current.IsConfigured) - return true; - - if (_notConfiguredReported) - { - // remember it's been reported so we don't flood the log - // no thread-safety so there may be a few log entries, doesn't matter - _notConfiguredReported = true; - LogHelper.Warn("Umbraco is not configured"); + // handle directory-urls used for asmx + // legacy - what's the point really? + if (/*maybeDoc &&*/ GlobalSettings.UseDirectoryUrls) + { + var asmxPos = lpath.IndexOf(".asmx/", StringComparison.OrdinalIgnoreCase); + if (asmxPos >= 0) + { + // use uri.AbsolutePath, not path, 'cos path has been lowercased + httpContext.RewritePath(uri.AbsolutePath.Substring(0, asmxPos + 5), // filePath + uri.AbsolutePath.Substring(asmxPos + 5), // pathInfo + uri.Query.TrimStart('?')); + maybeDoc = false; + } } - var installPath = UriUtility.ToAbsolute(SystemDirectories.Install); - var installUrl = string.Format("{0}/?redir=true&url={1}", installPath, HttpUtility.UrlEncode(uri.ToString())); - httpContext.Response.Redirect(installUrl, true); - return false; - } + // a document request should be + // /foo/bar/nil + // /foo/bar/nil/ + // /foo/bar/nil.aspx + // where /foo is not a reserved path + + // if the path contains an extension that is not .aspx + // then it cannot be a document request + var extension = Path.GetExtension(lpath); + if (maybeDoc && extension.IsNullOrWhiteSpace() == false && extension != ".aspx") + maybeDoc = false; + + // at that point, either we have no extension, or it is .aspx + + // if the path is reserved then it cannot be a document request + if (maybeDoc && GlobalSettings.IsReservedPathOrUrl(lpath, httpContext, _combinedRouteCollection.Value)) + maybeDoc = false; + + //NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :) + //if (!maybeDoc) + //{ + // Logger.Warn("Not a document"); + //} + + return maybeDoc; + } + + private bool EnsureRuntime(HttpContextBase httpContext, Uri uri) + { + var debug = Runtime.Debug; + var level = Runtime.Level; + switch (level) + { + case RuntimeLevel.Unknown: + case RuntimeLevel.Boot: + // not ready yet, but wait + ReportRuntime(level, "Umbraco is booting."); + + // let requests pile up and wait for 10s then show the splash anyway + if (UmbracoConfig.For.UmbracoSettings().Content.EnableSplashWhileLoading == false + && ((RuntimeState) Runtime).WaitForRunLevel(TimeSpan.FromSeconds(10))) return true; + + // redirect to booting page + httpContext.Response.StatusCode = 503; // temp not available + const string bootUrl = "~/config/splashes/booting.aspx"; + httpContext.Response.AddHeader("Retry-After", debug ? "1" : "30"); // seconds + httpContext.RewritePath(UriUtility.ToAbsolute(bootUrl) + "?url=" + HttpUtility.UrlEncode(uri.ToString())); + return false; // cannot serve content + + case RuntimeLevel.BootFailed: + // redirect to death page + ReportRuntime(level, "Umbraco has failed."); + + httpContext.Response.StatusCode = 503; // temp not available + const string deathUrl = "~/config/splashes/death.aspx"; + httpContext.Response.AddHeader("Retry-After", debug ? "1" : "300"); // seconds + httpContext.RewritePath(UriUtility.ToAbsolute(deathUrl) + "?url=" + HttpUtility.UrlEncode(uri.ToString())); + return false; // cannot serve content + + case RuntimeLevel.Run: + // ok + return true; + + case RuntimeLevel.Install: + case RuntimeLevel.Upgrade: + // redirect to install + ReportRuntime(level, "Umbraco must install or upgrade."); + var installPath = UriUtility.ToAbsolute(SystemDirectories.Install); + var installUrl = $"{installPath}/?redir=true&url={HttpUtility.UrlEncode(uri.ToString())}"; + httpContext.Response.Redirect(installUrl, true); + return false; // cannot serve content + + default: + throw new NotSupportedException($"Unexpected runtime level: {Current.RuntimeState.Level}."); + } + } + + private static bool _reported; + private static RuntimeLevel _reportedLevel; + + private void ReportRuntime(RuntimeLevel level, string message) + { + if (_reported && _reportedLevel == level) return; + _reported = true; + _reportedLevel = level; + Logger.Warn(message); + } + + // ensures Umbraco has at least one published node + // if not, rewrites to splash and return false + // if yes, return true + private bool EnsureHasContent(UmbracoContext context, HttpContextBase httpContext) + { + if (context.ContentCache.HasContent()) + return true; + + Logger.Warn("Umbraco has no content"); + + const string noContentUrl = "~/config/splashes/noNodes.aspx"; + httpContext.RewritePath(UriUtility.ToAbsolute(noContentUrl)); + + return false; + } // returns a value indicating whether redirection took place and the request has // been completed - because we don't want to Response.End() here to terminate // everything properly. - internal static bool HandleHttpResponseStatus(HttpContextBase context, PublishedContentRequest pcr) + internal static bool HandleHttpResponseStatus(HttpContextBase context, PublishedRequest pcr, ILogger logger) { var end = false; var response = context.Response; - LogHelper.Debug("Response status: Redirect={0}, Is404={1}, StatusCode={2}", - () => pcr.IsRedirect ? (pcr.IsRedirectPermanent ? "permanent" : "redirect") : "none", - () => pcr.Is404 ? "true" : "false", () => pcr.ResponseStatusCode); - + logger.Debug(() => $"Response status: Redirect={(pcr.IsRedirect ? (pcr.IsRedirectPermanent ? "permanent" : "redirect") : "none")}, Is404={(pcr.Is404 ? "true" : "false")}, StatusCode={pcr.ResponseStatusCode}"); + if(pcr.Cacheability != default(HttpCacheability)) response.Cache.SetCacheability(pcr.Cacheability); @@ -347,7 +377,7 @@ namespace Umbraco.Web response.TrySkipIisCustomErrors = UmbracoConfig.For.UmbracoSettings().WebRouting.TrySkipIisCustomErrors; if (response.TrySkipIisCustomErrors == false) - LogHelper.Warn("Status code is 404 yet TrySkipIisCustomErrors is false - IIS will take over."); + logger.Warn("Status code is 404 yet TrySkipIisCustomErrors is false - IIS will take over."); } if (pcr.ResponseStatusCode > 0) @@ -359,15 +389,14 @@ namespace Umbraco.Web //if (pcr.IsRedirect) // response.End(); // end response -- kills the thread and does not return! - if (pcr.IsRedirect) - { - response.Flush(); - // bypass everything and directly execute EndRequest event -- but returns - context.ApplicationInstance.CompleteRequest(); - // though some say that .CompleteRequest() does not properly shutdown the response - // and the request will hang until the whole code has run... would need to test? - LogHelper.Debug("Response status: redirecting, complete request now."); - } + if (pcr.IsRedirect == false) return end; + + response.Flush(); + // bypass everything and directly execute EndRequest event -- but returns + context.ApplicationInstance.CompleteRequest(); + // though some say that .CompleteRequest() does not properly shutdown the response + // and the request will hang until the whole code has run... would need to test? + logger.Debug("Response status: redirecting, complete request now."); return end; } @@ -376,10 +405,10 @@ namespace Umbraco.Web /// Rewrites to the default back office page. /// /// - private static void RewriteToBackOfficeHandler(HttpContextBase context) + private void RewriteToBackOfficeHandler(HttpContextBase context) { // GlobalSettings.Path has already been through IOHelper.ResolveUrl() so it begins with / and vdir (if any) - var rewritePath = GlobalSettings.Path.TrimEnd(new[] { '/' }) + "/Default"; + var rewritePath = GlobalSettings.Path.TrimEnd('/') + "/Default"; // rewrite the path to the path of the handler (i.e. /umbraco/RenderMvc) context.RewritePath(rewritePath, "", "", false); @@ -397,22 +426,22 @@ namespace Umbraco.Web } /// - /// Rewrites to the Umbraco handler - we always send the request via our MVC rendering engine, this will deal with - /// requests destined for webforms. + /// Rewrites to the Umbraco handler - we always send the request via our MVC rendering engine, this will deal with + /// requests destined for webforms. /// /// /// - private static void RewriteToUmbracoHandler(HttpContextBase context, PublishedContentRequest pcr) + private void RewriteToUmbracoHandler(HttpContextBase context, PublishedRequest pcr) { // NOTE: we do not want to use TransferRequest even though many docs say it is better with IIS7, turns out this is // not what we need. The purpose of TransferRequest is to ensure that .net processes all of the rules for the newly // rewritten url, but this is not what we want! // read: http://forums.iis.net/t/1146511.aspx - var query = pcr.Uri.Query.TrimStart(new[] { '?' }); + var query = pcr.Uri.Query.TrimStart('?'); // GlobalSettings.Path has already been through IOHelper.ResolveUrl() so it begins with / and vdir (if any) - var rewritePath = GlobalSettings.Path.TrimEnd(new[] { '/' }) + "/RenderMvc"; + var rewritePath = GlobalSettings.Path.TrimEnd('/') + "/RenderMvc"; // rewrite the path to the path of the handler (i.e. /umbraco/RenderMvc) context.RewritePath(rewritePath, "", query, false); @@ -430,11 +459,11 @@ namespace Umbraco.Web } - /// + /// /// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request /// /// - private static void DisposeHttpContextItems(HttpContext http) + private void DisposeHttpContextItems(HttpContext http) { // do not process if client-side request if (http.Request.Url.IsClientSideRequest()) @@ -458,7 +487,7 @@ namespace Umbraco.Web } catch (Exception ex) { - LogHelper.Error("Could not dispose item with key " + k, ex); + Logger.Error("Could not dispose item with key " + k, ex); } try { @@ -466,35 +495,70 @@ namespace Umbraco.Web } catch (Exception ex) { - LogHelper.Error("Could not dispose item key " + k, ex); + Logger.Error("Could not dispose item key " + k, ex); } } } - #endregion + #endregion - #region IHttpModule + #region IHttpModule - /// - /// Initialize the module, this will trigger for each new application - /// and there may be more than 1 application per application domain - /// - /// - public void Init(HttpApplication app) - { - app.BeginRequest += (sender, e) => - { - var httpContext = ((HttpApplication)sender).Context; - LogHelper.Debug("Begin request: {0}.", () => httpContext.Request.Url); - BeginRequest(new HttpContextWrapper(httpContext)); - }; + /// + /// Initialize the module, this will trigger for each new application + /// and there may be more than 1 application per application domain + /// + /// + public void Init(HttpApplication app) + { + if (Core.Composing.Current.RuntimeState.Level == RuntimeLevel.BootFailed) + { + // there's nothing we can do really + app.BeginRequest += (sender, args) => + { + // would love to avoid throwing, and instead display a customized Umbraco 500 + // page - however if we don't throw here, something else might go wrong, and + // it's this later exception that would be reported. could not figure out how + // to prevent it, either with httpContext.Response.End() or .ApplicationInstance + // .CompleteRequest() + + // also, if something goes wrong with our DI setup, the logging subsystem may + // not even kick in, so here we try to give as much detail as possible + + Exception e = Core.Composing.Current.RuntimeState.BootFailedException; + if (e == null) + throw new BootFailedException(BootFailedException.DefaultMessage); + var m = new StringBuilder(); + m.Append(BootFailedException.DefaultMessage); + while (e != null) + { + m.Append($"\n\n-> {e.GetType().FullName}: {e.Message}"); + if (string.IsNullOrWhiteSpace(e.StackTrace) == false) + m.Append($"\n{e.StackTrace}"); + e = e.InnerException; + } + throw new BootFailedException(m.ToString()); + }; + return; + } + + // modules are *not* instanciated by the container so we have to + // get our dependencies injected manually, through properties. + Core.Composing.Current.Container.InjectProperties(this); + + app.BeginRequest += (sender, e) => + { + var httpContext = ((HttpApplication) sender).Context; + Logger.Debug(() => $"Begin request: {httpContext.Request.Url}."); + BeginRequest(new HttpContextWrapper(httpContext)); + }; //disable asp.net headers (security) // This is the correct place to modify headers according to MS: // https://our.umbraco.org/forum/umbraco-7/using-umbraco-7/65241-Heap-error-from-header-manipulation?p=0#comment220889 - app.PostReleaseRequestState += (sender, args) => - { - var httpContext = ((HttpApplication)sender).Context; + app.PostReleaseRequestState += (sender, args) => + { + var httpContext = ((HttpApplication) sender).Context; try { httpContext.Response.Headers.Remove("Server"); @@ -503,58 +567,62 @@ namespace Umbraco.Web httpContext.Response.Headers.Remove("X-AspNet-Version"); httpContext.Response.Headers.Remove("X-AspNetMvc-Version"); } - catch (PlatformNotSupportedException ex) + catch (PlatformNotSupportedException) { // can't remove headers this way on IIS6 or cassini. } - }; + }; + + app.PostAuthenticateRequest += (sender, e) => + { + var httpContext = ((HttpApplication) sender).Context; + //ensure the thread culture is set + httpContext.User?.Identity?.EnsureCulture(); + }; app.PostResolveRequestCache += (sender, e) => - { - var httpContext = ((HttpApplication)sender).Context; - ProcessRequest(new HttpContextWrapper(httpContext)); - }; + { + var httpContext = ((HttpApplication) sender).Context; + ProcessRequest(new HttpContextWrapper(httpContext)); + }; - app.EndRequest += (sender, args) => - { - var httpContext = ((HttpApplication)sender).Context; - if (UmbracoContext.Current != null && UmbracoContext.Current.IsFrontEndUmbracoRequest) - { - LogHelper.Debug( - "Total milliseconds for umbraco request to process: {0}", () => DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds); - } + app.EndRequest += (sender, args) => + { + var httpContext = ((HttpApplication) sender).Context; - OnEndRequest(new UmbracoRequestEventArgs(UmbracoContext.Current, new HttpContextWrapper(httpContext))); + if (UmbracoContext.Current != null && UmbracoContext.Current.IsFrontEndUmbracoRequest) + { + Logger.Debug($"End Request. ({DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds}ms)"); + } - DisposeHttpContextItems(httpContext); - }; + OnEndRequest(new UmbracoRequestEventArgs(UmbracoContext.Current, new HttpContextWrapper(httpContext))); - } + DisposeHttpContextItems(httpContext); + }; + } - public void Dispose() - { - - } + public void Dispose() + { } #endregion #region Events public static event EventHandler RouteAttempt; + private void OnRouteAttempt(RoutableAttemptEventArgs args) { - if (RouteAttempt != null) - RouteAttempt(this, args); + RouteAttempt?.Invoke(this, args); } public static event EventHandler EndRequest; + private void OnEndRequest(UmbracoRequestEventArgs args) { - if (EndRequest != null) - EndRequest(this, args); + EndRequest?.Invoke(this, args); } - #endregion + #endregion /// /// This is used to be passed into the GlobalSettings.IsReservedPathOrUrl and will include some 'fake' routes @@ -563,33 +631,35 @@ namespace Umbraco.Web /// /// This is basically used to reserve paths dynamically /// - private readonly Lazy _combinedRouteCollection = new Lazy(() => + private readonly Lazy _combinedRouteCollection; + + private RouteCollection CreateRouteCollection() { - var allRoutes = new RouteCollection(); + var routes = new RouteCollection(); + foreach (var route in RouteTable.Routes) - { - allRoutes.Add(route); - } + routes.Add(route); + foreach (var reservedPath in ReservedPaths) { try { - allRoutes.Add("_umbreserved_" + reservedPath.ReplaceNonAlphanumericChars(""), - new Route(reservedPath.TrimStart('/'), new StopRoutingHandler())); + routes.Add("_umbreserved_" + reservedPath.ReplaceNonAlphanumericChars(""), + new Route(reservedPath.TrimStart('/'), new StopRoutingHandler())); } catch (Exception ex) { - LogHelper.Error("Could not add reserved path route", ex); + Logger.Error("Could not add reserved path route", ex); } } - return allRoutes; - }); + return routes; + } /// /// This is used internally to track any registered callback paths for Identity providers. If the request path matches /// any of the registered paths, then the module will let the request keep executing /// internal static readonly ConcurrentHashSet ReservedPaths = new ConcurrentHashSet(); - } + } } diff --git a/src/Umbraco.Web/UmbracoUserControl.cs b/src/Umbraco.Web/UmbracoUserControl.cs deleted file mode 100644 index 7b698139f4..0000000000 --- a/src/Umbraco.Web/UmbracoUserControl.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; -using System.Web.UI; -using Umbraco.Core; -using Umbraco.Core.Services; - -namespace Umbraco.Web -{ - /// - /// A base class for all Presentation UserControls to inherit from - /// - [Obsolete("Use the Umbraco.Web.UI.Controls.UmbracoUserControl instead")] - public abstract class UmbracoUserControl : Umbraco.Web.UI.Controls.UmbracoUserControl - { - /// - /// Default constructor - /// - /// - protected UmbracoUserControl(UmbracoContext umbracoContext) - : base(umbracoContext) - { - - } - - /// - /// Empty constructor, uses Singleton to resolve the UmbracoContext - /// - protected UmbracoUserControl() - : base() - { - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/UriUtility.cs b/src/Umbraco.Web/UriUtility.cs index 0d84a6db3e..6b55f8f218 100644 --- a/src/Umbraco.Web/UriUtility.cs +++ b/src/Umbraco.Web/UriUtility.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Text; using System.Web; using Umbraco.Core; using Umbraco.Core.Configuration; -using GlobalSettings = umbraco.GlobalSettings; +using Umbraco.Core.Configuration.UmbracoSettings; namespace Umbraco.Web { @@ -14,106 +14,105 @@ namespace Umbraco.Web static UriUtility() { - SetAppDomainAppVirtualPath(HttpRuntime.AppDomainAppVirtualPath); + ResetAppDomainAppVirtualPath(); } - // internal for unit testing only - internal static void SetAppDomainAppVirtualPath(string appPath) - { - _appPath = appPath ?? "/"; - _appPathPrefix = _appPath; - if (_appPathPrefix == "/") - _appPathPrefix = String.Empty; - } - - // will be "/" or "/foo" - public static string AppPath + // internal for unit testing only + internal static void SetAppDomainAppVirtualPath(string appPath) { - get { return _appPath; } + _appPath = appPath ?? "/"; + _appPathPrefix = _appPath; + if (_appPathPrefix == "/") + _appPathPrefix = String.Empty; } - // will be "" or "/foo" - public static string AppPathPrefix + internal static void ResetAppDomainAppVirtualPath() { - get { return _appPathPrefix; } + SetAppDomainAppVirtualPath(HttpRuntime.AppDomainAppVirtualPath); } - // adds the virtual directory if any - // see also VirtualPathUtility.ToAbsolute - public static string ToAbsolute(string url) + // will be "/" or "/foo" + public static string AppPath => _appPath; + + // will be "" or "/foo" + public static string AppPathPrefix => _appPathPrefix; + + // adds the virtual directory if any + // see also VirtualPathUtility.ToAbsolute + public static string ToAbsolute(string url) { - //return ResolveUrl(url); - url = url.TrimStart('~'); - return _appPathPrefix + url; + //return ResolveUrl(url); + url = url.TrimStart('~'); + return _appPathPrefix + url; } - // strips the virtual directory if any - // see also VirtualPathUtility.ToAppRelative + // strips the virtual directory if any + // see also VirtualPathUtility.ToAppRelative public static string ToAppRelative(string virtualPath) { if (virtualPath.InvariantStartsWith(_appPathPrefix) && (virtualPath.Length == _appPathPrefix.Length || virtualPath[_appPathPrefix.Length] == '/')) virtualPath = virtualPath.Substring(_appPathPrefix.Length); - if (virtualPath.Length == 0) - virtualPath = "/"; + if (virtualPath.Length == 0) + virtualPath = "/"; return virtualPath; } - // maps an internal umbraco uri to a public uri - // ie with virtual directory, .aspx if required... - public static Uri UriFromUmbraco(Uri uri) - { - var path = uri.GetSafeAbsolutePath(); + // maps an internal umbraco uri to a public uri + // ie with virtual directory, .aspx if required... + public static Uri UriFromUmbraco(Uri uri, IGlobalSettings globalSettings, IRequestHandlerSection requestConfig) + { + var path = uri.GetSafeAbsolutePath(); - if (path != "/") - { - if (!GlobalSettings.UseDirectoryUrls) - path += ".aspx"; - else if (UmbracoConfig.For.UmbracoSettings().RequestHandler.AddTrailingSlash) + if (path != "/") + { + if (!globalSettings.UseDirectoryUrls) + path += ".aspx"; + else if (requestConfig.AddTrailingSlash) path = path.EnsureEndsWith("/"); - } + } - path = ToAbsolute(path); + path = ToAbsolute(path); - return uri.Rewrite(path); - } + return uri.Rewrite(path); + } - // maps a public uri to an internal umbraco uri - // ie no virtual directory, no .aspx, lowercase... - public static Uri UriToUmbraco(Uri uri) - { - // note: no need to decode uri here because we're returning a uri - // so it will be re-encoded anyway - var path = uri.GetSafeAbsolutePath(); + // maps a public uri to an internal umbraco uri + // ie no virtual directory, no .aspx, lowercase... + public static Uri UriToUmbraco(Uri uri) + { + // note: no need to decode uri here because we're returning a uri + // so it will be re-encoded anyway + var path = uri.GetSafeAbsolutePath(); - path = path.ToLower(); - path = ToAppRelative(path); // strip vdir if any + path = path.ToLower(); + path = ToAppRelative(path); // strip vdir if any - //we need to check if the path is /default.aspx because this will occur when using a - //web server pre IIS 7 when requesting the root document - //if this is the case we need to change it to '/' - if (path.StartsWith("/default.aspx", StringComparison.InvariantCultureIgnoreCase)) - { - string rempath = path.Substring("/default.aspx".Length, path.Length - "/default.aspx".Length); - path = rempath.StartsWith("/") ? rempath : "/" + rempath; - } - if (path != "/") - { - path = path.TrimEnd('/'); - } + //we need to check if the path is /default.aspx because this will occur when using a + //web server pre IIS 7 when requesting the root document + //if this is the case we need to change it to '/' + if (path.StartsWith("/default.aspx", StringComparison.InvariantCultureIgnoreCase)) + { + string rempath = path.Substring("/default.aspx".Length, path.Length - "/default.aspx".Length); + path = rempath.StartsWith("/") ? rempath : "/" + rempath; + } + if (path != "/") + { + path = path.TrimEnd('/'); + } - //if any part of the path contains .aspx, replace it with nothing. - //sometimes .aspx is not at the end since we might have /home/sub1.aspx/customtemplate - path = path.Replace(".aspx", ""); + //if any part of the path contains .aspx, replace it with nothing. + //sometimes .aspx is not at the end since we might have /home/sub1.aspx/customtemplate + path = path.Replace(".aspx", ""); - return uri.Rewrite(path); - } + return uri.Rewrite(path); + } - #region ResolveUrl + #region ResolveUrl // http://www.codeproject.com/Articles/53460/ResolveUrl-in-ASP-NET-The-Perfect-Solution // note - // if browsing http://example.com/sub/page1.aspx then + // if browsing http://example.com/sub/page1.aspx then // ResolveUrl("page2.aspx") returns "/page2.aspx" // Page.ResolveUrl("page2.aspx") returns "/sub/page2.aspx" (relative...) // @@ -174,74 +173,74 @@ namespace Umbraco.Web #endregion - #region Uri string utilities + #region Uri string utilities - public static bool HasScheme(string uri) - { - return uri.IndexOf("://") > 0; - } + public static bool HasScheme(string uri) + { + return uri.IndexOf("://") > 0; + } - public static string StartWithScheme(string uri) - { - return StartWithScheme(uri, null); - } + public static string StartWithScheme(string uri) + { + return StartWithScheme(uri, null); + } - public static string StartWithScheme(string uri, string scheme) - { - return HasScheme(uri) ? uri : String.Format("{0}://{1}", scheme ?? Uri.UriSchemeHttp, uri); - } + public static string StartWithScheme(string uri, string scheme) + { + return HasScheme(uri) ? uri : String.Format("{0}://{1}", scheme ?? Uri.UriSchemeHttp, uri); + } - public static string EndPathWithSlash(string uri) - { - var pos1 = Math.Max(0, uri.IndexOf('?')); - var pos2 = Math.Max(0, uri.IndexOf('#')); - var pos = Math.Min(pos1, pos2); + public static string EndPathWithSlash(string uri) + { + var pos1 = Math.Max(0, uri.IndexOf('?')); + var pos2 = Math.Max(0, uri.IndexOf('#')); + var pos = Math.Min(pos1, pos2); - var path = pos > 0 ? uri.Substring(0, pos) : uri; - path = path.EnsureEndsWith('/'); + var path = pos > 0 ? uri.Substring(0, pos) : uri; + path = path.EnsureEndsWith('/'); - if (pos > 0) - path += uri.Substring(pos); + if (pos > 0) + path += uri.Substring(pos); - return path; - } + return path; + } - public static string TrimPathEndSlash(string uri) - { - var pos1 = Math.Max(0, uri.IndexOf('?')); - var pos2 = Math.Max(0, uri.IndexOf('#')); - var pos = Math.Min(pos1, pos2); + public static string TrimPathEndSlash(string uri) + { + var pos1 = Math.Max(0, uri.IndexOf('?')); + var pos2 = Math.Max(0, uri.IndexOf('#')); + var pos = Math.Min(pos1, pos2); - var path = pos > 0 ? uri.Substring(0, pos) : uri; - path = path.TrimEnd('/'); + var path = pos > 0 ? uri.Substring(0, pos) : uri; + path = path.TrimEnd('/'); - if (pos > 0) - path += uri.Substring(pos); + if (pos > 0) + path += uri.Substring(pos); - return path; - } + return path; + } - #endregion + #endregion - /// - /// Returns an faull url with the host, port, etc... - /// - /// An absolute path (i.e. starts with a '/' ) - /// - /// - /// - /// Based on http://stackoverflow.com/questions/3681052/get-absolute-url-from-relative-path-refactored-method - /// - internal static Uri ToFullUrl(string absolutePath, HttpContextBase httpContext) - { - if (httpContext == null) throw new ArgumentNullException("httpContext"); - if (String.IsNullOrEmpty(absolutePath)) - throw new ArgumentNullException("absolutePath"); - - if (!absolutePath.StartsWith("/")) - throw new FormatException("The absolutePath specified does not start with a '/'"); + /// + /// Returns an faull url with the host, port, etc... + /// + /// An absolute path (i.e. starts with a '/' ) + /// + /// + /// + /// Based on http://stackoverflow.com/questions/3681052/get-absolute-url-from-relative-path-refactored-method + /// + internal static Uri ToFullUrl(string absolutePath, HttpContextBase httpContext) + { + if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (string.IsNullOrEmpty(absolutePath)) + throw new ArgumentNullException("absolutePath"); - return new Uri(absolutePath, UriKind.Relative).MakeAbsolute(httpContext.Request.Url); - } + if (!absolutePath.StartsWith("/")) + throw new FormatException("The absolutePath specified does not start with a '/'"); + + return new Uri(absolutePath, UriKind.Relative).MakeAbsolute(httpContext.Request.Url); + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UrlHelperExtensions.cs b/src/Umbraco.Web/UrlHelperExtensions.cs index 9b017e1bd4..f1363f090a 100644 --- a/src/Umbraco.Web/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/UrlHelperExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -8,6 +8,8 @@ using System.Web.Routing; using ClientDependency.Core.Config; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Exceptions; +using Umbraco.Web.Composing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using Umbraco.Web.WebServices; @@ -91,12 +93,12 @@ namespace Umbraco.Web /// public static string GetUmbracoApiService(this UrlHelper url, string actionName, Type apiControllerType, RouteValueDictionary routeVals = null) { - Mandate.ParameterNotNullOrEmpty(actionName, "actionName"); - Mandate.ParameterNotNull(apiControllerType, "apiControllerType"); + if (string.IsNullOrEmpty(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName)); + if (apiControllerType == null) throw new ArgumentNullException(nameof(apiControllerType)); var area = ""; - var apiController = UmbracoApiControllerResolver.Current.RegisteredUmbracoApiControllers + var apiController = Current.UmbracoApiControllerTypes .SingleOrDefault(x => x == apiControllerType); if (apiController == null) throw new InvalidOperationException("Could not find the umbraco api controller of type " + apiControllerType.FullName); @@ -133,8 +135,8 @@ namespace Umbraco.Web /// public static string GetUmbracoApiService(this UrlHelper url, string actionName, string controllerName, string area, RouteValueDictionary routeVals = null) { - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - Mandate.ParameterNotNullOrEmpty(actionName, "actionName"); + if (string.IsNullOrEmpty(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); + if (string.IsNullOrEmpty(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName)); if (routeVals == null) { @@ -162,28 +164,28 @@ namespace Umbraco.Web /// public static string GetUrlWithCacheBust(this UrlHelper url, string actionName, string controllerName, RouteValueDictionary routeVals = null) { - var applicationJs = url.Action(actionName, controllerName, routeVals); + var applicationJs = url.Action(actionName, controllerName, routeVals); applicationJs = applicationJs + "?umb__rnd=" + GetCacheBustHash(); return applicationJs; } /// - /// + /// /// /// public static string GetCacheBustHash() { //make a hash of umbraco and client dependency version //in case the user bypasses the installer and just bumps the web.config or clientdep config - + //if in debug mode, always burst the cache if (GlobalSettings.DebugMode) { return DateTime.Now.Ticks.ToString(CultureInfo.InvariantCulture).GenerateHash(); - } + } - var version = UmbracoVersion.GetSemanticVersion().ToSemanticString(); - return string.Format("{0}.{1}", version, ClientDependencySettings.Instance.Version).GenerateHash(); + var version = Current.RuntimeState.SemanticVersion.ToSemanticString(); + return $"{version}.{ClientDependencySettings.Instance.Version}".GenerateHash(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/UrlHelperRenderExtensions.cs b/src/Umbraco.Web/UrlHelperRenderExtensions.cs index a451503803..100881241f 100644 --- a/src/Umbraco.Web/UrlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/UrlHelperRenderExtensions.cs @@ -1,9 +1,12 @@ -using System; +using System; using System.Linq; using System.Web; using System.Web.Mvc; using Umbraco.Core; +using Umbraco.Core.Exceptions; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; using Umbraco.Web.Models; using Umbraco.Web.Mvc; @@ -28,7 +31,7 @@ namespace Umbraco.Web /// The crop alias e.g. thumbnail /// /// - /// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be + /// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be /// set to false if using the result of this method for CSS. /// /// @@ -52,7 +55,7 @@ namespace Umbraco.Web /// The crop alias e.g. thumbnail /// /// - /// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be + /// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be /// set to false if using the result of this method for CSS. /// /// @@ -116,9 +119,9 @@ namespace Umbraco.Web /// /// /// - /// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be + /// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be /// set to false if using the result of this method for CSS. - /// + /// /// /// The . /// @@ -197,9 +200,9 @@ namespace Umbraco.Web /// /// /// - /// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be + /// Whether to HTML encode this URL - default is true - w3c standards require html attributes to be html encoded but this can be /// set to false if using the result of this method for CSS. - /// + /// /// /// The . /// @@ -223,7 +226,7 @@ namespace Umbraco.Web var url = imageUrl.GetCropUrl(width, height, imageCropperValue, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); - return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url); + return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url); } #endregion @@ -264,8 +267,8 @@ namespace Umbraco.Web /// public static string SurfaceAction(this UrlHelper url, string action, string controllerName, string area, object additionalRouteVals) { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + if (string.IsNullOrEmpty(action)) throw new ArgumentNullOrEmptyException(nameof(action)); + if (string.IsNullOrEmpty(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); var encryptedRoute = UmbracoHelper.CreateEncryptedRouteString(controllerName, action, area, additionalRouteVals); @@ -295,13 +298,12 @@ namespace Umbraco.Web /// public static string SurfaceAction(this UrlHelper url, string action, Type surfaceType, object additionalRouteVals) { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNull(surfaceType, "surfaceType"); + if (string.IsNullOrEmpty(action)) throw new ArgumentNullOrEmptyException(nameof(action)); + if (surfaceType == null) throw new ArgumentNullException(nameof(surfaceType)); var area = ""; - var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers - .SingleOrDefault(x => x == surfaceType); + var surfaceController = Current.SurfaceControllerTypes.SingleOrDefault(x => x == surfaceType); if (surfaceController == null) throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); var metaData = PluginController.GetMetadata(surfaceController); @@ -363,4 +365,4 @@ namespace Umbraco.Web return null; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs b/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs index 5e753fa7f8..70a4e51592 100644 --- a/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs +++ b/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.org.umbraco.our { /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute(Name="RepositorySoap", Namespace="http://packages.umbraco.org/webservices/")] @@ -55,7 +55,7 @@ namespace Umbraco.Web.org.umbraco.our { /// public Repository() { - this.Url = global::Umbraco.Web.Properties.Settings.Default.umbraco_org_umbraco_our_Repository; + this.Url = "http://our.umbraco.org/umbraco/webservices/api/repository.asmx"; if ((this.IsLocalFileSystemWebService(this.Url) == true)) { this.UseDefaultCredentials = true; this.useDefaultCredentialsSetExplicitly = false; @@ -480,7 +480,7 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.2612.0")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -549,7 +549,7 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.2612.0")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -738,7 +738,7 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.2612.0")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://packages.umbraco.org/webservices/")] public enum SubmitStatus { @@ -757,11 +757,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void CategoriesCompletedEventHandler(object sender, CategoriesCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CategoriesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -783,11 +783,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void ModulesCompletedEventHandler(object sender, ModulesCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ModulesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -809,11 +809,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void ModulesCategorizedCompletedEventHandler(object sender, ModulesCategorizedCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ModulesCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -835,11 +835,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void NitrosCompletedEventHandler(object sender, NitrosCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class NitrosCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -861,11 +861,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void NitrosCategorizedCompletedEventHandler(object sender, NitrosCategorizedCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class NitrosCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -887,11 +887,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void authenticateCompletedEventHandler(object sender, authenticateCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class authenticateCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -913,11 +913,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void fetchPackageCompletedEventHandler(object sender, fetchPackageCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class fetchPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -939,11 +939,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void fetchPackageByVersionCompletedEventHandler(object sender, fetchPackageByVersionCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class fetchPackageByVersionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -965,11 +965,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void fetchProtectedPackageCompletedEventHandler(object sender, fetchProtectedPackageCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class fetchProtectedPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -991,11 +991,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void SubmitPackageCompletedEventHandler(object sender, SubmitPackageCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SubmitPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -1017,11 +1017,11 @@ namespace Umbraco.Web.org.umbraco.our { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void PackageByGuidCompletedEventHandler(object sender, PackageByGuidCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class PackageByGuidCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { diff --git a/src/Umbraco.Web/Web References/org.umbraco.update/Reference.cs b/src/Umbraco.Web/Web References/org.umbraco.update/Reference.cs index 9e35deb713..ccd43e8edc 100644 --- a/src/Umbraco.Web/Web References/org.umbraco.update/Reference.cs +++ b/src/Umbraco.Web/Web References/org.umbraco.update/Reference.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.org.umbraco.update { /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute(Name="CheckForUpgradeSoap", Namespace="http://update.umbraco.org/")] @@ -37,7 +37,7 @@ namespace Umbraco.Web.org.umbraco.update { /// public CheckForUpgrade() { - this.Url = global::Umbraco.Web.Properties.Settings.Default.umbraco_org_umbraco_update_CheckForUpgrade; + this.Url = "http://update.umbraco.org/checkforupgrade.asmx"; if ((this.IsLocalFileSystemWebService(this.Url) == true)) { this.UseDefaultCredentials = true; this.useDefaultCredentialsSetExplicitly = false; @@ -180,7 +180,7 @@ namespace Umbraco.Web.org.umbraco.update { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.2612.0")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -225,7 +225,7 @@ namespace Umbraco.Web.org.umbraco.update { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.2612.0")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://update.umbraco.org/")] public enum UpgradeType { @@ -253,15 +253,15 @@ namespace Umbraco.Web.org.umbraco.update { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void InstallCompletedEventHandler(object sender, System.ComponentModel.AsyncCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] public delegate void CheckUpgradeCompletedEventHandler(object sender, CheckUpgradeCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1586.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.2556.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CheckUpgradeCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { diff --git a/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs b/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs index 8dca204597..0e7cf6453a 100644 --- a/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs +++ b/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs @@ -39,9 +39,9 @@ namespace Umbraco.Web.WebApi var effectiveEncoding = SelectCharacterEncoding(content == null ? null : content.Headers); - using (var streamWriter = new StreamWriter(writeStream, effectiveEncoding, + using (var streamWriter = new StreamWriter(writeStream, effectiveEncoding, //we are only writing a few chars so we don't need to allocate a large buffer - 128, + 128, //this is important! We don't want to close the stream, the base class is in charge of stream management, we just want to write to it. leaveOpen:true)) { diff --git a/src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs b/src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs index b7e14ced54..6a6e63f335 100644 --- a/src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs +++ b/src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.WebApi public class AngularJsonOnlyConfigurationAttribute : Attribute, IControllerConfiguration { public virtual void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { + { //remove all json/xml formatters then add our custom one var toRemove = controllerSettings.Formatters.Where(t => (t is JsonMediaTypeFormatter) || (t is XmlMediaTypeFormatter)).ToList(); foreach (var r in toRemove) @@ -21,4 +21,4 @@ namespace Umbraco.Web.WebApi controllerSettings.Formatters.Add(new AngularJsonMediaTypeFormatter()); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs index 195c488b1e..4bf7766e45 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs @@ -18,8 +18,13 @@ using Newtonsoft.Json.Serialization; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; using Umbraco.Web.Security; using Umbraco.Web.WebApi.Filters; using IModelBinder = System.Web.Http.ModelBinding.IModelBinder; @@ -38,16 +43,7 @@ namespace Umbraco.Web.WebApi.Binders where TPersisted : class, IContentBase where TModelSave : ContentBaseItemSave { - protected ApplicationContext ApplicationContext { get; private set; } - - /// - /// Constructor - /// - /// - internal ContentItemBaseBinder(ApplicationContext applicationContext) - { - ApplicationContext = applicationContext; - } + protected ServiceContext Services => Current.Services; // fixme - inject public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { @@ -96,14 +92,23 @@ namespace Umbraco.Web.WebApi.Binders /// private async Task GetModelAsync(HttpActionContext actionContext, ModelBindingContext bindingContext, MultipartFormDataStreamProvider provider) { - var request = actionContext.Request; + // note + // + // for some reason, due to the way we do async, HttpContext.Current is null + // which means that the 'current' UmbracoContext is null too since we are using HttpContextUmbracoContextAccessor + // trying to 'EnsureContext' fails because that accessor cannot access the HttpContext either to register the current UmbracoContext + // + // so either we go with an HybridUmbracoContextAccessor that relies on a ThreadStatic variable when HttpContext.Current is null + // and I don't like it + // or + // we try to force-set the current http context, because, hey... it's there. + // and then there is no need to event 'Ensure' anything + // read http://stackoverflow.com/questions/1992141/how-do-i-get-an-httpcontext-object-from-httpcontextbase-in-asp-net-mvc-1 - //IMPORTANT!!! We need to ensure the umbraco context here because this is running in an async thread + // what's below works but I cannot say I am proud of it + var request = actionContext.Request; var httpContext = (HttpContextBase) request.Properties["MS_HttpContext"]; - UmbracoContext.EnsureContext( - httpContext, - ApplicationContext.Current, - new WebSecurity(httpContext, ApplicationContext.Current)); + HttpContext.Current = httpContext.ApplicationInstance.Context; var content = request.Content; @@ -121,7 +126,7 @@ namespace Umbraco.Web.WebApi.Binders //deserialize into our model var model = JsonConvert.DeserializeObject(contentItem); - + //get the default body validator and validate the object var bodyValidator = actionContext.ControllerContext.Configuration.Services.GetBodyModelValidator(); var metadataProvider = actionContext.ControllerContext.Configuration.Services.GetModelMetadataProvider(); @@ -131,7 +136,7 @@ namespace Umbraco.Web.WebApi.Binders //get the files foreach (var file in result.FileData) { - //The name that has been assigned in JS has 2 parts and the second part indicates the property id + //The name that has been assigned in JS has 2 parts and the second part indicates the property id // for which the file belongs. var parts = file.Headers.ContentDisposition.Name.Trim(new char[] { '\"' }).Split('_'); if (parts.Length != 2) @@ -141,10 +146,10 @@ namespace Umbraco.Web.WebApi.Binders throw new HttpResponseException(response); } var propAlias = parts[1]; - + var fileName = file.Headers.ContentDisposition.FileName.Trim(new char[] {'\"'}); - model.UploadedFiles.Add(new ContentItemFile + model.UploadedFiles.Add(new ContentPropertyFile { TempFilePath = file.LocalFileName, PropertyAlias = propAlias, @@ -154,24 +159,24 @@ namespace Umbraco.Web.WebApi.Binders if (ContentControllerBase.IsCreatingAction(model.Action)) { - //we are creating new content + //we are creating new content model.PersistedContent = CreateNew(model); } else { //finally, let's lookup the real content item and create the DTO item - model.PersistedContent = GetExisting(model); + model.PersistedContent = GetExisting(model); } - + //create the dto from the persisted model if (model.PersistedContent != null) { - model.ContentDto = MapFromPersisted(model); + model.ContentDto = MapFromPersisted(model); } if (model.ContentDto != null) { //now map all of the saved values to the dto - MapPropertyValuesFromSaved(model, model.ContentDto); + MapPropertyValuesFromSaved(model, model.ContentDto); } model.Name = model.Name.Trim(); @@ -192,7 +197,7 @@ namespace Umbraco.Web.WebApi.Binders foreach (var propertyDto in dto.Properties) { if (propertyDto.Alias != p.Alias) continue; - propertyDto.Value = p.Value; + propertyDto.Value = p.Value; break; } } @@ -202,4 +207,4 @@ namespace Umbraco.Web.WebApi.Binders protected abstract TPersisted CreateNew(TModelSave model); protected abstract ContentItemDto MapFromPersisted(TModelSave model); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs index a19123f35e..0a399c24c5 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs @@ -1,45 +1,41 @@ using System; +using System.Collections.Generic; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; namespace Umbraco.Web.WebApi.Binders { - internal class ContentItemBinder : ContentItemBaseBinder + internal class ContentItemBinder : ContentItemBaseBinder { - - public ContentItemBinder(ApplicationContext applicationContext) - : base(applicationContext) - { - } - - /// - /// Constructor - /// - public ContentItemBinder() - : this(ApplicationContext.Current) - { - } - protected override IContent GetExisting(ContentItemSave model) { - return ApplicationContext.Services.ContentService.GetById(Convert.ToInt32(model.Id)); + return Services.ContentService.GetById(Convert.ToInt32(model.Id)); } protected override IContent CreateNew(ContentItemSave model) { - var contentType = ApplicationContext.Services.ContentTypeService.GetContentType(model.ContentTypeAlias); + var contentType = Services.ContentTypeService.Get(model.ContentTypeAlias); if (contentType == null) { throw new InvalidOperationException("No content type found with alias " + model.ContentTypeAlias); } - return new Content(model.Name, model.ParentId, contentType); + return new Content(model.Name, model.ParentId, contentType); } protected override ContentItemDto MapFromPersisted(ContentItemSave model) { - return Mapper.Map>(model.PersistedContent); + return MapFromPersisted(model.PersistedContent, model.LanguageId); + } + + internal static ContentItemDto MapFromPersisted(IContent content, int? languageId) + { + return ContextMapper.Map>(content, new Dictionary + { + [ContextMapper.LanguageKey] = languageId + }); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs index 0196c5c6f6..3a82bb7328 100644 --- a/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs @@ -3,32 +3,20 @@ using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; namespace Umbraco.Web.WebApi.Binders { internal class MediaItemBinder : ContentItemBaseBinder - { - public MediaItemBinder(ApplicationContext applicationContext) - : base(applicationContext) - { - } - - /// - /// Constructor - /// - public MediaItemBinder() - : this(ApplicationContext.Current) - { - } - + { protected override IMedia GetExisting(MediaItemSave model) { - return ApplicationContext.Services.MediaService.GetById(Convert.ToInt32(model.Id)); + return Services.MediaService.GetById(Convert.ToInt32(model.Id)); } protected override IMedia CreateNew(MediaItemSave model) { - var mediaType = ApplicationContext.Services.ContentTypeService.GetMediaType(model.ContentTypeAlias); + var mediaType = Services.MediaTypeService.Get(model.ContentTypeAlias); if (mediaType == null) { throw new InvalidOperationException("No media type found with alias " + model.ContentTypeAlias); @@ -41,4 +29,4 @@ namespace Umbraco.Web.WebApi.Binders return Mapper.Map>(model.PersistedContent); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs b/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs index 7a1913ce1a..57215ab5ee 100644 --- a/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/MemberBinder.cs @@ -14,26 +14,16 @@ using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.WebApi.Filters; using System.Linq; +using System.Net.Http; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services.Implement; using Umbraco.Web; +using Umbraco.Web.Composing; namespace Umbraco.Web.WebApi.Binders { internal class MemberBinder : ContentItemBaseBinder { - public MemberBinder(ApplicationContext applicationContext) - : base(applicationContext) - { - } - - /// - /// Constructor - /// - public MemberBinder() - : this(ApplicationContext.Current) - { - } - protected override ContentItemValidationHelper GetValidationHelper() { return new MemberValidationHelper(); @@ -46,7 +36,7 @@ namespace Umbraco.Web.WebApi.Binders /// protected override IMember GetExisting(MemberSave model) { - var scenario = ApplicationContext.Services.MemberService.GetMembershipScenario(); + var scenario = Services.MemberService.GetMembershipScenario(); var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); switch (scenario) { @@ -71,12 +61,12 @@ namespace Umbraco.Web.WebApi.Binders // { // var existing = GetExisting(model.Key); // FilterContentTypeProperties(existing.ContentType, existing.ContentType.PropertyTypes.Select(x => x.Alias).ToArray()); - // } + // } //} //generate a member for a generic membership provider //NOTE: We don't care about the password here, so just generate something - //var member = MemberService.CreateGenericMembershipProviderMember(model.Name, model.Email, model.Username, Guid.NewGuid().ToString("N")); + //var member = MemberService.CreateGenericMembershipProviderMember(model.Name, model.Email, model.Username, Guid.NewGuid().ToString("N")); //var convertResult = membershipUser.ProviderUserKey.TryConvertTo(); //if (convertResult.Success == false) @@ -93,7 +83,7 @@ namespace Umbraco.Web.WebApi.Binders private IMember GetExisting(Guid key) { - var member = ApplicationContext.Services.MemberService.GetByKey(key); + var member = Services.MemberService.GetByKey(key); if (member == null) { throw new InvalidOperationException("Could not find member with key " + key); @@ -116,7 +106,7 @@ namespace Umbraco.Web.WebApi.Binders if (provider.IsUmbracoMembershipProvider()) { - var contentType = ApplicationContext.Services.MemberTypeService.Get(model.ContentTypeAlias); + var contentType = Services.MemberTypeService.Get(model.ContentTypeAlias); if (contentType == null) { throw new InvalidOperationException("No member type found wth alias " + model.ContentTypeAlias); @@ -137,7 +127,7 @@ namespace Umbraco.Web.WebApi.Binders //If the default Member type exists, we'll use that to create the IMember - that way we can associate the custom membership // provider to our data - eventually we can support editing custom properties with a custom provider. - var memberType = ApplicationContext.Services.MemberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); + var memberType = Services.MemberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); if (memberType != null) { FilterContentTypeProperties(memberType, memberType.PropertyTypes.Select(x => x.Alias).ToArray()); @@ -193,48 +183,57 @@ namespace Umbraco.Web.WebApi.Binders /// We need to manually validate a few things here like email and login to make sure they are valid and aren't duplicates /// /// - /// - /// - protected override bool ValidatePropertyData(ContentItemBasic postedItem, HttpActionContext actionContext) + /// + /// + /// + public override bool ValidatePropertyData(ContentItemBasic postedItem, ContentItemDto dto, ModelStateDictionary modelState) { var memberSave = (MemberSave)postedItem; if (memberSave.Username.IsNullOrWhiteSpace()) { - actionContext.ModelState.AddPropertyError( + modelState.AddPropertyError( new ValidationResult("Invalid user name", new[] { "value" }), - string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login"); } if (memberSave.Email.IsNullOrWhiteSpace() || new EmailAddressAttribute().IsValid(memberSave.Email) == false) { - actionContext.ModelState.AddPropertyError( + modelState.AddPropertyError( new ValidationResult("Invalid email", new[] { "value" }), - string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email"); } //default provider! var membershipProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - var validEmail = ValidateUniqueEmail(memberSave, membershipProvider, actionContext); + var validEmail = ValidateUniqueEmail(memberSave, membershipProvider); if (validEmail == false) { - actionContext.ModelState.AddPropertyError( + modelState.AddPropertyError( new ValidationResult("Email address is already in use", new[] { "value" }), - string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email"); } - var validLogin = ValidateUniqueLogin(memberSave, membershipProvider, actionContext); + var validLogin = ValidateUniqueLogin(memberSave, membershipProvider); if (validLogin == false) { - actionContext.ModelState.AddPropertyError( + modelState.AddPropertyError( new ValidationResult("Username is already in use", new[] { "value" }), - string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login"); } - return base.ValidatePropertyData(postedItem, actionContext); + return base.ValidatePropertyData(postedItem, dto, modelState); } + /// + /// This ensures that the internal membership property types are removed from validation before processing the validation + /// since those properties are actually mapped to real properties of the IMember. + /// This also validates any posted data for fields that are sensitive. + /// + /// + /// + /// protected override bool ValidateProperties(ContentItemBasic postedItem, HttpActionContext actionContext) { var propertiesToValidate = postedItem.Properties.ToList(); @@ -245,13 +244,40 @@ namespace Umbraco.Web.WebApi.Binders propertiesToValidate.RemoveAll(property => property.Alias == remove); } - return ValidateProperties(propertiesToValidate.ToArray(), postedItem.PersistedContent.Properties.ToArray(), actionContext); - } - internal bool ValidateUniqueLogin(MemberSave contentItem, MembershipProvider membershipProvider, HttpActionContext actionContext) + var umbCtx = Current.UmbracoContext; // fixme inject? + + //if the user doesn't have access to sensitive values, then we need to validate the incoming properties to check + //if a sensitive value is being submitted. + if (umbCtx.Security.CurrentUser.HasAccessToSensitiveData() == false) + { + var sensitiveProperties = postedItem.PersistedContent.ContentType + .PropertyTypes.Where(x => postedItem.PersistedContent.ContentType.IsSensitiveProperty(x.Alias)) + .ToList(); + + foreach (var sensitiveProperty in sensitiveProperties) + { + var prop = propertiesToValidate.FirstOrDefault(x => x.Alias == sensitiveProperty.Alias); + + if (prop != null) + { + //this should not happen, this means that there was data posted for a sensitive property that + //the user doesn't have access to, which means that someone is trying to hack the values. + + var message = $"property with alias: {prop.Alias} cannot be posted"; + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, new InvalidOperationException(message)); + return false; + } + } + } + + return ValidateProperties(propertiesToValidate, postedItem.PersistedContent.Properties.ToList(), actionContext); + } + + internal bool ValidateUniqueLogin(MemberSave contentItem, MembershipProvider membershipProvider) { - if (contentItem == null) throw new ArgumentNullException("contentItem"); - if (membershipProvider == null) throw new ArgumentNullException("membershipProvider"); + if (contentItem == null) throw new ArgumentNullException(nameof(contentItem)); + if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider)); int totalRecs; var existingByName = membershipProvider.FindUsersByName(contentItem.Username.Trim(), 0, int.MaxValue, out totalRecs); @@ -282,16 +308,16 @@ namespace Umbraco.Web.WebApi.Binders break; default: //we don't support this for members - throw new HttpResponseException(HttpStatusCode.NotFound); + throw new ArgumentOutOfRangeException(); } return true; } - internal bool ValidateUniqueEmail(MemberSave contentItem, MembershipProvider membershipProvider, HttpActionContext actionContext) + internal bool ValidateUniqueEmail(MemberSave contentItem, MembershipProvider membershipProvider) { - if (contentItem == null) throw new ArgumentNullException("contentItem"); - if (membershipProvider == null) throw new ArgumentNullException("membershipProvider"); + if (contentItem == null) throw new ArgumentNullException(nameof(contentItem)); + if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider)); if (membershipProvider.RequiresUniqueEmail == false) { @@ -326,11 +352,11 @@ namespace Umbraco.Web.WebApi.Binders break; default: //we don't support this for members - throw new HttpResponseException(HttpStatusCode.NotFound); + throw new ArgumentOutOfRangeException(); } return true; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/CustomDateTimeConvertor.cs b/src/Umbraco.Web/WebApi/CustomDateTimeConvertor.cs index 00216e2ece..f7199646b3 100644 --- a/src/Umbraco.Web/WebApi/CustomDateTimeConvertor.cs +++ b/src/Umbraco.Web/WebApi/CustomDateTimeConvertor.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Umbraco.Core; +using Umbraco.Core.Exceptions; namespace Umbraco.Web.WebApi { @@ -11,10 +12,10 @@ namespace Umbraco.Web.WebApi internal class CustomDateTimeConvertor : IsoDateTimeConverter { private readonly string _dateTimeFormat; - + public CustomDateTimeConvertor(string dateTimeFormat) { - Mandate.ParameterNotNullOrEmpty(dateTimeFormat, "dateTimeFormat"); + if (string.IsNullOrEmpty(dateTimeFormat)) throw new ArgumentNullOrEmptyException(nameof(dateTimeFormat)); _dateTimeFormat = dateTimeFormat; } @@ -23,4 +24,4 @@ namespace Umbraco.Web.WebApi writer.WriteValue(((DateTime)value).ToString(_dateTimeFormat)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs b/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs index 1d35d19134..a62df6b5e4 100644 --- a/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs +++ b/src/Umbraco.Web/WebApi/EnableDetailedErrorsAttribute.cs @@ -14,4 +14,4 @@ namespace Umbraco.Web.WebApi actionContext.ControllerContext.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs index dfeeee536d..92589fc370 100644 --- a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs +++ b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs @@ -5,6 +5,7 @@ using System.Net.Http.Headers; using System.Web.Helpers; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Web.Composing; namespace Umbraco.Web.WebApi.Filters { @@ -34,7 +35,7 @@ namespace Umbraco.Web.WebApi.Filters /// /// /// - /// .Net provides us a way to validate one token with another for added security. With the way angular works, this + /// .Net provides us a way to validate one token with another for added security. With the way angular works, this /// means that we need to set 2 cookies since angular uses one cookie value to create the header value, then we want to validate /// this header value against our original cookie value. /// @@ -58,7 +59,7 @@ namespace Umbraco.Web.WebApi.Filters } catch (Exception ex) { - LogHelper.Error(typeof(AngularAntiForgeryHelper), "Could not validate XSRF token", ex); + Current.Logger.Error(typeof(AngularAntiForgeryHelper), "Could not validate XSRF token", ex); return false; } return true; @@ -114,6 +115,6 @@ namespace Umbraco.Web.WebApi.Filters cookieToken == null ? null : cookieToken, out failedReason); } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs b/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs index 8cf70826fe..911fff3e3c 100644 --- a/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/AppendCurrentEventMessagesAttribute.cs @@ -1,14 +1,15 @@ -using System; +using System; using System.Net.Http; using System.Web.Http.Filters; using Umbraco.Core.Events; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.UI; namespace Umbraco.Web.WebApi.Filters { /// - /// Automatically checks if any request is a non-GET and if the + /// Automatically checks if any request is a non-GET and if the /// resulting message is INotificationModel in which case it will append any Event Messages /// currently in the request. /// @@ -26,7 +27,7 @@ namespace Umbraco.Web.WebApi.Filters var notifications = obj.Value as INotificationModel; if (notifications == null) return; - var msgs = UmbracoContext.Current.GetCurrentEventMessages(); + var msgs = Current.EventMessages; if (msgs == null) return; foreach (var eventMessage in msgs.GetAll()) @@ -62,4 +63,4 @@ namespace Umbraco.Web.WebApi.Filters } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs b/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs index 77fee75693..3b2ee30aa0 100644 --- a/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; @@ -50,7 +50,7 @@ namespace Umbraco.Web.WebApi.Filters var actionContext = actionExecutedContext.ActionContext; if (actionContext.ActionArguments[_userIdParameter] == null) { - throw new InvalidOperationException("No argument found for the current action with the name: " + _userIdParameter); + throw new InvalidOperationException("No argument found for the current action with the name: " + _userIdParameter); } var user = UmbracoContext.Current.Security.CurrentUser; @@ -73,4 +73,4 @@ namespace Umbraco.Web.WebApi.Filters throw new InvalidOperationException("The id type: " + parameterValue.GetType() + " is not a supported id"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs b/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs index f472163433..ae51f37ef5 100644 --- a/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -6,6 +6,8 @@ using System.Web.Http.Controllers; using System.Web.Http.Filters; using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; @@ -35,7 +37,7 @@ namespace Umbraco.Web.WebApi.Filters //we need new tokens and append the custom header if changes have been made if (actionExecutedContext.ActionContext.Request.Properties.ContainsKey(typeof(CheckIfUserTicketDataIsStaleAttribute).Name)) - { + { var tokenFilter = new SetAngularAntiForgeryTokensAttribute(); tokenFilter.OnActionExecuted(actionExecutedContext); @@ -65,35 +67,35 @@ namespace Umbraco.Web.WebApi.Filters var userId = identity.Id.TryConvertTo(); if (userId == false) return; - var user = ApplicationContext.Current.Services.UserService.GetUserById(userId.Result); + var user = Current.Services.UserService.GetUserById(userId.Result); if (user == null) return; - + //a list of checks to execute, if any of them pass then we resync var checks = new Func[] { () => user.Username != identity.Username, () => { - var culture = UserExtensions.GetUserCulture(user, ApplicationContext.Current.Services.TextService); + var culture = UserExtensions.GetUserCulture(user, Current.Services.TextService, UmbracoConfig.For.GlobalSettings()); return culture != null && culture.ToString() != identity.Culture; - }, + }, () => user.AllowedSections.UnsortedSequenceEqual(identity.AllowedApplications) == false, () => user.Groups.Select(x => x.Alias).UnsortedSequenceEqual(identity.Roles) == false, () => { - var startContentIds = UserExtensions.CalculateContentStartNodeIds(user, ApplicationContext.Current.Services.EntityService); + var startContentIds = UserExtensions.CalculateContentStartNodeIds(user, Current.Services.EntityService); return startContentIds.UnsortedSequenceEqual(identity.StartContentNodes) == false; }, () => { - var startMediaIds = UserExtensions.CalculateMediaStartNodeIds(user, ApplicationContext.Current.Services.EntityService); + var startMediaIds = UserExtensions.CalculateMediaStartNodeIds(user, Current.Services.EntityService); return startMediaIds.UnsortedSequenceEqual(identity.StartMediaNodes) == false; } }; if (checks.Any(check => check())) { - await ReSync(user, actionContext); + await ReSync(user, actionContext); } } @@ -117,8 +119,8 @@ namespace Umbraco.Web.WebApi.Filters actionContext.Request.SetPrincipalForRequest(owinCtx.Result.Request.User); //flag that we've made changes - actionContext.Request.Properties[typeof(CheckIfUserTicketDataIsStaleAttribute).Name] = true; + actionContext.Request.Properties[typeof(CheckIfUserTicketDataIsStaleAttribute).Name] = true; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/ClearAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ClearAngularAntiForgeryTokenAttribute.cs index 76cacc53b7..613cc36af3 100644 --- a/src/Umbraco.Web/WebApi/Filters/ClearAngularAntiForgeryTokenAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/ClearAngularAntiForgeryTokenAttribute.cs @@ -32,4 +32,4 @@ namespace Umbraco.Web.WebApi.Filters context.Response.Headers.AddCookies(new[] { angularCookie, validationCookie }); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs index 88eda3c64e..c1a8c989e5 100644 --- a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs +++ b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Linq; using System.Net; using System.Net.Http; @@ -9,6 +8,7 @@ using System.Web.Http.ModelBinding; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.WebApi.Filters @@ -26,11 +26,15 @@ namespace Umbraco.Web.WebApi.Filters where TPersisted : class, IContentBase where TModelSave : ContentBaseItemSave { - + + /// + /// Validates the content item and updates the Response and ModelState accordingly + /// + /// + /// public void ValidateItem(HttpActionContext actionContext, string argumentName) { - var contentItem = actionContext.ActionArguments[argumentName] as TModelSave; - if (contentItem == null) + if (!(actionContext.ActionArguments[argumentName] is TModelSave contentItem)) { actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "No " + typeof(TModelSave) + " found in request"); return; @@ -45,7 +49,7 @@ namespace Umbraco.Web.WebApi.Filters //now do each validation step if (ValidateExistingContent(contentItem, actionContext) == false) return; if (ValidateProperties(contentItem, actionContext) == false) return; - if (ValidatePropertyData(contentItem, actionContext) == false) return; + if (ValidatePropertyData(contentItem, contentItem.ContentDto, actionContext.ModelState) == false) return; } /// @@ -58,7 +62,7 @@ namespace Umbraco.Web.WebApi.Filters { if (postedItem.PersistedContent == null) { - var message = string.Format("content with id: {0} was not found", postedItem.Id); + var message = $"content with id: {postedItem.Id} was not found"; actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); return false; } @@ -74,7 +78,7 @@ namespace Umbraco.Web.WebApi.Filters /// protected virtual bool ValidateProperties(ContentItemBasic postedItem, HttpActionContext actionContext) { - return ValidateProperties(postedItem.Properties.ToArray(), postedItem.PersistedContent.Properties.ToArray(), actionContext); + return ValidateProperties(postedItem.Properties.ToList(), postedItem.PersistedContent.Properties.ToList(), actionContext); } /// @@ -84,7 +88,7 @@ namespace Umbraco.Web.WebApi.Filters /// /// /// - protected bool ValidateProperties(ContentPropertyBasic[] postedProperties , Property[] persistedProperties, HttpActionContext actionContext) + protected bool ValidateProperties(List postedProperties , List persistedProperties, HttpActionContext actionContext) { foreach (var p in postedProperties) { @@ -93,91 +97,54 @@ namespace Umbraco.Web.WebApi.Filters //TODO: Do we return errors here ? If someone deletes a property whilst their editing then should we just //save the property data that remains? Or inform them they need to reload... not sure. This problem exists currently too i think. - var message = string.Format("property with alias: {0} was not found", p.Alias); + var message = $"property with alias: {p.Alias} was not found"; actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, new InvalidOperationException(message)); return false; } } return true; - } + } /// /// Validates the data for each property /// /// - /// + /// + /// /// /// /// All property data validation goes into the modelstate with a prefix of "Properties" /// - protected virtual bool ValidatePropertyData(ContentItemBasic postedItem, HttpActionContext actionContext) + public virtual bool ValidatePropertyData(ContentItemBasic postedItem, ContentItemDto dto, ModelStateDictionary modelState) { - foreach (var p in postedItem.ContentDto.Properties) + foreach (var p in dto.Properties) { var editor = p.PropertyEditor; + if (editor == null) { - var message = string.Format("The property editor with alias: {0} was not found for property with id {1}", p.DataType.PropertyEditorAlias, p.Id); - LogHelper.Warn>(message); - //actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); - //return false; + var message = $"Could not find property editor \"{p.DataType.EditorAlias}\" for property with id {p.Id}."; + Current.Logger.Warn>(message); continue; } - //get the posted value for this property - var postedValue = postedItem.Properties.Single(x => x.Alias == p.Alias).Value; + //get the posted value for this property, this may be null in cases where the property was marked as readonly which means + //the angular app will not post that value. + var postedProp = postedItem.Properties.FirstOrDefault(x => x.Alias == p.Alias); + if (postedProp == null) continue; - //get the pre-values for this property - var preValues = p.PreValues; + var postedValue = postedProp.Value; - //TODO: when we figure out how to 'override' certain pre-value properties we'll either need to: - // * Combine the preValues with the overridden values stored with the document type property (but how to combine?) - // * Or, pass in the overridden values stored with the doc type property separately - - foreach (var result in editor.ValueEditor.Validators.SelectMany(v => v.Validate(postedValue, preValues, editor))) - { - actionContext.ModelState.AddPropertyError(result, p.Alias); - } - - //Now we need to validate the property based on the PropertyType validation (i.e. regex and required) - // NOTE: These will become legacy once we have pre-value overrides. - if (p.IsRequired) - { - foreach (var result in p.PropertyEditor.ValueEditor.RequiredValidator.Validate(postedValue, "", preValues, editor)) - { - actionContext.ModelState.AddPropertyError(result, p.Alias); - } - } - - if (p.ValidationRegExp.IsNullOrWhiteSpace() == false) - { - - //We only want to execute the regex statement if: - // * the value is null or empty AND it is required OR - // * the value is not null or empty - //See: http://issues.umbraco.org/issue/U4-4669 - - var asString = postedValue as string; - - if ( - //Value is not null or empty - (postedValue != null && asString.IsNullOrWhiteSpace() == false) - //It's required - || (p.IsRequired)) - { - foreach (var result in p.PropertyEditor.ValueEditor.RegexValidator.Validate(postedValue, p.ValidationRegExp, preValues, editor)) - { - actionContext.ModelState.AddPropertyError(result, p.Alias); - } - } - - } + // validate + var valueEditor = editor.GetValueEditor(p.DataType.Configuration); + foreach (var r in valueEditor.Validate(postedValue, p.IsRequired, p.ValidationRegExp)) + modelState.AddPropertyError(r, p.Alias); } - return actionContext.ModelState.IsValid; + return modelState.IsValid; } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/EnableOverrideAuthorizationAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnableOverrideAuthorizationAttribute.cs index b2fdcb3c56..ef230bea49 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnableOverrideAuthorizationAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnableOverrideAuthorizationAttribute.cs @@ -13,4 +13,4 @@ namespace Umbraco.Web.WebApi.Filters { //TODO: Can we remove this and use the System.Web.Http.OverrideAuthorizationAttribute which uses IOverrideFilter instead? } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 129bac1c4d..f82d4ae7b8 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -1,28 +1,21 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; +using Umbraco.Core.Exceptions; +using Umbraco.Web.Composing; using Umbraco.Web.Editors; -using Umbraco.Web.Models.ContentEditing; -using umbraco.BusinessLogic.Actions; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.WebApi.Filters { /// - /// Auth filter to check if the current user has access to the content item (by id). + /// Auth filter to check if the current user has access to the content item (by id). /// /// - /// + /// /// This first checks if the user can access this based on their start node, and then checks node permissions - /// + /// /// By default the permission that is checked is browse but this can be specified in the ctor. /// NOTE: This cannot be an auth filter because that happens too soon and we don't have access to the action params. /// @@ -42,22 +35,18 @@ namespace Umbraco.Web.WebApi.Filters public EnsureUserPermissionForContentAttribute(string paramName) { - if (string.IsNullOrWhiteSpace(paramName)) throw new ArgumentException("Value cannot be null or whitespace.", "paramName"); - + if (string.IsNullOrEmpty(paramName)) throw new ArgumentNullOrEmptyException(nameof(paramName)); _paramName = paramName; _permissionToCheck = ActionBrowse.Instance.Letter; - } + } public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck) : this(paramName) { _permissionToCheck = permissionToCheck; } - - public override bool AllowMultiple - { - get { return true; } - } + + public override bool AllowMultiple => true; public override void OnActionExecuting(HttpActionContext actionContext) { @@ -70,7 +59,7 @@ namespace Umbraco.Web.WebApi.Filters int nodeId; if (_nodeId.HasValue == false) { - var parts = _paramName.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries); + var parts = _paramName.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries); if (actionContext.ActionArguments[parts[0]] == null) { @@ -90,7 +79,7 @@ namespace Umbraco.Web.WebApi.Filters { throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); } - nodeId = (int)prop.GetValue(actionContext.ActionArguments[parts[0]]); + nodeId = (int)prop.GetValue(actionContext.ActionArguments[parts[0]]); } } else @@ -101,9 +90,9 @@ namespace Umbraco.Web.WebApi.Filters if (ContentController.CheckPermissions( actionContext.Request.Properties, UmbracoContext.Current.Security.CurrentUser, - ApplicationContext.Current.Services.UserService, - ApplicationContext.Current.Services.ContentService, - ApplicationContext.Current.Services.EntityService, + Current.Services.UserService, + Current.Services.ContentService, + Current.Services.EntityService, nodeId, _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null)) { base.OnActionExecuting(actionContext); @@ -112,10 +101,10 @@ namespace Umbraco.Web.WebApi.Filters { throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); } - + } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs index 1289f25063..e34a9f9b8a 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs @@ -1,14 +1,12 @@ using System; -using System.Collections.Generic; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using Umbraco.Core; +using Umbraco.Core.Exceptions; using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Editors; -using umbraco.BusinessLogic.Actions; namespace Umbraco.Web.WebApi.Filters { @@ -16,7 +14,7 @@ namespace Umbraco.Web.WebApi.Filters /// Auth filter to check if the current user has access to the content item /// /// - /// Since media doesn't have permissions, this simply checks start node access + /// Since media doesn't have permissions, this simply checks start node access /// internal sealed class EnsureUserPermissionForMediaAttribute : ActionFilterAttribute { @@ -40,20 +38,18 @@ namespace Umbraco.Web.WebApi.Filters public EnsureUserPermissionForMediaAttribute(string paramName) { - Mandate.ParameterNotNullOrEmpty(paramName, "paramName"); + if (string.IsNullOrEmpty(paramName)) throw new ArgumentNullOrEmptyException(nameof(paramName)); _paramName = paramName; } + // fixme v8 guess this is not used anymore, source is ignored?! public EnsureUserPermissionForMediaAttribute(string paramName, DictionarySource source) { - Mandate.ParameterNotNullOrEmpty(paramName, "paramName"); + if (string.IsNullOrEmpty(paramName)) throw new ArgumentNullOrEmptyException(nameof(paramName)); _paramName = paramName; } - - public override bool AllowMultiple - { - get { return true; } - } + + public override bool AllowMultiple => true; private int GetNodeIdFromParameter(object parameterValue) { @@ -74,7 +70,7 @@ namespace Umbraco.Web.WebApi.Filters if (guidId != Guid.Empty) { - var found = ApplicationContext.Current.Services.EntityService.GetIdForKey(guidId, UmbracoObjectTypes.Media); + var found = Current.Services.EntityService.GetId(guidId, UmbracoObjectTypes.Media); if (found) return found.Result; } @@ -122,9 +118,9 @@ namespace Umbraco.Web.WebApi.Filters if (MediaController.CheckPermissions( actionContext.Request.Properties, - UmbracoContext.Current.Security.CurrentUser, - ApplicationContext.Current.Services.MediaService, - ApplicationContext.Current.Services.EntityService, + UmbracoContext.Current.Security.CurrentUser, + Current.Services.MediaService, + Current.Services.EntityService, nodeId)) { base.OnActionExecuting(actionContext); @@ -133,10 +129,6 @@ namespace Umbraco.Web.WebApi.Filters { throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); } - } - - - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/FeatureAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FeatureAuthorizeAttribute.cs index dd1954663f..0046770599 100644 --- a/src/Umbraco.Web/WebApi/Filters/FeatureAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FeatureAuthorizeAttribute.cs @@ -1,5 +1,7 @@ using System.Web.Http; using System.Web.Http.Controllers; +using LightInject; +using Umbraco.Core.Composing; using Umbraco.Web.Features; namespace Umbraco.Web.WebApi.Filters @@ -12,12 +14,16 @@ namespace Umbraco.Web.WebApi.Filters { protected override bool IsAuthorized(HttpActionContext actionContext) { - //if no features resolver has been set then return true, this will occur in unit tests and we don't want users to have to set a resolver + // if no features resolver has been set then return true, this will occur in unit + // tests and we don't want users to have to set a resolver //just so their unit tests work. - if (FeaturesResolver.HasCurrent == false) return true; + + // fixme inject? + var features = Current.Container?.TryGetInstance(); + if (features == null) return true; var controllerType = actionContext.ControllerContext.ControllerDescriptor.ControllerType; - return FeaturesResolver.Current.Features.IsEnabled(controllerType); + return features.IsControllerEnabled(controllerType); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs index ef2b70e44c..911533e389 100644 --- a/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs @@ -7,6 +7,7 @@ using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; using File = System.IO.File; @@ -65,7 +66,7 @@ namespace Umbraco.Web.WebApi.Filters } catch (System.Exception ex) { - LogHelper.Error("Could not delete temp file " + f.TempFilePath, ex); + Current.Logger.Error("Could not delete temp file " + f.TempFilePath, ex); } } } @@ -75,29 +76,29 @@ namespace Umbraco.Web.WebApi.Filters { if (actionExecutedContext == null) { - LogHelper.Warn("The actionExecutedContext is null!!??"); + Current.Logger.Warn("The actionExecutedContext is null!!??"); return; } if (actionExecutedContext.Request == null) { - LogHelper.Warn("The actionExecutedContext.Request is null!!??"); + Current.Logger.Warn("The actionExecutedContext.Request is null!!??"); return; } if (actionExecutedContext.Request.Content == null) { - LogHelper.Warn("The actionExecutedContext.Request.Content is null!!??"); + Current.Logger.Warn("The actionExecutedContext.Request.Content is null!!??"); return; } ObjectContent objectContent; - + try { objectContent = actionExecutedContext.Response.Content as ObjectContent; } catch (System.Exception ex) { - LogHelper.Error("Could not acquire actionExecutedContext.Response.Content", ex); + Current.Logger.Error("Could not acquire actionExecutedContext.Response.Content", ex); return; } @@ -120,7 +121,7 @@ namespace Umbraco.Web.WebApi.Filters tempFolders.Add(dir); } - LogHelper.Debug("Removing temp file " + f.TempFilePath); + Current.Logger.Debug("Removing temp file " + f.TempFilePath); try { @@ -128,7 +129,7 @@ namespace Umbraco.Web.WebApi.Filters } catch (System.Exception ex) { - LogHelper.Error("Could not delete temp file " + f.TempFilePath, ex); + Current.Logger.Error("Could not delete temp file " + f.TempFilePath, ex); } //clear out the temp path so it's not returned in the response @@ -136,23 +137,23 @@ namespace Umbraco.Web.WebApi.Filters } else { - LogHelper.Warn("The f.TempFilePath is null or whitespace!!??"); + Current.Logger.Warn("The f.TempFilePath is null or whitespace!!??"); } } } else { - LogHelper.Warn("The uploadedFiles.UploadedFiles is null!!??"); - } + Current.Logger.Warn("The uploadedFiles.UploadedFiles is null!!??"); + } } else { - LogHelper.Warn("The actionExecutedContext.Request.Content.Value is not IHaveUploadedFiles, it is " + objectContent.Value.GetType()); + Current.Logger.Warn("The actionExecutedContext.Request.Content.Value is not IHaveUploadedFiles, it is " + objectContent.Value.GetType()); } } else { - LogHelper.Warn("The actionExecutedContext.Request.Content is not ObjectContent, it is " + actionExecutedContext.Request.Content.GetType()); + Current.Logger.Warn("The actionExecutedContext.Request.Content is not ObjectContent, it is " + actionExecutedContext.Request.Content.GetType()); } } @@ -170,13 +171,13 @@ namespace Umbraco.Web.WebApi.Filters } catch (System.Exception ex) { - LogHelper.Error("Could not delete temp file " + file, ex); + Current.Logger.Error("Could not delete temp file " + file, ex); } } } } - + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs index 808d7c6da1..74001040e3 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingContentAttribute.cs @@ -3,17 +3,17 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Reflection; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; -using umbraco.BusinessLogic.Actions; using Umbraco.Core; +using Umbraco.Web.Composing; using Umbraco.Core.Models; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.WebApi.Filters { /// - /// This inspects the result of the action that returns a collection of content and removes + /// This inspects the result of the action that returns a collection of content and removes /// any item that the current user doesn't have access to /// internal sealed class FilterAllowedOutgoingContentAttribute : FilterAllowedOutgoingMediaAttribute @@ -22,40 +22,37 @@ namespace Umbraco.Web.WebApi.Filters private readonly IEntityService _entityService; private readonly char _permissionToCheck; - public FilterAllowedOutgoingContentAttribute(Type outgoingType) - : this(outgoingType, ApplicationContext.Current.Services.UserService, ApplicationContext.Current.Services.EntityService) + public FilterAllowedOutgoingContentAttribute(Type outgoingType) + : this(outgoingType, Current.Services.UserService, Current.Services.EntityService) { _permissionToCheck = ActionBrowse.Instance.Letter; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck) - : this(outgoingType, ApplicationContext.Current.Services.UserService, ApplicationContext.Current.Services.EntityService) + : this(outgoingType, Current.Services.UserService, Current.Services.EntityService) { _permissionToCheck = permissionToCheck; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName) - : this(outgoingType, propertyName, ApplicationContext.Current.Services.UserService, ApplicationContext.Current.Services.EntityService) + : this(outgoingType, propertyName, Current.Services.UserService, Current.Services.EntityService) { _permissionToCheck = ActionBrowse.Instance.Letter; } - public FilterAllowedOutgoingContentAttribute(Type outgoingType, IUserService userService, IEntityService entityService) : base(outgoingType) { - if (userService == null) throw new ArgumentNullException("userService"); - if (entityService == null) throw new ArgumentNullException("entityService"); - _userService = userService; - _entityService = entityService; + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); + _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); _permissionToCheck = ActionBrowse.Instance.Letter; } public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck, IUserService userService, IEntityService entityService) : base(outgoingType) { - if (userService == null) throw new ArgumentNullException("userService"); - if (entityService == null) throw new ArgumentNullException("entityService"); + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); + _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); _userService = userService; _entityService = entityService; _permissionToCheck = permissionToCheck; @@ -64,13 +61,12 @@ namespace Umbraco.Web.WebApi.Filters public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName, IUserService userService, IEntityService entityService) : base(outgoingType, propertyName) { - if (userService == null) throw new ArgumentNullException("userService"); - if (entityService == null) throw new ArgumentNullException("entityService"); + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); + _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); _userService = userService; _entityService = entityService; _permissionToCheck = ActionBrowse.Instance.Letter; - } - + } protected override void FilterItems(IUser user, IList items) { @@ -123,4 +119,4 @@ namespace Umbraco.Web.WebApi.Filters } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index beb67f3395..deaf7628e0 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -7,12 +7,12 @@ using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; +using Umbraco.Core.Composing; namespace Umbraco.Web.WebApi.Filters { /// - /// This inspects the result of the action that returns a collection of content and removes + /// This inspects the result of the action that returns a collection of content and removes /// any item that the current user doesn't have access to /// internal class FilterAllowedOutgoingMediaAttribute : ActionFilterAttribute @@ -34,20 +34,14 @@ namespace Umbraco.Web.WebApi.Filters /// /// Returns true so that other filters can execute along with this one /// - public override bool AllowMultiple - { - get { return true; } - } + public override bool AllowMultiple => true; protected virtual int[] GetUserStartNodes(IUser user) { - return user.CalculateMediaStartNodeIds(ApplicationContext.Current.Services.EntityService); + return user.CalculateMediaStartNodeIds(Current.Services.EntityService); } - protected virtual int RecycleBinId - { - get { return Constants.System.RecycleBinMedia; } - } + protected virtual int RecycleBinId => Constants.System.RecycleBinMedia; public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { diff --git a/src/Umbraco.Web/WebApi/Filters/FilterGrouping.cs b/src/Umbraco.Web/WebApi/Filters/FilterGrouping.cs index 581579a8d1..fc3dc65675 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterGrouping.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterGrouping.cs @@ -8,7 +8,7 @@ using System.Web.Http.Filters; namespace Umbraco.Web.WebApi.Filters { /// - /// Quickly split filters into different types + /// Quickly split filters into different types /// internal class FilterGrouping { diff --git a/src/Umbraco.Web/WebApi/Filters/HttpQueryStringFilterAttribute.cs b/src/Umbraco.Web/WebApi/Filters/HttpQueryStringFilterAttribute.cs index 5cdf7a7e94..1e6338cc70 100644 --- a/src/Umbraco.Web/WebApi/Filters/HttpQueryStringFilterAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/HttpQueryStringFilterAttribute.cs @@ -34,10 +34,10 @@ namespace Umbraco.Web.WebApi.Filters var formData = new FormDataCollection(queryStrings); - actionContext.ActionArguments[ParameterName] = formData; + actionContext.ActionArguments[ParameterName] = formData; } - + base.OnActionExecuting(actionContext); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/LegacyTreeAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/Filters/LegacyTreeAuthorizeAttribute.cs index 9a7094f266..5d1b90a09d 100644 --- a/src/Umbraco.Web/WebApi/Filters/LegacyTreeAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/LegacyTreeAuthorizeAttribute.cs @@ -1,6 +1,7 @@ using System.Web.Http; using System.Web.Http.Controllers; using Umbraco.Core; +using Umbraco.Web.Composing; namespace Umbraco.Web.WebApi.Filters { @@ -14,7 +15,7 @@ namespace Umbraco.Web.WebApi.Filters var treeRequest = httpContext.Result.Request.QueryString["treeType"]; if (treeRequest.IsNullOrWhiteSpace()) return false; - var tree = ApplicationContext.Current.Services.ApplicationTreeService.GetByAlias(treeRequest); + var tree = Current.Services.ApplicationTreeService.GetByAlias(treeRequest); if (tree == null) return false; return UmbracoContext.Current.Security.CurrentUser != null @@ -22,7 +23,7 @@ namespace Umbraco.Web.WebApi.Filters } return false; - + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs index 4c4d831241..82661c9023 100644 --- a/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net.Http.Formatting; using System.Web.Http.Controllers; using Umbraco.Core; +using Umbraco.Core.Exceptions; namespace Umbraco.Web.WebApi.Filters { @@ -19,7 +20,7 @@ namespace Umbraco.Web.WebApi.Filters /// public OutgoingDateTimeFormatAttribute(string format) { - Mandate.ParameterNotNullOrEmpty(format, "format"); + if (string.IsNullOrEmpty(format)) throw new ArgumentNullOrEmptyException(nameof(format)); _format = format; } @@ -41,4 +42,4 @@ namespace Umbraco.Web.WebApi.Filters } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs index ae10d073cd..ec32b61bca 100644 --- a/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using System.Web.Http.Filters; using Umbraco.Core; @@ -33,6 +33,6 @@ namespace Umbraco.Web.WebApi.Filters } base.OnActionExecuted(actionExecutedContext); - } + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingNoHyphenGuidFormatAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingNoHyphenGuidFormatAttribute.cs index 897213a142..1685171cbb 100644 --- a/src/Umbraco.Web/WebApi/Filters/OutgoingNoHyphenGuidFormatAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/OutgoingNoHyphenGuidFormatAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Net.Http.Formatting; using System.Web.Http.Controllers; @@ -21,4 +21,4 @@ namespace Umbraco.Web.WebApi.Filters } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/OverridableAuthorizationAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OverridableAuthorizationAttribute.cs index f5c536420b..2d110d9a8b 100644 --- a/src/Umbraco.Web/WebApi/Filters/OverridableAuthorizationAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/OverridableAuthorizationAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Web.Http; using System.Web.Http.Controllers; @@ -9,7 +9,7 @@ namespace Umbraco.Web.WebApi.Filters /// Abstract auth filter class that can be used to enable overriding class auth filters at the action level /// /// - /// To enable a class auth filter to be overridden by an action auth filter the EnableOverrideAuthorizationAttribute can be applied + /// To enable a class auth filter to be overridden by an action auth filter the EnableOverrideAuthorizationAttribute can be applied /// to the class. /// public abstract class OverridableAuthorizationAttribute : AuthorizeAttribute @@ -47,4 +47,4 @@ namespace Umbraco.Web.WebApi.Filters base.OnAuthorization(actionContext); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokensAttribute.cs b/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokensAttribute.cs index a0ed584ef8..b55d6aaa4f 100644 --- a/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokensAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/SetAngularAntiForgeryTokensAttribute.cs @@ -27,7 +27,7 @@ namespace Umbraco.Web.WebApi.Filters //if they are not valid for some strange reason - we need to continue setting valid ones string failedReason; if (AngularAntiForgeryHelper.ValidateHeaders(context.Request.Headers, out failedReason)) - { + { return; } } @@ -43,17 +43,17 @@ namespace Umbraco.Web.WebApi.Filters Path = "/", //must be js readable HttpOnly = false, - Secure = GlobalSettings.UseSSL + Secure = UmbracoConfig.For.GlobalSettings().UseHttps }; var validationCookie = new CookieHeaderValue(AngularAntiForgeryHelper.CsrfValidationCookieName, cookieToken) { Path = "/", HttpOnly = true, - Secure = GlobalSettings.UseSSL + Secure = UmbracoConfig.For.GlobalSettings().UseHttps }; context.Response.Headers.AddCookies(new[] { angularCookie, validationCookie }); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoApplicationAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoApplicationAuthorizeAttribute.cs index 1899584d6f..a479d8bce8 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoApplicationAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoApplicationAuthorizeAttribute.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Web.Http; using System.Web.Http.Controllers; +using Umbraco.Web.Composing; namespace Umbraco.Web.WebApi.Filters { @@ -33,12 +34,12 @@ namespace Umbraco.Web.WebApi.Filters { return true; } - - var authorized = UmbracoContext.Current.Security.CurrentUser != null - && _appNames.Any(app => UmbracoContext.Current.Security.UserHasSectionAccess( - app, UmbracoContext.Current.Security.CurrentUser)); + + var authorized = Current.UmbracoContext.Security.CurrentUser != null + && _appNames.Any(app => Current.UmbracoContext.Security.UserHasSectionAccess( + app, Current.UmbracoContext.Security.CurrentUser)); return authorized; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoBackOfficeLogoutAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoBackOfficeLogoutAttribute.cs index c42881f52a..c8dd64c83c 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoBackOfficeLogoutAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoBackOfficeLogoutAttribute.cs @@ -14,4 +14,4 @@ namespace Umbraco.Web.WebApi.Filters throw new NotSupportedException("This method is not supported and should not be used, it has been removed in Umbraco 7.4"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs index 8d02868091..7318c9d9b8 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs @@ -2,6 +2,7 @@ using System.Web.Http.Controllers; using System.Web.Http.Filters; using Umbraco.Core; +using Umbraco.Web.Composing; namespace Umbraco.Web.WebApi.Filters { @@ -39,16 +40,16 @@ namespace Umbraco.Web.WebApi.Filters return true; } - var apps = _treeAliases.Select(x => ApplicationContext.Current.Services.ApplicationTreeService + var apps = _treeAliases.Select(x => Current.Services.ApplicationTreeService .GetByAlias(x)) .WhereNotNull() .Select(x => x.ApplicationAlias) .Distinct() .ToArray(); - return UmbracoContext.Current.Security.CurrentUser != null - && apps.Any(app => UmbracoContext.Current.Security.UserHasSectionAccess( - app, UmbracoContext.Current.Security.CurrentUser)); + return Current.UmbracoContext.Security.CurrentUser != null + && apps.Any(app => Current.UmbracoContext.Security.UserHasSectionAccess( + app, Current.UmbracoContext.Security.CurrentUser)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoUserTimeoutFilterAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoUserTimeoutFilterAttribute.cs index 4dac187a4a..0d54ed99eb 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoUserTimeoutFilterAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoUserTimeoutFilterAttribute.cs @@ -1,12 +1,14 @@ -using System.Globalization; +using System; +using System.Globalization; using System.Web.Http.Filters; using Umbraco.Core.Security; +using Umbraco.Web.Security; namespace Umbraco.Web.WebApi.Filters { /// - /// This will check if the request is authenticated and if there's an auth ticket present we will - /// add a custom header to the response indicating how many seconds are remaining for the current + /// This will check if the request is authenticated and if there's an auth ticket present we will + /// add a custom header to the response indicating how many seconds are remaining for the current /// user's session. This allows us to keep track of a user's session effectively in the back office. /// public sealed class UmbracoUserTimeoutFilterAttribute : ActionFilterAttribute @@ -17,12 +19,13 @@ namespace Umbraco.Web.WebApi.Filters //this can occur if an error has already occurred. if (actionExecutedContext.Response == null) return; - + var httpContextAttempt = actionExecutedContext.Request.TryGetHttpContext(); if (httpContextAttempt.Success) - { + { + var ticket = httpContextAttempt.Result.GetUmbracoAuthTicket(); - if (ticket != null && ticket.Expired == false) + if (ticket?.Properties.ExpiresUtc != null && ticket.Properties.ExpiresUtc.Value < DateTimeOffset.UtcNow) { var remainingSeconds = httpContextAttempt.Result.GetRemainingAuthSeconds(); actionExecutedContext.Response.Headers.Add("X-Umb-User-Seconds", remainingSeconds.ToString(CultureInfo.InvariantCulture)); @@ -30,4 +33,4 @@ namespace Umbraco.Web.WebApi.Filters } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoWebApiRequireHttpsAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoWebApiRequireHttpsAttribute.cs index f292a63a20..560e5c0512 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoWebApiRequireHttpsAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoWebApiRequireHttpsAttribute.cs @@ -13,8 +13,8 @@ namespace Umbraco.Web.WebApi.Filters /// /// /// This will only redirect Head/Get requests, otherwise will respond with text - /// - /// References: + /// + /// References: /// http://issues.umbraco.org/issue/U4-8542 /// https://blogs.msdn.microsoft.com/carlosfigueira/2012/03/09/implementing-requirehttps-with-asp-net-web-api/ /// @@ -23,7 +23,7 @@ namespace Umbraco.Web.WebApi.Filters public override void OnAuthorization(HttpActionContext actionContext) { var request = actionContext.Request; - if (GlobalSettings.UseSSL && request.RequestUri.Scheme != Uri.UriSchemeHttps) + if (UmbracoConfig.For.GlobalSettings().UseHttps && request.RequestUri.Scheme != Uri.UriSchemeHttps) { HttpResponseMessage response; var uri = new UriBuilder(request.RequestUri) @@ -52,4 +52,4 @@ namespace Umbraco.Web.WebApi.Filters } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs index 82ea6d2d0a..0abdfb5d2f 100644 --- a/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.WebApi.Filters /// /// /// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/ - /// + /// /// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled /// public sealed class ValidateAngularAntiForgeryTokenAttribute : ActionFilterAttribute @@ -40,4 +40,4 @@ namespace Umbraco.Web.WebApi.Filters base.OnActionExecuting(actionContext); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/ValidationFilterAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ValidationFilterAttribute.cs index 556f890516..aacffab0cd 100644 --- a/src/Umbraco.Web/WebApi/Filters/ValidationFilterAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/ValidationFilterAttribute.cs @@ -1,5 +1,8 @@ -using System.Net; +using System; +using System.Diagnostics; +using System.Net; using System.Net.Http; +using System.Threading; using System.Web.Http.Controllers; using System.Web.Http.Filters; @@ -19,8 +22,6 @@ namespace Umbraco.Web.WebApi.Filters { actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, modelState); } - } } - -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/GuidNoHyphenConverter.cs b/src/Umbraco.Web/WebApi/GuidNoHyphenConverter.cs index 8a2172c538..dd5465732f 100644 --- a/src/Umbraco.Web/WebApi/GuidNoHyphenConverter.cs +++ b/src/Umbraco.Web/WebApi/GuidNoHyphenConverter.cs @@ -37,4 +37,4 @@ namespace Umbraco.Web.WebApi return typeof(Guid) == objectType; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs b/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs index a3cb3b9d22..fee1f25ada 100644 --- a/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs @@ -34,13 +34,13 @@ namespace Umbraco.Web.WebApi if (authorizationFilters.Any()) { - var cancelToken = new CancellationToken(); + var cancelToken = new CancellationToken(); var filterResult = await FilterContinuation(actionContext, cancelToken, authorizationFilters, 0); if (filterResult != null) { //this means that the authorization filter has returned a result - unauthorized so we cannot continue return filterResult; - } + } } return null; } @@ -60,7 +60,7 @@ namespace Umbraco.Web.WebApi ? Task.FromResult(null) : FilterContinuation(actionContext, token, filters, ++index)); } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs index 6263428969..e260277093 100644 --- a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs @@ -1,22 +1,15 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; using System.Web; -using System.Web.Http; using System.Web.Http.ModelBinding; -using System.Web.Http.Results; using Microsoft.Owin; using Umbraco.Core; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.WebApi { - + public static class HttpRequestMessageExtensions { @@ -27,15 +20,19 @@ namespace Umbraco.Web.WebApi /// internal static Attempt TryGetOwinContext(this HttpRequestMessage request) { + // occurs in unit tests? + if (request.Properties.TryGetValue("MS_OwinContext", out var o) && o is IOwinContext owinContext) + return Attempt.Succeed(owinContext); + var httpContext = request.TryGetHttpContext(); - try - { - return httpContext - ? Attempt.Succeed(httpContext.Result.GetOwinContext()) - : Attempt.Fail(); - } - catch (InvalidOperationException) - { + try + { + return httpContext + ? Attempt.Succeed(httpContext.Result.GetOwinContext()) + : Attempt.Fail(); + } + catch (InvalidOperationException) + { //this will occur if there is no OWIN environment which generally would only be in things like unit tests return Attempt.Fail(); } diff --git a/src/Umbraco.Web/WebApi/IsBackOfficeAttribute.cs b/src/Umbraco.Web/WebApi/IsBackOfficeAttribute.cs index 219be51d48..2d0b194022 100644 --- a/src/Umbraco.Web/WebApi/IsBackOfficeAttribute.cs +++ b/src/Umbraco.Web/WebApi/IsBackOfficeAttribute.cs @@ -10,4 +10,4 @@ namespace Umbraco.Web.WebApi public sealed class IsBackOfficeAttribute : Attribute { } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs b/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs index de1383706e..19683f7f86 100644 --- a/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs +++ b/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs @@ -29,6 +29,6 @@ namespace Umbraco.Web.WebApi } }; controllerSettings.Formatters.Add(jsonFormatter); - } + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs index 1ea7be719c..83c4a91781 100644 --- a/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs @@ -1,10 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Web; using System.Web.Http; -using Umbraco.Web.Security; -using umbraco.cms.businesslogic.member; using Umbraco.Core; namespace Umbraco.Web.WebApi @@ -14,7 +10,7 @@ namespace Umbraco.Web.WebApi /// to just authenticated members, and optionally of a particular type and/or group /// public sealed class MemberAuthorizeAttribute : AuthorizeAttribute - { + { private readonly UmbracoContext _umbracoContext; private UmbracoContext GetUmbracoContext() diff --git a/src/Umbraco.Web/WebApi/MvcVersionCheck.cs b/src/Umbraco.Web/WebApi/MvcVersionCheck.cs index c2f2580c95..3c84fa91ce 100644 --- a/src/Umbraco.Web/WebApi/MvcVersionCheck.cs +++ b/src/Umbraco.Web/WebApi/MvcVersionCheck.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Web.WebApi +namespace Umbraco.Web.WebApi { internal class WebApiVersionCheck { @@ -7,4 +7,4 @@ namespace Umbraco.Web.WebApi get { return typeof(System.Web.Http.ApiController).Assembly.GetName().Version; } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/NamespaceHttpControllerSelector.cs b/src/Umbraco.Web/WebApi/NamespaceHttpControllerSelector.cs index 0a6ce9f841..d27ec6e91f 100644 --- a/src/Umbraco.Web/WebApi/NamespaceHttpControllerSelector.cs +++ b/src/Umbraco.Web/WebApi/NamespaceHttpControllerSelector.cs @@ -25,27 +25,27 @@ namespace Umbraco.Web.WebApi public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { var routeData = request.GetRouteData(); - if (routeData == null + if (routeData == null || routeData.Route == null || routeData.Route.DataTokens == null || routeData.Route.DataTokens.ContainsKey("Namespaces") == false || routeData.Route.DataTokens["Namespaces"] == null) return base.SelectController(request); - + // Look up controller in route data object controllerName; routeData.Values.TryGetValue(ControllerKey, out controllerName); var controllerNameAsString = controllerName as string; - if (controllerNameAsString == null) + if (controllerNameAsString == null) return base.SelectController(request); - + //get the currently cached default controllers - this will not contain duplicate controllers found so if // this controller is found in the underlying cache we don't need to do anything var map = base.GetControllerMapping(); - if (map.ContainsKey(controllerNameAsString)) + if (map.ContainsKey(controllerNameAsString)) return base.SelectController(request); - - //the cache does not contain this controller because it's most likely a duplicate, + + //the cache does not contain this controller because it's most likely a duplicate, // so we need to sort this out ourselves and we can only do that if the namespace token // is formatted correctly. var namespaces = routeData.Route.DataTokens["Namespaces"] as IEnumerable; @@ -95,4 +95,4 @@ namespace Umbraco.Web.WebApi } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidator.cs b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidator.cs index c5563e6509..a9838f3413 100644 --- a/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidator.cs +++ b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidator.cs @@ -34,4 +34,4 @@ namespace Umbraco.Web.WebApi return _innerValidator.Validate(model, type, metadataProvider, actionContext, string.Empty); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs index 2593acfbe0..b13cf983a5 100644 --- a/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs +++ b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs @@ -13,8 +13,8 @@ namespace Umbraco.Web.WebApi public virtual void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) { //replace the normal validator with our custom one for this controller - controllerSettings.Services.Replace(typeof(IBodyModelValidator), - new PrefixlessBodyModelValidator(controllerSettings.Services.GetBodyModelValidator())); + controllerSettings.Services.Replace(typeof(IBodyModelValidator), + new PrefixlessBodyModelValidator(controllerSettings.Services.GetBodyModelValidator())); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/SessionHttpControllerRouteHandler.cs b/src/Umbraco.Web/WebApi/SessionHttpControllerRouteHandler.cs index d4d462c5f8..5dd641b25c 100644 --- a/src/Umbraco.Web/WebApi/SessionHttpControllerRouteHandler.cs +++ b/src/Umbraco.Web/WebApi/SessionHttpControllerRouteHandler.cs @@ -6,11 +6,11 @@ using System.Web.SessionState; namespace Umbraco.Web.WebApi { /// - /// A custom WebApi route handler that enables session on the HttpContext - use with caution! + /// A custom WebApi route handler that enables session on the HttpContext - use with caution! /// /// /// WebApi controllers (and REST in general) shouldn't have session state enabled since it's stateless, - /// enabling session state puts additional locks on requests so only use this when absolutley needed + /// enabling session state puts additional locks on requests so only use this when absolutley needed /// internal class SessionHttpControllerRouteHandler : HttpControllerRouteHandler { @@ -21,11 +21,11 @@ namespace Umbraco.Web.WebApi /// /// A custom WebApi handler that enables session on the HttpContext - /// + /// private class SessionHttpControllerHandler : HttpControllerHandler, IRequiresSessionState { public SessionHttpControllerHandler(RouteData routeData) : base(routeData) { } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/UmbracoApiController.cs b/src/Umbraco.Web/WebApi/UmbracoApiController.cs index 0356432c66..3616f032d8 100644 --- a/src/Umbraco.Web/WebApi/UmbracoApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoApiController.cs @@ -1,29 +1,10 @@ -using System.ComponentModel; -using System.Linq; -using System.Web.Http; -using System.Web.Http.ModelBinding; -using umbraco.interfaces; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Validation; -using Umbraco.Web.Models.ContentEditing; +using Umbraco.Core.Composing; namespace Umbraco.Web.WebApi { /// - /// The base class for auto-routed API controllers for Umbraco + /// Provides a base class for auto-routed Umbraco API controllers. /// public abstract class UmbracoApiController : UmbracoApiControllerBase, IDiscoverable - { - protected UmbracoApiController() - { - } - - protected UmbracoApiController(UmbracoContext umbracoContext) : base(umbracoContext) - { - } - - protected UmbracoApiController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) - { - } - } + { } } diff --git a/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs b/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs index 48b1bc5d46..eff5a9cee7 100644 --- a/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs +++ b/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs @@ -1,9 +1,13 @@ -using System; +using System; using System.Web; using System.Web.Http; +using LightInject; using Microsoft.Owin; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Web.Security; using Umbraco.Web.WebApi.Filters; @@ -11,125 +15,107 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.WebApi { /// - /// The base class for API controllers that expose Umbraco services - THESE ARE NOT AUTO ROUTED + /// Provides a base class for Umbraco API controllers. /// + /// These controllers are NOT auto-routed. [FeatureAuthorize] public abstract class UmbracoApiControllerBase : ApiController { - protected UmbracoApiControllerBase() - : this(UmbracoContext.Current) - { + private UmbracoHelper _umbracoHelper; - } + // for debugging purposes + internal Guid InstanceId { get; } = Guid.NewGuid(); - protected UmbracoApiControllerBase(UmbracoContext umbracoContext) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - UmbracoContext = umbracoContext; - InstanceId = Guid.NewGuid(); - } - - protected UmbracoApiControllerBase(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - if (umbracoHelper == null) throw new ArgumentNullException("umbracoHelper"); - UmbracoContext = umbracoContext; - InstanceId = Guid.NewGuid(); - _umbraco = umbracoHelper; - } - - private UmbracoHelper _umbraco; + // note + // properties marked as [Inject] below will be property-injected (vs constructor-injected) in + // order to keep the constuctor as light as possible, so that ppl implementing eg a SurfaceController + // don't need to implement complex constructors + need to refactor them each time we change ours. + // this means that these properties have a setter. + // what can go wrong? /// - /// Tries to retrieve the current HttpContext if one exists. + /// Gets or sets the Umbraco context. + /// + [Inject] + public virtual IGlobalSettings GlobalSettings { get; set; } + + /// + /// Gets or sets the Umbraco context. + /// + [Inject] + public virtual UmbracoContext UmbracoContext { get; set; } + + /// + /// Gets or sets the sql context. + /// + [Inject] + public ISqlContext SqlContext { get; set; } + + /// + /// Gets or sets the services context. + /// + [Inject] + public ServiceContext Services { get; set; } + + /// + /// Gets or sets the application cache. + /// + [Inject] + public CacheHelper ApplicationCache { get; set; } + + /// + /// Gets or sets the logger. + /// + [Inject] + public ILogger Logger { get; set; } + + /// + /// Gets or sets the profiling logger. + /// + [Inject] + public ProfilingLogger ProfilingLogger { get; set; } + + /// + /// Gets or sets the runtime state. + /// + [Inject] + internal IRuntimeState RuntimeState { get; set; } + + /// + /// Gets the application url. + /// + protected Uri ApplicationUrl => RuntimeState.ApplicationUrl; + + /// + /// Gets the membership helper. + /// + public MembershipHelper Members => Umbraco.MembershipHelper; + + /// + /// Gets the Umbraco helper. + /// + public UmbracoHelper Umbraco => _umbracoHelper + ?? (_umbracoHelper = new UmbracoHelper(UmbracoContext, Services, ApplicationCache)); + + /// + /// Gets the web security helper. + /// + public WebSecurity Security => UmbracoContext.Security; + + /// + /// Tries to get the current HttpContext. /// - /// protected Attempt TryGetHttpContext() { return Request.TryGetHttpContext(); } /// - /// Tries to retrieve the current HttpContext if one exists. + /// Tries to get the current OWIN context. /// - /// protected Attempt TryGetOwinContext() { return Request.TryGetOwinContext(); } - - /// - /// Returns an ILogger - /// - public ILogger Logger - { - get { return ProfilingLogger.Logger; } - } - - /// - /// Returns a ProfilingLogger - /// - public virtual ProfilingLogger ProfilingLogger - { - get { return UmbracoContext.Application.ProfilingLogger; } - } - - /// - /// Returns the current ApplicationContext - /// - public virtual ApplicationContext ApplicationContext - { - get { return UmbracoContext.Application; } - } - - /// - /// Returns a ServiceContext - /// - public ServiceContext Services - { - get { return ApplicationContext.Services; } - } - - /// - /// Returns a DatabaseContext - /// - public DatabaseContext DatabaseContext - { - get { return ApplicationContext.DatabaseContext; } - } - - /// - /// Returns an UmbracoHelper object - /// - public virtual UmbracoHelper Umbraco - { - get { return _umbraco ?? (_umbraco = new UmbracoHelper(UmbracoContext)); } - } - - /// - /// Returns the current UmbracoContext - /// - public virtual UmbracoContext UmbracoContext { get; private set; } - - /// - /// Returns the WebSecurity instance - /// - public WebSecurity Security - { - get { return UmbracoContext.Security; } - } - - /// - /// Returns the MemberHelper instance - /// - public MembershipHelper Members - { - get { return Umbraco.MembershipHelper; } - } - - /// - /// Useful for debugging - /// - internal Guid InstanceId { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/UmbracoApiControllerResolver.cs b/src/Umbraco.Web/WebApi/UmbracoApiControllerResolver.cs deleted file mode 100644 index cea26b8de7..0000000000 --- a/src/Umbraco.Web/WebApi/UmbracoApiControllerResolver.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Web.WebApi -{ - internal sealed class UmbracoApiControllerResolver : ManyObjectsResolverBase - { - public UmbracoApiControllerResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable apiControllers) - : base(serviceProvider, logger, apiControllers) - { - - } - - /// - /// Gets all of the umbraco api controller types - /// - public IEnumerable RegisteredUmbracoApiControllers - { - get { return InstanceTypes; } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs b/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs new file mode 100644 index 0000000000..c5c7f9dd2a --- /dev/null +++ b/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.WebApi +{ + // unless we want to modify the content of the collection + // which we are not doing at the moment + // we can inherit from BuilderCollectionBase and just be enumerable + + internal class UmbracoApiControllerTypeCollection : BuilderCollectionBase + { + public UmbracoApiControllerTypeCollection(IEnumerable items) + : base(items) + { } + } +} diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs index 858027b62a..6099366690 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizeAttribute.cs @@ -1,14 +1,15 @@ using System; using System.Web.Http; using Umbraco.Core; +using Umbraco.Web.Composing; using Umbraco.Web.Security; namespace Umbraco.Web.WebApi { /// - /// Ensures authorization is successful for a back office user - /// + /// Ensures authorization is successful for a back office user. + /// public sealed class UmbracoAuthorizeAttribute : AuthorizeAttribute { private readonly bool _requireApproval; @@ -18,34 +19,29 @@ namespace Umbraco.Web.WebApi /// internal static bool Enable = true; - private readonly ApplicationContext _applicationContext; + // fixme - inject! private readonly UmbracoContext _umbracoContext; + private readonly IRuntimeState _runtimeState; - private ApplicationContext GetApplicationContext() - { - return _applicationContext ?? ApplicationContext.Current; - } + private IRuntimeState RuntimeState => _runtimeState ?? Current.RuntimeState; - private UmbracoContext GetUmbracoContext() - { - return _umbracoContext ?? UmbracoContext.Current; - } + private UmbracoContext UmbracoContext => _umbracoContext ?? Current.UmbracoContext; /// /// THIS SHOULD BE ONLY USED FOR UNIT TESTS /// /// - public UmbracoAuthorizeAttribute(UmbracoContext umbracoContext) + /// + public UmbracoAuthorizeAttribute(UmbracoContext umbracoContext, IRuntimeState runtimeState) { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); _umbracoContext = umbracoContext; - _applicationContext = _umbracoContext.Application; + _runtimeState = runtimeState; } public UmbracoAuthorizeAttribute() : this(true) - { - - } + { } public UmbracoAuthorizeAttribute(bool requireApproval) { @@ -61,16 +57,11 @@ namespace Umbraco.Web.WebApi try { - var appContext = GetApplicationContext(); - var umbContext = GetUmbracoContext(); - - //we need to that the app is configured and that a user is logged in - if (appContext.IsConfigured == false) - return false; - - var isLoggedIn = umbContext.Security.ValidateCurrentUser(false, _requireApproval) == ValidateRequestAttempt.Success; - - return isLoggedIn; + // if not configured (install or upgrade) then we can continue + // otherwise we need to ensure that a user is logged in + return RuntimeState.Level == RuntimeLevel.Install + || RuntimeState.Level == RuntimeLevel.Upgrade + || UmbracoContext.Security.ValidateCurrentUser(false, _requireApproval) == ValidateRequestAttempt.Success; } catch (Exception) { @@ -78,4 +69,4 @@ namespace Umbraco.Web.WebApi } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index dfe8ee5ce5..ad217b43fd 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -1,20 +1,14 @@ -using System; -using System.Web; -using System.Web.Http; -using Umbraco.Core.Configuration; -using Umbraco.Web.Security; -using Umbraco.Web.WebApi.Filters; -using umbraco.BusinessLogic; +using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; namespace Umbraco.Web.WebApi { /// - /// A base controller that ensures all requests are authorized - the user is logged in. + /// Provides a base class for autorized auto-routed Umbraco API controllers. /// /// - /// This controller will also append a custom header to the response if the user is logged in using forms authentication + /// This controller will also append a custom header to the response if the user is logged in using forms authentication /// which indicates the seconds remaining before their timeout expires. /// [IsBackOffice] @@ -27,51 +21,9 @@ namespace Umbraco.Web.WebApi [EnableDetailedErrors] public abstract class UmbracoAuthorizedApiController : UmbracoApiController { - - - protected UmbracoAuthorizedApiController() - { - } - - protected UmbracoAuthorizedApiController(UmbracoContext umbracoContext) : base(umbracoContext) - { - } - - protected UmbracoAuthorizedApiController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) - { - } - - protected UmbracoAuthorizedApiController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper, BackOfficeUserManager backOfficeUserManager) : base(umbracoContext, umbracoHelper) - { - _userManager = backOfficeUserManager; - } - private BackOfficeUserManager _userManager; + protected BackOfficeUserManager UserManager - { - get { return _userManager ?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager()); } - } - - private bool _userisValidated = false; - - /// - /// Returns the currently logged in Umbraco User - /// - [Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object, or Security.GetUserId() if you want to just get the user id")] - protected User UmbracoUser - { - get - { - //throw exceptions if not valid (true) - if (!_userisValidated) - { - Security.ValidateCurrentUser(true); - _userisValidated = true; - } - - return new User(Security.CurrentUser); - } - } - + => _userManager ?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager()); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/UnhandedExceptionLoggerConfigurationAttribute.cs b/src/Umbraco.Web/WebApi/UnhandedExceptionLoggerConfigurationAttribute.cs index 51c43b0821..6dc1a49eb1 100644 --- a/src/Umbraco.Web/WebApi/UnhandedExceptionLoggerConfigurationAttribute.cs +++ b/src/Umbraco.Web/WebApi/UnhandedExceptionLoggerConfigurationAttribute.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.WebApi /// /// /// Important to note that the will only be called if the controller has an ExceptionFilter applied - /// to it, so to kill two birds with one stone, this class inherits from ExceptionFilterAttribute purely to force webapi to use the + /// to it, so to kill two birds with one stone, this class inherits from ExceptionFilterAttribute purely to force webapi to use the /// IExceptionLogger (strange) /// public class UnhandedExceptionLoggerConfigurationAttribute : ExceptionFilterAttribute, IControllerConfiguration @@ -24,6 +24,6 @@ namespace Umbraco.Web.WebApi { controllerSettings.Services.Add(typeof(IExceptionLogger), new UnhandledExceptionLogger()); } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/UnhandledExceptionLogger.cs b/src/Umbraco.Web/WebApi/UnhandledExceptionLogger.cs index 7c6d9df294..7c289889c2 100644 --- a/src/Umbraco.Web/WebApi/UnhandledExceptionLogger.cs +++ b/src/Umbraco.Web/WebApi/UnhandledExceptionLogger.cs @@ -1,5 +1,6 @@ using System.Web.Http.ExceptionHandling; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; namespace Umbraco.Web.WebApi @@ -12,7 +13,7 @@ namespace Umbraco.Web.WebApi private readonly ILogger _logger; public UnhandledExceptionLogger() - : this(ApplicationContext.Current.ProfilingLogger.Logger) + : this(Current.Logger) { } @@ -29,8 +30,8 @@ namespace Umbraco.Web.WebApi && context.Exception != null) { _logger.Error(context.ExceptionContext.ActionContext.ControllerContext.Controller.GetType(), "Unhandled controller exception occurred", context.Exception); - } + } } - + } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/WebApiHelper.cs b/src/Umbraco.Web/WebApi/WebApiHelper.cs deleted file mode 100644 index d0559f0b17..0000000000 --- a/src/Umbraco.Web/WebApi/WebApiHelper.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Net.Http; -using System.Web; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.Hosting; -using System.Web.Http.Routing; - -namespace Umbraco.Web.WebApi -{ - internal static class WebApiHelper - { - /// - /// A helper method to create a WebAPI HttpControllerContext which can be used to execute a controller manually - /// - /// - /// - /// - /// - internal static HttpControllerContext CreateContext(HttpMethod method, Uri uri, HttpContextBase httpContext) - { - var config = new HttpConfiguration(GlobalConfiguration.Configuration.Routes); - IHttpRouteData route = new HttpRouteData(new HttpRoute()); - var req = new HttpRequestMessage(method, uri); - req.Properties[HttpPropertyKeys.HttpConfigurationKey] = config; - req.Properties[HttpPropertyKeys.HttpRouteDataKey] = route; - req.Properties["MS_HttpContext"] = httpContext; - return new HttpControllerContext(config, route, req); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs deleted file mode 100644 index 7863033c4b..0000000000 --- a/src/Umbraco.Web/WebBootManager.cs +++ /dev/null @@ -1,558 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Web; -using System.Web.Configuration; -using System.Web.Http; -using System.Web.Http.Dispatcher; -using System.Web.Mvc; -using System.Web.Routing; -using ClientDependency.Core.CompositeFiles.Providers; -using ClientDependency.Core.Config; -using Examine.Providers; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Dictionary; -using Umbraco.Core.Logging; -using Umbraco.Core.Macros; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Profiling; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Core.Sync; -using Umbraco.Web.Dictionary; -using Umbraco.Web.Install; -using Umbraco.Web.Media; -using Umbraco.Web.Media.ThumbnailProviders; -using Umbraco.Web.Mvc; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.Routing; -using Umbraco.Web.Security; -using Umbraco.Web.UI.JavaScript; -using Umbraco.Web.WebApi; -using Umbraco.Core.Cache; -using Umbraco.Core.Events; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using Umbraco.Web.Editors; -using Umbraco.Web.Features; -using Umbraco.Web.HealthCheck; -using Umbraco.Web.Profiling; -using Umbraco.Web.Search; -using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; -using ProfilingViewEngine = Umbraco.Core.Profiling.ProfilingViewEngine; - - -namespace Umbraco.Web -{ - /// - /// A bootstrapper for the Umbraco application which initializes all objects including the Web portion of the application - /// - public class WebBootManager : CoreBootManager - { - private readonly bool _isForTesting; - - //needs to be static because we have the public static method GetIndexesForColdBoot - private static ExamineStartup _examineStartup; - - public WebBootManager(UmbracoApplicationBase umbracoApplication) - : base(umbracoApplication) - { - _isForTesting = false; - } - - /// - /// Constructor for unit tests, ensures some resolvers are not initialized - /// - /// - /// - /// - internal WebBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger, bool isForTesting) - : base(umbracoApplication, logger) - { - _isForTesting = isForTesting; - } - - /// - /// Creates and returns the service context for the app - /// - /// - /// - /// - protected override ServiceContext CreateServiceContext(DatabaseContext dbContext, IScopeProvider scopeProvider) - { - //use a request based messaging factory - var evtMsgs = new ScopeLifespanMessagesFactory(new SingletonHttpContextAccessor(), scopeProvider); - return new ServiceContext( - new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()), - new PetaPocoUnitOfWorkProvider(scopeProvider), - ApplicationCache, - ProfilingLogger.Logger, - evtMsgs); - } - - /// - /// Initialize objects before anything during the boot cycle happens - /// - /// - public override IBootManager Initialize() - { - base.Initialize(); - - _examineStartup = new ExamineStartup(ApplicationContext); - _examineStartup.Initialize(); - - ConfigureClientDependency(); - - //set master controller factory - ControllerBuilder.Current.SetControllerFactory( - new MasterControllerFactory(FilteredControllerFactoriesResolver.Current)); - - //set the render view engine - ViewEngines.Engines.Add(new RenderViewEngine()); - //set the plugin view engine - ViewEngines.Engines.Add(new PluginViewEngine()); - - //set model binder - ModelBinderProviders.BinderProviders.Add(RenderModelBinder.Instance); // is a provider - - ////add the profiling action filter - //GlobalFilters.Filters.Add(new ProfilingActionFilter()); - - // Disable the X-AspNetMvc-Version HTTP Header - MvcHandler.DisableMvcResponseHeader = true; - - InstallHelper insHelper = new InstallHelper(UmbracoContext.Current); - insHelper.DeleteLegacyInstaller(); - - return this; - } - - /// - /// Override this method in order to ensure that the UmbracoContext is also created, this can only be - /// created after resolution is frozen! - /// - protected override void FreezeResolution() - { - base.FreezeResolution(); - - //before we do anything, we'll ensure the umbraco context - //see: http://issues.umbraco.org/issue/U4-1717 - var httpContext = new HttpContextWrapper(UmbracoApplication.Context); - UmbracoContext.EnsureContext( - httpContext, - ApplicationContext, - new WebSecurity(httpContext, ApplicationContext), - UmbracoConfig.For.UmbracoSettings(), - UrlProviderResolver.Current.Providers, - false); - } - - /// - /// Ensure the current profiler is the web profiler - /// - protected override void InitializeProfilerResolver() - { - base.InitializeProfilerResolver(); - //Set the profiler to be the web profiler - var profiler = new WebProfiler(); - ProfilerResolver.Current.SetProfiler(profiler); - profiler.Start(); - } - - /// - /// Ensure that the OnApplicationStarted methods of the IApplicationEvents are called - /// - /// - /// - public override IBootManager Complete(Action afterComplete) - { - //Wrap viewengines in the profiling engine - WrapViewEngines(ViewEngines.Engines); - - //add global filters - ConfigureGlobalFilters(); - - //set routes - CreateRoutes(); - - base.Complete(afterComplete); - - //Now, startup all of our legacy startup handler - ApplicationEventsResolver.Current.InstantiateLegacyStartupHandlers(); - - _examineStartup.Complete(); - - //Now ensure webapi is initialized after everything - GlobalConfiguration.Configuration.EnsureInitialized(); - - return this; - } - - - - internal static void ConfigureGlobalFilters() - { - GlobalFilters.Filters.Add(new EnsurePartialViewMacroViewContextFilterAttribute()); - } - - internal static void WrapViewEngines(IList viewEngines) - { - if (viewEngines == null || viewEngines.Count == 0) return; - - var originalEngines = viewEngines.Select(e => e).ToArray(); - viewEngines.Clear(); - foreach (var engine in originalEngines) - { - var wrappedEngine = engine is ProfilingViewEngine ? engine : new ProfilingViewEngine(engine); - viewEngines.Add(wrappedEngine); - } - } - - /// - /// Creates the application cache based on the HttpRuntime cache - /// - protected override CacheHelper CreateApplicationCache() - { - //create a web-based cache helper - var cacheHelper = new CacheHelper( - //we need to have the dep clone runtime cache provider to ensure - //all entities are cached properly (cloned in and cloned out) - new DeepCloneRuntimeCacheProvider(new HttpRuntimeCacheProvider(HttpRuntime.Cache)), - new StaticCacheProvider(), - //we need request based cache when running in web-based context - new HttpRequestCacheProvider(), - new IsolatedRuntimeCache(type => - //we need to have the dep clone runtime cache provider to ensure - //all entities are cached properly (cloned in and cloned out) - new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); - - return cacheHelper; - } - - /// - /// Creates the routes - /// - protected internal void CreateRoutes() - { - var umbracoPath = GlobalSettings.UmbracoMvcArea; - - //Create the front-end route - var defaultRoute = RouteTable.Routes.MapRoute( - "Umbraco_default", - umbracoPath + "/RenderMvc/{action}/{id}", - new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional } - ); - defaultRoute.RouteHandler = new RenderRouteHandler(ControllerBuilder.Current.GetControllerFactory()); - - //register install routes - RouteTable.Routes.RegisterArea(); - - //register all back office routes - RouteTable.Routes.RegisterArea(); - - //plugin controllers must come first because the next route will catch many things - RoutePluginControllers(); - } - - private void ConfigureClientDependency() - { - // Backwards compatibility - set the path and URL type for ClientDependency 1.5.1 [LK] - XmlFileMapper.FileMapDefaultFolder = "~/App_Data/TEMP/ClientDependency"; - BaseCompositeFileProcessingProvider.UrlTypeDefault = CompositeUrlType.Base64QueryStrings; - - // Now we need to detect if we are running umbracoLocalTempStorage as EnvironmentTemp and in that case we want to change the CDF file - // location to be there - if (GlobalSettings.LocalTempStorageLocation == LocalTempStorage.EnvironmentTemp) - { - var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); - var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", - //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 - appDomainHash); - - //set the file map and composite file default location to the %temp% location - BaseCompositeFileProcessingProvider.CompositeFilePathDefaultFolder - = XmlFileMapper.FileMapDefaultFolder - = Path.Combine(cachePath, "ClientDependency"); - } - - var section = ConfigurationManager.GetSection("system.web/httpRuntime") as HttpRuntimeSection; - if (section != null) - { - //set the max url length for CDF to be the smallest of the max query length, max request length - ClientDependency.Core.CompositeFiles.CompositeDependencyHandler.MaxHandlerUrlLength = Math.Min(section.MaxQueryStringLength, section.MaxRequestLength); - } - - //Register a custom renderer - used to process property editor dependencies - var renderer = new DependencyPathRenderer(); - renderer.Initialize("Umbraco.DependencyPathRenderer", new NameValueCollection - { - { "compositeFileHandlerPath", ClientDependencySettings.Instance.CompositeFileHandlerPath } - }); - ClientDependencySettings.Instance.MvcRendererCollection.Add(renderer); - } - - private void RoutePluginControllers() - { - var umbracoPath = GlobalSettings.UmbracoMvcArea; - - //we need to find the plugin controllers and route them - var pluginControllers = - SurfaceControllerResolver.Current.RegisteredSurfaceControllers.Concat( - UmbracoApiControllerResolver.Current.RegisteredUmbracoApiControllers).ToArray(); - - //local controllers do not contain the attribute - var localControllers = pluginControllers.Where(x => PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace()); - foreach (var s in localControllers) - { - if (TypeHelper.IsTypeAssignableFrom(s)) - { - RouteLocalSurfaceController(s, umbracoPath); - } - else if (TypeHelper.IsTypeAssignableFrom(s)) - { - RouteLocalApiController(s, umbracoPath); - } - } - - //need to get the plugin controllers that are unique to each area (group by) - var pluginSurfaceControlleres = pluginControllers.Where(x => !PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace()); - var groupedAreas = pluginSurfaceControlleres.GroupBy(controller => PluginController.GetMetadata(controller).AreaName); - //loop through each area defined amongst the controllers - foreach (var g in groupedAreas) - { - //create an area for the controllers (this will throw an exception if all controllers are not in the same area) - var pluginControllerArea = new PluginControllerArea(g.Select(PluginController.GetMetadata)); - //register it - RouteTable.Routes.RegisterArea(pluginControllerArea); - } - } - - private void RouteLocalApiController(Type controller, string umbracoPath) - { - var meta = PluginController.GetMetadata(controller); - - //url to match - var routePath = meta.IsBackOffice == false - ? umbracoPath + "/Api/" + meta.ControllerName + "/{action}/{id}" - : umbracoPath + "/BackOffice/Api/" + meta.ControllerName + "/{action}/{id}"; - - var route = RouteTable.Routes.MapHttpRoute( - string.Format("umbraco-{0}-{1}", "api", meta.ControllerName), - routePath, - new { controller = meta.ControllerName, id = UrlParameter.Optional }, - new[] { meta.ControllerNamespace }); - //web api routes don't set the data tokens object - if (route.DataTokens == null) - { - route.DataTokens = new RouteValueDictionary(); - } - route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "api"); //ensure the umbraco token is set - } - - private void RouteLocalSurfaceController(Type controller, string umbracoPath) - { - var meta = PluginController.GetMetadata(controller); - var route = RouteTable.Routes.MapRoute( - string.Format("umbraco-{0}-{1}", "surface", meta.ControllerName), - umbracoPath + "/Surface/" + meta.ControllerName + "/{action}/{id}",//url to match - new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, - new[] { meta.ControllerNamespace }); //look in this namespace to create the controller - route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "surface"); //ensure the umbraco token is set - route.DataTokens.Add("UseNamespaceFallback", false); //Don't look anywhere else except this namespace! - //make it use our custom/special SurfaceMvcHandler - route.RouteHandler = new SurfaceRouteHandler(); - } - - /// - /// Initializes all web based and core resolves - /// - protected override void InitializeResolvers() - { - base.InitializeResolvers(); - - FeaturesResolver.Current = new FeaturesResolver(new UmbracoFeatures()); - - TourFilterResolver.Current = new TourFilterResolver(ServiceProvider, LoggerResolver.Current.Logger); - - SearchableTreeResolver.Current = new SearchableTreeResolver(ServiceProvider, LoggerResolver.Current.Logger, ApplicationContext.Services.ApplicationTreeService, () => PluginManager.ResolveSearchableTrees()); - - XsltExtensionsResolver.Current = new XsltExtensionsResolver(ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolveXsltExtensions()); - - EditorValidationResolver.Current = new EditorValidationResolver(ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolveTypes()); - - //set the default RenderMvcController - DefaultRenderMvcControllerResolver.Current = new DefaultRenderMvcControllerResolver(typeof(RenderMvcController)); - - //Override the default server messenger, we need to check if the legacy dist calls is enabled, if that is the - // case, then we'll set the default messenger to be the old one, otherwise we'll set it to the db messenger - // which will always be on. - if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) - { - //set the legacy one by default - this maintains backwards compat - ServerMessengerResolver.Current.SetServerMessenger(new BatchedWebServiceServerMessenger(() => - { - //we should not proceed to change this if the app/database is not configured since there will - // be no user, plus we don't need to have server messages sent if this is the case. - if (ApplicationContext.IsConfigured && ApplicationContext.DatabaseContext.IsDatabaseConfigured) - { - //disable if they are not enabled - if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled == false) - { - return null; - } - - try - { - var user = ApplicationContext.Services.UserService.GetUserById(UmbracoConfig.For.UmbracoSettings().DistributedCall.UserId); - return new Tuple(user.Username, user.RawPasswordValue); - } - catch (Exception e) - { - LoggerResolver.Current.Logger.Error("An error occurred trying to set the IServerMessenger during application startup", e); - return null; - } - } - LoggerResolver.Current.Logger.Warn("Could not initialize the DefaultServerMessenger, the application is not configured or the database is not configured"); - return null; - })); - } - else - { - ServerMessengerResolver.Current.SetServerMessenger(new BatchedDatabaseServerMessenger( - ApplicationContext, - true, - //Default options for web including the required callbacks to build caches - new DatabaseServerMessengerOptions - { - //These callbacks will be executed if the server has not been synced - // (i.e. it is a new server or the lastsynced.txt file has been removed) - InitializingCallbacks = new Action[] - { - //rebuild the xml cache file if the server is not synced - () => global::umbraco.content.Instance.RefreshContentFromDatabase(), - //rebuild indexes if the server is not synced - // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific - // indexes then they can adjust this logic themselves. - () => _examineStartup.RebuildIndexes() - } - })); - } - - SurfaceControllerResolver.Current = new SurfaceControllerResolver( - ServiceProvider, LoggerResolver.Current.Logger, - PluginManager.ResolveSurfaceControllers()); - - UmbracoApiControllerResolver.Current = new UmbracoApiControllerResolver( - ServiceProvider, LoggerResolver.Current.Logger, - PluginManager.ResolveUmbracoApiControllers()); - - // both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be - // discovered when CoreBootManager configures the converters. We HAVE to remove one of them - // here because there cannot be two converters for one property editor - and we want the full - // RteMacroRenderingValueConverter that converts macros, etc. So remove TinyMceValueConverter. - // (the limited one, defined in Core, is there for tests) - PropertyValueConvertersResolver.Current.RemoveType(); - // same for other converters - PropertyValueConvertersResolver.Current.RemoveType(); - PropertyValueConvertersResolver.Current.RemoveType(); - PropertyValueConvertersResolver.Current.RemoveType(); - - PublishedCachesResolver.Current = new PublishedCachesResolver(new PublishedCaches( - new PublishedCache.XmlPublishedCache.PublishedContentCache(), - new PublishedCache.XmlPublishedCache.PublishedMediaCache(ApplicationContext))); - - GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), - new NamespaceHttpControllerSelector(GlobalConfiguration.Configuration)); - - FilteredControllerFactoriesResolver.Current = new FilteredControllerFactoriesResolver( - ServiceProvider, LoggerResolver.Current.Logger, - // add all known factories, devs can then modify this list on application - // startup either by binding to events or in their own global.asax - new[] - { - typeof (RenderControllerFactory) - }); - - UrlProviderResolver.Current = new UrlProviderResolver( - ServiceProvider, LoggerResolver.Current.Logger, - //typeof(AliasUrlProvider), // not enabled by default - typeof(DefaultUrlProvider), - typeof(CustomRouteUrlProvider) - ); - - ContentLastChanceFinderResolver.Current = new ContentLastChanceFinderResolver( - // handled by ContentLastChanceFinderByNotFoundHandlers for the time being - // soon as we get rid of INotFoundHandler support, we must enable this - //new ContentFinderByLegacy404() - - // implement INotFoundHandler support... remove once we get rid of it - new ContentLastChanceFinderByNotFoundHandlers()); - - ContentFinderResolver.Current = new ContentFinderResolver( - ServiceProvider, LoggerResolver.Current.Logger, - // all built-in finders in the correct order, devs can then modify this list - // on application startup via an application event handler. - typeof(ContentFinderByPageIdQuery), - typeof(ContentFinderByNiceUrl), - typeof(ContentFinderByIdPath), - - // these will be handled by ContentFinderByNotFoundHandlers so they can be enabled/disabled - // via the config file... soon as we get rid of INotFoundHandler support, we must enable - // them here. - //typeof (ContentFinderByNiceUrlAndTemplate), - //typeof (ContentFinderByProfile), - //typeof (ContentFinderByUrlAlias), - - // note: that one should run *after* NiceUrlAndTemplate, UrlAlias... but at the moment - // it cannot be done - just make sure to do it properly in v8! - typeof(ContentFinderByRedirectUrl), - - // implement INotFoundHandler support... remove once we get rid of it - typeof(ContentFinderByNotFoundHandlers) - ); - - SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper()); - - // ain't that a bit dirty? - PublishedCache.XmlPublishedCache.PublishedContentCache.UnitTesting = _isForTesting; - - ThumbnailProvidersResolver.Current = new ThumbnailProvidersResolver( - ServiceProvider, LoggerResolver.Current.Logger, - () => PluginManager.ResolveThumbnailProviders()); - - ImageUrlProviderResolver.Current = new ImageUrlProviderResolver( - ServiceProvider, LoggerResolver.Current.Logger, - () => PluginManager.ResolveImageUrlProviders()); - - CultureDictionaryFactoryResolver.Current = new CultureDictionaryFactoryResolver( - new DefaultCultureDictionaryFactory()); - - HealthCheckResolver.Current = new HealthCheckResolver(LoggerResolver.Current.Logger, - () => PluginManager.ResolveTypes()); - HealthCheckNotificationMethodResolver.Current = new HealthCheckNotificationMethodResolver(LoggerResolver.Current.Logger, - () => PluginManager.ResolveTypes()); - } - - /// - /// The method used to create indexes on a cold boot - /// - /// - /// A cold boot is when the server determines it will not (or cannot) process instructions in the cache table and - /// will rebuild it's own caches itself. - /// TODO: Does this method need to exist? Is it used publicly elsewhere (i.e. outside of core?) - /// - public static IEnumerable GetIndexesForColdBoot() - { - return _examineStartup.GetIndexesForColdBoot(); - } - } -} - diff --git a/src/Umbraco.Web/WebServices/BulkPublishController.cs b/src/Umbraco.Web/WebServices/BulkPublishController.cs index e477db539a..daea5cc58f 100644 --- a/src/Umbraco.Web/WebServices/BulkPublishController.cs +++ b/src/Umbraco.Web/WebServices/BulkPublishController.cs @@ -4,16 +4,13 @@ using System.Text; using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Publishing; using Umbraco.Core.Services; using Umbraco.Web.Mvc; -using umbraco; -using umbraco.cms.businesslogic.web; namespace Umbraco.Web.WebServices { /// - /// A REST controller used for the publish dialog in order to publish bulk items at once + /// Represents a REST controller used for the publish dialog in order to bulk-publish bulk content items. /// [ValidateMvcAngularAntiForgeryToken] public class BulkPublishController : UmbracoAuthorizedController @@ -29,44 +26,42 @@ namespace Umbraco.Web.WebServices public JsonResult PublishDocument(int documentId, bool publishDescendants, bool includeUnpublished) { var content = Services.ContentService.GetById(documentId); - var doc = new Document(content); - //var contentService = (ContentService) Services.ContentService; + if (publishDescendants == false) { - //var result = contentService.SaveAndPublishInternal(content); - var result = doc.SaveAndPublish(UmbracoUser.Id); + content.PublishValues(); // fixme variants? validation - when this returns null? + var result = Services.ContentService.SaveAndPublish(content, Security.CurrentUser.Id); return Json(new { success = result.Success, - message = GetMessageForStatus(result.Result) + message = GetMessageForStatus(result) }); } else { - /*var result = ((ContentService) Services.ContentService) - .PublishWithChildrenInternal(content, UmbracoUser.Id, includeUnpublished) - .ToArray();*/ - var result = doc.PublishWithSubs(UmbracoUser.Id, includeUnpublished).ToArray(); + // fixme variants? + var result = Services.ContentService.SaveAndPublishBranch(content, includeUnpublished); + return Json(new { success = result.All(x => x.Success), - message = GetMessageForStatuses(result.Select(x => x.Result).ToArray(), content) + message = GetMessageForStatuses(result.ToArray(), content) }); } } - private string GetMessageForStatuses(PublishStatus[] statuses, IContent doc) + private string GetMessageForStatuses(PublishResult[] statuses, IContent doc) { //if all are successful then just say it was successful - if (statuses.All(x => ((int) x.StatusType) < 10)) + if (statuses.All(x => x.Success)) { - return ui.Text("publish", "nodePublishAll", doc.Name, UmbracoUser); + return Services.TextService.Localize("publish/nodePublishAll", new[] { doc.Name}); } //if they are not all successful the we'll add each error message to the output (one per line) var sb = new StringBuilder(); foreach (var msg in statuses - .Where(x => ((int)x.StatusType) >= 10) + .Where(x => ((int)x.Result) >= 10) .Select(GetMessageForStatus) .Where(msg => msg.IsNullOrWhiteSpace() == false)) { @@ -75,33 +70,32 @@ namespace Umbraco.Web.WebServices return sb.ToString(); } - private string GetMessageForStatus(PublishStatus status) + private string GetMessageForStatus(PublishResult status) { - switch (status.StatusType) + switch (status.Result) { - case PublishStatusType.Success: - case PublishStatusType.SuccessAlreadyPublished: - return ui.Text("publish", "nodePublish", status.ContentItem.Name, UmbracoUser); - case PublishStatusType.FailedPathNotPublished: - return ui.Text("publish", "contentPublishedFailedByParent", - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), UmbracoUser); - case PublishStatusType.FailedHasExpired: - case PublishStatusType.FailedAwaitingRelease: - case PublishStatusType.FailedIsTrashed: - return "Cannot publish document with a status of " + status.StatusType; - case PublishStatusType.FailedCancelledByEvent: - return ui.Text("publish", "contentPublishedFailedByEvent", - string.Format("'{0}' ({1})", status.ContentItem.Name, status.ContentItem.Id), UmbracoUser); - case PublishStatusType.FailedContentInvalid: - return ui.Text("publish", "contentPublishedFailedInvalid", + case PublishResultType.Success: + case PublishResultType.SuccessAlready: + return Services.TextService.Localize("publish/nodePublish", new[] { status.Content.Name}); + case PublishResultType.FailedPathNotPublished: + return Services.TextService.Localize("publish/contentPublishedFailedByParent", + new [] { string.Format("{0} ({1})", status.Content.Name, status.Content.Id) }); + case PublishResultType.FailedHasExpired: + case PublishResultType.FailedAwaitingRelease: + case PublishResultType.FailedIsTrashed: + return "Cannot publish document with a status of " + status.Result; + case PublishResultType.FailedCancelledByEvent: + return Services.TextService.Localize("publish/contentPublishedFailedByEvent", + new [] { string.Format("'{0}' ({1})", status.Content.Name, status.Content.Id) }); + case PublishResultType.FailedContentInvalid: + return Services.TextService.Localize("publish/contentPublishedFailedInvalid", new []{ - string.Format("'{0}' ({1})", status.ContentItem.Name, status.ContentItem.Id), + string.Format("'{0}' ({1})", status.Content.Name, status.Content.Id), string.Format("'{0}'", string.Join(", ", status.InvalidProperties.Select(x => x.Alias))) - }, - UmbracoUser); + }); default: - return status.StatusType.ToString(); + return status.Result.ToString(); } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebServices/CoreStringsController.cs b/src/Umbraco.Web/WebServices/CoreStringsController.cs index de7bcd55d5..2f84e10d54 100644 --- a/src/Umbraco.Web/WebServices/CoreStringsController.cs +++ b/src/Umbraco.Web/WebServices/CoreStringsController.cs @@ -1,19 +1,13 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Mvc; +using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Strings; -using Umbraco.Core.Models; -using Umbraco.Core.Publishing; -using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Mvc; -using umbraco; namespace Umbraco.Web.WebServices { /// - /// A REST controller used for accessing Core.Strings services. + /// Represents a REST controller used for accessing Core.Strings services. /// public class CoreStringsController : UmbracoAuthorizedController { @@ -23,7 +17,7 @@ namespace Umbraco.Web.WebServices { // always return a proper camel-cased alias // when checking... javascript does a case-unsensitive comparison - return value == null + return value == null ? Json(new {error = "no value."}, JsonRequestBehavior.AllowGet) : Json(new { alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.CamelCase) }, JsonRequestBehavior.AllowGet); } @@ -32,8 +26,8 @@ namespace Umbraco.Web.WebServices public JavaScriptResult ServicesJavaScript() { var controllerPath = Url.GetCoreStringsControllerPath(); - var js = ShortStringHelperResolver.Current.Helper.GetShortStringServicesJavaScript(controllerPath); + var js = Current.ShortStringHelper.GetShortStringServicesJavaScript(controllerPath); return JavaScript(js); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebServices/DomainsApiController.cs b/src/Umbraco.Web/WebServices/DomainsApiController.cs index 30288fe8df..d74a225aee 100644 --- a/src/Umbraco.Web/WebServices/DomainsApiController.cs +++ b/src/Umbraco.Web/WebServices/DomainsApiController.cs @@ -1,19 +1,16 @@ using System; -using System.Collections.Generic; using System.Linq; +using System.Collections.Generic; using System.Net; using System.Net.Http; -using System.Text; using System.Web.Http; -using System.Web.Services.Description; using Umbraco.Core; +using Umbraco.Core.Services; using Umbraco.Core.Models; -using Umbraco.Web.Routing; using Umbraco.Web.WebApi; //using umbraco.cms.businesslogic.language; -using umbraco.BusinessLogic.Actions; -using umbraco.cms.businesslogic.web; using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.WebServices { @@ -28,7 +25,7 @@ namespace Umbraco.Web.WebServices // can't pass multiple complex args in json post request... public PostBackModel SaveLanguageAndDomains(PostBackModel model) { - var node = ApplicationContext.Current.Services.ContentService.GetById(model.NodeId); + var node = Services.ContentService.GetById(model.NodeId); if (node == null) { @@ -38,7 +35,9 @@ namespace Umbraco.Web.WebServices throw new HttpResponseException(response); } - if (UmbracoUser.GetPermissions(node.Path).Contains(ActionAssignDomain.Instance.Letter) == false) + var permission = Services.UserService.GetPermissions(Security.CurrentUser, node.Path); + + if (permission.AssignedPermissions.Contains(ActionAssignDomain.Instance.Letter.ToString(), StringComparer.Ordinal) == false) { var response = Request.CreateResponse(HttpStatusCode.BadRequest); response.Content = new StringContent("You do not have permission to assign domains on that node."); @@ -75,7 +74,7 @@ namespace Umbraco.Web.WebServices { var response = Request.CreateResponse(HttpStatusCode.BadRequest); response.Content = new StringContent("Saving domain failed"); - response.ReasonPhrase = saveAttempt.Result.StatusType.ToString(); + response.ReasonPhrase = saveAttempt.Result.Result.ToString(); throw new HttpResponseException(response); } } @@ -132,7 +131,7 @@ namespace Umbraco.Web.WebServices xnames.Add(xcontent.Name); if (xcontent.ParentId < -1) xnames.Add("Recycle Bin"); - xcontent = xcontent.Parent(); + xcontent = xcontent.Parent(Services.ContentService); } xnames.Reverse(); domainModel.Other = "/" + string.Join("/", xnames); @@ -151,7 +150,7 @@ namespace Umbraco.Web.WebServices { var response = Request.CreateResponse(HttpStatusCode.BadRequest); response.Content = new StringContent("Saving new domain failed"); - response.ReasonPhrase = saveAttempt.Result.StatusType.ToString(); + response.ReasonPhrase = saveAttempt.Result.Result.ToString(); throw new HttpResponseException(response); } } diff --git a/src/Umbraco.Web/WebServices/EmbedMediaService.cs b/src/Umbraco.Web/WebServices/EmbedMediaService.cs index 3f18b3a2c3..00d0ad8ede 100644 --- a/src/Umbraco.Web/WebServices/EmbedMediaService.cs +++ b/src/Umbraco.Web/WebServices/EmbedMediaService.cs @@ -7,22 +7,20 @@ using System.Web.Script.Serialization; using System.Xml; using Umbraco.Core.Configuration; using Umbraco.Core.Media; -using umbraco.BusinessLogic; -using Umbraco.Web.BaseRest; namespace Umbraco.Web.WebServices { - [RestExtension("EmbedMediaService")] + //TODO: Convert this to MVC and see if we still need it + public class EmbedMediaService { - [RestExtensionMethod(ReturnXml = false)] public static string Embed() { - var currentUser = User.GetCurrent(); + var currentUser = UmbracoContext.Current.Security.CurrentUser; if (currentUser == null) throw new UnauthorizedAccessException("You must be logged in to use this service"); - + var url = HttpContext.Current.Request.Form["url"]; var width = int.Parse(HttpContext.Current.Request.Form["width"]); var height = int.Parse(HttpContext.Current.Request.Form["height"]); @@ -31,7 +29,7 @@ namespace Umbraco.Web.WebServices //todo cache embed doc var xmlConfig = new XmlDocument(); - xmlConfig.Load(GlobalSettings.FullpathToRoot + Path.DirectorySeparatorChar + "config" + Path.DirectorySeparatorChar + "EmbeddedMedia.config"); + xmlConfig.Load(GlobalSettings.FullPathToRoot + Path.DirectorySeparatorChar + "config" + Path.DirectorySeparatorChar + "EmbeddedMedia.config"); foreach (XmlNode node in xmlConfig.SelectNodes("//provider")) { diff --git a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs index 1218ddc2da..34ffd1426b 100644 --- a/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs +++ b/src/Umbraco.Web/WebServices/ExamineManagementApiController.cs @@ -1,18 +1,22 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Net; using System.Net.Http; +using System.Reflection; using System.Web.Http; using Examine; using Examine.LuceneEngine; using Examine.LuceneEngine.Providers; using Examine.Providers; -using Lucene.Net.Index; using Lucene.Net.Search; -using Lucene.Net.Store; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Composing; +using Umbraco.Core.Services; +using Umbraco.Examine; using Umbraco.Web.Search; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; @@ -22,6 +26,17 @@ namespace Umbraco.Web.WebServices [ValidateAngularAntiForgeryToken] public class ExamineManagementApiController : UmbracoAuthorizedApiController { + public ExamineManagementApiController(IExamineManager examineManager, ILogger logger, IRuntimeCacheProvider runtimeCacheProvider) + { + _examineManager = examineManager; + _logger = logger; + _runtimeCacheProvider = runtimeCacheProvider; + } + + private readonly IExamineManager _examineManager; + private readonly ILogger _logger; + private readonly IRuntimeCacheProvider _runtimeCacheProvider; + /// /// Checks if the member internal index is consistent with the data stored in the database /// @@ -31,10 +46,9 @@ namespace Umbraco.Web.WebServices { var total = Services.MemberService.Count(); - var criteria = ExamineManager.Instance.SearchProviderCollection[Constants.Examine.InternalMemberSearcher] - .CreateSearchCriteria().RawQuery("__IndexType:member"); - var totalIndexed = ExamineManager.Instance.SearchProviderCollection[Constants.Examine.InternalMemberSearcher].Search(criteria); - + var searcher = _examineManager.GetSearcher(Constants.Examine.InternalMemberIndexer); + var criteria = searcher.CreateCriteria().RawQuery("__IndexType:member"); + var totalIndexed = searcher.Search(criteria); return total == totalIndexed.TotalItemCount; } @@ -47,10 +61,9 @@ namespace Umbraco.Web.WebServices { var total = Services.MediaService.Count(); - var criteria = ExamineManager.Instance.SearchProviderCollection[Constants.Examine.InternalSearcher] - .CreateSearchCriteria().RawQuery("__IndexType:media"); - var totalIndexed = ExamineManager.Instance.SearchProviderCollection[Constants.Examine.InternalSearcher].Search(criteria); - + var searcher = _examineManager.GetSearcher(Constants.Examine.InternalIndexer); + var criteria = searcher.CreateCriteria().RawQuery("__IndexType:media"); + var totalIndexed = searcher.Search(criteria); return total == totalIndexed.TotalItemCount; } @@ -63,10 +76,9 @@ namespace Umbraco.Web.WebServices { var total = Services.ContentService.Count(); - var criteria = ExamineManager.Instance.SearchProviderCollection[Constants.Examine.InternalSearcher] - .CreateSearchCriteria().RawQuery("__IndexType:content"); - var totalIndexed = ExamineManager.Instance.SearchProviderCollection[Constants.Examine.InternalSearcher].Search(criteria); - + var searcher = _examineManager.GetSearcher(Constants.Examine.InternalIndexer); + var criteria = searcher.CreateCriteria().RawQuery("__IndexType:content"); + var totalIndexed = searcher.Search(criteria); return total == totalIndexed.TotalItemCount; } @@ -76,7 +88,7 @@ namespace Umbraco.Web.WebServices /// public IEnumerable GetIndexerDetails() { - return ExamineManager.Instance.IndexProviderCollection.Select(CreateModel).OrderBy(x => + return _examineManager.IndexProviders.Select(CreateModel).OrderBy(x => { //order by name , but strip the "Indexer" from the end if it exists return x.Name.TrimEnd("Indexer"); @@ -90,23 +102,27 @@ namespace Umbraco.Web.WebServices public IEnumerable GetSearcherDetails() { var model = new List( - ExamineManager.Instance.SearchProviderCollection.Cast().Select(searcher => + _examineManager.IndexProviders.Select(indexer => { + var searcher = indexer.Value.GetSearcher(); + var searcherName = (searcher as BaseLuceneSearcher)?.Name ?? string.Concat(indexer.Key, "Searcher"); + var indexerModel = new ExamineSearcherModel() { - Name = searcher.Name + Name = searcherName }; var props = TypeHelper.CachedDiscoverableProperties(searcher.GetType(), mustWrite: false) //ignore these properties - .Where(x => new[] {"Description"}.InvariantContains(x.Name) == false) - .OrderBy(x => x.Name); + .Where(x => new[] {"Description"}.InvariantContains(x.Name) == false) + .Where(x => x.GetCustomAttribute()?.State != EditorBrowsableState.Never) + .OrderBy(x => x.Name); foreach (var p in props) { - indexerModel.ProviderProperties.Add(p.Name, p.GetValue(searcher, null).ToString()); + indexerModel.ProviderProperties.Add(p.Name, p.GetValue(searcher, null)?.ToString()); } return indexerModel; }).OrderBy(x => - { + { //order by name , but strip the "Searcher" from the end if it exists return x.Name.TrimEnd("Searcher"); })); @@ -119,13 +135,12 @@ namespace Umbraco.Web.WebServices { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } - + if (query.IsNullOrWhiteSpace()) - return SearchResults.Empty(); + return LuceneSearchResults.Empty(); - LuceneSearcher searcher; - var msg = ValidateLuceneSearcher(searcherName, out searcher); + var msg = ValidateLuceneSearcher(searcherName, out var searcher); if (msg.IsSuccessStatusCode) { if (queryType.InvariantEquals("text")) @@ -134,35 +149,11 @@ namespace Umbraco.Web.WebServices } if (queryType.InvariantEquals("lucene")) { - return searcher.Search(searcher.CreateSearchCriteria().RawQuery(query)); + return searcher.Search(searcher.CreateCriteria().RawQuery(query)); } throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); } - throw new HttpResponseException(msg); - } - - /// - /// Optimizes an index - /// - public HttpResponseMessage PostOptimizeIndex(string indexerName) - { - LuceneIndexer indexer; - var msg = ValidateLuceneIndexer(indexerName, out indexer); - if (msg.IsSuccessStatusCode) - { - try - { - indexer.OptimizeIndex(); - } - catch (Exception ex) - { - var response = Request.CreateResponse(HttpStatusCode.Conflict); - response.Content = new StringContent(string.Format("The index could not be optimized, most likely there is another thread currently writing to the index. Error: {0}", ex)); - response.ReasonPhrase = "Could Not Optimize"; - return response; - } - } - return msg; + throw new HttpResponseException(msg); } /// @@ -172,11 +163,10 @@ namespace Umbraco.Web.WebServices /// public HttpResponseMessage PostRebuildIndex(string indexerName) { - LuceneIndexer indexer; - var msg = ValidateLuceneIndexer(indexerName, out indexer); + var msg = ValidateLuceneIndexer(indexerName, out LuceneIndexer indexer); if (msg.IsSuccessStatusCode) { - LogHelper.Info(string.Format("Rebuilding index '{0}'", indexerName)); + _logger.Info($"Rebuilding index '{indexerName}'"); //remove it in case there's a handler there alraedy indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; @@ -185,57 +175,38 @@ namespace Umbraco.Web.WebServices var cacheKey = "temp_indexing_op_" + indexer.Name; //put temp val in cache which is used as a rudimentary way to know when the indexing is done - ApplicationContext.ApplicationCache.RuntimeCache.InsertCacheItem(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5), isSliding: false); + ApplicationCache.RuntimeCache.InsertCacheItem(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5), isSliding: false); try { indexer.RebuildIndex(); } - catch (LockObtainFailedException) - { - //this will occur if the index is locked (which it should defo not be!) so in this case we'll forcibly unlock it and try again - - try - { - IndexWriter.Unlock(indexer.GetLuceneDirectory()); - indexer.RebuildIndex(); - } - catch (Exception e) - { - return HandleException(e, indexer); - } - } catch (Exception ex) { - return HandleException(ex, indexer); + //ensure it's not listening + indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; + Logger.Error("An error occurred rebuilding index", ex); + var response = Request.CreateResponse(HttpStatusCode.Conflict); + response.Content = new StringContent($"The index could not be rebuilt at this time, most likely there is another thread currently writing to the index. Error: {ex}"); + response.ReasonPhrase = "Could Not Rebuild"; + return response; } } return msg; } - private HttpResponseMessage HandleException(Exception ex, LuceneIndexer indexer) - { - //ensure it's not listening - indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; - LogHelper.Error("An error occurred rebuilding index", ex); - var response = Request.CreateResponse(HttpStatusCode.Conflict); - response.Content = new StringContent(string.Format("The index could not be rebuilt at this time, most likely there is another thread currently writing to the index. Error: {0}", ex)); - response.ReasonPhrase = "Could Not Rebuild"; - return response; - } - //static listener so it's not GC'd - private static void Indexer_IndexOperationComplete(object sender, EventArgs e) + private void Indexer_IndexOperationComplete(object sender, EventArgs e) { var indexer = (LuceneIndexer) sender; //ensure it's not listening anymore indexer.IndexOperationComplete -= Indexer_IndexOperationComplete; - LogHelper.Info(string.Format("Rebuilding index '{0}' done, {1} items committed (can differ from the number of items in the index)", indexer.Name, indexer.CommitCount)); + _logger.Info($"Rebuilding index '{indexer.Name}' done, {indexer.CommitCount} items committed (can differ from the number of items in the index)"); var cacheKey = "temp_indexing_op_" + indexer.Name; - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(cacheKey); + _runtimeCacheProvider.ClearCacheItem(cacheKey); } /// @@ -244,57 +215,39 @@ namespace Umbraco.Web.WebServices /// /// /// - /// This is kind of rudimentary since there's no way we can know that the index has rebuilt, we + /// This is kind of rudimentary since there's no way we can know that the index has rebuilt, we /// have a listener for the index op complete so we'll just check if that key is no longer there in the runtime cache /// public ExamineIndexerModel PostCheckRebuildIndex(string indexerName) { - LuceneIndexer indexer; - var msg = ValidateLuceneIndexer(indexerName, out indexer); + var msg = ValidateLuceneIndexer(indexerName, out LuceneIndexer indexer); if (msg.IsSuccessStatusCode) { var cacheKey = "temp_indexing_op_" + indexerName; - var found = ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem(cacheKey); + var found = ApplicationCache.RuntimeCache.GetCacheItem(cacheKey); //if its still there then it's not done return found != null - ? null - : CreateModel(indexer); - } - throw new HttpResponseException(msg); - } - - /// - /// Checks if the index is optimized - /// - /// - /// - public ExamineIndexerModel PostCheckOptimizeIndex(string indexerName) - { - LuceneIndexer indexer; - var msg = ValidateLuceneIndexer(indexerName, out indexer); - if (msg.IsSuccessStatusCode) - { - var isOptimized = indexer.IsIndexOptimized(); - return !isOptimized ? null - : CreateModel(indexer); + : CreateModel(new KeyValuePair(indexerName, indexer)); } throw new HttpResponseException(msg); } - private ExamineIndexerModel CreateModel(BaseIndexProvider indexer) + private ExamineIndexerModel CreateModel(KeyValuePair indexerKeyVal) { + var indexer = indexerKeyVal.Value; + var indexName = indexerKeyVal.Key; var indexerModel = new ExamineIndexerModel() { - IndexCriteria = indexer.IndexerData, - Name = indexer.Name + FieldDefinitions = indexer.FieldDefinitionCollection, + Name = indexName }; - + var props = TypeHelper.CachedDiscoverableProperties(indexer.GetType(), mustWrite: false) //ignore these properties - .Where(x => new[] {"IndexerData", "Description", "WorkingFolder"}.InvariantContains(x.Name) == false) - .OrderBy(x => x.Name); - + .Where(x => new[] {"IndexerData", "Description", "WorkingFolder"}.InvariantContains(x.Name) == false) + .OrderBy(x => x.Name); + foreach (var p in props) { var val = p.GetValue(indexer, null); @@ -302,22 +255,20 @@ namespace Umbraco.Web.WebServices { // Do not warn for new new attribute that is optional if(string.Equals(p.Name, "DirectoryFactory", StringComparison.InvariantCultureIgnoreCase) == false) - LogHelper.Warn("Property value was null when setting up property on indexer: " + indexer.Name + " property: " + p.Name); + Logger.Warn("Property value was null when setting up property on indexer: " + indexName + " property: " + p.Name); val = string.Empty; } indexerModel.ProviderProperties.Add(p.Name, val.ToString()); } - var luceneIndexer = indexer as LuceneIndexer; - if (luceneIndexer != null) + if (indexer is LuceneIndexer luceneIndexer) { indexerModel.IsLuceneIndex = true; if (luceneIndexer.IndexExists()) { - Exception indexError; - indexerModel.IsHealthy = luceneIndexer.IsHealthy(out indexError); + indexerModel.IsHealthy = luceneIndexer.IsHealthy(out var indexError); if (indexerModel.IsHealthy == false) { @@ -328,15 +279,11 @@ namespace Umbraco.Web.WebServices indexerModel.DocumentCount = luceneIndexer.GetIndexDocumentCount(); indexerModel.FieldCount = luceneIndexer.GetIndexFieldCount(); - indexerModel.IsOptimized = luceneIndexer.IsIndexOptimized(); - indexerModel.DeletionCount = luceneIndexer.GetDeletedDocumentsCount(); } else { indexerModel.DocumentCount = 0; indexerModel.FieldCount = 0; - indexerModel.IsOptimized = true; - indexerModel.DeletionCount = 0; } } return indexerModel; @@ -344,48 +291,46 @@ namespace Umbraco.Web.WebServices private HttpResponseMessage ValidateLuceneSearcher(string searcherName, out LuceneSearcher searcher) { - if (ExamineManager.Instance.SearchProviderCollection.Cast().Any(x => x.Name == searcherName)) + foreach (var indexer in _examineManager.IndexProviders) { - searcher = ExamineManager.Instance.SearchProviderCollection[searcherName] as LuceneSearcher; - if (searcher == null) - { - var response = Request.CreateResponse(HttpStatusCode.BadRequest); - response.Content = new StringContent(string.Format("The searcher {0} is not of type {1}", searcherName, typeof(LuceneSearcher))); - response.ReasonPhrase = "Wrong Searcher Type"; - return response; - } - //return Ok! - return Request.CreateResponse(HttpStatusCode.OK); + var s = indexer.Value.GetSearcher(); + var sName = (s as BaseLuceneSearcher)?.Name ?? string.Concat(indexer.Key, "Searcher"); + if (sName != searcherName) continue; + searcher = s as LuceneSearcher; + + //Found it, return OK + if (searcher != null) return Request.CreateResponse(HttpStatusCode.OK); + + //Return an error since it's not the right type + var response = Request.CreateResponse(HttpStatusCode.BadRequest); + response.Content = new StringContent($"The searcher {searcherName} is not of type {typeof(LuceneSearcher)}"); + response.ReasonPhrase = "Wrong Searcher Type"; + return response; } searcher = null; var response1 = Request.CreateResponse(HttpStatusCode.BadRequest); - response1.Content = new StringContent(string.Format("No searcher found with name = {0}", searcherName)); + response1.Content = new StringContent($"No searcher found with name = {searcherName}"); response1.ReasonPhrase = "Searcher Not Found"; return response1; } - private HttpResponseMessage ValidateLuceneIndexer(string indexerName, out LuceneIndexer indexer) - { - if (ExamineManager.Instance.IndexProviderCollection.Any(x => x.Name == indexerName)) + private HttpResponseMessage ValidateLuceneIndexer(string indexerName, out T indexer) + where T : class, IIndexer + { + indexer = null; + + if (_examineManager.IndexProviders.ContainsKey(indexerName) + && _examineManager.IndexProviders[indexerName] is T casted) { - indexer = ExamineManager.Instance.IndexProviderCollection[indexerName] as LuceneIndexer; - if (indexer == null) - { - var response1 = Request.CreateResponse(HttpStatusCode.BadRequest); - response1.Content = new StringContent(string.Format("The indexer {0} is not of type {1}", indexerName, typeof(LuceneIndexer))); - response1.ReasonPhrase = "Wrong Indexer Type"; - return response1; - } //return Ok! + indexer = casted; return Request.CreateResponse(HttpStatusCode.OK); } - indexer = null; - var response = Request.CreateResponse(HttpStatusCode.BadRequest); - response.Content = new StringContent(string.Format("No indexer found with name = {0}", indexerName)); + response.Content = new StringContent($"No indexer found with name = {indexerName} of type {typeof(T)}"); response.ReasonPhrase = "Indexer Not Found"; return response; } diff --git a/src/Umbraco.Web/WebServices/FolderBrowserService.cs b/src/Umbraco.Web/WebServices/FolderBrowserService.cs deleted file mode 100644 index 15a6c10880..0000000000 --- a/src/Umbraco.Web/WebServices/FolderBrowserService.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Web.Script.Serialization; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Web.Media.ThumbnailProviders; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic.Tags; -using Umbraco.Web.BaseRest; -using Tag = umbraco.cms.businesslogic.Tags.Tag; - -namespace Umbraco.Web.WebServices -{ - //TODO: Can we convert this to MVC please instead of /base? - [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")] - [RestExtension("FolderBrowserService")] - public class FolderBrowserService - { - [RestExtensionMethod(ReturnXml = false)] - public static string GetChildren(int parentId) - { - var currentUser = GetCurrentUser(); - AuthorizeAccess(parentId, currentUser); - - // Get children and filter - var data = new List(); - var service = ApplicationContext.Current.Services.EntityService; - - var entities = service.GetChildren(parentId, UmbracoObjectTypes.Media); - foreach (UmbracoEntity entity in entities) - { - var uploadFieldProperty = entity.AdditionalData - .Select(x => x.Value as UmbracoEntity.EntityProperty) - .Where(x => x != null) - .FirstOrDefault(x => x.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias); - - //var uploadFieldProperty = entity.UmbracoProperties.FirstOrDefault(x => x.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias); - - var thumbnailUrl = uploadFieldProperty == null ? "" : ThumbnailProvidersResolver.Current.GetThumbnailUrl((string)uploadFieldProperty.Value); - - var item = new - { - Id = entity.Id, - Path = entity.Path, - Name = entity.Name, - Tags = string.Join(",", Tag.GetTags(entity.Id).Select(x => x.TagCaption)), - MediaTypeAlias = entity.ContentTypeAlias, - EditUrl = string.Format("editMedia.aspx?id={0}", entity.Id), - FileUrl = uploadFieldProperty == null - ? "" - : uploadFieldProperty.Value, - ThumbnailUrl = string.IsNullOrEmpty(thumbnailUrl) - ? IOHelper.ResolveUrl(string.Format("{0}/images/thumbnails/{1}", SystemDirectories.Umbraco, entity.ContentTypeThumbnail)) - : thumbnailUrl - }; - - data.Add(item); - - } - - return new JavaScriptSerializer().Serialize(data); - } - - [RestExtensionMethod(ReturnXml = false)] - public static string Delete(string nodeIds) - { - var currentUser = GetCurrentUser(); - - var nodeIdParts = nodeIds.Split(','); - - foreach (var nodeIdPart in nodeIdParts.Where(x => string.IsNullOrEmpty(x) == false)) - { - int nodeId; - if (Int32.TryParse(nodeIdPart, out nodeId) == false) - continue; - - var node = new global::umbraco.cms.businesslogic.media.Media(nodeId); - AuthorizeAccess(node, currentUser); - - node.delete(("," + node.Path + ",").Contains(",-21,")); - } - - return new JavaScriptSerializer().Serialize(new { success = true }); - } - - private static User GetCurrentUser() - { - var currentUser = User.GetCurrent(); - if (currentUser == null) - throw new UnauthorizedAccessException("You must be logged in to use this service"); - - return currentUser; - } - - private static void AuthorizeAccess(global::umbraco.cms.businesslogic.media.Media mediaItem, User currentUser) - { - if (("," + mediaItem.Path + ",").Contains("," + currentUser.StartMediaId + ",") == false) - throw new UnauthorizedAccessException("You do not have access to this Media node"); - } - - private static void AuthorizeAccess(int parentId, User currentUser) - { - var service = ApplicationContext.Current.Services.EntityService; - var parentMedia = service.Get(parentId, UmbracoObjectTypes.Media); - var mediaPath = parentMedia == null ? parentId.ToString(CultureInfo.InvariantCulture) : parentMedia.Path; - - if (("," + mediaPath + ",").Contains("," + currentUser.StartMediaId + ",") == false) - throw new UnauthorizedAccessException("You do not have access to this Media node"); - } - } -} diff --git a/src/Umbraco.Web/WebServices/NuCacheStatusController.cs b/src/Umbraco.Web/WebServices/NuCacheStatusController.cs new file mode 100644 index 0000000000..d1c9c0451e --- /dev/null +++ b/src/Umbraco.Web/WebServices/NuCacheStatusController.cs @@ -0,0 +1,65 @@ +using System; +using System.Web.Http; +using Umbraco.Web.Cache; +using Umbraco.Web.Composing; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.PublishedCache.NuCache; +using Umbraco.Web.WebApi; + +namespace Umbraco.Web.WebServices +{ + public class NuCacheStatusController : UmbracoAuthorizedApiController + { + private readonly IPublishedSnapshotService _publishedSnapshotService; + + public NuCacheStatusController(IPublishedSnapshotService publishedSnapshotService) + { + if (publishedSnapshotService == null) throw new ArgumentNullException(nameof(publishedSnapshotService)); + _publishedSnapshotService = publishedSnapshotService; + } + + private PublishedSnapshotService PublishedSnapshotService + { + get + { + var svc = _publishedSnapshotService as PublishedSnapshotService; + if (svc == null) + throw new NotSupportedException("Not running NuCache."); + return svc; + } + } + + [HttpPost] + public string RebuildDbCache() + { + // fixme - should wrap in a service scope once we have them + var service = PublishedSnapshotService; + service.RebuildContentDbCache(); + service.RebuildMediaDbCache(); + service.RebuildMemberDbCache(); + return service.GetStatus(); + } + + [HttpGet] + public string GetStatus() + { + var service = PublishedSnapshotService; + return service.GetStatus(); + } + + [HttpGet] + public string Collect() + { + var service = PublishedSnapshotService; + GC.Collect(); + service.Collect(); + return service.GetStatus(); + } + + [HttpPost] + public void ReloadCache() + { + Current.DistributedCache.RefreshAllPublishedSnapshot(); + } + } +} diff --git a/src/Umbraco.Web/WebServices/PublishedStatusController.cs b/src/Umbraco.Web/WebServices/PublishedStatusController.cs new file mode 100644 index 0000000000..d5846cce10 --- /dev/null +++ b/src/Umbraco.Web/WebServices/PublishedStatusController.cs @@ -0,0 +1,32 @@ +using System; +using System.Web.Http; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.WebApi; + +namespace Umbraco.Web.WebServices +{ + public class PublishedStatusController : UmbracoAuthorizedApiController + { + private readonly IPublishedSnapshotService _publishedSnapshotService; + + public PublishedStatusController(IPublishedSnapshotService publishedSnapshotService) + { + _publishedSnapshotService = publishedSnapshotService ?? throw new ArgumentNullException(nameof(publishedSnapshotService)); + } + + [HttpGet] + public string GetPublishedStatusUrl() + { + if (_publishedSnapshotService is PublishedCache.XmlPublishedCache.PublishedSnapshotService) + return "views/dashboard/developer/xmldataintegrityreport.html"; + + //if (service is PublishedCache.PublishedNoCache.PublishedSnapshotService) + // return "views/dashboard/developer/nocache.html"; + + if (_publishedSnapshotService is PublishedCache.NuCache.PublishedSnapshotService) + return "views/dashboard/developer/nucache.html"; + + throw new NotSupportedException("Not supported: " + _publishedSnapshotService.GetType().FullName); + } + } +} diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index fe93fb1e06..615b45034e 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -1,31 +1,22 @@ using System; -using System.Linq; -using System.Text; using System.Web.Mvc; -using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; -using Umbraco.Web.Macros; using Umbraco.Web.Mvc; -using umbraco; -using umbraco.cms.businesslogic.macro; using System.Collections.Generic; -using umbraco.cms.helpers; +using umbraco.cms.presentation.Trees; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Template = umbraco.cms.businesslogic.template.Template; +using Umbraco.Core.Services.Implement; namespace Umbraco.Web.WebServices { /// - /// A REST controller used to save files such as templates, partial views, macro files, etc... + /// Represents REST controller used to save files such as templates, partial views, macro files, etc. /// - /// - /// This isn't fully implemented yet but we should migrate all of the logic in the umbraco.presentation.webservices.codeEditorSave - /// over to this controller. - /// + /// This isn't fully implemented yet but we should migrate all of the logic + /// in the umbraco.presentation.webservices.codeEditorSave over to this controller. [ValidateMvcAngularAntiForgeryToken] public class SaveFileController : UmbracoAuthorizedController { @@ -98,7 +89,7 @@ namespace Umbraco.Web.WebServices oldname = oldname.TrimStart(pathPrefix); } - var currentView = oldname.IsNullOrWhiteSpace() + var currentView = oldname.IsNullOrWhiteSpace() ? get(svce, filename) : get(svce, oldname); @@ -108,7 +99,7 @@ namespace Umbraco.Web.WebServices currentView.Path = filename; currentView.Content = contents; - + Attempt attempt; @@ -116,91 +107,98 @@ namespace Umbraco.Web.WebServices { var partialView = currentView as PartialView; if (partialView != null && validate != null && validate(svce, partialView) == false) - return Failed(ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), + return Failed(Services.TextService.Localize("speechBubbles/partialViewErrorText"), Services.TextService.Localize("speechBubbles/partialViewErrorHeader"), new FileSecurityException("File '" + currentView.Path + "' is not a valid partial view file.")); attempt = save(svce, currentView); } catch (Exception e) { - return Failed(ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), e); + return Failed(Services.TextService.Localize("speechBubbles/partialViewErrorText"), Services.TextService.Localize("speechBubbles/partialViewErrorHeader"), e); } if (attempt.Success == false) { - return Failed(ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), + return Failed(Services.TextService.Localize("speechBubbles/partialViewErrorText"), Services.TextService.Localize("speechBubbles/partialViewErrorHeader"), attempt.Exception); } - return Success(ui.Text("speechBubbles", "partialViewSavedText"), ui.Text("speechBubbles", "partialViewSavedHeader"), new { name = currentView.Name, path = currentView.Path }); + return Success(Services.TextService.Localize("speechBubbles/partialViewSavedText"), Services.TextService.Localize("speechBubbles/partialViewSavedHeader"), new { name = currentView.Name, path = currentView.Path }); } - /// - /// Saves a template - /// - /// - /// - /// - /// - /// - /// - [HttpPost] - public JsonResult SaveTemplate(string templateName, string templateAlias, string templateContents, int templateId, int masterTemplateId) - { - //TODO: Change this over to use the new API - Also this will be migrated to a TemplateEditor or ViewEditor when it's all moved to angular + // fixme - remove code + ///// + ///// Saves a template + ///// + ///// + ///// + ///// + ///// + ///// + ///// + //[HttpPost] + //public JsonResult SaveTemplate(string templateName, string templateAlias, string templateContents, int templateId, int masterTemplateId) + //{ + // //TODO: Change this over to use the new API - Also this will be migrated to a TemplateEditor or ViewEditor when it's all moved to angular - Template t; - bool pathChanged = false; - try - { - t = new Template(templateId) - { - Text = templateName.CleanForXss('[', ']', '(', ')', ':'), - Alias = templateAlias.CleanForXss('[', ']', '(', ')', ':'), - Design = templateContents - }; + // Template t; + // bool pathChanged = false; + // try + // { + // t = new Template(templateId) + // { + // Text = templateName.CleanForXss('[', ']', '(', ')', ':'), + // Alias = templateAlias.CleanForXss('[', ']', '(', ')', ':'), + // Design = templateContents + // }; - //check if the master page has changed - we need to normalize both - if it's 0 or -1, then make it 0... this is easy - // to do with Math.Max - if (Math.Max(t.MasterTemplate, 0) != Math.Max(masterTemplateId, 0)) - { - t.MasterTemplate = Math.Max(masterTemplateId, 0); - pathChanged = true; - } - } - catch (ArgumentException ex) - { - //the template does not exist - return Failed("Template does not exist", ui.Text("speechBubbles", "templateErrorHeader"), ex); - } + // //check if the master page has changed - we need to normalize both - if it's 0 or -1, then make it 0... this is easy + // // to do with Math.Max + // if (Math.Max(t.MasterTemplate, 0) != Math.Max(masterTemplateId, 0)) + // { + // t.MasterTemplate = Math.Max(masterTemplateId, 0); + // pathChanged = true; + // } + // } + // catch (ArgumentException ex) + // { + // //the template does not exist + // return Failed("Template does not exist", Services.TextService.Localize("speechBubbles/templateErrorHeader"), ex); + // } - try - { - t.Save(); + // try + // { + // t.Save(); - //ensure the correct path is synced as the parent might have been changed - // http://issues.umbraco.org/issue/U4-2300 - if (pathChanged) - { - //need to re-look it up - t = new Template(templateId); - } - var syncPath = "-1,init," + t.Path.Replace("-1,", ""); + // //ensure the correct path is synced as the parent might have been changed + // // http://issues.umbraco.org/issue/U4-2300 + // if (pathChanged) + // { + // //need to re-look it up + // t = new Template(templateId); + // } + // var syncPath = "-1,init," + t.Path.Replace("-1,", ""); - return Success(ui.Text("speechBubbles", "templateSavedText"), ui.Text("speechBubbles", "templateSavedHeader"), - new - { - path = syncPath, - contents = t.Design, - alias = t.Alias // might have been updated! - }); - } - catch (Exception ex) - { - return Failed(ui.Text("speechBubbles", "templateErrorText"), ui.Text("speechBubbles", "templateErrorHeader"), ex); - } - } + // return Success(Services.TextService.Localize("speechBubbles/templateSavedText"), Services.TextService.Localize("speechBubbles/templateSavedHeader"), + // new + // { + // path = syncPath, + // contents = t.Design, + // alias = t.Alias // might have been updated! + // }); + // } + // catch (Exception ex) + // { + // return Failed(Services.TextService.Localize("speechBubbles/templateErrorText"), Services.TextService.Localize("speechBubbles/templateErrorHeader"), ex); + // } + //} + + //[HttpPost] + //public JsonResult SaveXslt(string fileName, string oldName, string fileContents, bool ignoreDebugging) + //{ + + //} [HttpPost] public JsonResult SaveScript(string filename, string oldName, string contents) @@ -221,20 +219,20 @@ namespace Umbraco.Web.WebServices try { if (svce.ValidateScript(script) == false) - return Failed(ui.Text("speechBubbles", "scriptErrorText"), ui.Text("speechBubbles", "scriptErrorHeader"), + return Failed(Services.TextService.Localize("speechBubbles/scriptErrorText"), Services.TextService.Localize("speechBubbles/scriptErrorHeader"), new FileSecurityException("File '" + filename + "' is not a valid script file.")); - + svce.SaveScript(script); } catch (Exception e) { - return Failed(ui.Text("speechBubbles", "scriptErrorText"), ui.Text("speechBubbles", "scriptErrorHeader"), e); + return Failed(Services.TextService.Localize("speechBubbles/scriptErrorText"), Services.TextService.Localize("speechBubbles/scriptErrorHeader"), e); } - return Success(ui.Text("speechBubbles", "scriptSavedText"), ui.Text("speechBubbles", "scriptSavedHeader"), + return Success(Services.TextService.Localize("speechBubbles/scriptSavedText"), Services.TextService.Localize("speechBubbles/scriptSavedHeader"), new { - path = DeepLink.GetTreePathFromFilePath(script.Path), + path = BaseTree.GetTreePathFromFilePath(script.Path), name = script.Path, url = script.VirtualPath, contents = script.Content @@ -261,20 +259,20 @@ namespace Umbraco.Web.WebServices try { if (svce.ValidateStylesheet(stylesheet) == false) - return Failed(ui.Text("speechBubbles", "cssErrorText"), ui.Text("speechBubbles", "cssErrorHeader"), + return Failed(Services.TextService.Localize("speechBubbles/cssErrorText"), Services.TextService.Localize("speechBubbles/cssErrorHeader"), new FileSecurityException("File '" + filename + "' is not a valid stylesheet file.")); svce.SaveStylesheet(stylesheet); } catch (Exception e) { - return Failed(ui.Text("speechBubbles", "cssErrorText"), ui.Text("speechBubbles", "cssErrorHeader"), e); + return Failed(Services.TextService.Localize("speechBubbles/cssErrorText"), Services.TextService.Localize("speechBubbles/cssErrorHeader"), e); } - return Success(ui.Text("speechBubbles", "cssSavedText"), ui.Text("speechBubbles", "cssSavedHeader"), + return Success(Services.TextService.Localize("speechBubbles/cssSavedText"), Services.TextService.Localize("speechBubbles/cssSavedHeader"), new { - path = DeepLink.GetTreePathFromFilePath(stylesheet.Path), + path = BaseTree.GetTreePathFromFilePath(stylesheet.Path), name = stylesheet.Path, url = stylesheet.VirtualPath, contents = stylesheet.Content @@ -308,7 +306,7 @@ namespace Umbraco.Web.WebServices private JsonResult Failed(string message, string header, Exception exception = null) { if (exception != null) - LogHelper.Error("An error occurred saving a file. " + message, exception); + Logger.Error("An error occurred saving a file. " + message, exception); return Json(new { success = false, diff --git a/src/Umbraco.Web/WebServices/TagsController.cs b/src/Umbraco.Web/WebServices/TagsController.cs index e314e0a609..6241cfacf0 100644 --- a/src/Umbraco.Web/WebServices/TagsController.cs +++ b/src/Umbraco.Web/WebServices/TagsController.cs @@ -1,10 +1,4 @@ using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using AutoMapper; -using Examine; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Web.Models; using Umbraco.Web.WebApi; diff --git a/src/Umbraco.Web/WebServices/UmbracoAuthorizedHttpHandler.cs b/src/Umbraco.Web/WebServices/UmbracoAuthorizedHttpHandler.cs index c4a9b8d9d2..248eea3d93 100644 --- a/src/Umbraco.Web/WebServices/UmbracoAuthorizedHttpHandler.cs +++ b/src/Umbraco.Web/WebServices/UmbracoAuthorizedHttpHandler.cs @@ -1,25 +1,20 @@ using System; -using System.Linq; -using System.Web; -using System.Web.Security; +using System.Security; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Web.Security; -using umbraco; -using umbraco.BasePages; -using umbraco.BusinessLogic; -using umbraco.businesslogic.Exceptions; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; namespace Umbraco.Web.WebServices { public abstract class UmbracoAuthorizedHttpHandler : UmbracoHttpHandler { protected UmbracoAuthorizedHttpHandler() - : base() - { - } + { } - protected UmbracoAuthorizedHttpHandler(UmbracoContext umbracoContext) - : base(umbracoContext) + protected UmbracoAuthorizedHttpHandler(UmbracoContext umbracoContext, ServiceContext services, CacheHelper appCache) + : base(umbracoContext, services, appCache) { } @@ -62,9 +57,9 @@ namespace Umbraco.Web.WebServices { return true; } - var hasAccess = UserHasAppAccess(app, UmbracoUser); + var hasAccess = UserHasAppAccess(app, Security.CurrentUser); if (!hasAccess && throwExceptions) - throw new UserAuthorizationException("The user does not have access to the required application"); + throw new SecurityException("The user does not have access to the required application"); return hasAccess; } @@ -74,7 +69,7 @@ namespace Umbraco.Web.WebServices /// /// /// - protected bool UserHasAppAccess(string app, User user) + protected bool UserHasAppAccess(string app, IUser user) { return Security.UserHasSectionAccess(app, user); } @@ -101,22 +96,6 @@ namespace Umbraco.Web.WebServices return result == ValidateRequestAttempt.Success; } - /// - /// Returns the current user - /// - [Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object")] - protected User UmbracoUser - { - get - { - if (!_hasValidated) - { - Security.ValidateCurrentUser(); - _hasValidated = true; - } - return new User(Security.CurrentUser); - } - } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebServices/UmbracoAuthorizedWebService.cs b/src/Umbraco.Web/WebServices/UmbracoAuthorizedWebService.cs index abc216d943..a48d3ef421 100644 --- a/src/Umbraco.Web/WebServices/UmbracoAuthorizedWebService.cs +++ b/src/Umbraco.Web/WebServices/UmbracoAuthorizedWebService.cs @@ -1,15 +1,10 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web; -using System.Web.Security; -using Umbraco.Core.Configuration; +using System.Security; using Umbraco.Web.Security; -using umbraco.BasePages; -using umbraco.BusinessLogic; using Umbraco.Core; -using umbraco.businesslogic.Exceptions; +using Umbraco.Core.Cache; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; namespace Umbraco.Web.WebServices { @@ -18,16 +13,6 @@ namespace Umbraco.Web.WebServices /// public abstract class UmbracoAuthorizedWebService : UmbracoWebService { - protected UmbracoAuthorizedWebService() - : base() - { - } - - protected UmbracoAuthorizedWebService(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - private bool _hasValidated = false; /// @@ -48,7 +33,7 @@ namespace Umbraco.Web.WebServices /// protected bool ValidateCredentials(string username, string password) { - return UmbracoContext.Security.ValidateBackOfficeCredentials(username, password); + return UmbracoContext.Security.ValidateBackOfficeCredentials(username, password); } /// @@ -61,15 +46,15 @@ namespace Umbraco.Web.WebServices { //ensure we have a valid user first! if (!AuthorizeRequest(throwExceptions)) return false; - + //if it is empty, don't validate if (app.IsNullOrWhiteSpace()) { return true; } - var hasAccess = UserHasAppAccess(app, UmbracoUser); + var hasAccess = UserHasAppAccess(app, Security.CurrentUser); if (!hasAccess && throwExceptions) - throw new UserAuthorizationException("The user does not have access to the required application"); + throw new SecurityException("The user does not have access to the required application"); return hasAccess; } @@ -79,7 +64,7 @@ namespace Umbraco.Web.WebServices /// /// /// - protected bool UserHasAppAccess(string app, User user) + protected bool UserHasAppAccess(string app, IUser user) { return Security.UserHasSectionAccess(app, user); } @@ -106,22 +91,5 @@ namespace Umbraco.Web.WebServices return result == ValidateRequestAttempt.Success; } - /// - /// Returns the current user - /// - [Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object")] - protected User UmbracoUser - { - get - { - if (!_hasValidated) - { - Security.ValidateCurrentUser(); - _hasValidated = true; - } - return new User(Security.CurrentUser); - } - } - } } diff --git a/src/Umbraco.Web/WebServices/UmbracoHttpHandler.cs b/src/Umbraco.Web/WebServices/UmbracoHttpHandler.cs index d315f0d20d..200f482cac 100644 --- a/src/Umbraco.Web/WebServices/UmbracoHttpHandler.cs +++ b/src/Umbraco.Web/WebServices/UmbracoHttpHandler.cs @@ -3,99 +3,73 @@ using System.Web; using System.Web.Mvc; using System.Web.Routing; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Security; namespace Umbraco.Web.WebServices { public abstract class UmbracoHttpHandler : IHttpHandler { - public abstract void ProcessRequest(HttpContext context); - public abstract bool IsReusable { get; } - - protected UmbracoHttpHandler() - : this(UmbracoContext.Current) - { - - } - - protected UmbracoHttpHandler(UmbracoContext umbracoContext) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - UmbracoContext = umbracoContext; - Umbraco = new UmbracoHelper(umbracoContext); - } - - /// - /// Returns the current ApplicationContext - /// - public ApplicationContext ApplicationContext - { - get { return UmbracoContext.Application; } - } - - /// - /// Returns an ILogger - /// - public ILogger Logger - { - get { return ProfilingLogger.Logger; } - } - - /// - /// Returns a ProfilingLogger - /// - public ProfilingLogger ProfilingLogger - { - get { return UmbracoContext.Application.ProfilingLogger; } - } - - /// - /// Returns the current UmbracoContext - /// - public UmbracoContext UmbracoContext { get; private set; } - - /// - /// Returns an UmbracoHelper object - /// - public UmbracoHelper Umbraco { get; private set; } - private UrlHelper _url; - /// - /// Returns a UrlHelper - /// - /// - /// This URL helper is created without any route data and an empty request context - /// - public UrlHelper Url + protected UmbracoHttpHandler() + : this(Current.UmbracoContext, Current.Services, Current.ApplicationCache) + { } + + protected UmbracoHttpHandler(UmbracoContext umbracoContext, ServiceContext services, CacheHelper appCache) { - get { return _url ?? (_url = new UrlHelper(HttpContext.Current.Request.RequestContext)); } + if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext)); + UmbracoContext = umbracoContext; + Umbraco = new UmbracoHelper(umbracoContext, services, appCache); + + // fixme inject somehow + Logger = Current.Logger; + ProfilingLogger = Current.ProfilingLogger; + Services = Current.Services; } - /// - /// Returns a ServiceContext - /// - public ServiceContext Services - { - get { return ApplicationContext.Services; } - } + public abstract void ProcessRequest(HttpContext context); + + public abstract bool IsReusable { get; } /// - /// Returns a DatabaseContext + /// Gets the logger. /// - public DatabaseContext DatabaseContext - { - get { return ApplicationContext.DatabaseContext; } - } + public ILogger Logger { get; } /// - /// Returns a WebSecurity instance + /// Gets the ProfilingLogger. /// - public WebSecurity Security - { - get { return UmbracoContext.Security; } - } + public ProfilingLogger ProfilingLogger { get; } + + /// + /// Gets the Umbraco context. + /// + public UmbracoContext UmbracoContext { get; } + + /// + /// Gets the Umbraco helper. + /// + public UmbracoHelper Umbraco { get; } + + /// + /// Gets the services context. + /// + public ServiceContext Services { get; } + + /// + /// Gets the web security helper. + /// + public WebSecurity Security => UmbracoContext.Security; + + /// + /// Gets the Url helper. + /// + /// This URL helper is created without any route data and an empty request context. + public UrlHelper Url => _url ?? (_url = new UrlHelper(HttpContext.Current.Request.RequestContext)); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebServices/UmbracoWebService.cs b/src/Umbraco.Web/WebServices/UmbracoWebService.cs index 55b2d54295..4eb7293f80 100644 --- a/src/Umbraco.Web/WebServices/UmbracoWebService.cs +++ b/src/Umbraco.Web/WebServices/UmbracoWebService.cs @@ -4,8 +4,12 @@ using System.Web.Mvc; using System.Web.Routing; using System.Web.Services; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Security; namespace Umbraco.Web.WebServices @@ -15,88 +19,58 @@ namespace Umbraco.Web.WebServices /// public abstract class UmbracoWebService : WebService { - protected UmbracoWebService() - : this(UmbracoContext.Current) - { - - } - - protected UmbracoWebService(UmbracoContext umbracoContext) - { - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - UmbracoContext = umbracoContext; - Umbraco = new UmbracoHelper(umbracoContext); - } - - /// - /// Returns the current ApplicationContext - /// - public ApplicationContext ApplicationContext - { - get { return UmbracoContext.Application; } - } - - /// - /// Returns an ILogger - /// - public ILogger Logger - { - get { return ProfilingLogger.Logger; } - } - - /// - /// Returns a ProfilingLogger - /// - public ProfilingLogger ProfilingLogger - { - get { return UmbracoContext.Application.ProfilingLogger; } - } - - /// - /// Returns the current UmbracoContext - /// - public UmbracoContext UmbracoContext { get; private set; } - - /// - /// Returns an UmbracoHelper object - /// - public UmbracoHelper Umbraco { get; private set; } - private UrlHelper _url; - /// - /// Returns a UrlHelper - /// - /// - /// This URL helper is created without any route data and an empty request context - /// - public UrlHelper Url + protected UmbracoWebService() { - get { return _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); } + UmbracoContext = Current.UmbracoContext; + Umbraco = new UmbracoHelper(UmbracoContext, Current.Services, Current.ApplicationCache); + + Logger = Current.Logger; + ProfilingLogger = Current.ProfilingLogger; + Services = Current.Services; + GlobalSettings = UmbracoConfig.For.GlobalSettings(); } /// - /// Returns a ServiceContext + /// Gets the logger. /// - public ServiceContext Services - { - get { return ApplicationContext.Services; } - } + public ILogger Logger { get; } /// - /// Returns a DatabaseContext + /// Gets the ProfilingLogger. /// - public DatabaseContext DatabaseContext - { - get { return ApplicationContext.DatabaseContext; } - } + public ProfilingLogger ProfilingLogger { get; } /// - /// Returns a WebSecurity instance + /// Gets the Umbraco context. /// - public WebSecurity Security - { - get { return UmbracoContext.Security; } - } + public UmbracoContext UmbracoContext { get; } + + /// + /// Gets the Umbraco helper. + /// + public UmbracoHelper Umbraco { get; } + + /// + /// Gets the services context. + /// + public ServiceContext Services { get; } + + /// + /// Gets the global settings. + /// + public IGlobalSettings GlobalSettings { get; } + + /// + /// Gets the web security helper. + /// + public WebSecurity Security => UmbracoContext.Security; + + /// + /// Gets the Url helper. + /// + /// This URL helper is created without any route data and an empty request context. + public UrlHelper Url => _url ?? (_url = new UrlHelper(Context.Request.RequestContext)); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs b/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs index 3d752a287b..5c16c83e57 100644 --- a/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs +++ b/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs @@ -1,8 +1,7 @@ -using System; +using System; using System.Web.Http; -using Umbraco.Core; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; @@ -11,75 +10,52 @@ namespace Umbraco.Web.WebServices [ValidateAngularAntiForgeryToken] public class XmlDataIntegrityController : UmbracoAuthorizedApiController { + private readonly PublishedSnapshotService _publishedSnapshotService; + + public XmlDataIntegrityController(IPublishedSnapshotService publishedSnapshotService) + { + if (publishedSnapshotService == null) throw new ArgumentNullException(nameof(publishedSnapshotService)); + _publishedSnapshotService = publishedSnapshotService as PublishedSnapshotService; + if (_publishedSnapshotService == null) throw new NotSupportedException("Unsupported IPublishedSnapshotService, only the Xml one is supported."); + } + [HttpPost] public bool FixContentXmlTable() { - Services.ContentService.RebuildXmlStructures(); - return CheckContentXmlTable(); + _publishedSnapshotService.RebuildContentAndPreviewXml(); + return _publishedSnapshotService.VerifyContentAndPreviewXml(); } [HttpPost] public bool FixMediaXmlTable() { - Services.MediaService.RebuildXmlStructures(); - return CheckMediaXmlTable(); + _publishedSnapshotService.RebuildMediaXml(); + return _publishedSnapshotService.VerifyMediaXml(); } [HttpPost] public bool FixMembersXmlTable() { - Services.MemberService.RebuildXmlStructures(); - return CheckMembersXmlTable(); + _publishedSnapshotService.RebuildMemberXml(); + return _publishedSnapshotService.VerifyMemberXml(); } [HttpGet] public bool CheckContentXmlTable() { - var totalPublished = Services.ContentService.CountPublished(); - - var subQuery = new Sql() - .Select("DISTINCT cmsContentXml.nodeId") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId); - - var totalXml = ApplicationContext.DatabaseContext.Database.ExecuteScalar("SELECT COUNT(*) FROM (" + subQuery.SQL + ") as tmp"); - - return totalXml == totalPublished; + return _publishedSnapshotService.VerifyContentAndPreviewXml(); } - + [HttpGet] public bool CheckMediaXmlTable() { - var total = Services.MediaService.CountNotTrashed(); - var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); - var subQuery = new Sql() - .Select("Count(*)") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == mediaObjectType); - var totalXml = ApplicationContext.DatabaseContext.Database.ExecuteScalar(subQuery); - - return totalXml == total; + return _publishedSnapshotService.VerifyMediaXml(); } [HttpGet] public bool CheckMembersXmlTable() { - var total = Services.MemberService.Count(); - var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); - var subQuery = new Sql() - .Select("Count(*)") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(dto => dto.NodeObjectType == memberObjectType); - var totalXml = ApplicationContext.DatabaseContext.Database.ExecuteScalar(subQuery); - - return totalXml == total; + return _publishedSnapshotService.VerifyMemberXml(); } - - } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/_Legacy/Actions/Action.cs b/src/Umbraco.Web/_Legacy/Actions/Action.cs new file mode 100644 index 0000000000..99022eb16d --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/Action.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Composing; +using Umbraco.Core.Services; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// Actions and Actionhandlers are a key concept to umbraco and a developer whom wish to apply + /// businessrules whenever data is changed within umbraco, by implementing the IActionHandler + /// interface it's possible to invoke methods (foreign to umbraco) - this can be used whenever + /// there is a specific rule which needs to be applied to content. + /// + /// The Action class itself has responsibility for registering actions and actionhandlers, + /// and contains methods which will be invoked whenever a change is made to ex. a document, media or member + /// + /// An action/actionhandler will automatically be registered, using reflection + /// which is enabling thirdparty developers to extend the core functionality of + /// umbraco without changing the codebase. + /// + [Obsolete("Actions and ActionHandlers are obsolete and should no longer be used")] + public class Action + { + private static readonly Dictionary ActionJs = new Dictionary(); + + /// + /// Jacascript for the contextmenu + /// Suggestion: this method should be moved to the presentation layer. + /// + /// + /// String representation + public string ReturnJavascript(string language) + { + return findActions(language); + } + + /// + /// Returns a list of JavaScript file paths. + /// + /// + public static List GetJavaScriptFileReferences() + { + return Current.Actions + .Where(x => !string.IsNullOrWhiteSpace(x.JsSource)) + .Select(x => x.JsSource).ToList(); + //return ActionJsReference; + } + + /// + /// Javascript menuitems - tree contextmenu + /// Umbraco console + /// + /// Suggestion: this method should be moved to the presentation layer. + /// + /// + /// + private static string findActions(string language) + { + if (!ActionJs.ContainsKey(language)) + { + string _actionJsList = ""; + + foreach (IAction action in Current.Actions) + { + // Adding try/catch so this rutine doesn't fail if one of the actions fail + // Add to language JsList + try + { + // NH: Add support for css sprites + string icon = action.Icon; + if (!string.IsNullOrEmpty(icon) && icon.StartsWith(".")) + icon = icon.Substring(1, icon.Length - 1); + else + icon = "images/" + icon; + + _actionJsList += string.Format(",\n\tmenuItem(\"{0}\", \"{1}\", \"{2}\", \"{3}\")", + action.Letter, icon, Current.Services.TextService.Localize("actions/"+ action.Alias, new[] { language }), action.JsFunctionName); + } + catch (Exception ee) + { + Current.Logger.Error("Error registrering action to javascript", ee); + } + } + + if (_actionJsList.Length > 0) + _actionJsList = _actionJsList.Substring(2, _actionJsList.Length - 2); + + _actionJsList = "\nvar menuMethods = new Array(\n" + _actionJsList + "\n)\n"; + ActionJs.Add(language, _actionJsList); + } + + return ActionJs[language]; + + } + + internal static List FromEntityPermission(EntityPermission entityPermission) + { + List list = new List(); + foreach (var c in entityPermission.AssignedPermissions.Where(x => x.Length == 1).Select(x => x.ToCharArray()[0])) + { + IAction action = Current.Actions.ToList().Find( + delegate (IAction a) + { + return a.Letter == c; + } + ); + if (action != null) + list.Add(action); + } + return list; + } + + /// + /// Returns a list of IActions that are permission assignable + /// + /// + public static List GetPermissionAssignable() + { + return Current.Actions.ToList().FindAll(x => x.CanBePermissionAssigned); + } + + /// + /// Check if the current IAction is using legacy javascript methods + /// + /// + /// false if the Iaction is incompatible with 4.5 + public static bool ValidateActionJs(IAction action) + { + return !action.JsFunctionName.Contains("+"); + } + + /// + /// Method to convert the old modal calls to the new ones + /// + /// + /// + public static string ConvertLegacyJs(string javascript) + { + MatchCollection tags = + Regex.Matches(javascript, "openModal[^;]*;", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + foreach (Match tag in tags) + { + string[] function = tag.Value.Split(','); + if (function.Length > 0) + { + string newFunction = "UmbClientMgr.openModalWindow" + function[0].Substring(9).Replace("parent.nodeID", "UmbClientMgr.mainTree().getActionNode().nodeId").Replace("nodeID", "UmbClientMgr.mainTree().getActionNode().nodeId").Replace("parent.returnRandom()", "'" + Guid.NewGuid().ToString() + "'"); + newFunction += ", " + function[1]; + newFunction += ", true"; + newFunction += ", " + function[2]; + newFunction += ", " + function[3]; + javascript = javascript.Replace(tag.Value, newFunction); + } + } + + return javascript; + } + } + + /// + /// This class is used to manipulate IActions that are implemented in a wrong way + /// For instance incompatible trees with 4.0 vs 4.5 + /// + public class PlaceboAction : IAction + { + public char Letter { get; set; } + public bool ShowInNotifier { get; set; } + public bool CanBePermissionAssigned { get; set; } + public string Icon { get; set; } + public string Alias { get; set; } + public string JsFunctionName { get; set; } + public string JsSource { get; set; } + + public PlaceboAction() { } + public PlaceboAction(IAction legacyAction) + { + Letter = legacyAction.Letter; + ShowInNotifier = legacyAction.ShowInNotifier; + CanBePermissionAssigned = legacyAction.CanBePermissionAssigned; + Icon = legacyAction.Icon; + Alias = legacyAction.Alias; + JsFunctionName = legacyAction.JsFunctionName; + JsSource = legacyAction.JsSource; + } + } + +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionAssignDomain.cs b/src/Umbraco.Web/_Legacy/Actions/ActionAssignDomain.cs new file mode 100644 index 0000000000..51abac9998 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionAssignDomain.cs @@ -0,0 +1,82 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when a domain is being assigned to a document + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.AdministrationCategory)] + public class ActionAssignDomain : IAction + { + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionAssignDomain() { } + + public static ActionAssignDomain Instance { get; } = new ActionAssignDomain(); + + #region IAction Members + + public char Letter + { + get + { + return 'I'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionAssignDomain()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "assignDomain"; + } + } + + public string Icon + { + get + { + return "home"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionAudit.cs b/src/Umbraco.Web/_Legacy/Actions/ActionAudit.cs new file mode 100644 index 0000000000..db780f4051 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionAudit.cs @@ -0,0 +1,83 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; +using Umbraco.Web.UI.Pages; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked upon viewing audittrailing on a document + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.AdministrationCategory)] + public class ActionAudit : IAction + { + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionAudit() { } + + public static ActionAudit Instance { get; } = new ActionAudit(); + + #region IAction Members + + public char Letter + { + get + { + return 'Z'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionAudit()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "auditTrail"; + } + } + + public string Icon + { + get + { + return "time"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return true; + } + } + + #endregion + } +} diff --git a/src/umbraco.cms/Actions/ActionBrowse.cs b/src/Umbraco.Web/_Legacy/Actions/ActionBrowse.cs similarity index 88% rename from src/umbraco.cms/Actions/ActionBrowse.cs rename to src/Umbraco.Web/_Legacy/Actions/ActionBrowse.cs index 497f64efbe..1425b27917 100644 --- a/src/umbraco.cms/Actions/ActionBrowse.cs +++ b/src/Umbraco.Web/_Legacy/Actions/ActionBrowse.cs @@ -1,20 +1,17 @@ -using System; -using umbraco.interfaces; -using umbraco.BasePages; -using Umbraco.Core; -using Umbraco.Core.CodeAnnotations; - -namespace umbraco.BusinessLogic.Actions +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions { - /// - /// This action is used as a security constraint that grants a user the ability to view nodes in a tree - /// that has permissions applied to it. - /// - /// - /// This action should not be invoked. It is used as the minimum required permission to view nodes in the content tree. By - /// granting a user this permission, the user is able to see the node in the tree but not edit the document. This may be used by other trees - /// that support permissions in the future. - /// + /// + /// This action is used as a security constraint that grants a user the ability to view nodes in a tree + /// that has permissions applied to it. + /// + /// + /// This action should not be invoked. It is used as the minimum required permission to view nodes in the content tree. By + /// granting a user this permission, the user is able to see the node in the tree but not edit the document. This may be used by other trees + /// that support permissions in the future. + /// [ActionMetadata(Constants.Conventions.PermissionCategories.ContentCategory)] public class ActionBrowse : IAction { @@ -26,7 +23,7 @@ namespace umbraco.BusinessLogic.Actions get { return instance; } } - #region IAction Members + #region IAction Members public char Letter { @@ -63,7 +60,6 @@ namespace umbraco.BusinessLogic.Actions get { return ""; } } - #endregion - - } -} + #endregion + } +} diff --git a/src/umbraco.cms/Actions/ActionChangeDocType.cs b/src/Umbraco.Web/_Legacy/Actions/ActionChangeDocType.cs similarity index 91% rename from src/umbraco.cms/Actions/ActionChangeDocType.cs rename to src/Umbraco.Web/_Legacy/Actions/ActionChangeDocType.cs index ef817aa997..eeb627a4ad 100644 --- a/src/umbraco.cms/Actions/ActionChangeDocType.cs +++ b/src/Umbraco.Web/_Legacy/Actions/ActionChangeDocType.cs @@ -1,98 +1,96 @@ -using System; -using umbraco.interfaces; -using umbraco.BasePages; -using Umbraco.Core; -using Umbraco.Core.CodeAnnotations; - -namespace umbraco.BusinessLogic.Actions -{ - /// - /// This action is invoked when the document type of a piece of content is changed - /// - [ActionMetadata(Constants.Conventions.PermissionCategories.AdministrationCategory)] - public class ActionChangeDocType : IAction - { - //create singleton -#pragma warning disable 612,618 - private static readonly ActionChangeDocType m_instance = new ActionChangeDocType(); -#pragma warning restore 612,618 - - /// - /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. - /// All Umbraco assemblies should use the singleton instantiation (this.Instance) - /// When this applicatio is refactored, this constuctor should be made private. - /// - [Obsolete("Use the singleton instantiation instead of a constructor")] - public ActionChangeDocType() { } - - public static ActionChangeDocType Instance - { - get { return m_instance; } - } - - #region IAction Members - - public char Letter - { - get - { - - return '7'; - } - } - - public string JsFunctionName - { - get - { - return string.Format("{0}.actionChangeDocType()", ClientTools.Scripts.GetAppActions); - } - } - - public string JsSource - { - get - { - - return null; - } - } - - public string Alias - { - get - { - - return "changeDocType"; - } - } - - public string Icon - { - get - { - - return "axis-rotation-2"; - } - } - - public bool ShowInNotifier - { - get - { - - return true; - } - } - public bool CanBePermissionAssigned - { - get - { - - return true; - } - } - #endregion - - } -} +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when the document type of a piece of content is changed + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.AdministrationCategory)] + public class ActionChangeDocType : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionChangeDocType m_instance = new ActionChangeDocType(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionChangeDocType() { } + + public static ActionChangeDocType Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + + return '7'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionChangeDocType()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + + return null; + } + } + + public string Alias + { + get + { + + return "changeDocType"; + } + } + + public string Icon + { + get + { + + return "axis-rotation-2"; + } + } + + public bool ShowInNotifier + { + get + { + + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionCollection.cs b/src/Umbraco.Web/_Legacy/Actions/ActionCollection.cs new file mode 100644 index 0000000000..849fb3b619 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionCollection.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web._Legacy.Actions +{ + public class ActionCollection : BuilderCollectionBase + { + public ActionCollection(IEnumerable items) + : base(items) + { } + + internal T GetAction() + where T : IAction + { + return this.OfType().SingleOrDefault(); + } + + internal IEnumerable GetByLetters(IEnumerable letters) + { + var all = this.ToArray(); + return letters.Select(x => all.FirstOrDefault(y => y.Letter.ToString(CultureInfo.InvariantCulture) == x)) + .WhereNotNull() + .ToArray(); + } + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionCollectionBuilder.cs b/src/Umbraco.Web/_Legacy/Actions/ActionCollectionBuilder.cs new file mode 100644 index 0000000000..39058d6836 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionCollectionBuilder.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using LightInject; +using Umbraco.Core.Composing; + +namespace Umbraco.Web._Legacy.Actions +{ + internal class ActionCollectionBuilder : ICollectionBuilder + { + private static Func> _producer; + + // for tests only - does not register the collection + public ActionCollectionBuilder() + { } + + public ActionCollectionBuilder(IServiceContainer container) + { + var collectionLifetime = CollectionLifetime; + + // register the collection - special lifetime + // the lifetime here is custom ResettablePerContainerLifetime which will manage one + // single instance of the collection (much alike PerContainerLifetime) but can be resetted + // to force a new collection to be created. + // this is needed because of the weird things we do during install, where we'd use the + // infamous DirtyBackdoorToConfiguration to reset the ActionResolver way after Resolution + // had frozen. This has been replaced by the possibility here to set the producer at any + // time - but the builder is internal - and all this will be gone eventually. + container.Register(factory => factory.GetInstance().CreateCollection(), collectionLifetime); + } + + public ActionCollection CreateCollection() + { + var actions = new List(); + foreach (var type in _producer()) + { + var getter = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static); + var instance = getter == null + ? Activator.CreateInstance(type) as IAction + : getter.GetValue(null, null) as IAction; + if (instance == null) continue; + actions.Add(instance); + } + return new ActionCollection(actions); + } + + public void SetProducer(Func> producer) + { + _producer = producer; + CollectionLifetime.Reset(); + } + + private ResettablePerContainerLifetime CollectionLifetime { get; } = new ResettablePerContainerLifetime(); + + private class ResettablePerContainerLifetime : ILifetime + { + private object _instance; + + public object GetInstance(Func createInstance, Scope scope) + { + // not dealing with disposable instances, actions are not disposable + return _instance ?? (_instance = createInstance()); + } + + public void Reset() + { + _instance = null; + } + } + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionCopy.cs b/src/Umbraco.Web/_Legacy/Actions/ActionCopy.cs new file mode 100644 index 0000000000..a86b872d5f --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionCopy.cs @@ -0,0 +1,96 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when copying a document, media, member + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.StructureCategory)] + public class ActionCopy : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionCopy m_instance = new ActionCopy(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionCopy() { } + + public static ActionCopy Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + + return 'O'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionCopy()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + + return null; + } + } + + public string Alias + { + get + { + + return "copy"; + } + } + + public string Icon + { + get + { + + return "documents"; + } + } + + public bool ShowInNotifier + { + get + { + + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + + return true; + } + } + #endregion + } +} diff --git a/src/umbraco.cms/Actions/ActionCreateBlueprintFromContent.cs b/src/Umbraco.Web/_Legacy/Actions/ActionCreateBlueprintFromContent.cs similarity index 82% rename from src/umbraco.cms/Actions/ActionCreateBlueprintFromContent.cs rename to src/Umbraco.Web/_Legacy/Actions/ActionCreateBlueprintFromContent.cs index b905f96d84..e00de39aea 100644 --- a/src/umbraco.cms/Actions/ActionCreateBlueprintFromContent.cs +++ b/src/Umbraco.Web/_Legacy/Actions/ActionCreateBlueprintFromContent.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using umbraco.interfaces; -using Umbraco.Core; +using Umbraco.Core; using Umbraco.Core.CodeAnnotations; -namespace umbraco.businesslogic.Actions +namespace Umbraco.Web._Legacy.Actions { [ActionMetadata(Constants.Conventions.PermissionCategories.ContentCategory)] public class ActionCreateBlueprintFromContent : IAction @@ -34,6 +28,5 @@ namespace umbraco.businesslogic.Actions Icon = "blueprint"; Alias = "createblueprint"; } - } } diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionDelete.cs b/src/Umbraco.Web/_Legacy/Actions/ActionDelete.cs new file mode 100644 index 0000000000..8b7f115c5a --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionDelete.cs @@ -0,0 +1,90 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when a document, media, member is deleted + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.ContentCategory)] + public class ActionDelete : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionDelete m_instance = new ActionDelete(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionDelete() { } + + public static ActionDelete Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return 'D'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionDelete()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "delete"; + } + } + + public string Icon + { + get + { + return "delete"; + } + } + + public bool ShowInNotifier + { + get + { + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionEmptyTranscan.cs b/src/Umbraco.Web/_Legacy/Actions/ActionEmptyTranscan.cs new file mode 100644 index 0000000000..0aa36ee85e --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionEmptyTranscan.cs @@ -0,0 +1,86 @@ +using System; +using Umbraco.Web.UI.Pages; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when the trash can is emptied + /// + public class ActionEmptyTranscan : IAction + { + //create singleton + private static readonly ActionEmptyTranscan InnerInstance = new ActionEmptyTranscan(); + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionEmptyTranscan() { } + + public static ActionEmptyTranscan Instance + { + get { return InnerInstance; } + } + + #region IAction Members + + public char Letter + { + get + { + return 'N'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionEmptyTranscan()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "emptyRecycleBin"; + } + } + + public string Icon + { + get + { + return "trash"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return false; + } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionExport.cs b/src/Umbraco.Web/_Legacy/Actions/ActionExport.cs new file mode 100644 index 0000000000..3dc35073fe --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionExport.cs @@ -0,0 +1,88 @@ +using System; +using Umbraco.Web.UI.Pages; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when exporting a document type + /// + public class ActionExport : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionExport m_instance = new ActionExport(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionExport() { } + + public static ActionExport Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return '9'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionExport()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "exportDocumentType"; + } + } + + public string Icon + { + get + { + return "download-alt"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return false; + } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionImport.cs b/src/Umbraco.Web/_Legacy/Actions/ActionImport.cs new file mode 100644 index 0000000000..e29f5e3d5c --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionImport.cs @@ -0,0 +1,88 @@ +using System; +using Umbraco.Web.UI.Pages; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when importing a document type + /// + public class ActionImport : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionImport m_instance = new ActionImport(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionImport() { } + + public static ActionImport Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return '8'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionImport()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "importDocumentType"; + } + } + + public string Icon + { + get + { + return "page-up"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return false; + } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionMove.cs b/src/Umbraco.Web/_Legacy/Actions/ActionMove.cs new file mode 100644 index 0000000000..cd37199506 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionMove.cs @@ -0,0 +1,96 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked upon creation of a document, media, member + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.StructureCategory)] + public class ActionMove : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionMove m_instance = new ActionMove(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionMove() { } + + public static ActionMove Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + + return 'M'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionMove()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + + return null; + } + } + + public string Alias + { + get + { + + return "move"; + } + } + + public string Icon + { + get + { + + return "enter"; + } + } + + public bool ShowInNotifier + { + get + { + + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionNew.cs b/src/Umbraco.Web/_Legacy/Actions/ActionNew.cs new file mode 100644 index 0000000000..b6c1738bd9 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionNew.cs @@ -0,0 +1,83 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked upon creation of a document + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.ContentCategory)] + public class ActionNew : IAction + { + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionNew() { } + + public static ActionNew Instance { get; } = new ActionNew(); + + #region IAction Members + + public char Letter + { + get + { + return 'C'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionNew()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "create"; + } + } + + public string Icon + { + get + { + return "add"; + } + } + + public bool ShowInNotifier + { + get + { + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + return true; + } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionNewFolder.cs b/src/Umbraco.Web/_Legacy/Actions/ActionNewFolder.cs new file mode 100644 index 0000000000..b4f16b3dd0 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionNewFolder.cs @@ -0,0 +1,89 @@ +using System; +using Umbraco.Web.UI.Pages; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked upon creation of a document + /// + [Obsolete("This class is no longer used and will be removed in future versions")] + public class ActionNewFolder : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionNewFolder m_instance = new ActionNewFolder(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionNewFolder() { } + + public static ActionNewFolder Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return '!'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionNewFolder()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "createFolder"; + } + } + + public string Icon + { + get + { + return "plus-sign-alt"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return false; + } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionNotify.cs b/src/Umbraco.Web/_Legacy/Actions/ActionNotify.cs new file mode 100644 index 0000000000..fadcaa7a22 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionNotify.cs @@ -0,0 +1,89 @@ +using System; +using Umbraco.Web.UI.Pages; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when a notification is sent + /// + public class ActionNotify : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionNotify m_instance = new ActionNotify(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionNotify() { } + + public static ActionNotify Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + + return 'T'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionNotify()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + + return null; + } + } + + public string Alias + { + get + { + return "notify"; + } + } + + public string Icon + { + get + { + return "megaphone"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return false; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionNull.cs b/src/Umbraco.Web/_Legacy/Actions/ActionNull.cs new file mode 100644 index 0000000000..78c5175fb6 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionNull.cs @@ -0,0 +1,56 @@ +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This permission is assigned to a node when there are no permissions assigned to the node. + /// This is used internally to assign no permissions to a node for a user and shouldn't be used in code. + /// + public class ActionNull : IAction + { + //create singleton + private static readonly ActionNull instance = new ActionNull(); + private ActionNull() { } + public static ActionNull Instance + { + get { return instance; } + } + + #region IAction Members + + public char Letter + { + get { return '-'; } + } + + public bool ShowInNotifier + { + get { return false; } + } + + public bool CanBePermissionAssigned + { + get { return false; } + } + + public string Icon + { + get { return string.Empty; } + } + + public string Alias + { + get { return string.Empty; } + } + + public string JsFunctionName + { + get { return string.Empty; } + } + + public string JsSource + { + get { return string.Empty; } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionPackage.cs b/src/Umbraco.Web/_Legacy/Actions/ActionPackage.cs new file mode 100644 index 0000000000..c713b0e212 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionPackage.cs @@ -0,0 +1,88 @@ +using System; +using Umbraco.Web.UI.Pages; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked upon creation of a document, media, member + /// + public class ActionPackage : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionPackage m_instance = new ActionPackage(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionPackage() { } + + public static ActionPackage Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return 'X'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionPackage()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return ""; + } + } + + public string Alias + { + get + { + return "importPackage"; + } + } + + public string Icon + { + get + { + return "gift"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return false; + } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionPackageCreate.cs b/src/Umbraco.Web/_Legacy/Actions/ActionPackageCreate.cs new file mode 100644 index 0000000000..ab036a17b6 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionPackageCreate.cs @@ -0,0 +1,88 @@ +using System; +using Umbraco.Web.UI.Pages; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked upon creation of a document, media, member + /// + public class ActionPackageCreate : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionPackageCreate m_instance = new ActionPackageCreate(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionPackageCreate() { } + + public static ActionPackageCreate Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return 'Y'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionPackageCreate()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "createPackage"; + } + } + + public string Icon + { + get + { + return "gift"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return false; + } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionProtect.cs b/src/Umbraco.Web/_Legacy/Actions/ActionProtect.cs new file mode 100644 index 0000000000..8e2f765eeb --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionProtect.cs @@ -0,0 +1,96 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when a document is protected or unprotected + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.AdministrationCategory)] + public class ActionProtect : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionProtect m_instance = new ActionProtect(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionProtect() { } + + public static ActionProtect Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + + return 'P'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionProtect()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + + return null; + } + } + + public string Alias + { + get + { + + return "protect"; + } + } + + public string Icon + { + get + { + + return "lock"; + } + } + + public bool ShowInNotifier + { + get + { + + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionPublish.cs b/src/Umbraco.Web/_Legacy/Actions/ActionPublish.cs new file mode 100644 index 0000000000..d48e91a504 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionPublish.cs @@ -0,0 +1,90 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when a document is being published + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.ContentCategory)] + public class ActionPublish : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionPublish m_instance = new ActionPublish(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionPublish() { } + + public static ActionPublish Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return 'U'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionPublish()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "publish"; + } + } + + public string Icon + { + get + { + return "globe"; + } + } + + public bool ShowInNotifier + { + get + { + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionQuit.cs b/src/Umbraco.Web/_Legacy/Actions/ActionQuit.cs new file mode 100644 index 0000000000..6e8fe64c4b --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionQuit.cs @@ -0,0 +1,88 @@ +using System; +using Umbraco.Web.UI.Pages; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when a user logs out + /// + [Obsolete("This should not be used and will be removed from the codebase in future versions")] + public class ActionQuit : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionQuit m_instance = new ActionQuit(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionQuit() { } + + public static ActionQuit Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return 'Q'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionQuit()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "logout"; + } + } + + public string Icon + { + get + { + return "signout"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return false; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionRePublish.cs b/src/Umbraco.Web/_Legacy/Actions/ActionRePublish.cs new file mode 100644 index 0000000000..8f3036daad --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionRePublish.cs @@ -0,0 +1,87 @@ +using System; +using Umbraco.Web.UI.Pages; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when all documents are being republished + /// + public class ActionRePublish : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionRePublish m_instance = new ActionRePublish(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionRePublish() { } + + public static ActionRePublish Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return 'B'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionRePublish()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "republish"; + } + } + + public string Icon + { + get + { + return "globe"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return false; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionRefresh.cs b/src/Umbraco.Web/_Legacy/Actions/ActionRefresh.cs new file mode 100644 index 0000000000..178a05698b --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionRefresh.cs @@ -0,0 +1,94 @@ +using System; +using Umbraco.Web.UI.Pages; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when a node reloads its children + /// Concerns only the tree itself and thus you should not handle + /// this action from without umbraco. + /// + [LegacyActionMenuItem("umbracoMenuActions", "RefreshNode")] + public class ActionRefresh : IAction + { + //create singleton + private static readonly ActionRefresh InnerInstance = new ActionRefresh(); + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionRefresh() { } + + public static ActionRefresh Instance + { + get { return InnerInstance; } + } + + #region IAction Members + + public char Letter + { + get + { + + return 'L'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionRefresh()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + + return null; + } + } + + public string Alias + { + get + { + + return "refreshNode"; + } + } + + public string Icon + { + get + { + + return "refresh"; + } + } + + public bool ShowInNotifier + { + get + { + + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + + return false; + } + } + #endregion + } +} diff --git a/src/umbraco.cms/Actions/ActionRestore.cs b/src/Umbraco.Web/_Legacy/Actions/ActionRestore.cs similarity index 92% rename from src/umbraco.cms/Actions/ActionRestore.cs rename to src/Umbraco.Web/_Legacy/Actions/ActionRestore.cs index 9b38918fd4..60cfd3443f 100644 --- a/src/umbraco.cms/Actions/ActionRestore.cs +++ b/src/Umbraco.Web/_Legacy/Actions/ActionRestore.cs @@ -1,8 +1,4 @@ -using System; -using umbraco.interfaces; -using umbraco.BasePages; - -namespace umbraco.BusinessLogic.Actions +namespace Umbraco.Web._Legacy.Actions { /// /// This action is invoked when the content item is to be restored from the recycle bin @@ -84,4 +80,4 @@ namespace umbraco.BusinessLogic.Actions #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionRights.cs b/src/Umbraco.Web/_Legacy/Actions/ActionRights.cs new file mode 100644 index 0000000000..31db2b02ba --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionRights.cs @@ -0,0 +1,96 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when rights are changed on a document + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.ContentCategory)] + public class ActionRights : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionRights m_instance = new ActionRights(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionRights() { } + + public static ActionRights Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + + return 'R'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionRights()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + + return null; + } + } + + public string Alias + { + get + { + + return "rights"; + } + } + + public string Icon + { + get + { + + return "vcard"; + } + } + + public bool ShowInNotifier + { + get + { + + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionRollback.cs b/src/Umbraco.Web/_Legacy/Actions/ActionRollback.cs new file mode 100644 index 0000000000..587b1f1bbb --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionRollback.cs @@ -0,0 +1,95 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when copying a document is being rolled back + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.AdministrationCategory)] + public class ActionRollback : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionRollback m_instance = new ActionRollback(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionRollback() { } + + public static ActionRollback Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + + return 'K'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionRollback()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return ""; + } + } + + public string Alias + { + get + { + + return "rollback"; + } + } + + public string Icon + { + get + { + + return "undo"; + } + } + + public bool ShowInNotifier + { + get + { + + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionSendToTranslate.cs b/src/Umbraco.Web/_Legacy/Actions/ActionSendToTranslate.cs new file mode 100644 index 0000000000..021394b691 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionSendToTranslate.cs @@ -0,0 +1,91 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when a send to translate request occurs + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.ContentCategory)] + public class ActionSendToTranslate : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionSendToTranslate m_instance = new ActionSendToTranslate(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionSendToTranslate() { } + + public static ActionSendToTranslate Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return '5'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionSendToTranslate()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "sendToTranslate"; + } + } + + public string Icon + { + get + { + return "chat"; + } + } + + public bool ShowInNotifier + { + get + { + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + return true; + } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionSort.cs b/src/Umbraco.Web/_Legacy/Actions/ActionSort.cs new file mode 100644 index 0000000000..5512c3222d --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionSort.cs @@ -0,0 +1,96 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when children to a document, media, member is being sorted + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.StructureCategory)] + public class ActionSort : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionSort m_instance = new ActionSort(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionSort() { } + + public static ActionSort Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + + return 'S'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionSort()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + + return null; + } + } + + public string Alias + { + get + { + + return "sort"; + } + } + + public string Icon + { + get + { + + return "navigation-vertical"; + } + } + + public bool ShowInNotifier + { + get + { + + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionToPublish.cs b/src/Umbraco.Web/_Legacy/Actions/ActionToPublish.cs new file mode 100644 index 0000000000..8a8536554f --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionToPublish.cs @@ -0,0 +1,91 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when children to a document is being sent to published (by an editor without publishrights) + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.ContentCategory)] + public class ActionToPublish : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionToPublish m_instance = new ActionToPublish(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionToPublish() { } + + public static ActionToPublish Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return 'H'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionToPublish()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "sendtopublish"; + } + } + + public string Icon + { + get + { + return "outbox"; + } + } + + public bool ShowInNotifier + { + get + { + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + //SD: Changed this to true so that any user may be able to perform this action, not just a writer + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionTranslate.cs b/src/Umbraco.Web/_Legacy/Actions/ActionTranslate.cs new file mode 100644 index 0000000000..e82137d475 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionTranslate.cs @@ -0,0 +1,90 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when a translation occurs + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.AdministrationCategory)] + public class ActionTranslate : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionTranslate m_instance = new ActionTranslate(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionTranslate() { } + + public static ActionTranslate Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return '4'; + } + } + + public string JsFunctionName + { + get + { + return ""; + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "translate"; + } + } + + public string Icon + { + get + { + return "comments"; + } + } + + public bool ShowInNotifier + { + get + { + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + return true; + } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionUnPublish.cs b/src/Umbraco.Web/_Legacy/Actions/ActionUnPublish.cs new file mode 100644 index 0000000000..7ab09e0b1d --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionUnPublish.cs @@ -0,0 +1,90 @@ +using System; + +namespace Umbraco.Web._Legacy.Actions +{ + + + + /// + /// This action is invoked when a document is being unpublished + /// + public class ActionUnPublish : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionUnPublish m_instance = new ActionUnPublish(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance). + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionUnPublish() { } + + public static ActionUnPublish Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return 'Z'; + } + } + + public string JsFunctionName + { + get + { + return ""; + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "unpublish"; + } + } + + public string Icon + { + get + { + return "circle-dotted"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return false; + } + } + #endregion + } + +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionUpdate.cs b/src/Umbraco.Web/_Legacy/Actions/ActionUpdate.cs new file mode 100644 index 0000000000..6661df8997 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ActionUpdate.cs @@ -0,0 +1,90 @@ +using System; +using Umbraco.Web.UI.Pages; +using Umbraco.Core; +using Umbraco.Core.CodeAnnotations; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// This action is invoked when copying a document or media + /// + [ActionMetadata(Constants.Conventions.PermissionCategories.ContentCategory)] + public class ActionUpdate : IAction + { + //create singleton +#pragma warning disable 612,618 + private static readonly ActionUpdate m_instance = new ActionUpdate(); +#pragma warning restore 612,618 + + /// + /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. + /// All Umbraco assemblies should use the singleton instantiation (this.Instance) + /// When this applicatio is refactored, this constuctor should be made private. + /// + [Obsolete("Use the singleton instantiation instead of a constructor")] + public ActionUpdate() { } + + public static ActionUpdate Instance + { + get { return m_instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return 'A'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionUpdate()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "update"; + } + } + + public string Icon + { + get + { + return "save"; + } + } + + public bool ShowInNotifier + { + get + { + return true; + } + } + public bool CanBePermissionAssigned + { + get + { + return true; + } + } + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/ContextMenuSeperator.cs b/src/Umbraco.Web/_Legacy/Actions/ContextMenuSeperator.cs new file mode 100644 index 0000000000..2c66932a04 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/ContextMenuSeperator.cs @@ -0,0 +1,51 @@ +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// Used simply to define context menu seperator items. + /// This should not be used directly in any code except for creating menus. + /// + public class ContextMenuSeperator : IAction + { + //create singleton + private static readonly ContextMenuSeperator instance = new ContextMenuSeperator(); + private ContextMenuSeperator() { } + public static ContextMenuSeperator Instance + { + get { return instance; } + } + + #region IAction Members + + public char Letter + { + get { return ','; } + } + + public string JsFunctionName + { + get { return string.Empty; } + } + public string JsSource + { + get { return string.Empty; } + } + public string Alias + { + get { return string.Empty; } + } + public string Icon + { + get { return string.Empty; } + } + public bool ShowInNotifier + { + get { return false; } + } + public bool CanBePermissionAssigned + { + get { return false; } + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/IAction.cs b/src/Umbraco.Web/_Legacy/Actions/IAction.cs new file mode 100644 index 0000000000..410a407517 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/IAction.cs @@ -0,0 +1,18 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Web._Legacy.Actions +{ + public interface IAction : IDiscoverable + { + char Letter { get; } + bool ShowInNotifier { get; } + bool CanBePermissionAssigned { get; } + string Icon { get; } + string Alias { get; } + string JsFunctionName { get; } + /// + /// A path to a supporting JavaScript file for the IAction. A script tag will be rendered out with the reference to the JavaScript file. + /// + string JsSource { get; } + } +} diff --git a/src/Umbraco.Web/_Legacy/Actions/LegacyActionMenuItemAttribute.cs b/src/Umbraco.Web/_Legacy/Actions/LegacyActionMenuItemAttribute.cs new file mode 100644 index 0000000000..ca9ace9630 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Actions/LegacyActionMenuItemAttribute.cs @@ -0,0 +1,46 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Web._Legacy.Actions +{ + /// + /// The attribute to assign to any IAction objects. + /// + /// + /// This is purely used for compatibility reasons for old IActions used in v7 that haven't been upgraded to + /// the new format. + /// + [AttributeUsage(AttributeTargets.Class)] + internal sealed class LegacyActionMenuItemAttribute : Attribute + { + /// + /// This constructor defines both the angular service and method name to use + /// + /// + /// + public LegacyActionMenuItemAttribute(string serviceName, string methodName) + { + if (string.IsNullOrEmpty(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName)); + if (string.IsNullOrEmpty(methodName)) throw new ArgumentNullOrEmptyException(nameof(methodName)); + + MethodName = methodName; + ServiceName = serviceName; + } + + /// + /// This constructor will assume that the method name equals the type name of the action menu class + /// + /// + public LegacyActionMenuItemAttribute(string serviceName) + { + if (string.IsNullOrEmpty(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName)); + + MethodName = ""; + ServiceName = serviceName; + } + + public string MethodName { get; } + public string ServiceName { get; } + } +} diff --git a/src/Umbraco.Web/_Legacy/BusinessLogic/Task.cs b/src/Umbraco.Web/_Legacy/BusinessLogic/Task.cs new file mode 100644 index 0000000000..3539c225fb --- /dev/null +++ b/src/Umbraco.Web/_Legacy/BusinessLogic/Task.cs @@ -0,0 +1,264 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Web._Legacy.BusinessLogic +{ + /// + /// An umbraco task is currently only used with the translation workflow in umbraco. But is extendable to cover other taskbased system as well. + /// A task represent a simple job, it will always be assigned to a user, related to a node, and contain a comment about the task. + /// The user attached to the task can complete the task, and the author of the task can reopen tasks that are not complete correct. + /// + /// Tasks can in umbraco be used for setting up simple workflows, and contains basic controls structures to determine if the task is completed or not. + /// + [Obsolete("Use Umbraco.Core.Service.ITaskService instead")] + public class Task + { + #region Properties + + public Umbraco.Core.Models.Task TaskEntity { get; } + + /// + /// Gets or sets the id. + /// + /// The id. + public int Id + { + get { return TaskEntity.Id; } + set + { + TaskEntity.Id = value; + _entityEntity = null; + } + } + + /// + /// Gets or sets a value indicating whether this is closed. + /// + /// true if closed; otherwise, false. + public bool Closed + { + get { return TaskEntity.Closed; } + set { TaskEntity.Closed = value; } + } + + /* + private CMSNode _node; + + /// + /// Gets or sets the node. + /// + /// The node. + public CMSNode Node + { + get { return _node ?? (_node = new CMSNode(TaskEntity.EntityId)); } + set + { + _node = value; + TaskEntity.EntityId = value.Id; + } + } + */ + + private IUmbracoEntity _entityEntity; + + public IUmbracoEntity TaskEntityEntity => _entityEntity ?? (_entityEntity = Current.Services.EntityService.Get(TaskEntity.EntityId)); + + private TaskType _type; + + /// + /// Gets or sets the type. + /// + /// The type. + public TaskType Type + { + get { return _type ?? (_type = new TaskType(TaskEntity.TaskType)); } + set + { + _type = value; + TaskEntity.TaskType = new Umbraco.Core.Models.TaskType(_type.Alias) + { + Id = _type.Id + }; + } + } + + + private IUser _parentUser; + + /// + /// Gets or sets the parent user. + /// + /// The parent user. + public IUser ParentUser + { + get { return _parentUser ?? (_parentUser = Current.Services.UserService.GetUserById(TaskEntity.OwnerUserId)); } + set + { + _parentUser = value; + TaskEntity.OwnerUserId = _parentUser.Id; + } + } + + /// + /// Gets or sets the comment. + /// + /// The comment. + public string Comment + { + get { return TaskEntity.Comment; } + set { TaskEntity.Comment = value; } + } + + /// + /// Gets or sets the date. + /// + /// The date. + public DateTime Date + { + get { return TaskEntity.CreateDate; } + set { TaskEntity.CreateDate = value; } + } + + private IUser _user; + + /// + /// Gets or sets the user. + /// + /// The user. + public IUser User + { + get { return _user ?? (_user = Current.Services.UserService.GetUserById(TaskEntity.AssigneeUserId)); } + set + { + _user = value; + TaskEntity.AssigneeUserId = _user.Id; + } + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public Task() + { } + + internal Task(Umbraco.Core.Models.Task task) + { + TaskEntity = task; + } + + /// + /// Initializes a new instance of the class. + /// + /// The task id. + public Task(int TaskId) + { + TaskEntity = Current.Services.TaskService.GetTaskById(TaskId); + if (TaskEntity == null) throw new NullReferenceException("No task found with id " + TaskId); + } + + #endregion + + #region Public Methods + + /// + /// Deletes the current task. + /// Generally tasks should not be deleted and closed instead. + /// + public void Delete() + { + Current.Services.TaskService.Delete(TaskEntity); + } + + /// + /// Saves this instance. + /// + public void Save() + { + Current.Services.TaskService.Save(TaskEntity); + } + + #endregion + + #region static methods + + /// + /// Returns all tasks by type + /// + /// + /// + public static Tasks GetTasksByType(int taskType) + { + var foundTaskType = Current.Services.TaskService.GetTaskTypeById(taskType); + if (foundTaskType == null) return null; + + var result = new Tasks(); + var tasks = Current.Services.TaskService.GetTasks(taskTypeAlias: foundTaskType.Alias); + foreach (var task in tasks) + { + result.Add(new Task(task)); + } + + return result; + } + + /// + /// Get all tasks assigned to a node + /// + /// + /// + public static Tasks GetTasks(int nodeId) + { + var result = new Tasks(); + var tasks = Current.Services.TaskService.GetTasks(itemId:nodeId); + foreach (var task in tasks) + { + result.Add(new Task(task)); + } + + return result; + } + + /// + /// Retrieves a collection of open tasks assigned to the user + /// + /// The User who have the tasks assigned + /// If true both open and closed tasks will be returned + /// A collections of tasks + public static Tasks GetTasks(IUser User, bool IncludeClosed) + { + var result = new Tasks(); + var tasks = Current.Services.TaskService.GetTasks(assignedUser:User.Id, includeClosed:IncludeClosed); + foreach (var task in tasks) + { + result.Add(new Task(task)); + } + + return result; + } + + /// + /// Retrieves a collection of open tasks assigned to the user + /// + /// The User who have the tasks assigned + /// If true both open and closed tasks will be returned + /// A collections of tasks + public static Tasks GetOwnedTasks(IUser User, bool IncludeClosed) + { + var result = new Tasks(); + var tasks = Current.Services.TaskService.GetTasks(ownerUser:User.Id, includeClosed: IncludeClosed); + foreach (var task in tasks) + { + result.Add(new Task(task)); + } + return result; + } + + #endregion + } +} diff --git a/src/Umbraco.Web/_Legacy/BusinessLogic/TaskType.cs b/src/Umbraco.Web/_Legacy/BusinessLogic/TaskType.cs new file mode 100644 index 0000000000..abdc4af264 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/BusinessLogic/TaskType.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; + + +namespace Umbraco.Web._Legacy.BusinessLogic +{ + [Obsolete("Use Umbraco.Core.Service.ITaskService instead")] + public class TaskType + { + internal Umbraco.Core.Models.TaskType TaskTypeEntity; + + #region Public Properties + + public int Id + { + get { return TaskTypeEntity.Id; } + set { TaskTypeEntity.Id = value; } + } + + public string Alias + { + get { return TaskTypeEntity.Alias; } + set { TaskTypeEntity.Alias = value; } + } + + /// + /// All tasks associated with this task type + /// + public Tasks Tasks + { + get + { + //lazy load the tasks + if (_tasks == null) + { + _tasks = Task.GetTasksByType(this.Id); + } + return _tasks; + } + } + + #endregion + + #region Private/protected members + + private Tasks _tasks; + + #endregion + + #region Constructors + + internal TaskType(Umbraco.Core.Models.TaskType tt) + { + TaskTypeEntity = tt; + } + + public TaskType() + { + } + + public TaskType(string TypeAlias) + { + TaskTypeEntity = Current.Services.TaskService.GetTaskTypeByAlias(TypeAlias); + if (TaskTypeEntity == null) throw new NullReferenceException("No task type found by alias " + TypeAlias); + } + + public TaskType(int TaskTypeId) + { + TaskTypeEntity = Current.Services.TaskService.GetTaskTypeById(TaskTypeId); + if (TaskTypeEntity == null) throw new NullReferenceException("No task type found by alias " + TaskTypeId); + } + #endregion + + #region Public methods + + public void Save() + { + Current.Services.TaskService.Save(TaskTypeEntity); + } + + /// + /// Deletes the current task type. + /// This will remove all tasks associated with this type + /// + public void Delete() + { + Current.Services.TaskService.Delete(TaskTypeEntity); + } + + #endregion + + #region Static methods + /// + /// Returns all task types stored in the database + /// + /// + public static IEnumerable GetAll() + { + return Current.Services.TaskService.GetAllTaskTypes() + .Select(x => new TaskType(x)); + } + #endregion + + } +} diff --git a/src/Umbraco.Web/_Legacy/BusinessLogic/Tasks.cs b/src/Umbraco.Web/_Legacy/BusinessLogic/Tasks.cs new file mode 100644 index 0000000000..2b5cc09662 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/BusinessLogic/Tasks.cs @@ -0,0 +1,28 @@ +using System.Collections; + +namespace Umbraco.Web._Legacy.BusinessLogic +{ + /// + /// A collection of tasks. + /// + public class Tasks : CollectionBase + { + /// + /// Adds the specified new task. + /// + /// The new task. + public virtual void Add(Task NewTask) + { + this.List.Add(NewTask); + } + + /// + /// Gets the at the specified index. + /// + /// + public virtual Task this[int Index] + { + get { return (Task)this.List[Index]; } + } + } +} diff --git a/src/umbraco.controls/TreePicker/BaseTreePicker.cs b/src/Umbraco.Web/_Legacy/Controls/BaseTreePicker.cs similarity index 79% rename from src/umbraco.controls/TreePicker/BaseTreePicker.cs rename to src/Umbraco.Web/_Legacy/Controls/BaseTreePicker.cs index b6c9985ed1..7de4b9d4e0 100644 --- a/src/umbraco.controls/TreePicker/BaseTreePicker.cs +++ b/src/Umbraco.Web/_Legacy/Controls/BaseTreePicker.cs @@ -1,273 +1,309 @@ -using System; -using System.Web.UI; -using ClientDependency.Core; -using ClientDependency.Core.Controls; -using umbraco.interfaces; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using umbraco.cms.businesslogic; -namespace umbraco.uicontrols.TreePicker -{ - - /* - [ClientDependency(0, ClientDependencyType.Javascript, "Application/NamespaceManager.js", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Css, "modal/style.css", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "modal/modal.js", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "Application/UmbracoClientManager.js", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "Application/UmbracoUtils.js", "UmbracoClient")]*/ - - [ValidationProperty("Value")] - public abstract class BaseTreePicker : Control, INamingContainer - { - - protected HiddenField ItemIdValue; - protected HtmlAnchor DeleteLink; - protected HtmlAnchor ChooseLink; - protected HtmlGenericControl ItemTitle; - protected HtmlGenericControl ButtonContainer; - protected HtmlGenericControl RootContainer; - - public BaseTreePicker() - { - ShowDelete = true; - ModalHeight = 400; - ModalWidth = 300; - ShowHeader = true; - } - - /// - /// Wraps the hidden vield value - /// - public string Value - { - get - { - EnsureChildControls(); - return ItemIdValue.Value; - } - set - { - EnsureChildControls(); - ItemIdValue.Value = value; - } - } - - public int ModalWidth { get; set; } - public int ModalHeight { get; set; } - public bool ShowDelete { get; set; } - public bool ShowHeader { get; set; } - - /// - /// Need to specify the tree picker url (iframe) - /// - public abstract string TreePickerUrl { get; } - - /// - /// The title to specify for the picker window - /// - public abstract string ModalWindowTitle { get; } - - /// - /// If item has been selected or stored, this will query the db for its title - /// - protected virtual string GetItemTitle() - { - if (!string.IsNullOrEmpty(ItemIdValue.Value)) - { - try - { - return new CMSNode(int.Parse(ItemIdValue.Value)).Text; - } - catch (ArgumentException) { /*the node does not exist! we will ignore*/ } - } - return ""; - } - - /// - /// Just like GetItemTitle, except returns the full path (breadcrumbs) of the node - /// - protected virtual string GetItemBreadcrumbs() - { - if (!string.IsNullOrEmpty(ItemIdValue.Value)) - { - try - { - int nodeId = -1; - if (int.TryParse(ItemIdValue.Value, out nodeId)) - { - CMSNode node = new CMSNode(nodeId); - string title = node.Text; - string separator = " > "; - while (node != null && node.Level > 1) - { - node = node.Parent; - title = node.Text + separator + title; - } - return title; - } - else - { - return ItemIdValue.Value; - } - - } - catch (ArgumentException) { /*the node does not exist! we will ignore*/ } - } - return ""; - } - - /// - /// Outputs the JavaScript instances used to make this control work - /// - protected virtual string GetJSScript() - { - /* 0 = this control's client id - * 1 = label - * 2 = itemIdValueClientID - * 3 = itemTitleClientID - * 4 = itemPickerUrl - * 5 = popup width - * 6 = popup height - * 7 = show header - * 8 = umbraco path - */ - return string.Format(@" - var mc_{0} = new Umbraco.Controls.TreePicker('{0}','{1}','{2}','{3}','{4}',{5},{6},{7},'{8}');", - new string[] - { - this.ClientID, - ModalWindowTitle, - ItemIdValue.ClientID, - ItemTitle.ClientID, - TreePickerUrl, - ModalWidth.ToString(), - ModalHeight.ToString(), - ShowHeader.ToString().ToLower(), - Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco).TrimEnd('/') - }); - } - - /// - /// Registers the required JS classes required to make this control work - /// - protected virtual void RenderJSComponents() - { - const string scriptKey = "BaseTreePickerScripts"; - if (ScriptManager.GetCurrent(Page).IsInAsyncPostBack) - { - ScriptManager.RegisterStartupScript(this, this.GetType(), scriptKey, BaseTreePickerScripts.BaseTreePicker, true); - } - else - { - Page.ClientScript.RegisterClientScriptBlock(typeof(BaseTreePicker), scriptKey, BaseTreePickerScripts.BaseTreePicker, true); - } - } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - EnsureChildControls(); - - //disable view state for this control - this.EnableViewState = false; - } - - /// - /// Create the native .net child controls for this control - /// - protected override void CreateChildControls() - { - base.CreateChildControls(); - - RootContainer = new HtmlGenericControl("span"); - RootContainer.Attributes.Add("class", "umb-tree-picker"); - this.Controls.Add(RootContainer); - - //create the hidden field - ItemIdValue = new HiddenField(); - ItemIdValue.ID = "ContentIdValue"; - RootContainer.Controls.Add(ItemIdValue); - - - ButtonContainer = new HtmlGenericControl("span"); - ButtonContainer.ID = "btns"; - - //add item title with padding - ItemTitle = new HtmlGenericControl("span"); - ItemTitle.ID = "title"; - ItemTitle.Style.Add(HtmlTextWriterStyle.FontWeight, "bold"); - ItemTitle.Attributes.Add("class", "treePickerTitle"); // solely for styling, e.g. with an underline or dotted border, etc. - ButtonContainer.Controls.Add(ItemTitle); - ButtonContainer.Attributes.Add("class", "buttons"); - ButtonContainer.Controls.Add(new LiteralControl(" ")); - ButtonContainer.Controls.Add(new LiteralControl(" ")); - - //add the delete link with padding - DeleteLink = new HtmlAnchor(); - DeleteLink.HRef = "#"; //set on pre-render - DeleteLink.Style.Add(HtmlTextWriterStyle.Color, "red"); - DeleteLink.Title = ui.GetText("delete"); - DeleteLink.InnerText = ui.GetText("delete"); - DeleteLink.Attributes.Add("class", "clear"); - - ButtonContainer.Controls.Add(DeleteLink); - ButtonContainer.Controls.Add(new LiteralControl(" ")); - ButtonContainer.Controls.Add(new LiteralControl(" ")); - if (!ShowDelete) - { - DeleteLink.Style.Add(HtmlTextWriterStyle.Display, "none"); - } - - RootContainer.Controls.Add(ButtonContainer); - - //add choose link with padding - ChooseLink = new HtmlAnchor(); - ChooseLink.HRef = "#"; //filled in on pre-render - ChooseLink.InnerText = ui.GetText("choose") + "..."; - ChooseLink.Attributes.Add("data-section", this.TreePickerUrl); - ChooseLink.Attributes.Add("class", "choose"); - - RootContainer.Controls.Add(ChooseLink); - } - - /// - /// Registers the JavaScript required for the control to function and hides/shows controls depending on it's properties - /// - /// - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - //hide the buttons if no item, otherwise get the item title - if (string.IsNullOrEmpty(ItemIdValue.Value)) - { - ButtonContainer.Style.Add(HtmlTextWriterStyle.Display, "none"); - } - else - { - ItemTitle.InnerText = GetItemTitle(); - ItemTitle.Attributes.Add("title", GetItemBreadcrumbs()); // Adding full path/meta info (Issue U4-192) - } - /* - ChooseLink.HRef = string.Format("javascript:mc_{0}.LaunchPicker();", this.ClientID); - DeleteLink.HRef = string.Format("javascript:mc_{0}.ClearSelection();", this.ClientID); - */ - - RenderJSComponents(); - - /* - if (ScriptManager.GetCurrent(Page).IsInAsyncPostBack) - { - ScriptManager.RegisterStartupScript(this, this.GetType(), this.ClientID + "TreePicker", GetJSScript(), true); - } - else - { - Page.ClientScript.RegisterStartupScript(this.GetType(), this.ClientID + "TreePicker", GetJSScript(), true); - }*/ - - } - - - } -} +using System; +using System.Web.UI; +using ClientDependency.Core; +using ClientDependency.Core.Controls; +using System.Web.UI.WebControls; +using System.Web.UI.HtmlControls; +using umbraco.cms.businesslogic; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Services; + +namespace Umbraco.Web._Legacy.Controls +{ + + /* + [ClientDependency(0, ClientDependencyType.Javascript, "Application/NamespaceManager.js", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Css, "modal/style.css", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Javascript, "modal/modal.js", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Javascript, "Application/UmbracoClientManager.js", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Javascript, "Application/UmbracoUtils.js", "UmbracoClient")]*/ + + [ValidationProperty("Value")] + public abstract class BaseTreePicker : Control, INamingContainer + { + + protected HiddenField ItemIdValue; + protected HtmlAnchor DeleteLink; + protected HtmlAnchor ChooseLink; + protected HtmlGenericControl ItemTitle; + protected HtmlGenericControl ButtonContainer; + protected HtmlGenericControl RootContainer; + + public BaseTreePicker() + { + ShowDelete = true; + ModalHeight = 400; + ModalWidth = 300; + ShowHeader = true; + } + + /// + /// Wraps the hidden vield value + /// + public string Value + { + get + { + EnsureChildControls(); + return ItemIdValue.Value; + } + set + { + EnsureChildControls(); + ItemIdValue.Value = value; + } + } + + public int ModalWidth { get; set; } + public int ModalHeight { get; set; } + public bool ShowDelete { get; set; } + public bool ShowHeader { get; set; } + + /// + /// Need to specify the tree picker url (iframe) + /// + public abstract string TreePickerUrl { get; } + + /// + /// The title to specify for the picker window + /// + public abstract string ModalWindowTitle { get; } + + /// + /// If item has been selected or stored, this will query the db for its title + /// + protected virtual string GetItemTitle() + { + if (!string.IsNullOrEmpty(ItemIdValue.Value)) + { + try + { + return Current.Services.EntityService.Get(int.Parse(ItemIdValue.Value)).Name; + } + catch (ArgumentException) { /*the node does not exist! we will ignore*/ } + } + return ""; + } + + /// + /// Just like GetItemTitle, except returns the full path (breadcrumbs) of the node + /// + protected virtual string GetItemBreadcrumbs() + { + if (!string.IsNullOrEmpty(ItemIdValue.Value)) + { + try + { + int nodeId = -1; + if (int.TryParse(ItemIdValue.Value, out nodeId)) + { + var n = Current.Services.EntityService.Get(nodeId); + string title = n.Name; + string separator = " > "; + while (n.Level > 1) + { + n = Current.Services.EntityService.Get(n.ParentId); + title = n.Name + separator + title; + } + return title; + } + else + { + return ItemIdValue.Value; + } + + } + catch (ArgumentException) { /*the node does not exist! we will ignore*/ } + } + return ""; + } + + /// + /// Outputs the JavaScript instances used to make this control work + /// + protected virtual string GetJSScript() + { + /* 0 = this control's client id + * 1 = label + * 2 = itemIdValueClientID + * 3 = itemTitleClientID + * 4 = itemPickerUrl + * 5 = popup width + * 6 = popup height + * 7 = show header + * 8 = umbraco path + */ + return string.Format(@" + var mc_{0} = new Umbraco.Controls.TreePicker('{0}','{1}','{2}','{3}','{4}',{5},{6},{7},'{8}');", + new string[] + { + this.ClientID, + ModalWindowTitle, + ItemIdValue.ClientID, + ItemTitle.ClientID, + TreePickerUrl, + ModalWidth.ToString(), + ModalHeight.ToString(), + ShowHeader.ToString().ToLower(), + Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco).TrimEnd('/') + }); + } + + /// + /// Registers the required JS classes required to make this control work + /// + protected virtual void RenderJSComponents() + { + const string BaseTreePickerScriptJs = @"/// +(function ($) { + $(document).ready(function () { + // Tooltip only Text + $('.umb-tree-picker a.choose').click(function () { + var that = this; + var s = $(that).data(""section""); + UmbClientMgr.openAngularModalWindow({ + template: 'views/common/dialogs/treepicker.html', + section: s, + callback: function (data) { + //this returns the content object picked + var p = jQuery(that).parent(); + p.find("".buttons"").show(); + + p.find(""input"").val(data.id); + p.find("".treePickerTitle"").text(data.name).show(); + p.find("".clear"").show(); + } + }); + + return false; + }); + + $('.umb-tree-picker a.clear').click(function () { + jQuery(this).parent().parent().find(""input"").val(""-1""); + jQuery(this).parent().parent().find("".treePickerTitle"").text("""").hide(); + jQuery(this).hide(); + }); + }); +})(jQuery); +"; + + const string scriptKey = "BaseTreePickerScripts"; + if (ScriptManager.GetCurrent(Page).IsInAsyncPostBack) + { + ScriptManager.RegisterStartupScript(this, this.GetType(), scriptKey, BaseTreePickerScriptJs, true); + } + else + { + Page.ClientScript.RegisterClientScriptBlock(typeof(BaseTreePicker), scriptKey, BaseTreePickerScriptJs, true); + } + } + + protected override void OnInit(EventArgs e) + { + base.OnInit(e); + + EnsureChildControls(); + + //disable view state for this control + this.EnableViewState = false; + } + + /// + /// Create the native .net child controls for this control + /// + protected override void CreateChildControls() + { + base.CreateChildControls(); + + RootContainer = new HtmlGenericControl("span"); + RootContainer.Attributes.Add("class", "umb-tree-picker"); + this.Controls.Add(RootContainer); + + //create the hidden field + ItemIdValue = new HiddenField(); + ItemIdValue.ID = "ContentIdValue"; + RootContainer.Controls.Add(ItemIdValue); + + + ButtonContainer = new HtmlGenericControl("span"); + ButtonContainer.ID = "btns"; + + //add item title with padding + ItemTitle = new HtmlGenericControl("span"); + ItemTitle.ID = "title"; + ItemTitle.Style.Add(HtmlTextWriterStyle.FontWeight, "bold"); + ItemTitle.Attributes.Add("class", "treePickerTitle"); // solely for styling, e.g. with an underline or dotted border, etc. + ButtonContainer.Controls.Add(ItemTitle); + ButtonContainer.Attributes.Add("class", "buttons"); + ButtonContainer.Controls.Add(new LiteralControl(" ")); + ButtonContainer.Controls.Add(new LiteralControl(" ")); + + //add the delete link with padding + DeleteLink = new HtmlAnchor(); + DeleteLink.HRef = "#"; //set on pre-render + DeleteLink.Style.Add(HtmlTextWriterStyle.Color, "red"); + DeleteLink.Title = Current.Services.TextService.Localize("delete"); + DeleteLink.InnerText = Current.Services.TextService.Localize("delete"); + DeleteLink.Attributes.Add("class", "clear"); + + ButtonContainer.Controls.Add(DeleteLink); + ButtonContainer.Controls.Add(new LiteralControl(" ")); + ButtonContainer.Controls.Add(new LiteralControl(" ")); + if (!ShowDelete) + { + DeleteLink.Style.Add(HtmlTextWriterStyle.Display, "none"); + } + + RootContainer.Controls.Add(ButtonContainer); + + //add choose link with padding + ChooseLink = new HtmlAnchor(); + ChooseLink.HRef = "#"; //filled in on pre-render + ChooseLink.InnerText = Current.Services.TextService.Localize("choose") + "..."; + ChooseLink.Attributes.Add("data-section", this.TreePickerUrl); + ChooseLink.Attributes.Add("class", "choose"); + + RootContainer.Controls.Add(ChooseLink); + } + + /// + /// Registers the JavaScript required for the control to function and hides/shows controls depending on it's properties + /// + /// + protected override void OnPreRender(EventArgs e) + { + base.OnPreRender(e); + + //hide the buttons if no item, otherwise get the item title + if (string.IsNullOrEmpty(ItemIdValue.Value)) + { + ButtonContainer.Style.Add(HtmlTextWriterStyle.Display, "none"); + } + else + { + ItemTitle.InnerText = GetItemTitle(); + ItemTitle.Attributes.Add("title", GetItemBreadcrumbs()); // Adding full path/meta info (Issue U4-192) + } + /* + ChooseLink.HRef = string.Format("javascript:mc_{0}.LaunchPicker();", this.ClientID); + DeleteLink.HRef = string.Format("javascript:mc_{0}.ClearSelection();", this.ClientID); + */ + + RenderJSComponents(); + + /* + if (ScriptManager.GetCurrent(Page).IsInAsyncPostBack) + { + ScriptManager.RegisterStartupScript(this, this.GetType(), this.ClientID + "TreePicker", GetJSScript(), true); + } + else + { + Page.ClientScript.RegisterStartupScript(this.GetType(), this.ClientID + "TreePicker", GetJSScript(), true); + }*/ + + } + + + } +} diff --git a/src/umbraco.controls/CodeArea.cs b/src/Umbraco.Web/_Legacy/Controls/CodeArea.cs similarity index 88% rename from src/umbraco.controls/CodeArea.cs rename to src/Umbraco.Web/_Legacy/Controls/CodeArea.cs index d281300413..db4a8a9bda 100644 --- a/src/umbraco.controls/CodeArea.cs +++ b/src/Umbraco.Web/_Legacy/Controls/CodeArea.cs @@ -1,243 +1,243 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Text; -using System.Web; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using ClientDependency.Core; -using System.Linq; -using ClientDependency.Core.Controls; -using Umbraco.Core.Configuration; - -namespace umbraco.uicontrols -{ - - [ClientDependency(ClientDependencyType.Javascript, "CodeArea/javascript.js", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "CodeArea/UmbracoEditor.js", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Css, "CodeArea/styles.css", "UmbracoClient")] - [ClientDependency(ClientDependencyType.Javascript, "Application/jQuery/jquery-fieldselection.js", "UmbracoClient")] - public class CodeArea : WebControl - { - - public CodeArea() - { - //set the default to Css - CodeBase = EditorType.Css; - } - - protected TextBox CodeTextBox; - - public bool AutoResize { get; set; } - public bool AutoSuggest { get; set; } - public string EditorMimeType { get; set; } - - public ScrollingMenu Menu = new ScrollingMenu(); - - public int OffSetX { get; set; } - public int OffSetY { get; set; } - public string Text - { - get - { - EnsureChildControls(); - return CodeTextBox.Text; - } - set - { - EnsureChildControls(); - CodeTextBox.Text = value; - } - } - - public bool CodeMirrorEnabled - { - get - { - return UmbracoConfig.For.UmbracoSettings().Content.ScriptEditorDisable == false; - } - } - - public EditorType CodeBase { get; set; } - public string ClientSaveMethod { get; set; } - - public enum EditorType { JavaScript, Css, Python, XML, HTML, Razor, HtmlMixed } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - EnsureChildControls(); - - if (CodeMirrorEnabled) - { - ClientDependencyLoader.Instance.RegisterDependency(0, "lib/CodeMirror/lib/codemirror.js", "UmbracoRoot", ClientDependencyType.Javascript); - - - ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/mode/" + CodeBase.ToString().ToLower() + "/" + CodeBase.ToString().ToLower() + ".js", "UmbracoRoot", ClientDependencyType.Javascript); - if (CodeBase == EditorType.HtmlMixed) - { - ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/xml/xml.js", "UmbracoRoot", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/javascript/javascript.js", "UmbracoRoot", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/css/css.js", "UmbracoRoot", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/htmlmixed/htmlmixed.js", "UmbracoRoot", ClientDependencyType.Javascript); - - } - - ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/addon/edit/matchbrackets.js", "UmbracoRoot", ClientDependencyType.Javascript); - - ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/search/search.js", "UmbracoRoot", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/search/searchcursor.js", "UmbracoRoot", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/dialog/dialog.js", "UmbracoRoot", ClientDependencyType.Javascript); - ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/dialog/dialog.css", "UmbracoRoot", ClientDependencyType.Css); - - ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/lib/codemirror.css", "UmbracoRoot", ClientDependencyType.Css); - //ClientDependencyLoader.Instance.RegisterDependency(3, "CodeMirror/css/umbracoCustom.css", "UmbracoClient", ClientDependencyType.Css); - ClientDependencyLoader.Instance.RegisterDependency(4, "CodeArea/styles.css", "UmbracoClient", ClientDependencyType.Css); - } - } - - protected override void CreateChildControls() - { - base.CreateChildControls(); - CodeTextBox = new TextBox(); - CodeTextBox.ID = "CodeTextBox"; - - if (CodeMirrorEnabled == false) - { - CodeTextBox.Attributes.Add("class", "codepress"); - CodeTextBox.Attributes.Add("wrap", "off"); - } - - CodeTextBox.TextMode = TextBoxMode.MultiLine; - - this.Controls.Add(Menu); - this.Controls.Add(CodeTextBox); - - } - - /// - /// Client ID is different if the code editor is turned on/off - /// - public override string ClientID - { - get - { - if (CodeMirrorEnabled == false) - return CodeTextBox.ClientID; - else - return base.ClientID; - } - } - - protected override void Render(HtmlTextWriter writer) - { - EnsureChildControls(); - - var jsEventCode = new StringBuilder(); - - - if (CodeMirrorEnabled == false) - { - CodeTextBox.RenderControl(writer); - jsEventCode.Append(RenderBasicEditor()); - } - else - { - writer.WriteBeginTag("div"); - writer.WriteAttribute("id", this.ClientID); - writer.WriteAttribute("class", "umb-editor umb-codeeditor " + this.CssClass); - this.ControlStyle.AddAttributesToRender(writer); - writer.Write(HtmlTextWriter.TagRightChar); - Menu.RenderControl(writer); - - writer.WriteBeginTag("div"); - writer.WriteAttribute("class", "code-container"); - this.ControlStyle.AddAttributesToRender(writer); - writer.Write(HtmlTextWriter.TagRightChar); - CodeTextBox.RenderControl(writer); - writer.WriteEndTag("div"); - writer.WriteEndTag("div"); - - jsEventCode.Append(RenderCodeEditor()); - } - - jsEventCode.Append(@" - //TODO: for now this is a global var, need to refactor all this so that is using proper js standards - //with correct objects, and proper accessors to these objects. - var UmbEditor; - $(document).ready(function () { - //create the editor - UmbEditor = new Umbraco.Controls.CodeEditor.UmbracoEditor(" + (CodeMirrorEnabled == false).ToString().ToLower() + @", '" + ClientID + @"');"); - - if (this.AutoResize) - { - if (CodeMirrorEnabled) - { - //reduce the width if using code mirror because of the line numbers - OffSetX += 20; - OffSetY += 50; - } - - //add the resize code - jsEventCode.Append(@" - var m_textEditor = jQuery('#" + ClientID + @"'); - - //with codemirror adding divs for line numbers, we need to target a different element - m_textEditor = m_textEditor.find('iframe').length > 0 ? m_textEditor.children('div').get(0) : m_textEditor.get(0); - - jQuery(window).resize(function(){ resizeTextArea(m_textEditor, " + OffSetX + "," + OffSetY + @"); }); - jQuery(document).ready(function(){ resizeTextArea(m_textEditor, " + OffSetX + "," + OffSetY + @"); });"); - - } - - jsEventCode.Append(@" - });"); - - writer.WriteLine(@"", jsEventCode); - - } - - protected string RenderBasicEditor() - { - string jsEventCode = @" - var m_textEditor = document.getElementById('" + this.ClientID + @"'); - tab.watch('" + this.ClientID + @"'); - "; - return jsEventCode; - } - - protected string RenderCodeEditor() - { - var extraKeys = ""; - var editorMimetype = ""; - - if (string.IsNullOrEmpty(EditorMimeType) == false) - editorMimetype = @", - mode: """ + EditorMimeType + "\""; - - - - var jsEventCode = @" - var textarea = document.getElementById('" + CodeTextBox.ClientID + @"'); - var codeEditor = CodeMirror.fromTextArea(textarea, { - tabMode: ""shift"", - matchBrackets: true, - indentUnit: 4, - indentWithTabs: true, - enterMode: ""keep"", - lineWrapping: false" + - editorMimetype + @", - lineNumbers: true" + - extraKeys + @" - }); - "; - - return jsEventCode; - } - } - -} +using System; +using System.Collections; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Web; +using System.Web.SessionState; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.HtmlControls; +using ClientDependency.Core; +using System.Linq; +using ClientDependency.Core.Controls; +using Umbraco.Core.Configuration; + +namespace Umbraco.Web._Legacy.Controls +{ + + [ClientDependency(ClientDependencyType.Javascript, "CodeArea/javascript.js", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Javascript, "CodeArea/UmbracoEditor.js", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Css, "CodeArea/styles.css", "UmbracoClient")] + [ClientDependency(ClientDependencyType.Javascript, "Application/jQuery/jquery-fieldselection.js", "UmbracoClient")] + public class CodeArea : WebControl + { + + public CodeArea() + { + //set the default to Css + CodeBase = EditorType.Css; + } + + protected TextBox CodeTextBox; + + public bool AutoResize { get; set; } + public bool AutoSuggest { get; set; } + public string EditorMimeType { get; set; } + + public ScrollingMenu Menu = new ScrollingMenu(); + + public int OffSetX { get; set; } + public int OffSetY { get; set; } + public string Text + { + get + { + EnsureChildControls(); + return CodeTextBox.Text; + } + set + { + EnsureChildControls(); + CodeTextBox.Text = value; + } + } + + public bool CodeMirrorEnabled + { + get + { + return UmbracoConfig.For.UmbracoSettings().Content.ScriptEditorDisable == false; + } + } + + public EditorType CodeBase { get; set; } + public string ClientSaveMethod { get; set; } + + public enum EditorType { JavaScript, Css, XML, HTML, Razor, HtmlMixed } + + protected override void OnInit(EventArgs e) + { + base.OnInit(e); + EnsureChildControls(); + + if (CodeMirrorEnabled) + { + ClientDependencyLoader.Instance.RegisterDependency(0, "lib/CodeMirror/lib/codemirror.js", "UmbracoRoot", ClientDependencyType.Javascript); + + + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/mode/" + CodeBase.ToString().ToLower() + "/" + CodeBase.ToString().ToLower() + ".js", "UmbracoRoot", ClientDependencyType.Javascript); + if (CodeBase == EditorType.HtmlMixed) + { + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/xml/xml.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/javascript/javascript.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/css/css.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/mode/htmlmixed/htmlmixed.js", "UmbracoRoot", ClientDependencyType.Javascript); + + } + + ClientDependencyLoader.Instance.RegisterDependency(1, "lib/CodeMirror/addon/edit/matchbrackets.js", "UmbracoRoot", ClientDependencyType.Javascript); + + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/search/search.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/search/searchcursor.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/dialog/dialog.js", "UmbracoRoot", ClientDependencyType.Javascript); + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/addon/dialog/dialog.css", "UmbracoRoot", ClientDependencyType.Css); + + ClientDependencyLoader.Instance.RegisterDependency(2, "lib/CodeMirror/lib/codemirror.css", "UmbracoRoot", ClientDependencyType.Css); + //ClientDependencyLoader.Instance.RegisterDependency(3, "CodeMirror/css/umbracoCustom.css", "UmbracoClient", ClientDependencyType.Css); + ClientDependencyLoader.Instance.RegisterDependency(4, "CodeArea/styles.css", "UmbracoClient", ClientDependencyType.Css); + } + } + + protected override void CreateChildControls() + { + base.CreateChildControls(); + CodeTextBox = new TextBox(); + CodeTextBox.ID = "CodeTextBox"; + + if (CodeMirrorEnabled == false) + { + CodeTextBox.Attributes.Add("class", "codepress"); + CodeTextBox.Attributes.Add("wrap", "off"); + } + + CodeTextBox.TextMode = TextBoxMode.MultiLine; + + this.Controls.Add(Menu); + this.Controls.Add(CodeTextBox); + + } + + /// + /// Client ID is different if the code editor is turned on/off + /// + public override string ClientID + { + get + { + if (CodeMirrorEnabled == false) + return CodeTextBox.ClientID; + else + return base.ClientID; + } + } + + protected override void Render(HtmlTextWriter writer) + { + EnsureChildControls(); + + var jsEventCode = new StringBuilder(); + + + if (CodeMirrorEnabled == false) + { + CodeTextBox.RenderControl(writer); + jsEventCode.Append(RenderBasicEditor()); + } + else + { + writer.WriteBeginTag("div"); + writer.WriteAttribute("id", this.ClientID); + writer.WriteAttribute("class", "umb-editor umb-codeeditor " + this.CssClass); + this.ControlStyle.AddAttributesToRender(writer); + writer.Write(HtmlTextWriter.TagRightChar); + Menu.RenderControl(writer); + + writer.WriteBeginTag("div"); + writer.WriteAttribute("class", "code-container"); + this.ControlStyle.AddAttributesToRender(writer); + writer.Write(HtmlTextWriter.TagRightChar); + CodeTextBox.RenderControl(writer); + writer.WriteEndTag("div"); + writer.WriteEndTag("div"); + + jsEventCode.Append(RenderCodeEditor()); + } + + jsEventCode.Append(@" + //TODO: for now this is a global var, need to refactor all this so that is using proper js standards + //with correct objects, and proper accessors to these objects. + var UmbEditor; + $(document).ready(function () { + //create the editor + UmbEditor = new Umbraco.Controls.CodeEditor.UmbracoEditor(" + (CodeMirrorEnabled == false).ToString().ToLower() + @", '" + ClientID + @"');"); + + if (this.AutoResize) + { + if (CodeMirrorEnabled) + { + //reduce the width if using code mirror because of the line numbers + OffSetX += 20; + OffSetY += 50; + } + + //add the resize code + jsEventCode.Append(@" + var m_textEditor = jQuery('#" + ClientID + @"'); + + //with codemirror adding divs for line numbers, we need to target a different element + m_textEditor = m_textEditor.find('iframe').length > 0 ? m_textEditor.children('div').get(0) : m_textEditor.get(0); + + jQuery(window).resize(function(){ resizeTextArea(m_textEditor, " + OffSetX + "," + OffSetY + @"); }); + jQuery(document).ready(function(){ resizeTextArea(m_textEditor, " + OffSetX + "," + OffSetY + @"); });"); + + } + + jsEventCode.Append(@" + });"); + + writer.WriteLine(@"", jsEventCode); + + } + + protected string RenderBasicEditor() + { + string jsEventCode = @" + var m_textEditor = document.getElementById('" + this.ClientID + @"'); + tab.watch('" + this.ClientID + @"'); + "; + return jsEventCode; + } + + protected string RenderCodeEditor() + { + var extraKeys = ""; + var editorMimetype = ""; + + if (string.IsNullOrEmpty(EditorMimeType) == false) + editorMimetype = @", + mode: """ + EditorMimeType + "\""; + + + + var jsEventCode = @" + var textarea = document.getElementById('" + CodeTextBox.ClientID + @"'); + var codeEditor = CodeMirror.fromTextArea(textarea, { + tabMode: ""shift"", + matchBrackets: true, + indentUnit: 4, + indentWithTabs: true, + enterMode: ""keep"", + lineWrapping: false" + + editorMimetype + @", + lineNumbers: true" + + extraKeys + @" + }); + "; + + return jsEventCode; + } + } + +} diff --git a/src/Umbraco.Web/_Legacy/Controls/DataAttributes.cs b/src/Umbraco.Web/_Legacy/Controls/DataAttributes.cs new file mode 100644 index 0000000000..0f83ecd53e --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Controls/DataAttributes.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Web.UI.WebControls; + +namespace Umbraco.Web._Legacy.Controls +{ + internal class DataAttributes : Dictionary + { + + public void AppendTo(WebControl c) + { + foreach (var keyval in this) + c.Attributes.Add("data-" + keyval.Key, keyval.Value); + } + } +} diff --git a/src/umbraco.controls/FieldDropDownList.cs b/src/Umbraco.Web/_Legacy/Controls/FieldDropDownList.cs similarity index 95% rename from src/umbraco.controls/FieldDropDownList.cs rename to src/Umbraco.Web/_Legacy/Controls/FieldDropDownList.cs index 9520dc2e4b..cf85b107e0 100644 --- a/src/umbraco.controls/FieldDropDownList.cs +++ b/src/Umbraco.Web/_Legacy/Controls/FieldDropDownList.cs @@ -1,72 +1,72 @@ -using System.Web; -using System.Web.UI; -using System.Web.UI.WebControls; - -namespace umbraco.uicontrols -{ - public class FieldDropDownList : DropDownList - { - private bool _customOptionsStarted; - private bool _standardOptionsStarted; - - public string CustomPropertiesLabel { get; set; } - public string StandardPropertiesLabel { get; set; } - public string ChooseText { get; set; } - - protected override void RenderContents(HtmlTextWriter writer) - { - if (Items.Count > 0 && Items[0].Text.StartsWith("#")) - { - Items.Insert(0, new ListItem(ChooseText, "")); - SelectedIndex = 0; - - base.RenderContents(writer); - return; - } - - writer.Write("", ChooseText); - - foreach (ListItem item in Items) - { - if (!_customOptionsStarted) - { - RenderOptionGroupBeginTag(CustomPropertiesLabel, writer); - _customOptionsStarted = true; - } - else if (item.Text.StartsWith("@") && !_standardOptionsStarted) - { - _standardOptionsStarted = true; - RenderOptionGroupEndTag(writer); - RenderOptionGroupBeginTag(StandardPropertiesLabel, writer); - } - - writer.WriteBeginTag("option"); - writer.WriteAttribute("value", item.Value, true); - - foreach (string key in item.Attributes.Keys) - writer.WriteAttribute(key, item.Attributes[key]); - - writer.Write(HtmlTextWriter.TagRightChar); - HttpUtility.HtmlEncode(item.Text.Replace("@", ""), writer); - writer.WriteEndTag("option"); - writer.WriteLine(); - } - - RenderOptionGroupEndTag(writer); - } - - private void RenderOptionGroupBeginTag(string name, HtmlTextWriter writer) - { - writer.WriteBeginTag("optgroup"); - writer.WriteAttribute("label", name); - writer.Write(HtmlTextWriter.TagRightChar); - writer.WriteLine(); - } - - private void RenderOptionGroupEndTag(HtmlTextWriter writer) - { - writer.WriteEndTag("optgroup"); - writer.WriteLine(); - } - } -} \ No newline at end of file +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; + +namespace Umbraco.Web._Legacy.Controls +{ + public class FieldDropDownList : DropDownList + { + private bool _customOptionsStarted; + private bool _standardOptionsStarted; + + public string CustomPropertiesLabel { get; set; } + public string StandardPropertiesLabel { get; set; } + public string ChooseText { get; set; } + + protected override void RenderContents(HtmlTextWriter writer) + { + if (Items.Count > 0 && Items[0].Text.StartsWith("#")) + { + Items.Insert(0, new ListItem(ChooseText, "")); + SelectedIndex = 0; + + base.RenderContents(writer); + return; + } + + writer.Write("", ChooseText); + + foreach (ListItem item in Items) + { + if (!_customOptionsStarted) + { + RenderOptionGroupBeginTag(CustomPropertiesLabel, writer); + _customOptionsStarted = true; + } + else if (item.Text.StartsWith("@") && !_standardOptionsStarted) + { + _standardOptionsStarted = true; + RenderOptionGroupEndTag(writer); + RenderOptionGroupBeginTag(StandardPropertiesLabel, writer); + } + + writer.WriteBeginTag("option"); + writer.WriteAttribute("value", item.Value, true); + + foreach (string key in item.Attributes.Keys) + writer.WriteAttribute(key, item.Attributes[key]); + + writer.Write(HtmlTextWriter.TagRightChar); + HttpUtility.HtmlEncode(item.Text.Replace("@", ""), writer); + writer.WriteEndTag("option"); + writer.WriteLine(); + } + + RenderOptionGroupEndTag(writer); + } + + private void RenderOptionGroupBeginTag(string name, HtmlTextWriter writer) + { + writer.WriteBeginTag("optgroup"); + writer.WriteAttribute("label", name); + writer.Write(HtmlTextWriter.TagRightChar); + writer.WriteLine(); + } + + private void RenderOptionGroupEndTag(HtmlTextWriter writer) + { + writer.WriteEndTag("optgroup"); + writer.WriteLine(); + } + } +} diff --git a/src/umbraco.controls/MenuButton.cs b/src/Umbraco.Web/_Legacy/Controls/MenuButton.cs similarity index 90% rename from src/umbraco.controls/MenuButton.cs rename to src/Umbraco.Web/_Legacy/Controls/MenuButton.cs index 0f24f65f22..bf44f3a379 100644 --- a/src/umbraco.controls/MenuButton.cs +++ b/src/Umbraco.Web/_Legacy/Controls/MenuButton.cs @@ -1,73 +1,70 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.IO; - -namespace umbraco.uicontrols -{ - public class MenuButton : System.Web.UI.WebControls.LinkButton - { - public MenuButtonType ButtonType { get; set; } - internal DataAttributes Data { get; private set; } - public string Hotkey { get; set; } - - public string Icon { get; set; } - - public MenuButton() - { - Data = new DataAttributes(); - CssClass = "btn"; - } - - - protected override void Render(System.Web.UI.HtmlTextWriter writer) - { - //setup a hotkey if present - if (ButtonType == MenuButtonType.Primary && String.IsNullOrEmpty(Hotkey)) - Data.Add("hotkey", "ctrl+" + this.Text.ToLower()[0]); - else if (!string.IsNullOrEmpty(Hotkey)) - Data.Add("shortcut", Hotkey); - - Data.AppendTo(this); - - string cssClass = "btn"; - - if (Icon != null) - { - cssClass = "btn editorIcon"; - var i = Icon.Trim('.'); - - if (!string.IsNullOrEmpty(i)) - { - this.ToolTip = this.Text; - - if (i.Contains(".")) - this.Text = "" + this.ToolTip + " " + this.ToolTip; - else - this.Text = " " + this.ToolTip; - } - } - - cssClass += " btn-" + Enum.GetName(ButtonType.GetType(), ButtonType).ToLower() + " " + CssClass; - this.CssClass = cssClass.Trim(); - - - base.Render(writer); - } - - } - - - public enum MenuButtonType - { - Default, - Primary, - Info, - Success, - Warning, - Danger, - Inverse, - Link - } -} +using System; +using Umbraco.Core.IO; + +namespace Umbraco.Web._Legacy.Controls +{ + public class MenuButton : System.Web.UI.WebControls.LinkButton + { + public MenuButtonType ButtonType { get; set; } + internal DataAttributes Data { get; private set; } + public string Hotkey { get; set; } + + public string Icon { get; set; } + + public MenuButton() + { + Data = new DataAttributes(); + CssClass = "btn"; + } + + + protected override void Render(System.Web.UI.HtmlTextWriter writer) + { + //setup a hotkey if present + if (ButtonType == MenuButtonType.Primary && String.IsNullOrEmpty(Hotkey)) + Data.Add("hotkey", "ctrl+" + this.Text.ToLower()[0]); + else if (!string.IsNullOrEmpty(Hotkey)) + Data.Add("shortcut", Hotkey); + + Data.AppendTo(this); + + string cssClass = "btn"; + + if (Icon != null) + { + cssClass = "btn editorIcon"; + var i = Icon.Trim('.'); + + if (!string.IsNullOrEmpty(i)) + { + this.ToolTip = this.Text; + + if (i.Contains(".")) + this.Text = "" + this.ToolTip + " " + this.ToolTip; + else + this.Text = " " + this.ToolTip; + } + } + + cssClass += " btn-" + Enum.GetName(ButtonType.GetType(), ButtonType).ToLower() + " " + CssClass; + this.CssClass = cssClass.Trim(); + + + base.Render(writer); + } + + } + + + public enum MenuButtonType + { + Default, + Primary, + Info, + Success, + Warning, + Danger, + Inverse, + Link + } +} diff --git a/src/umbraco.controls/MenuButtonI.cs b/src/Umbraco.Web/_Legacy/Controls/MenuButtonI.cs similarity index 93% rename from src/umbraco.controls/MenuButtonI.cs rename to src/Umbraco.Web/_Legacy/Controls/MenuButtonI.cs index fac05ac49d..28ae95557a 100644 --- a/src/umbraco.controls/MenuButtonI.cs +++ b/src/Umbraco.Web/_Legacy/Controls/MenuButtonI.cs @@ -1,60 +1,61 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Web; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using ClientDependency.Core; - -namespace umbraco.uicontrols { - - public class MenuImageButton : System.Web.UI.WebControls.ImageButton, MenuIconI { - private string _OnClickCommand = ""; - - public string AltText { - get { return this.AlternateText; } - set { - this.AlternateText = value; - this.Attributes.Add("title", value); - } - } - public int IconWidth { - get { return (int)this.Width.Value; } - set { this.Width = value; } - } - public int IconHeight { - get { return (int)this.Height.Value; } - set { this.Height = value; } - } - - - public string ImageURL { - get { return base.ImageUrl; } - set { base.ImageUrl = value; } - } - - public string OnClickCommand { - get { return _OnClickCommand; } - set { _OnClickCommand = value; } - } - - protected override void CreateChildControls() - { - this.Width = Unit.Pixel(20); - this.Height = Unit.Pixel(20); - this.Style.Clear(); - this.Attributes.Add("class", "btn btn-default editorIcon"); - - - if (_OnClickCommand != "") - { - this.Attributes.Add("onClick", OnClickCommand); - } - base.CreateChildControls(); - } - } -} \ No newline at end of file +using System; +using System.Collections; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Web; +using System.Web.SessionState; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.HtmlControls; +using ClientDependency.Core; + +namespace Umbraco.Web._Legacy.Controls +{ + + public class MenuImageButton : System.Web.UI.WebControls.ImageButton, MenuIconI { + private string _OnClickCommand = ""; + + public string AltText { + get { return this.AlternateText; } + set { + this.AlternateText = value; + this.Attributes.Add("title", value); + } + } + public int IconWidth { + get { return (int)this.Width.Value; } + set { this.Width = value; } + } + public int IconHeight { + get { return (int)this.Height.Value; } + set { this.Height = value; } + } + + + public string ImageURL { + get { return base.ImageUrl; } + set { base.ImageUrl = value; } + } + + public string OnClickCommand { + get { return _OnClickCommand; } + set { _OnClickCommand = value; } + } + + protected override void CreateChildControls() + { + this.Width = Unit.Pixel(20); + this.Height = Unit.Pixel(20); + this.Style.Clear(); + this.Attributes.Add("class", "btn btn-default editorIcon"); + + + if (_OnClickCommand != "") + { + this.Attributes.Add("onClick", OnClickCommand); + } + base.CreateChildControls(); + } + } +} diff --git a/src/umbraco.controls/MenuIcon.cs b/src/Umbraco.Web/_Legacy/Controls/MenuIcon.cs similarity index 86% rename from src/umbraco.controls/MenuIcon.cs rename to src/Umbraco.Web/_Legacy/Controls/MenuIcon.cs index 53b21bc657..fe8b654b8a 100644 --- a/src/umbraco.controls/MenuIcon.cs +++ b/src/Umbraco.Web/_Legacy/Controls/MenuIcon.cs @@ -1,75 +1,70 @@ -using System.ComponentModel; -using System.Web.UI; -using ClientDependency.Core; - -namespace umbraco.uicontrols { - - [ClientDependency(ClientDependencyType.Css, "menuicon/style.css", "UmbracoClient")] - internal class MenuIcon : System.Web.UI.WebControls.Image, MenuIconI - { - private string _OnClickCommand = ""; - private string _AltText = "init"; - - - public string ID1 { - get { return this.ID; } - set { this.ID = value; } - } - - public string AltText { - get { return this.AlternateText; } - set { - _AltText = value; - this.AlternateText = value; - this.Attributes.Add("title", value); - } - } - public int IconWidth { - get { return (int)this.Width.Value; } - set { this.Width = value; } - } - public int IconHeight { - get { return (int)this.Height.Value; } - set { this.Height = value; } - } - - public string ImageURL { - get { return this.ImageUrl; } - set { this.ImageUrl = value; } - } - - public string OnClickCommand { - get { return _OnClickCommand; } - set { _OnClickCommand = value; } - } - - protected override void OnLoad(System.EventArgs EventArguments) { - - - // NH 17-01-2007. Trying to avoid inline styling soup - // Me.Width = WebControls.Unit.Pixel(22) - // Me.Height = WebControls.Unit.Pixel(23) - //Me.Style.Add("border", "0px") - this.Attributes.Add("class", "editorIcon"); - this.Attributes.Add("onMouseover", "this.className='editorIconOver'"); - string holder = ""; -// if (this.ID != "") { - //holder = this.ID.Substring(0, this.ID.LastIndexOf("_")) + "_menu"; - this.Attributes.Add("onMouseout", "hoverIconOut('" + holder + "','" + this.ID + "');"); - this.Attributes.Add("onMouseup", "hoverIconOut('" + holder + "','" + this.ID + "');"); -// } else { - this.Attributes.Add("onMouseout", "this.className='editorIcon'"); - this.Attributes.Add("onMouseup", "this.className='editorIcon'"); -// } - this.Attributes.Add("onMouseDown", "this.className='editorIconDown'; return false;"); - this.AlternateText = _AltText; - this.Attributes.Add("title", _AltText); - - if (_OnClickCommand != "") { - this.Attributes.Add("onClick", OnClickCommand); - } - } - - - } -} \ No newline at end of file +using System.ComponentModel; +using System.Web.UI; +using ClientDependency.Core; + +namespace Umbraco.Web._Legacy.Controls +{ + + [ClientDependency(ClientDependencyType.Css, "menuicon/style.css", "UmbracoClient")] + internal class MenuIcon : System.Web.UI.WebControls.Image, MenuIconI + { + private string _OnClickCommand = ""; + private string _AltText = "init"; + + public string AltText { + get { return this.AlternateText; } + set { + _AltText = value; + this.AlternateText = value; + this.Attributes.Add("title", value); + } + } + public int IconWidth { + get { return (int)this.Width.Value; } + set { this.Width = value; } + } + public int IconHeight { + get { return (int)this.Height.Value; } + set { this.Height = value; } + } + + public string ImageURL { + get { return this.ImageUrl; } + set { this.ImageUrl = value; } + } + + public string OnClickCommand { + get { return _OnClickCommand; } + set { _OnClickCommand = value; } + } + + protected override void OnLoad(System.EventArgs EventArguments) { + + + // NH 17-01-2007. Trying to avoid inline styling soup + // Me.Width = WebControls.Unit.Pixel(22) + // Me.Height = WebControls.Unit.Pixel(23) + //Me.Style.Add("border", "0px") + this.Attributes.Add("class", "editorIcon"); + this.Attributes.Add("onMouseover", "this.className='editorIconOver'"); + string holder = ""; +// if (this.ID != "") { + //holder = this.ID.Substring(0, this.ID.LastIndexOf("_")) + "_menu"; + this.Attributes.Add("onMouseout", "hoverIconOut('" + holder + "','" + this.ID + "');"); + this.Attributes.Add("onMouseup", "hoverIconOut('" + holder + "','" + this.ID + "');"); +// } else { + this.Attributes.Add("onMouseout", "this.className='editorIcon'"); + this.Attributes.Add("onMouseup", "this.className='editorIcon'"); +// } + this.Attributes.Add("onMouseDown", "this.className='editorIconDown'; return false;"); + this.AlternateText = _AltText; + this.Attributes.Add("title", _AltText); + + if (_OnClickCommand != "") { + this.Attributes.Add("onClick", OnClickCommand); + } + } + + + } +} diff --git a/src/umbraco.controls/MenuIconI.cs b/src/Umbraco.Web/_Legacy/Controls/MenuIconI.cs similarity index 88% rename from src/umbraco.controls/MenuIconI.cs rename to src/Umbraco.Web/_Legacy/Controls/MenuIconI.cs index 7c4aff85da..06716c6cb1 100644 --- a/src/umbraco.controls/MenuIconI.cs +++ b/src/Umbraco.Web/_Legacy/Controls/MenuIconI.cs @@ -1,28 +1,29 @@ -namespace umbraco.uicontrols { - public interface MenuIconI { - string ImageURL { - get; - set; - } - string ID { - get; - set; - } - string OnClickCommand { - get; - set; - } - string AltText { - get; - set; - } - int IconWidth { - get; - set; - } - int IconHeight { - get; - set; - } - } -} +namespace Umbraco.Web._Legacy.Controls +{ + public interface MenuIconI { + string ImageURL { + get; + set; + } + string ID { + get; + set; + } + string OnClickCommand { + get; + set; + } + string AltText { + get; + set; + } + int IconWidth { + get; + set; + } + int IconHeight { + get; + set; + } + } +} diff --git a/src/umbraco.controls/Panel.cs b/src/Umbraco.Web/_Legacy/Controls/Panel.cs similarity index 83% rename from src/umbraco.controls/Panel.cs rename to src/Umbraco.Web/_Legacy/Controls/Panel.cs index 0149627da5..00dc679b31 100644 --- a/src/umbraco.controls/Panel.cs +++ b/src/Umbraco.Web/_Legacy/Controls/Panel.cs @@ -1,131 +1,114 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Web; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using ClientDependency.Core; -using umbraco.BusinessLogic; - -namespace umbraco.uicontrols -{ - public class UmbracoPanel : Panel - { - private ScrollingMenu _menu = new ScrollingMenu(); - public UmbracoPanel() - { - - } - public UmbracoPanel(object input) - { - - } - - private bool _hasMenu = false; - private string _StatusBarText = ""; - private string _text; - private bool _autoResize = true; - - public bool hasMenu - { - get { return _hasMenu; } - set { _hasMenu = value; } - } - - public bool AutoResize - { - get { return _autoResize; } - set { _autoResize = value; } - } - - public string Text - { - get - { - if (_text == "") - _text = " "; - - return _text; - } - set { _text = value; } - } - - public string StatusBarText - { - get { return _StatusBarText; } - set { _StatusBarText = value; } - } - - public ScrollingMenu Menu - { - get { return _menu; } - } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - EnsureChildControls(); - } - - - internal HtmlGenericControl header = new HtmlGenericControl(); - internal HtmlGenericControl row = new HtmlGenericControl(); - internal HtmlGenericControl leftcol = new HtmlGenericControl(); - internal HtmlGenericControl rightcol = new HtmlGenericControl(); - internal HtmlGenericControl title = new HtmlGenericControl(); - - internal HtmlGenericControl body = new HtmlGenericControl(); - - protected override void CreateChildControls() - { - //fucking webforms controls and their rendering.. - CssClass = "umb-panel form-horizontal umb-panel-nobody"; - ID = base.ClientID + "_container"; - - header.TagName = "div"; - header.ID = base.ClientID + "_header"; - header.Attributes.Add("class","umb-panel-header"); - - row.TagName = "div"; - row.Attributes.Add("class", "row-fluid"); - header.Controls.Add(row); - - leftcol.TagName = "span"; - leftcol.Attributes.Add("class", "span8"); - - title.TagName = "h1"; - title.Attributes.Add("class", "headline"); - leftcol.Controls.Add(title); - - row.Controls.Add(leftcol); - - rightcol.TagName = "span"; - rightcol.Attributes.Add("class", "span4"); - rightcol.Controls.Add(Menu); - row.Controls.Add(rightcol); - - body.TagName = "div"; - body.Attributes.Add("class", "umb-panel-body row-fluid"); - - Width = Unit.Empty; - Height = Unit.Empty; - - Controls.AddAt(0,header); - - - base.CreateChildControls(); - } - - - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - title.InnerHtml = Text; - } - - } -} \ No newline at end of file +using System; +using System.Web.UI.WebControls; +using System.Web.UI.HtmlControls; + +namespace Umbraco.Web._Legacy.Controls +{ + public class UmbracoPanel : Panel + { + private ScrollingMenu _menu = new ScrollingMenu(); + + private bool _hasMenu = false; + private string _StatusBarText = ""; + private string _text; + private bool _autoResize = true; + + public bool hasMenu + { + get { return _hasMenu; } + set { _hasMenu = value; } + } + + public bool AutoResize + { + get { return _autoResize; } + set { _autoResize = value; } + } + + public string Text + { + get + { + if (_text == "") + _text = " "; + + return _text; + } + set { _text = value; } + } + + public string StatusBarText + { + get { return _StatusBarText; } + set { _StatusBarText = value; } + } + + public ScrollingMenu Menu + { + get { return _menu; } + } + + protected override void OnInit(EventArgs e) + { + base.OnInit(e); + EnsureChildControls(); + } + + + internal HtmlGenericControl header = new HtmlGenericControl(); + internal HtmlGenericControl row = new HtmlGenericControl(); + internal HtmlGenericControl leftcol = new HtmlGenericControl(); + internal HtmlGenericControl rightcol = new HtmlGenericControl(); + internal HtmlGenericControl title = new HtmlGenericControl(); + + internal HtmlGenericControl body = new HtmlGenericControl(); + + protected override void CreateChildControls() + { + //fucking webforms controls and their rendering.. + CssClass = "umb-panel form-horizontal umb-panel-nobody"; + ID = base.ClientID + "_container"; + + header.TagName = "div"; + header.ID = base.ClientID + "_header"; + header.Attributes.Add("class","umb-panel-header"); + + row.TagName = "div"; + row.Attributes.Add("class", "row-fluid"); + header.Controls.Add(row); + + leftcol.TagName = "span"; + leftcol.Attributes.Add("class", "span8"); + + title.TagName = "h1"; + title.Attributes.Add("class", "headline"); + leftcol.Controls.Add(title); + + row.Controls.Add(leftcol); + + rightcol.TagName = "span"; + rightcol.Attributes.Add("class", "span4"); + rightcol.Controls.Add(Menu); + row.Controls.Add(rightcol); + + body.TagName = "div"; + body.Attributes.Add("class", "umb-panel-body row-fluid"); + + Width = Unit.Empty; + Height = Unit.Empty; + + Controls.AddAt(0,header); + + + base.CreateChildControls(); + } + + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + title.InnerHtml = Text; + } + + } +} diff --git a/src/Umbraco.Web/_Legacy/Controls/ProgressBar.cs b/src/Umbraco.Web/_Legacy/Controls/ProgressBar.cs new file mode 100644 index 0000000000..7a30a5cbb1 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Controls/ProgressBar.cs @@ -0,0 +1,24 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Services; + +namespace Umbraco.Web._Legacy.Controls +{ + [Obsolete("Use Umbraco.Web.UI.Controls.ProgressBar")] + public class ProgressBar : System.Web.UI.WebControls.Panel + { + private string _title = Current.Services.TextService.Localize("publish/inProgress"); + public string Title { get; set; } + + protected override void Render(System.Web.UI.HtmlTextWriter writer) + { + if(!string.IsNullOrEmpty(Title)) + _title = Title; + + base.CssClass = "umb-loader"; + + base.Render(writer); + } + } +} diff --git a/src/umbraco.controls/PropertyPanel.cs b/src/Umbraco.Web/_Legacy/Controls/PropertyPanel.cs similarity index 97% rename from src/umbraco.controls/PropertyPanel.cs rename to src/Umbraco.Web/_Legacy/Controls/PropertyPanel.cs index 786081e6ae..6ce9c40b46 100644 --- a/src/umbraco.controls/PropertyPanel.cs +++ b/src/Umbraco.Web/_Legacy/Controls/PropertyPanel.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Web.UI; -namespace umbraco.uicontrols +namespace Umbraco.Web._Legacy.Controls { public class PropertyPanel : System.Web.UI.WebControls.Panel { @@ -16,7 +16,7 @@ namespace umbraco.uicontrols get { return _text; } set { _text = value; } } - + protected override void OnLoad(EventArgs eventArguments) { } @@ -30,7 +30,7 @@ namespace umbraco.uicontrols if (string.IsNullOrEmpty(_text)) CssClass += " hidelabel"; - + writer.WriteLine("
"); @@ -70,4 +70,4 @@ namespace umbraco.uicontrols } -} \ No newline at end of file +} diff --git a/src/umbraco.controls/ScrollingMenu.cs b/src/Umbraco.Web/_Legacy/Controls/ScrollingMenu.cs similarity index 88% rename from src/umbraco.controls/ScrollingMenu.cs rename to src/Umbraco.Web/_Legacy/Controls/ScrollingMenu.cs index 520c7a9d75..0db40c484f 100644 --- a/src/umbraco.controls/ScrollingMenu.cs +++ b/src/Umbraco.Web/_Legacy/Controls/ScrollingMenu.cs @@ -1,168 +1,154 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Web; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using ClientDependency.Core; -using umbraco.IO; - - -namespace umbraco.uicontrols -{ - - [ToolboxData("<{0}:ScrollingMenu runat=server>")] - public class ScrollingMenu : System.Web.UI.WebControls.PlaceHolder - { - public ArrayList Icons = new ArrayList(); - private string iconIds; - private int extraMenuWidth = 0; - - public MenuIconI NewIcon(int Index) - { - MenuIcon Icon = new MenuIcon(); - Icons.Insert(Index, Icon); - return Icon; - } - - public MenuIconI NewIcon() - { - MenuIcon icon = new MenuIcon(); - Icons.Add(icon); - return icon; - } - - internal MenuSplitButton NewSplitButton() - { - var menu = new MenuSplitButton(); - Icons.Add(menu); - return menu; - } - - public MenuButton NewButton(int index = -1) - { - MenuButton btn = new MenuButton(); - - if (index > -1) - Icons.Insert(index, btn); - else - Icons.Add(btn); - - return btn; - } - - public MenuImageButton NewImageButton() - { - MenuImageButton icon = new MenuImageButton(); - Icons.Add(icon); - return icon; - } - - public MenuImageButton NewImageButton(int Index) - { - MenuImageButton icon = new MenuImageButton(); - Icons.Insert(Index, icon); - return icon; - } - - - public System.Web.UI.WebControls.DropDownList NewDropDownList() - { - DropDownList Icon = new DropDownList(); - Icons.Add(Icon); - return Icon; - } - - public void NewElement(string ElementName, string ElementId, string ElementClass, int ExtraWidth) - { - Icons.Add(new LiteralControl("<" + ElementName + " class=\"" + ElementClass + "\" id=\"" + ElementId + "\">")); - extraMenuWidth = extraMenuWidth + ExtraWidth; - } - - public void InsertSplitter() - { - Splitter icon = new Splitter(); - Icons.Add(icon); - } - public void InsertSplitter(int Index) - { - Splitter icon = new Splitter(); - Icons.Insert(Index, icon); - } - - /// - /// Inserts a new web control into the scrolling menu - /// - /// - /// The additional width to extend the scrolling menu by if the control being inserted is wider than the standard - public void InsertNewControl(Control control, int extraWidth = 0) - { - Icons.Add(control); - // _extraMenuWidth = _extraMenuWidth + extraWidth; - } - - - /// - /// Finds the index of the n-th Splitter in this Menu - /// - /// - /// The index of the n-th Splitter, or -1 if the number of Splitters is smaller than n - public int FindSplitter(int n) - { - var count = 0; - for (var i = 0; i < Icons.Count; i++) - { - if (Icons[i].GetType() == typeof(Splitter)) - { - count++; - - if (count == n) - return i; - } - } - - return -1; - } - - - private HtmlGenericControl toolbar; - private HtmlGenericControl wrap; - private HtmlGenericControl group; - - protected override void CreateChildControls() - { - toolbar = new HtmlGenericControl { TagName = "div" }; - toolbar.Attributes.Add("class", "btn-toolbar umb-btn-toolbar"); - this.Controls.Add(toolbar); - - group = new HtmlGenericControl { TagName = "div", ID = this.ClientID + "_group" }; - group.Attributes.Add("class", "btn-group"); - toolbar.Controls.Add(group); - } - - protected override void OnLoad(EventArgs e) - { - foreach (Control item in Icons) - { - group.Controls.Add(item); - if (item.ID != "") - iconIds = iconIds + item.ID + ","; - } - base.OnLoad(e); - } - - - - protected override void OnInit(EventArgs e) - { - - EnsureChildControls(); - base.OnInit(e); - } - - } -} \ No newline at end of file +using System; +using System.Collections; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.HtmlControls; + + +namespace Umbraco.Web._Legacy.Controls +{ + + [ToolboxData("<{0}:ScrollingMenu runat=server>")] + public class ScrollingMenu : System.Web.UI.WebControls.PlaceHolder + { + public ArrayList Icons = new ArrayList(); + private string iconIds; + private int extraMenuWidth = 0; + + public MenuIconI NewIcon(int Index) + { + MenuIcon Icon = new MenuIcon(); + Icons.Insert(Index, Icon); + return Icon; + } + + public MenuIconI NewIcon() + { + MenuIcon icon = new MenuIcon(); + Icons.Add(icon); + return icon; + } + + public MenuButton NewButton(int index = -1) + { + MenuButton btn = new MenuButton(); + + if (index > -1) + Icons.Insert(index, btn); + else + Icons.Add(btn); + + return btn; + } + + public MenuImageButton NewImageButton() + { + MenuImageButton icon = new MenuImageButton(); + Icons.Add(icon); + return icon; + } + + public MenuImageButton NewImageButton(int Index) + { + MenuImageButton icon = new MenuImageButton(); + Icons.Insert(Index, icon); + return icon; + } + + + public System.Web.UI.WebControls.DropDownList NewDropDownList() + { + DropDownList Icon = new DropDownList(); + Icons.Add(Icon); + return Icon; + } + + public void NewElement(string ElementName, string ElementId, string ElementClass, int ExtraWidth) + { + Icons.Add(new LiteralControl("<" + ElementName + " class=\"" + ElementClass + "\" id=\"" + ElementId + "\">")); + extraMenuWidth = extraMenuWidth + ExtraWidth; + } + + public void InsertSplitter() + { + Splitter icon = new Splitter(); + Icons.Add(icon); + } + public void InsertSplitter(int Index) + { + Splitter icon = new Splitter(); + Icons.Insert(Index, icon); + } + + /// + /// Inserts a new web control into the scrolling menu + /// + /// + /// The additional width to extend the scrolling menu by if the control being inserted is wider than the standard + public void InsertNewControl(Control control, int extraWidth = 0) + { + Icons.Add(control); + // _extraMenuWidth = _extraMenuWidth + extraWidth; + } + + + /// + /// Finds the index of the n-th Splitter in this Menu + /// + /// + /// The index of the n-th Splitter, or -1 if the number of Splitters is smaller than n + public int FindSplitter(int n) + { + var count = 0; + for (var i = 0; i < Icons.Count; i++) + { + if (Icons[i].GetType() == typeof(Splitter)) + { + count++; + + if (count == n) + return i; + } + } + + return -1; + } + + + private HtmlGenericControl toolbar; + private HtmlGenericControl wrap; + private HtmlGenericControl group; + + protected override void CreateChildControls() + { + toolbar = new HtmlGenericControl { TagName = "div" }; + toolbar.Attributes.Add("class", "btn-toolbar umb-btn-toolbar"); + this.Controls.Add(toolbar); + + group = new HtmlGenericControl { TagName = "div", ID = this.ClientID + "_group" }; + group.Attributes.Add("class", "btn-group"); + toolbar.Controls.Add(group); + } + + protected override void OnLoad(EventArgs e) + { + foreach (Control item in Icons) + { + group.Controls.Add(item); + if (item.ID != "") + iconIds = iconIds + item.ID + ","; + } + base.OnLoad(e); + } + + + + protected override void OnInit(EventArgs e) + { + + EnsureChildControls(); + base.OnInit(e); + } + + } +} diff --git a/src/umbraco.controls/Splitter.cs b/src/Umbraco.Web/_Legacy/Controls/Splitter.cs similarity index 86% rename from src/umbraco.controls/Splitter.cs rename to src/Umbraco.Web/_Legacy/Controls/Splitter.cs index fb42b00c28..6e418bbf01 100644 --- a/src/umbraco.controls/Splitter.cs +++ b/src/Umbraco.Web/_Legacy/Controls/Splitter.cs @@ -1,15 +1,16 @@ -using System.ComponentModel; -using System.Web.UI; -using Umbraco.Core.IO; - -namespace umbraco.uicontrols { - internal class Splitter : System.Web.UI.WebControls.Image { - - protected override void OnLoad(System.EventArgs EventArguments) { - this.Height = System.Web.UI.WebControls.Unit.Pixel(21); - this.Style.Add("border", "0px"); - this.Attributes.Add("class", "editorIconSplit"); - this.ImageUrl = SystemDirectories.UmbracoClient + "/menuicon/images/split.gif"; - } - } -} \ No newline at end of file +using System.ComponentModel; +using System.Web.UI; +using Umbraco.Core.IO; + +namespace Umbraco.Web._Legacy.Controls +{ + internal class Splitter : System.Web.UI.WebControls.Image { + + protected override void OnLoad(System.EventArgs EventArguments) { + this.Height = System.Web.UI.WebControls.Unit.Pixel(21); + this.Style.Add("border", "0px"); + this.Attributes.Add("class", "editorIconSplit"); + this.ImageUrl = SystemDirectories.UmbracoClient + "/menuicon/images/split.gif"; + } + } +} diff --git a/src/umbraco.controls/TabPage.cs b/src/Umbraco.Web/_Legacy/Controls/TabPage.cs similarity index 91% rename from src/umbraco.controls/TabPage.cs rename to src/Umbraco.Web/_Legacy/Controls/TabPage.cs index aae552de98..282cec36e5 100644 --- a/src/umbraco.controls/TabPage.cs +++ b/src/Umbraco.Web/_Legacy/Controls/TabPage.cs @@ -1,100 +1,100 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Web; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; - -namespace umbraco.uicontrols { - - public class TabPage : WebControl - { - // Ensure that a TabPage cannot be instatiated outside - // this assembly -> New instances of a tabpage can only be retrieved through the tabview - private bool _hasMenu = true; - private readonly ScrollingMenu _menu = new ScrollingMenu(); - protected LiteralControl ErrorHeaderControl = new LiteralControl(); - private LiteralControl _closeButtonControl = new LiteralControl(); - private readonly ValidationSummary _vs = new ValidationSummary(); - private readonly Control _tempErr = new Control(); - internal TabView parent; - - - public string Text { get; set; } - - internal TabPage() - { - - } - - public ValidationSummary ValidationSummaryControl - { - get { return _vs; } - } - - public string ErrorHeader { get; set; } - public string CloseCaption { get; set; } - public bool Active { get; set; } - - public Control ErrorControl - { - get { return _tempErr; } - } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - _vs.ShowSummary = true; - _vs.Attributes.Remove("style"); - _vs.Style.Clear(); - _vs.ForeColor = new Color(); - _vs.ID = String.Format("{0}_validationSummary", this.ID); - - // Add error pane - _tempErr.Visible = false; - _tempErr.ID = String.Format("{0}_errorPaneContainer", this.ID); - _tempErr.Controls.Add(_closeButtonControl); - _tempErr.Controls.Add(ErrorHeaderControl); - _tempErr.Controls.Add(new LiteralControl("

")); - _tempErr.Controls.Add(_vs); - _tempErr.Controls.Add(new LiteralControl("

")); - - this.Controls.Add(_tempErr); - } - - public ScrollingMenu Menu - { - get { return parent.Menu; } - } - - public bool HasMenu - { - get { return _hasMenu; } - set { _hasMenu = value; } - } - - protected override void Render(System.Web.UI.HtmlTextWriter writer) - { - _closeButtonControl.Text = "

"; - ErrorHeaderControl.Text = ErrorHeader; - - var activeClass = string.Empty; - - if (this.ID == parent.ActiveTabId) - activeClass = "active"; - - writer.WriteLine("
"); - writer.WriteLine("
"); - - this.RenderChildren(writer); - - writer.WriteLine("
"); - writer.WriteLine("
"); - } - } -} +using System; +using System.Collections; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Web; +using System.Web.SessionState; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.HtmlControls; + +namespace Umbraco.Web._Legacy.Controls +{ + + public class TabPage : WebControl + { + // Ensure that a TabPage cannot be instatiated outside + // this assembly -> New instances of a tabpage can only be retrieved through the tabview + private bool _hasMenu = true; + protected LiteralControl ErrorHeaderControl = new LiteralControl(); + private LiteralControl _closeButtonControl = new LiteralControl(); + private readonly ValidationSummary _vs = new ValidationSummary(); + private readonly Control _tempErr = new Control(); + internal TabView parent; + + + public string Text { get; set; } + + internal TabPage() + { + + } + + public ValidationSummary ValidationSummaryControl + { + get { return _vs; } + } + + public string ErrorHeader { get; set; } + public string CloseCaption { get; set; } + public bool Active { get; set; } + + public Control ErrorControl + { + get { return _tempErr; } + } + + protected override void OnInit(EventArgs e) + { + base.OnInit(e); + + _vs.ShowSummary = true; + _vs.Attributes.Remove("style"); + _vs.Style.Clear(); + _vs.ForeColor = new Color(); + _vs.ID = String.Format("{0}_validationSummary", this.ID); + + // Add error pane + _tempErr.Visible = false; + _tempErr.ID = String.Format("{0}_errorPaneContainer", this.ID); + _tempErr.Controls.Add(_closeButtonControl); + _tempErr.Controls.Add(ErrorHeaderControl); + _tempErr.Controls.Add(new LiteralControl("

")); + _tempErr.Controls.Add(_vs); + _tempErr.Controls.Add(new LiteralControl("

")); + + this.Controls.Add(_tempErr); + } + + public ScrollingMenu Menu + { + get { return parent.Menu; } + } + + public bool HasMenu + { + get { return _hasMenu; } + set { _hasMenu = value; } + } + + protected override void Render(System.Web.UI.HtmlTextWriter writer) + { + _closeButtonControl.Text = "

"; + ErrorHeaderControl.Text = ErrorHeader; + + var activeClass = string.Empty; + + if (this.ID == parent.ActiveTabId) + activeClass = "active"; + + writer.WriteLine("
"); + writer.WriteLine("
"); + + this.RenderChildren(writer); + + writer.WriteLine("
"); + writer.WriteLine("
"); + } + } +} diff --git a/src/umbraco.controls/TabView.cs b/src/Umbraco.Web/_Legacy/Controls/TabView.cs similarity index 79% rename from src/umbraco.controls/TabView.cs rename to src/Umbraco.Web/_Legacy/Controls/TabView.cs index 334a9f4212..4c3779076e 100644 --- a/src/umbraco.controls/TabView.cs +++ b/src/Umbraco.Web/_Legacy/Controls/TabView.cs @@ -1,152 +1,145 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Web; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using ClientDependency.Core; -using System.Web.UI.HtmlControls; - -namespace umbraco.uicontrols { - - public class TabView : umbraco.uicontrols.UmbracoPanel - { - public readonly ArrayList Tabs = new ArrayList(); - protected ArrayList Panels = new ArrayList(); - protected Dictionary TabPages = new Dictionary(); - - private string _status = ""; - - private HtmlGenericControl _tabList = new HtmlGenericControl(); - private HtmlGenericControl _body = new HtmlGenericControl(); - private HtmlGenericControl _tabsHolder = new HtmlGenericControl(); - - private HiddenField _activeTabHolder = new HiddenField(); - - protected override void CreateChildControls() - { - base.CreateChildControls(); - - _tabList.TagName = "ul"; - _tabList.Attributes.Add("class", "nav nav-tabs umb-nav-tabs span12 -padding-left"); - base.row.Controls.Add(_tabList); - - _body.TagName = "div"; - _body.Attributes.Add("class", "umb-panel-body umb-scrollable row-fluid"); - base.Controls.Add(_body); - - _tabsHolder.TagName = "div"; - _tabsHolder.Attributes.Add("class", "tab-content form-horizontal"); - _tabsHolder.ID = this.ID + "_content"; - _body.Controls.Add(_tabsHolder); - - for (int i = 0; i < Tabs.Count; i++) - { - var tabPage = TabPages.ElementAt(i).Value; - tabPage.Active = false; - - if (tabPage.ID == ActiveTabId) - tabPage.Active = true; - - _tabsHolder.Controls.Add(tabPage); - } - - _activeTabHolder.ID = "activeTabHolder"; - _body.Controls.Add(_activeTabHolder); - - } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - base.CssClass = "umb-panel tabbable"; - } - - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - - if (string.IsNullOrEmpty(Text)) - Text = " "; - - - for (int i = 0; i < Tabs.Count; i++) - { - var tabPage = TabPages.ElementAt(i).Value; - string tabPageCaption = tabPage.Text; - string tabId = tabPage.ID; - - HtmlGenericControl li = new HtmlGenericControl(); - li.TagName = "li"; - if (tabId == ActiveTabId) - li.Attributes.Add("class", "active"); - _tabList.Controls.Add(li); - - HtmlGenericControl a = new HtmlGenericControl(); - a.TagName = "a"; - a.Attributes.Add("href", "#"+tabId); - a.Attributes.Add("onclick", "$('#" + _activeTabHolder.ClientID + "').val('" + tabId + "');"); - a.Attributes.Add("data-toggle", "tab"); - a.InnerText = tabPageCaption; - li.Controls.Add(a); - } - } - - public ArrayList GetPanels() - { - return Panels; - } - - public TabPage NewTabPage(string text) - { - Tabs.Add(text); - TabPage tp = new TabPage(); - tp.Width = this.Width; - tp.ID = "tab0" + (TabPages.Count + 1); - tp.Text = text; - tp.parent = this; - - Panels.Add(tp); - TabPages.Add(tp.ID, tp); - - _tabsHolder.Controls.Add(tp); - return tp; - } - - - public string Status - { - get { return _status; } - set { _status = value; } - } - - private bool _autoResize = true; - public bool AutoResize - { - get { return _autoResize; } - set { _autoResize = value; } - } - - public string ActiveTabId - { - get - { - - if (_activeTabHolder.Value != "") - return _activeTabHolder.Value; - - if (TabPages.Count > 0) - return TabPages.ElementAt(0).Value.ID; - - - - return "tab01"; - } - } - } -} \ No newline at end of file +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Web; +using System.Web.SessionState; +using System.Web.UI; +using System.Web.UI.WebControls; +using ClientDependency.Core; +using System.Web.UI.HtmlControls; + +namespace Umbraco.Web._Legacy.Controls +{ + + public class TabView : Umbraco.Web._Legacy.Controls.UmbracoPanel + { + public readonly ArrayList Tabs = new ArrayList(); + protected ArrayList Panels = new ArrayList(); + protected Dictionary TabPages = new Dictionary(); + + private string _status = ""; + + private HtmlGenericControl _tabList = new HtmlGenericControl(); + private HtmlGenericControl _body = new HtmlGenericControl(); + private HtmlGenericControl _tabsHolder = new HtmlGenericControl(); + + private HiddenField _activeTabHolder = new HiddenField(); + + protected override void CreateChildControls() + { + base.CreateChildControls(); + + _tabList.TagName = "ul"; + _tabList.Attributes.Add("class", "nav nav-tabs umb-nav-tabs span12 -padding-left"); + base.row.Controls.Add(_tabList); + + _body.TagName = "div"; + _body.Attributes.Add("class", "umb-panel-body umb-scrollable row-fluid"); + base.Controls.Add(_body); + + _tabsHolder.TagName = "div"; + _tabsHolder.Attributes.Add("class", "tab-content form-horizontal"); + _tabsHolder.ID = this.ID + "_content"; + _body.Controls.Add(_tabsHolder); + + for (int i = 0; i < Tabs.Count; i++) + { + var tabPage = TabPages.ElementAt(i).Value; + tabPage.Active = false || tabPage.ID == ActiveTabId; + + _tabsHolder.Controls.Add(tabPage); + } + + _activeTabHolder.ID = "activeTabHolder"; + _body.Controls.Add(_activeTabHolder); + + } + + protected override void OnInit(EventArgs e) + { + base.OnInit(e); + base.CssClass = "umb-panel tabbable"; + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + + if (string.IsNullOrEmpty(Text)) + Text = " "; + + + for (int i = 0; i < Tabs.Count; i++) + { + var tabPage = TabPages.ElementAt(i).Value; + string tabPageCaption = tabPage.Text; + string tabId = tabPage.ID; + + HtmlGenericControl li = new HtmlGenericControl(); + li.TagName = "li"; + if (tabId == ActiveTabId) + li.Attributes.Add("class", "active"); + _tabList.Controls.Add(li); + + HtmlGenericControl a = new HtmlGenericControl(); + a.TagName = "a"; + a.Attributes.Add("href", "#"+tabId); + a.Attributes.Add("onclick", "$('#" + _activeTabHolder.ClientID + "').val('" + tabId + "');"); + a.Attributes.Add("data-toggle", "tab"); + a.InnerText = tabPageCaption; + li.Controls.Add(a); + } + } + + public ArrayList GetPanels() + { + return Panels; + } + + public TabPage NewTabPage(string text) + { + Tabs.Add(text); + TabPage tp = new TabPage(); + tp.Width = this.Width; + tp.ID = "tab0" + (TabPages.Count + 1); + tp.Text = text; + tp.parent = this; + + Panels.Add(tp); + TabPages.Add(tp.ID, tp); + + _tabsHolder.Controls.Add(tp); + return tp; + } + + + public string Status + { + get { return _status; } + set { _status = value; } + } + + + + public string ActiveTabId + { + get + { + + if (_activeTabHolder.Value != "") + return _activeTabHolder.Value; + + if (TabPages.Count > 0) + return TabPages.ElementAt(0).Value.ID; + + + + return "tab01"; + } + } + } +} diff --git a/src/umbraco.controls/feedback.cs b/src/Umbraco.Web/_Legacy/Controls/feedback.cs similarity index 87% rename from src/umbraco.controls/feedback.cs rename to src/Umbraco.Web/_Legacy/Controls/feedback.cs index b9fb2e56ee..4c4b5bf143 100644 --- a/src/umbraco.controls/feedback.cs +++ b/src/Umbraco.Web/_Legacy/Controls/feedback.cs @@ -1,55 +1,56 @@ -using System; -using System.Collections.Generic; -using System.Text; -using ClientDependency.Core; - -namespace umbraco.uicontrols { - - [ClientDependency(ClientDependencyType.Css, "ui/default.css", "UmbracoClient")] - public class Feedback : System.Web.UI.WebControls.Panel - { - - public Feedback() { - - } - - protected override void OnInit(EventArgs e) { - } - - protected override void OnLoad(System.EventArgs EventArguments) { - } - - public feedbacktype type { get; set; } - - private string _text = string.Empty; - public string Text { - get { - - return _text; - - } - set { _text = value; } - } - - public enum feedbacktype{ - notice, - error, - success - } - - protected override void Render(System.Web.UI.HtmlTextWriter writer) { - if (_text != string.Empty) { - base.CreateChildControls(); - - string styleString = ""; - foreach (string key in this.Style.Keys) { - styleString += key + ":" + this.Style[key] + ";"; - } - - writer.WriteLine("

"); - writer.WriteLine(_text); - writer.WriteLine("

"); - } - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Text; +using ClientDependency.Core; + +namespace Umbraco.Web._Legacy.Controls +{ + + [ClientDependency(ClientDependencyType.Css, "ui/default.css", "UmbracoClient")] + public class Feedback : System.Web.UI.WebControls.Panel + { + + public Feedback() { + + } + + protected override void OnInit(EventArgs e) { + } + + protected override void OnLoad(System.EventArgs EventArguments) { + } + + public feedbacktype type { get; set; } + + private string _text = string.Empty; + public string Text { + get { + + return _text; + + } + set { _text = value; } + } + + public enum feedbacktype{ + notice, + error, + success + } + + protected override void Render(System.Web.UI.HtmlTextWriter writer) { + if (_text != string.Empty) { + base.CreateChildControls(); + + string styleString = ""; + foreach (string key in this.Style.Keys) { + styleString += key + ":" + this.Style[key] + ";"; + } + + writer.WriteLine("

"); + writer.WriteLine(_text); + writer.WriteLine("

"); + } + } + } +} diff --git a/src/umbraco.controls/pane.cs b/src/Umbraco.Web/_Legacy/Controls/pane.cs similarity index 94% rename from src/umbraco.controls/pane.cs rename to src/Umbraco.Web/_Legacy/Controls/pane.cs index 499695debf..2d37a2c099 100644 --- a/src/umbraco.controls/pane.cs +++ b/src/Umbraco.Web/_Legacy/Controls/pane.cs @@ -1,117 +1,117 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Web; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using ClientDependency.Core; - -namespace umbraco.uicontrols -{ - - public class Pane : System.Web.UI.WebControls.Panel - { - - - public Pane() - { - - } - - private string m_Text = string.Empty; - public string Text - { - get { return m_Text; } - set { m_Text = value; } - } - - private string m_title = string.Empty; - public string Title - { - get { return m_title; } - set { m_title = value; } - } - - public void addProperty(string Caption, Control C, params BaseValidator[] validators) - { - - PropertyPanel pp = new PropertyPanel(); - pp.Controls.Add(C); - - foreach (var validator in validators) - { - validator.Display = ValidatorDisplay.Dynamic; - pp.Controls.Add(validator); - } - pp.Text = Caption; - - this.Controls.Add(pp); - } - - public void addProperty(string Caption, Control C) - { - - PropertyPanel pp = new PropertyPanel(); - pp.Controls.Add(C); - pp.Text = Caption; - - this.Controls.Add(pp); - } - - public void addProperty(Control ctrl) - { - - PropertyPanel pp = new PropertyPanel(); - pp.Controls.Add(ctrl); - this.Controls.Add(pp); - } - - - protected override void OnLoad(System.EventArgs EventArguments) - { - - } - - protected override void Render(System.Web.UI.HtmlTextWriter writer) - { - this.ViewStateMode = ViewStateMode.Disabled; - this.CreateChildControls(); - - string styleString = ""; - - foreach (string key in this.Style.Keys) - { - styleString += key + ":" + this.Style[key] + ";"; - } - - - writer.WriteLine("
"); - if (!string.IsNullOrEmpty(m_title)) - writer.WriteLine("
" + m_title + "
"); - writer.WriteLine("
"); - - if (!string.IsNullOrEmpty(m_Text)) - writer.WriteLine("

" + m_Text + "

"); - - try - { - this.RenderChildren(writer); - } - catch (Exception ex) - { - writer.WriteLine("Error creating control
"); - writer.WriteLine(ex.ToString()); - } - - writer.WriteLine("
"); - writer.WriteLine("
"); - - } - - } - -} \ No newline at end of file +using System; +using System.Collections; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Web; +using System.Web.SessionState; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.HtmlControls; +using ClientDependency.Core; + +namespace Umbraco.Web._Legacy.Controls +{ + + public class Pane : System.Web.UI.WebControls.Panel + { + + + public Pane() + { + + } + + private string m_Text = string.Empty; + public string Text + { + get { return m_Text; } + set { m_Text = value; } + } + + private string m_title = string.Empty; + public string Title + { + get { return m_title; } + set { m_title = value; } + } + + public void addProperty(string Caption, Control C, params BaseValidator[] validators) + { + + PropertyPanel pp = new PropertyPanel(); + pp.Controls.Add(C); + + foreach (var validator in validators) + { + validator.Display = ValidatorDisplay.Dynamic; + pp.Controls.Add(validator); + } + pp.Text = Caption; + + this.Controls.Add(pp); + } + + public void addProperty(string Caption, Control C) + { + + PropertyPanel pp = new PropertyPanel(); + pp.Controls.Add(C); + pp.Text = Caption; + + this.Controls.Add(pp); + } + + public void addProperty(Control ctrl) + { + + PropertyPanel pp = new PropertyPanel(); + pp.Controls.Add(ctrl); + this.Controls.Add(pp); + } + + + protected override void OnLoad(System.EventArgs EventArguments) + { + + } + + protected override void Render(System.Web.UI.HtmlTextWriter writer) + { + this.ViewStateMode = ViewStateMode.Disabled; + this.CreateChildControls(); + + string styleString = ""; + + foreach (string key in this.Style.Keys) + { + styleString += key + ":" + this.Style[key] + ";"; + } + + + writer.WriteLine("
"); + if (!string.IsNullOrEmpty(m_title)) + writer.WriteLine("
" + m_title + "
"); + writer.WriteLine("
"); + + if (!string.IsNullOrEmpty(m_Text)) + writer.WriteLine("

" + m_Text + "

"); + + try + { + this.RenderChildren(writer); + } + catch (Exception ex) + { + writer.WriteLine("Error creating control
"); + writer.WriteLine(ex.ToString()); + } + + writer.WriteLine("
"); + writer.WriteLine("
"); + + } + + } + +} diff --git a/src/Umbraco.Web/_Legacy/PackageActions/PackageHelper.cs b/src/Umbraco.Web/_Legacy/PackageActions/PackageHelper.cs new file mode 100644 index 0000000000..c479b720da --- /dev/null +++ b/src/Umbraco.Web/_Legacy/PackageActions/PackageHelper.cs @@ -0,0 +1,84 @@ +using System; +using System.Xml; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Xml; + +namespace Umbraco.Web._Legacy.PackageActions +{ + public class PackageHelper + { + //Helper method to replace umbraco tags that breaks the xml format.. + public static string ParseToValidXml(ITemplate templateObj, ref bool hasAspNetContentBeginning, string template, bool toValid) + { + string retVal = template; + if (toValid) + { + // test for asp:content as the first part of the design + if (retVal.StartsWith("") + 1); + retVal = retVal.Substring(0, retVal.Length - 14); + } + //shorten empty macro tags.. + retVal = retVal.Replace(">", " />"); + retVal = retVal.Replace(">", " />"); + + retVal = retVal.Replace("", ""); + retVal = retVal.Replace("", ""); + retVal = retVal.Replace("", ""); + + // add asp content element + if (hasAspNetContentBeginning) + { + retVal = MasterPageHelper.GetMasterContentElement(templateObj/*templateObj.MasterTemplate*/) + retVal + ""; + } + } + + return retVal; + } + + public static XmlNode ParseStringToXmlNode(string value) + { + XmlDocument doc = new XmlDocument(); + XmlNode node = XmlHelper.AddTextNode(doc, "error", ""); + + try + { + doc.LoadXml(value); + return doc.SelectSingleNode("."); + } + catch + { + return node; + } + + } + } +} diff --git a/src/Umbraco.Web/_Legacy/PackageActions/addApplication.cs b/src/Umbraco.Web/_Legacy/PackageActions/addApplication.cs new file mode 100644 index 0000000000..09c6df23c4 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/PackageActions/addApplication.cs @@ -0,0 +1,65 @@ +using System; +using System.Xml; +using Umbraco.Core; +using Umbraco.Core._Legacy.PackageActions; +using Umbraco.Web.Composing; + +namespace Umbraco.Web._Legacy.PackageActions +{ + /// + /// This class implements the IPackageAction Interface, used to execute code when packages are installed. + /// All IPackageActions only takes a PackageName and a XmlNode as input, and executes based on the data in the xmlnode. + /// + public class addApplication : IPackageAction + { + + #region IPackageAction Members + + /// + /// Installs a new application in umbraco. + /// + /// Name of the package. + /// The XML data. + /// + /// + /// + /// true if successfull + public bool Execute(string packageName, XmlNode xmlData) + { + string name = xmlData.Attributes["appName"].Value; + string alias = xmlData.Attributes["appAlias"].Value; + string icon = xmlData.Attributes["appIcon"].Value; + + Current.Services.SectionService.MakeNew(name, alias, icon); + + return true; + } + + public bool Undo(string packageName, XmlNode xmlData) + { + string alias = xmlData.Attributes["appAlias"].Value; + var section = Current.Services.SectionService.GetByAlias(alias); + if (section != null) + { + Current.Services.SectionService.DeleteSection(section); + } + return true; + } + /// + /// Action alias. + /// + /// + public string Alias() + { + return "addApplication"; + } + + #endregion + + public XmlNode SampleXml() + { + throw new NotImplementedException(); + } + + } +} diff --git a/src/umbraco.cms/businesslogic/Packager/PackageActions/addApplicationTree.cs b/src/Umbraco.Web/_Legacy/PackageActions/addApplicationTree.cs similarity index 77% rename from src/umbraco.cms/businesslogic/Packager/PackageActions/addApplicationTree.cs rename to src/Umbraco.Web/_Legacy/PackageActions/addApplicationTree.cs index 6d6d3db6cf..b83cb964c2 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageActions/addApplicationTree.cs +++ b/src/Umbraco.Web/_Legacy/PackageActions/addApplicationTree.cs @@ -1,83 +1,83 @@ -using System.Collections.Generic; -using System.Text; -using System.Xml; - -namespace umbraco.cms.businesslogic.packager.standardPackageActions -{ - /*Build in standard actions */ - - /// - /// This class implements the IPackageAction Interface, used to execute code when packages are installed. - /// All IPackageActions only takes a PackageName and a XmlNode as input, and executes based on the data in the xmlnode. - /// - public class addApplicationTree : umbraco.interfaces.IPackageAction - { - - #region IPackageAction Members - - /// - /// Executes the specified package action. - /// - /// Name of the package. - /// The XML data. - /// - /// - /// - /// - public bool Execute(string packageName, XmlNode xmlData) - { - bool silent = bool.Parse(xmlData.Attributes["silent"].Value); - bool initialize = bool.Parse(xmlData.Attributes["initialize"].Value); - byte sortOrder = byte.Parse(xmlData.Attributes["sortOrder"].Value); - - string applicationAlias = xmlData.Attributes["applicationAlias"].Value; - string treeAlias = xmlData.Attributes["treeAlias"].Value; - string treeTitle = xmlData.Attributes["treeTitle"].Value; - string iconOpened = xmlData.Attributes["iconOpened"].Value; - string iconClosed = xmlData.Attributes["iconClosed"].Value; - - string assemblyName = xmlData.Attributes["assemblyName"].Value; - string type = xmlData.Attributes["treeHandlerType"].Value; - string action = xmlData.Attributes["action"].Value; - - - BusinessLogic.ApplicationTree.MakeNew(silent, initialize, sortOrder, applicationAlias, treeAlias, treeTitle, iconClosed, iconOpened, assemblyName, type, action); - - return true; - } - - /// - /// Undoes the action - /// - /// Name of the package. - /// The XML data. - /// - public bool Undo(string packageName, XmlNode xmlData) - { - string treeAlias = xmlData.Attributes["treeAlias"].Value; - BusinessLogic.ApplicationTree.getByAlias(treeAlias).Delete(); - return true; - } - - /// - /// Action alias. - /// - /// - public string Alias() - { - return "addApplicationTree"; - } - - #endregion - - - public XmlNode SampleXml() - { - - string sample = ""; - return helper.parseStringToXmlNode(sample); - } - } -} +using System.Xml; +using Umbraco.Core; +using Umbraco.Core._Legacy.PackageActions; +using Umbraco.Web.Composing; + +namespace Umbraco.Web._Legacy.PackageActions +{ + /*Build in standard actions */ + + /// + /// This class implements the IPackageAction Interface, used to execute code when packages are installed. + /// All IPackageActions only takes a PackageName and a XmlNode as input, and executes based on the data in the xmlnode. + /// + public class addApplicationTree : IPackageAction + { + + #region IPackageAction Members + + /// + /// Executes the specified package action. + /// + /// Name of the package. + /// The XML data. + /// + /// + /// + /// + public bool Execute(string packageName, XmlNode xmlData) + { + bool initialize = bool.Parse(xmlData.Attributes["initialize"].Value); + byte sortOrder = byte.Parse(xmlData.Attributes["sortOrder"].Value); + + string applicationAlias = xmlData.Attributes["applicationAlias"].Value; + string treeAlias = xmlData.Attributes["treeAlias"].Value; + string treeTitle = xmlData.Attributes["treeTitle"].Value; + string iconOpened = xmlData.Attributes["iconOpened"].Value; + string iconClosed = xmlData.Attributes["iconClosed"].Value; + string type = xmlData.Attributes["treeHandlerType"].Value; + + Current.Services.ApplicationTreeService.MakeNew(initialize, sortOrder, applicationAlias, treeAlias, treeTitle, iconClosed, iconOpened, type); + + return true; + } + + /// + /// Undoes the action + /// + /// Name of the package. + /// The XML data. + /// + public bool Undo(string packageName, XmlNode xmlData) + { + string treeAlias = xmlData.Attributes["treeAlias"].Value; + var found = Current.Services.ApplicationTreeService.GetByAlias(treeAlias); + if (found != null) + { + Current.Services.ApplicationTreeService.DeleteTree(found); + } + return true; + } + + /// + /// Action alias. + /// + /// + public string Alias() + { + return "addApplicationTree"; + } + + #endregion + + + public XmlNode SampleXml() + { + + string sample = ""; + return PackageHelper.ParseStringToXmlNode(sample); + } + } +} diff --git a/src/Umbraco.Web/_Legacy/PackageActions/addDashboardSection.cs b/src/Umbraco.Web/_Legacy/PackageActions/addDashboardSection.cs new file mode 100644 index 0000000000..9b3bc5829b --- /dev/null +++ b/src/Umbraco.Web/_Legacy/PackageActions/addDashboardSection.cs @@ -0,0 +1,108 @@ +using System; +using System.Xml; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Xml; +using Umbraco.Core._Legacy.PackageActions; + +namespace Umbraco.Web._Legacy.PackageActions +{ + /// + /// + /// + public class addDashboardSection : IPackageAction + { + #region IPackageAction Members + + /// + /// Installs a dashboard section. This action reuses the action XML, so it has to be valid dashboard markup. + /// + /// Name of the package. + /// The XML data. + /// true if successfull + /// + /// + /// + ///
+ /// + /// default + /// content + /// + /// + /// /usercontrols/dashboard/latestEdits.ascx + /// /usercontrols/umbracoBlog/dashboardBlogPostCreate.ascx + /// + /// + /// /usercontrols/umbracoBlog/dashboardBlogPostCreate.ascx + /// + ///
+ ///
+ ///
+ ///
+ public bool Execute(string packageName, XmlNode xmlData) + { + //this will need a complete section node to work... + + if (xmlData.HasChildNodes) + { + string sectionAlias = xmlData.Attributes["dashboardAlias"].Value; + string dbConfig = SystemFiles.DashboardConfig; + + XmlNode section = xmlData.SelectSingleNode("./section"); + XmlDocument dashboardFile = XmlHelper.OpenAsXmlDocument(dbConfig); + + //don't continue if it already exists + var found = dashboardFile.SelectNodes("//section[@alias='" + sectionAlias + "']"); + if (found == null || found.Count <= 0) + { + + XmlNode importedSection = dashboardFile.ImportNode(section, true); + + XmlAttribute alias = XmlHelper.AddAttribute(dashboardFile, "alias", sectionAlias); + importedSection.Attributes.Append(alias); + + dashboardFile.DocumentElement.AppendChild(importedSection); + + dashboardFile.Save(IOHelper.MapPath(dbConfig)); + } + + return true; + } + + return false; + } + + + public string Alias() + { + return "addDashboardSection"; + } + + public bool Undo(string packageName, XmlNode xmlData) + { + + string sectionAlias = xmlData.Attributes["dashboardAlias"].Value; + string dbConfig = SystemFiles.DashboardConfig; + XmlDocument dashboardFile = XmlHelper.OpenAsXmlDocument(dbConfig); + + XmlNode section = dashboardFile.SelectSingleNode("//section [@alias = '" + sectionAlias + "']"); + + if (section != null) + { + + dashboardFile.SelectSingleNode("/dashBoard").RemoveChild(section); + dashboardFile.Save(IOHelper.MapPath(dbConfig)); + } + + return true; + } + + #endregion + + public XmlNode SampleXml() + { + throw new NotImplementedException(); + } + + } +} diff --git a/src/Umbraco.Web/_Legacy/PackageActions/addProxyFeedHost.cs b/src/Umbraco.Web/_Legacy/PackageActions/addProxyFeedHost.cs new file mode 100644 index 0000000000..ac02bc0c33 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/PackageActions/addProxyFeedHost.cs @@ -0,0 +1,100 @@ +using System.Xml; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Xml; +using Umbraco.Core._Legacy.PackageActions; + +namespace Umbraco.Web._Legacy.PackageActions +{ + public class addProxyFeedHost : IPackageAction + { + #region IPackageAction Members + + public bool Execute(string packageName, XmlNode xmlData) + { + var hostname = xmlData.Attributes["host"].Value; + if (string.IsNullOrEmpty(hostname)) + return false; + + var xdoc = XmlHelper.OpenAsXmlDocument(SystemFiles.FeedProxyConfig); + + xdoc.PreserveWhitespace = true; + + var xn = xdoc.SelectSingleNode("//feedProxy"); + if (xn != null) + { + var insert = true; + + if (xn.HasChildNodes) + { + foreach (XmlNode node in xn.SelectNodes("//allow")) + { + if (node.Attributes["host"] != null && node.Attributes["host"].Value == hostname) + insert = false; + } + } + + if (insert) + { + var newHostname = XmlHelper.AddTextNode(xdoc, "allow", string.Empty); + newHostname.Attributes.Append(XmlHelper.AddAttribute(xdoc, "host", hostname)); + xn.AppendChild(newHostname); + + xdoc.Save(IOHelper.MapPath(SystemFiles.FeedProxyConfig)); + + return true; + } + } + + return false; + } + + public string Alias() + { + return "addProxyFeedHost"; + } + + public bool Undo(string packageName, XmlNode xmlData) + { + var hostname = xmlData.Attributes["host"].Value; + if (string.IsNullOrEmpty(hostname)) + return false; + + var xdoc = XmlHelper.OpenAsXmlDocument(SystemFiles.FeedProxyConfig); + xdoc.PreserveWhitespace = true; + + var xn = xdoc.SelectSingleNode("//feedProxy"); + if (xn != null) + { + bool inserted = false; + if (xn.HasChildNodes) + { + foreach (XmlNode node in xn.SelectNodes("//allow")) + { + if (node.Attributes["host"] != null && node.Attributes["host"].Value == hostname) + { + xn.RemoveChild(node); + inserted = true; + } + } + } + + if (inserted) + { + xdoc.Save(IOHelper.MapPath(SystemFiles.FeedProxyConfig)); + return true; + } + } + + return false; + } + + #endregion + + public XmlNode SampleXml() + { + string sample = ""; + return PackageHelper.ParseStringToXmlNode(sample); + } + } +} diff --git a/src/Umbraco.Web/_Legacy/PackageActions/addStringToHtmlElement.cs b/src/Umbraco.Web/_Legacy/PackageActions/addStringToHtmlElement.cs new file mode 100644 index 0000000000..bf8ca00619 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/PackageActions/addStringToHtmlElement.cs @@ -0,0 +1,206 @@ +using System; +using System.Xml; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Xml; +using Umbraco.Core._Legacy.PackageActions; +using Umbraco.Web.Composing; + +namespace Umbraco.Web._Legacy.PackageActions +{ + /// + /// This class implements the IPackageAction Interface, used to execute code when packages are installed. + /// All IPackageActions only takes a PackageName and a XmlNode as input, and executes based on the data in the xmlnode. + /// addStringToHtmlElement adds a string to specific HTML element in a specific template, and can either append or prepend it. + /// It uses the action xml node to do this, exemple action xml node: + /// + /// The above will add the string "hello world!" to the first html element with the id "newsSection" in the template "news" + /// + public class addStringToHtmlElement : IPackageAction + { + #region IPackageAction Members + + /// + /// Executes the specified package action. + /// + /// Name of the package. + /// The XML data. + /// + /// + /// + /// True if executed successfully + public bool Execute(string packageName, XmlNode xmlData) + { + + + string templateAlias = xmlData.Attributes["templateAlias"].Value; + string htmlElementId = xmlData.Attributes["htmlElementId"].Value; + string position = xmlData.Attributes["position"].Value; + string value = XmlHelper.GetNodeValue(xmlData); + var tmp = Current.Services.FileService.GetTemplate(templateAlias); + + if (UmbracoConfig.For.UmbracoSettings().Templates.UseAspNetMasterPages) + //value = tmp.EnsureMasterPageSyntax(value); + value = MasterPageHelper.EnsureMasterPageSyntax(templateAlias, value); + + _addStringToHtmlElement(tmp, value, htmlElementId, position); + + return true; + } + + + /// + /// Undoes the addStringToHtml Execute() method, by removing the same string from the same template. + /// + /// Name of the package. + /// The XML data. + /// + public bool Undo(string packageName, XmlNode xmlData) + { + string templateAlias = xmlData.Attributes["templateAlias"].Value; + string htmlElementId = xmlData.Attributes["htmlElementId"].Value; + string value = XmlHelper.GetNodeValue(xmlData); + var tmp = Current.Services.FileService.GetTemplate(templateAlias); + + if (UmbracoConfig.For.UmbracoSettings().Templates.UseAspNetMasterPages) + //value = tmp.EnsureMasterPageSyntax(value); + value = MasterPageHelper.EnsureMasterPageSyntax(templateAlias, value); + + _removeStringFromHtmlElement(tmp, value, htmlElementId); + return true; + } + + /// + /// Action alias. + /// + /// + public string Alias() + { + return "addStringToHtmlElement"; + } + + private void _addStringToHtmlElement(ITemplate tmp, string value, string htmlElementId, string position) + { + bool hasAspNetContentBeginning = false; + string design = ""; + string directive = ""; + + if (tmp != null) + { + try + { + XmlDocument templateXml = new XmlDocument(); + templateXml.PreserveWhitespace = true; + + //Make sure that directive is remove before hacked non html4 compatiple replacement action... + design = tmp.Content; + + + splitDesignAndDirective(ref design, ref directive); + + //making sure that the template xml has a root node... + if (string.IsNullOrWhiteSpace(tmp.MasterTemplateAlias) == false) + templateXml.LoadXml(PackageHelper.ParseToValidXml(tmp, ref hasAspNetContentBeginning, "" + design + "", true)); + else + templateXml.LoadXml(PackageHelper.ParseToValidXml(tmp, ref hasAspNetContentBeginning, design, true)); + + XmlNode xmlElement = templateXml.SelectSingleNode("//* [@id = '" + htmlElementId + "']"); + + if (xmlElement != null) + { + + if (position == "beginning") + { + xmlElement.InnerXml = "\n" + PackageHelper.ParseToValidXml(tmp, ref hasAspNetContentBeginning, value, true) + "\n" + xmlElement.InnerXml; + } + else + { + xmlElement.InnerXml = xmlElement.InnerXml + "\n" + PackageHelper.ParseToValidXml(tmp, ref hasAspNetContentBeginning, value, true) + "\n"; + } + } + + tmp.Content = directive + "\n" + PackageHelper.ParseToValidXml(tmp, ref hasAspNetContentBeginning, templateXml.OuterXml, false); + Current.Services.FileService.SaveTemplate(tmp); + } + catch (Exception ex) + { + Current.Logger.Error("An error occurred", ex); + } + } + else + { + Current.Logger.Debug("template not found"); + } + } + + private void _removeStringFromHtmlElement(ITemplate tmp, string value, string htmlElementId) + { + bool hasAspNetContentBeginning = false; + string design = ""; + string directive = ""; + + + if (tmp != null) + { + try + { + XmlDocument templateXml = new XmlDocument(); + templateXml.PreserveWhitespace = true; + + //Make sure that directive is remove before hacked non html4 compatiple replacement action... + design = tmp.Content; + splitDesignAndDirective(ref design, ref directive); + + //making sure that the template xml has a root node... + if (string.IsNullOrWhiteSpace(tmp.MasterTemplateAlias) == false) + templateXml.LoadXml(PackageHelper.ParseToValidXml(tmp, ref hasAspNetContentBeginning, "" + design + "", true)); + else + templateXml.LoadXml(PackageHelper.ParseToValidXml(tmp, ref hasAspNetContentBeginning, design, true)); + + XmlNode xmlElement = templateXml.SelectSingleNode("//* [@id = '" + htmlElementId + "']"); + + + + if (xmlElement != null) + { + string repValue = PackageHelper.ParseToValidXml(tmp, ref hasAspNetContentBeginning, value, true); + xmlElement.InnerXml = xmlElement.InnerXml.Replace(repValue, ""); + } + + tmp.Content = directive + "\n" + PackageHelper.ParseToValidXml(tmp, ref hasAspNetContentBeginning, templateXml.OuterXml, false); + Current.Services.FileService.SaveTemplate(tmp); + } + catch (Exception ex) + { + Current.Logger.Error("An error occurred", ex); + } + } + else + { + Current.Logger.Debug("template not found"); + } + } + + + + private void splitDesignAndDirective(ref string design, ref string directive) + { + if (design.StartsWith("<%@")) + { + directive = design.Substring(0, design.IndexOf("%>") + 2).Trim(Environment.NewLine.ToCharArray()); + design = design.Substring(design.IndexOf("%>") + 3).Trim(Environment.NewLine.ToCharArray()); + } + } + + #endregion + + public XmlNode SampleXml() + { + throw new NotImplementedException(); + } + + } +} diff --git a/src/Umbraco.Web/_Legacy/PackageActions/allowDoctype.cs b/src/Umbraco.Web/_Legacy/PackageActions/allowDoctype.cs new file mode 100644 index 0000000000..146925b85f --- /dev/null +++ b/src/Umbraco.Web/_Legacy/PackageActions/allowDoctype.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections; +using System.Linq; +using System.Xml; +using Umbraco.Core.Models; +using Umbraco.Core._Legacy.PackageActions; +using Umbraco.Web.Composing; + +namespace Umbraco.Web._Legacy.PackageActions +{ + /// + /// This class implements the IPackageAction Interface, used to execute code when packages are installed. + /// All IPackageActions only takes a PackageName and a XmlNode as input, and executes based on the data in the xmlnode. + /// + public class allowDoctype : IPackageAction + { + + #region IPackageAction Members + + /// + /// Allows a documentType to be created below another documentType. + /// + /// Name of the package. + /// The XML data. + /// + /// + /// + /// Returns true on success + public bool Execute(string packageName, XmlNode xmlData) + { + string doctypeName = xmlData.Attributes["documentTypeAlias"].Value; + string parentDoctypeName = xmlData.Attributes["parentDocumentTypeAlias"].Value; + + //global::umbraco.cms.businesslogic.ContentType ct = global::umbraco.cms.businesslogic.ContentType.GetByAlias(doctypeName); + //global::umbraco.cms.businesslogic.ContentType parentct = global::umbraco.cms.businesslogic.ContentType.GetByAlias(parentDoctypeName); + var ct = Current.Services.ContentTypeService.Get(doctypeName); + var parentct = Current.Services.ContentTypeService.Get(parentDoctypeName); + + if (ct != null && parentct != null) + { + bool containsId = false; + ArrayList tmp = new ArrayList(); + + foreach (int i in parentct.AllowedContentTypes.Select(x => x.Id.Value).ToList()) + { + tmp.Add(i); + if (i == ct.Id) + containsId = true; + } + + if (!containsId) + { + + int[] ids = new int[tmp.Count + 1]; + for (int i = 0; i < tmp.Count; i++) ids[i] = (int)tmp[i]; + ids[ids.Length - 1] = ct.Id; + + //parentct.AllowedChildContentTypeIDs = ids; + var so = 0; + parentct.AllowedContentTypes = ids.Select(x => new ContentTypeSort(x, so++)); + //parentct.Save(); + Current.Services.ContentTypeService.Save(parentct); + return true; + } + } + return false; + } + + //this has no undo. + /// + /// This action has no undo. + /// + /// Name of the package. + /// The XML data. + /// + public bool Undo(string packageName, XmlNode xmlData) + { + return true; + } + + /// + /// Action Alias. + /// + /// + public string Alias() + { + return "allowDocumenttype"; + } + + #endregion + + public XmlNode SampleXml() + { + throw new NotImplementedException(); + } + + } +} diff --git a/src/Umbraco.Web/_Legacy/PackageActions/moveRootDocument.cs b/src/Umbraco.Web/_Legacy/PackageActions/moveRootDocument.cs new file mode 100644 index 0000000000..83d9387352 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/PackageActions/moveRootDocument.cs @@ -0,0 +1,102 @@ + +//TODO: MIgrate this to core: http://issues.umbraco.org/issue/U4-5857 + +//using System; +//using System.Xml; + +//namespace umbraco.cms.businesslogic.packager.standardPackageActions +//{ +// /// +// /// This class implements the IPackageAction Interface, used to execute code when packages are installed. +// /// All IPackageActions only takes a PackageName and a XmlNode as input, and executes based on the data in the xmlnode. +// /// +// public class moveRootDocument : umbraco.interfaces.IPackageAction +// { +// #region IPackageAction Members + +// /// +// /// Executes the specified package action. +// /// +// /// Name of the package. +// /// The XML data. +// /// +// /// +// /// +// /// True if executed succesfully +// public bool Execute(string packageName, XmlNode xmlData) +// { + +// string documentName = xmlData.Attributes["documentName"].Value; +// string parentDocumentType = xmlData.Attributes["parentDocumentType"].Value; +// string parentDocumentName = ""; + +// if (xmlData.Attributes["parentDocumentName"] != null) +// parentDocumentName = xmlData.Attributes["parentDocumentName"].Value; + +// int parentDocid = 0; + +// ContentType ct = ContentType.GetByAlias(parentDocumentType); +// Content[] docs = web.Document.getContentOfContentType(ct); + +// if (docs.Length > 0) +// { +// if (String.IsNullOrEmpty(parentDocumentName)) +// parentDocid = docs[0].Id; +// else +// { +// foreach (Content doc in docs) +// { +// if (doc.Text == parentDocumentName) +// parentDocid = doc.Id; +// } +// } +// } + +// if (parentDocid > 0) +// { +// web.Document[] rootDocs = web.Document.GetRootDocuments(); + +// foreach (web.Document rootDoc in rootDocs) +// { +// if (rootDoc.Text == documentName) +// { +// rootDoc.Move(parentDocid); +// rootDoc.PublishWithSubs(new umbraco.BusinessLogic.User(0)); +// } +// } +// } + + +// return true; +// } + +// //this has no undo. +// /// +// /// This action has no undo. +// /// +// /// Name of the package. +// /// The XML data. +// /// +// public bool Undo(string packageName, XmlNode xmlData) +// { +// return true; +// } + +// /// +// /// Action alias +// /// +// /// +// public string Alias() +// { +// return "moveRootDocument"; +// } + +// #endregion + +// public XmlNode SampleXml() +// { +// throw new NotImplementedException(); +// } + +// } +//} diff --git a/src/Umbraco.Web/_Legacy/PackageActions/publishRootDocument.cs b/src/Umbraco.Web/_Legacy/PackageActions/publishRootDocument.cs new file mode 100644 index 0000000000..61c334f184 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/PackageActions/publishRootDocument.cs @@ -0,0 +1,76 @@ +using System; +using System.Xml; +using Umbraco.Core; +using Umbraco.Core._Legacy.PackageActions; +using Umbraco.Web.Composing; + +namespace Umbraco.Web._Legacy.PackageActions +{ + /// + /// This class implements the IPackageAction Interface, used to execute code when packages are installed. + /// All IPackageActions only takes a PackageName and a XmlNode as input, and executes based on the data in the xmlnode. + /// + public class publishRootDocument : IPackageAction + { + #region IPackageAction Members + + /// + /// Executes the specified package action. + /// + /// Name of the package. + /// The XML data. + /// + /// + /// + /// True if executed succesfully + public bool Execute(string packageName, XmlNode xmlData) + { + + string documentName = xmlData.Attributes["documentName"].Value; + + int parentDocid = 0; + + //global::umbraco.cms.businesslogic.web.Document[] rootDocs = global::umbraco.cms.businesslogic.web.Document.GetRootDocuments(); + var rootDocs = Current.Services.ContentService.GetRootContent(); + + foreach (var rootDoc in rootDocs) + { + if (rootDoc.Name.Trim() == documentName.Trim() && rootDoc != null && rootDoc.ContentType != null) + { + // fixme variants? + Current.Services.ContentService.SaveAndPublishBranch(rootDoc, true); + break; + } + } + return true; + } + + //this has no undo. + /// + /// This action has no undo. + /// + /// Name of the package. + /// The XML data. + /// + public bool Undo(string packageName, XmlNode xmlData) + { + return true; + } + + /// + /// Action alias + /// + /// + public string Alias() + { + return "publishRootDocument"; + } + #endregion + + public XmlNode SampleXml() + { + throw new NotImplementedException(); + } + + } +} diff --git a/src/Umbraco.Web/_Legacy/PackageActions/removeStringFromTemplate.cs b/src/Umbraco.Web/_Legacy/PackageActions/removeStringFromTemplate.cs new file mode 100644 index 0000000000..fd2f40178b --- /dev/null +++ b/src/Umbraco.Web/_Legacy/PackageActions/removeStringFromTemplate.cs @@ -0,0 +1,34 @@ +using System; +using System.Xml; +using Umbraco.Core._Legacy.PackageActions; + +namespace Umbraco.Web._Legacy.PackageActions +{ + public class removeStringFromTemplate : IPackageAction + { + #region IPackageAction Members + + public bool Execute(string packageName, XmlNode xmlData) + { + addStringToHtmlElement ast = new addStringToHtmlElement(); + return ast.Undo(packageName, xmlData); + } + + public string Alias() + { + return "removeStringFromHtmlElement"; + } + + public bool Undo(string packageName, XmlNode xmlData) + { + return true; + } + + public XmlNode SampleXml() + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/Umbraco.Web/_Legacy/Packager/Installer.cs similarity index 84% rename from src/umbraco.cms/businesslogic/Packager/Installer.cs rename to src/Umbraco.Web/_Legacy/Packager/Installer.cs index a502ef5a32..fd339e6ed1 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/Umbraco.Web/_Legacy/Packager/Installer.cs @@ -1,33 +1,32 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.IO; using System.Xml; using System.Linq; +using ICSharpCode.SharpZipLib.Zip; using Umbraco.Core; -using Umbraco.Core.Auditing; using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.Core.Packaging; -using umbraco.cms.businesslogic.web; -using umbraco.BusinessLogic; using System.Diagnostics; -using System.IO.Compression; -using umbraco.cms.businesslogic.template; -using umbraco.interfaces; +using Umbraco.Core.Models; +using Umbraco.Core.Composing; using Umbraco.Core.Events; -using Umbraco.Core.Packaging.Models; +using Umbraco.Core.Models.Packaging; using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Core.Xml; +using File = System.IO.File; namespace umbraco.cms.businesslogic.packager { /// /// The packager is a component which enables sharing of both data and functionality components between different umbraco installations. - /// - /// The output is a .umb (a zip compressed file) which contains the exported documents/medias/macros/documenttypes (etc.) + /// + /// The output is a .umb (a zip compressed file) which contains the exported documents/medias/macroes/documenttypes (etc.) /// in a Xml document, along with the physical files used (images/usercontrols/xsl documents etc.) - /// + /// /// Partly implemented, import of packages is done, the export is *under construction*. /// /// @@ -77,11 +76,6 @@ namespace umbraco.cms.businesslogic.packager ///

public List BinaryFileErrors { get { return _binaryFileErrors; } } - /// - /// Indicates that the package contains legacy property editors - /// - public bool ContainsLegacyPropertyEditors { get; private set; } - public bool ContainsStyleSheeConflicts { get; private set; } public IDictionary ConflictingStyleSheetNames { get { return _conflictingStyleSheetNames; } } @@ -177,9 +171,9 @@ namespace umbraco.cms.businesslogic.packager /// public string Import(string inputFile, bool deleteFile) { - using (DisposableTimer.DebugDuration( - () => "Importing package file " + inputFile, - () => "Package file " + inputFile + "imported")) + using (Current.ProfilingLogger.DebugDuration( + $"Importing package file {inputFile}.", + $"Package file {inputFile} imported.")) { var tempDir = ""; if (File.Exists(IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + inputFile))) @@ -195,7 +189,7 @@ namespace umbraco.cms.businesslogic.packager } catch (Exception exception) { - LogHelper.Error(string.Format("Error importing file {0}", fi.FullName), exception); + Current.Logger.Error(string.Format("Error importing file {0}", fi.FullName), exception); throw; } } @@ -266,9 +260,9 @@ namespace umbraco.cms.businesslogic.packager public void InstallFiles(int packageId, string tempDir) { - using (DisposableTimer.DebugDuration( - () => "Installing package files for package id " + packageId + " into temp folder " + tempDir, - () => "Package file installation complete for package id " + packageId)) + using (Current.ProfilingLogger.DebugDuration( + "Installing package files for package id " + packageId + " into temp folder " + tempDir, + "Package file installation complete for package id " + packageId)) { //retrieve the manifest to continue installation var insPack = InstalledPackage.GetById(packageId); @@ -303,12 +297,12 @@ namespace umbraco.cms.businesslogic.packager // my-icon.png // /umbraco/Images/ // my-icon.png - // + // // // my-icon.png // /App_Plugins/MyPlugin/Images // my-icon.png - // + // // // Since this file unzips as a flat list of files, moving the file the first time means // that when you try to do that a second time, it would result in a FileNotFoundException @@ -319,7 +313,7 @@ namespace umbraco.cms.businesslogic.packager } - // Once we're done copying, remove all the files + // Once we're done copying, remove all the files foreach (XmlNode n in Config.DocumentElement.SelectNodes("//file")) { var sourceFile = GetFileName(tempDir, XmlHelper.GetNodeValue(n.SelectSingleNode("guid"))); @@ -329,14 +323,14 @@ namespace umbraco.cms.businesslogic.packager } catch (Exception exception) { - LogHelper.Error("Package install error", exception); + Current.Logger.Error("Package install error", exception); throw; } // log that a user has install files if (_currentUserId > -1) { - Audit.Add(AuditTypes.PackagerInstall, + Current.Services.AuditService.Add(AuditType.PackagerInstall, string.Format("Package '{0}' installed. Package guid: {1}", insPack.Data.Name, insPack.Data.PackageGuid), _currentUserId, -1); } @@ -347,9 +341,9 @@ namespace umbraco.cms.businesslogic.packager public void InstallBusinessLogic(int packageId, string tempDir) { - using (DisposableTimer.DebugDuration( - () => "Installing business logic for package id " + packageId + " into temp folder " + tempDir, - () => "Package business logic installation complete for package id " + packageId)) + using (Current.ProfilingLogger.DebugDuration( + "Installing business logic for package id " + packageId + " into temp folder " + tempDir, + "Package business logic installation complete for package id " + packageId)) { InstalledPackage insPack; try @@ -359,48 +353,25 @@ namespace umbraco.cms.businesslogic.packager //bool saveNeeded = false; // Get current user, with a fallback - var currentUser = new User(0); + var currentUser = Current.Services.UserService.GetUserById(Constants.Security.SuperId); - //if there's a context, try to resolve the user - this will return null if there is a context but no - // user found when there are old/invalid cookies lying around most likely during installation. - // in that case we'll keep using the admin user - if (string.IsNullOrEmpty(BasePages.UmbracoEnsuredPage.umbracoUserContextID) == false) - { - if (BasePages.UmbracoEnsuredPage.ValidateUserContextID(BasePages.UmbracoEnsuredPage.umbracoUserContextID)) - { - var userById = User.GetCurrent(); - if (userById != null) - currentUser = userById; - } - } + //TODO: Get rid of this entire class! Until then all packages will be installed by the admin user //Xml as XElement which is used with the new PackagingService var rootElement = Config.DocumentElement.GetXElement(); - var packagingService = ApplicationContext.Current.Services.PackagingService; + var packagingService = Current.Services.PackagingService; //Perhaps it would have been a good idea to put the following into methods eh?!? - #region Stylesheets - foreach (XmlNode n in Config.DocumentElement.SelectNodes("Stylesheets/Stylesheet")) + #region DataTypes + var dataTypeElement = rootElement.Descendants("DataTypes").FirstOrDefault(); + if (dataTypeElement != null) { - StyleSheet s = StyleSheet.Import(n, currentUser); - - insPack.Data.Stylesheets.Add(s.Id.ToString(CultureInfo.InvariantCulture)); - //saveNeeded = true; - } - - //if (saveNeeded) { insPack.Save(); saveNeeded = false; } - #endregion - - #region Templates - var templateElement = rootElement.Descendants("Templates").FirstOrDefault(); - if (templateElement != null) - { - var templates = packagingService.ImportTemplates(templateElement, currentUser.Id); - foreach (var template in templates) + var dataTypeDefinitions = packagingService.ImportDataTypeDefinitions(dataTypeElement, currentUser.Id); + foreach (var dataTypeDefinition in dataTypeDefinitions) { - insPack.Data.Templates.Add(template.Id.ToString(CultureInfo.InvariantCulture)); + insPack.Data.DataTypes.Add(dataTypeDefinition.Id.ToString(CultureInfo.InvariantCulture)); } } #endregion @@ -424,18 +395,6 @@ namespace umbraco.cms.businesslogic.packager } #endregion - #region DataTypes - var dataTypeElement = rootElement.Descendants("DataTypes").FirstOrDefault(); - if (dataTypeElement != null) - { - var dataTypeDefinitions = packagingService.ImportDataTypeDefinitions(dataTypeElement, currentUser.Id); - foreach (var dataTypeDefinition in dataTypeDefinitions) - { - insPack.Data.DataTypes.Add(dataTypeDefinition.Id.ToString(CultureInfo.InvariantCulture)); - } - } - #endregion - #region Macros var macroItemsElement = rootElement.Descendants("Macros").FirstOrDefault(); if (macroItemsElement != null) @@ -445,6 +404,18 @@ namespace umbraco.cms.businesslogic.packager } #endregion + #region Templates + var templateElement = rootElement.Descendants("Templates").FirstOrDefault(); + if (templateElement != null) + { + var templates = packagingService.ImportTemplates(templateElement, currentUser.Id); + foreach (var template in templates) + { + insPack.Data.Templates.Add(template.Id.ToString(CultureInfo.InvariantCulture)); + } + } + #endregion + #region DocumentTypes //Check whether the root element is a doc type rather then a complete package var docTypeElement = rootElement.Name.LocalName.Equals("DocumentType") || @@ -463,6 +434,64 @@ namespace umbraco.cms.businesslogic.packager } #endregion + #region Stylesheets + foreach (XmlNode n in Config.DocumentElement.SelectNodes("Stylesheets/Stylesheet")) + { + //StyleSheet s = StyleSheet.Import(n, currentUser); + + + string stylesheetName = XmlHelper.GetNodeValue(n.SelectSingleNode("Name")); + //StyleSheet s = GetByName(stylesheetName); + var s = Current.Services.FileService.GetStylesheetByName(stylesheetName); + if (s == null) + { + s = new Stylesheet(XmlHelper.GetNodeValue(n.SelectSingleNode("FileName"))) { Content = XmlHelper.GetNodeValue(n.SelectSingleNode("Content")) }; + Current.Services.FileService.SaveStylesheet(s); + } + + foreach (XmlNode prop in n.SelectNodes("Properties/Property")) + { + string alias = XmlHelper.GetNodeValue(prop.SelectSingleNode("Alias")); + var sp = s.Properties.SingleOrDefault(p => p != null && p.Alias == alias); + string name = XmlHelper.GetNodeValue(prop.SelectSingleNode("Name")); + if (sp == null) + { + //sp = StylesheetProperty.MakeNew( + // name, + // s, + // u); + + sp = new StylesheetProperty(name, "#" + name.ToSafeAlias(), ""); + s.AddProperty(sp); + } + else + { + //sp.Text = name; + //Changing the name requires removing the current property and then adding another new one + if (sp.Name != name) + { + s.RemoveProperty(sp.Name); + var newProp = new StylesheetProperty(name, sp.Alias, sp.Value); + s.AddProperty(newProp); + sp = newProp; + } + } + sp.Alias = alias; + sp.Value = XmlHelper.GetNodeValue(prop.SelectSingleNode("Value")); + } + //s.saveCssToFile(); + Current.Services.FileService.SaveStylesheet(s); + + + + + insPack.Data.Stylesheets.Add(s.Id.ToString(CultureInfo.InvariantCulture)); + //saveNeeded = true; + } + + //if (saveNeeded) { insPack.Save(); saveNeeded = false; } + #endregion + #region Documents var documentElement = rootElement.Descendants("DocumentSet").FirstOrDefault(); if (documentElement != null) @@ -495,16 +524,11 @@ namespace umbraco.cms.businesslogic.packager } #endregion - // Trigger update of Apps / Trees config. - // (These are ApplicationStartupHandlers so just instantiating them will trigger them) - new ApplicationRegistrar(); - new ApplicationTreeRegistrar(); - insPack.Save(); } catch (Exception exception) { - LogHelper.Error("Error installing businesslogic", exception); + Current.Logger.Error("Error installing businesslogic", exception); throw; } @@ -520,7 +544,6 @@ namespace umbraco.cms.businesslogic.packager /// public void InstallCleanUp(int packageId, string tempDir) { - if (Directory.Exists(tempDir)) { Directory.Delete(tempDir, true); @@ -592,21 +615,7 @@ namespace umbraco.cms.businesslogic.packager } } - if (ContainsUnsecureFiles) - { - //Now we want to see if the DLLs contain any legacy data types since we want to warn people about that - string[] assemblyErrors; - var assembliesWithReferences = PackageBinaryInspector.ScanAssembliesForTypeReference(tempDir, out assemblyErrors).ToArray(); - if (assemblyErrors.Any()) - { - ContainsBinaryFileErrors = true; - BinaryFileErrors.AddRange(assemblyErrors); - } - if (assembliesWithReferences.Any()) - { - ContainsLegacyPropertyEditors = true; - } - } + //this will check for existing macros with the same alias //since we will not overwrite on import it's a good idea to inform the user what will be overwritten @@ -615,7 +624,7 @@ namespace umbraco.cms.businesslogic.packager var alias = n.SelectSingleNode("alias").InnerText; if (!string.IsNullOrEmpty(alias)) { - var m = ApplicationContext.Current.Services.MacroService.GetByAlias(alias); + var m = Current.Services.MacroService.GetByAlias(alias); if (m != null) { ContainsMacroConflict = true; @@ -632,13 +641,13 @@ namespace umbraco.cms.businesslogic.packager var alias = n.SelectSingleNode("Alias").InnerText; if (!string.IsNullOrEmpty(alias)) { - var t = Template.GetByAlias(alias); + var t = Current.Services.FileService.GetTemplate(alias); if (t != null) { ContainsTemplateConflicts = true; - if (_conflictingTemplateAliases.ContainsKey(t.Text) == false) + if (_conflictingTemplateAliases.ContainsKey(t.Alias) == false) { - _conflictingTemplateAliases.Add(t.Text, alias); + _conflictingTemplateAliases.Add(t.Alias, alias); } } } @@ -649,13 +658,13 @@ namespace umbraco.cms.businesslogic.packager var alias = n.SelectSingleNode("Name").InnerText; if (!string.IsNullOrEmpty(alias)) { - var s = StyleSheet.GetByName(alias); + var s = Current.Services.FileService.GetStylesheetByName(alias); if (s != null) { ContainsStyleSheeConflicts = true; - if (_conflictingStyleSheetNames.ContainsKey(s.Text) == false) + if (_conflictingStyleSheetNames.ContainsKey(s.Alias) == false) { - _conflictingStyleSheetNames.Add(s.Text, alias); + _conflictingStyleSheetNames.Add(s.Alias, alias); } } } @@ -720,7 +729,7 @@ namespace umbraco.cms.businesslogic.packager path = path.Replace("[$DATA]", SystemDirectories.Data); } - //to support virtual dirs we try to lookup the file... + //to support virtual dirs we try to lookup the file... path = IOHelper.FindFile(path); @@ -751,6 +760,8 @@ namespace umbraco.cms.businesslogic.packager private static string UnPack(string zipName, bool deleteFile) { + // Unzip + //the temp directory will be the package GUID - this keeps it consistent! //the zipName is always the package Guid.umb @@ -763,28 +774,48 @@ namespace umbraco.cms.businesslogic.packager if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); Directory.CreateDirectory(tempDir); - //Have to open zip & get each entry & unzip to flatten - //Some Umbraco packages are nested in another folder, where others have all the files at the root - using (var archive = ZipFile.OpenRead(zipName)) + var s = new ZipInputStream(File.OpenRead(zipName)); + + ZipEntry theEntry; + while ((theEntry = s.GetNextEntry()) != null) { - foreach (var entry in archive.Entries) + string fileName = Path.GetFileName(theEntry.Name); + + if (fileName != String.Empty) { - //Name will be empty if it's a folder - //Otherwise its the filename - where FullName will include any nested folders too - if (string.IsNullOrEmpty(entry.Name) == false) + FileStream streamWriter = File.Create(tempDir + Path.DirectorySeparatorChar + fileName); + + int size = 2048; + byte[] data = new byte[2048]; + while (true) { - var fullPath = Path.Combine(tempDir, entry.Name); - entry.ExtractToFile(fullPath); + size = s.Read(data, 0, data.Length); + if (size > 0) + { + streamWriter.Write(data, 0, size); + } + else + { + break; + } } + + streamWriter.Close(); + } } + // Clean up + s.Close(); + if (deleteFile) { File.Delete(zipName); } + return tempDir; + } #endregion @@ -800,18 +831,16 @@ namespace umbraco.cms.businesslogic.packager private void OnPackageInstalled(InstalledPackage insPack) { // getting an InstallationSummary for sending to the PackagingService.ImportedPackage event - var fileService = ApplicationContext.Current.Services.FileService; - var macroService = ApplicationContext.Current.Services.MacroService; - var contentTypeService = ApplicationContext.Current.Services.ContentTypeService; - var dataTypeService = ApplicationContext.Current.Services.DataTypeService; - var localizationService = ApplicationContext.Current.Services.LocalizationService; + var fileService = Current.Services.FileService; + var macroService = Current.Services.MacroService; + var contentTypeService = Current.Services.ContentTypeService; + var dataTypeService = Current.Services.DataTypeService; + var localizationService = Current.Services.LocalizationService; var installationSummary = insPack.GetInstallationSummary(contentTypeService, dataTypeService, fileService, localizationService, macroService); installationSummary.PackageInstalled = true; - var metadata = insPack.GetMetaData(); - - var args = new ImportPackageEventArgs(installationSummary, metadata, false); + var args = new ImportPackageEventArgs(installationSummary, false); PackagingService.OnImportedPackage(args); } } diff --git a/src/Umbraco.Web/_Legacy/Packager/Package.cs b/src/Umbraco.Web/_Legacy/Packager/Package.cs new file mode 100644 index 0000000000..2116e64ea1 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Packager/Package.cs @@ -0,0 +1,145 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.Membership; + +namespace umbraco.cms.businesslogic.packager +{ + [Obsolete("This class is not used and will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + public class Package + { + public Package() + { + } + + /// + /// Initialize package install status object by specifying the internal id of the installation. + /// The id is specific to the local umbraco installation and cannot be used to identify the package in general. + /// Use the Package(Guid) constructor to check whether a package has been installed + /// + /// The internal id. + public Package(int Id) + { + Initialize(Id); + } + + public Package(Guid Id) + { + using (var scope = Current.ScopeProvider.CreateScope()) + { + int installStatusId = scope.Database.ExecuteScalar( + "select id from umbracoInstalledPackages where package = @package and upgradeId = 0", new { package = Id}); + + if (installStatusId > 0) + Initialize(installStatusId); + else + throw new ArgumentException("Package with id '" + Id.ToString() + "' is not installed"); + + scope.Complete(); + } + } + + private void Initialize(int id) + { + using (var scope = Current.ScopeProvider.CreateScope()) + { + var f = scope.Database.Fetch( + "select id, uninstalled, upgradeId, installDate, userId, package, versionMajor, versionMinor, versionPatch from umbracoInstalledPackages where id = @id", + new { id }).FirstOrDefault(); + if (f != null) + { + Id = id; + Uninstalled = f.uninstalled; + UpgradeId = f.upgradeId; + InstallDate = f.installDate; + User = Current.Services.UserService.GetUserById(f.userId); + PackageId = f.package; + VersionMajor = f.versionMajor; + VersionMinor = f.versionMinor; + VersionPatch = f.versionPatch; + } + + scope.Complete(); + } + } + + [MethodImpl(MethodImplOptions.Synchronized)] // ;-(( + public void Save() + { + using (var scope = Current.ScopeProvider.CreateScope()) + { + // check if package status exists + if (Id == 0) + { + // The method is synchronized + scope.Database.Execute("INSERT INTO umbracoInstalledPackages (uninstalled, upgradeId, installDate, userId, versionMajor, versionMinor, versionPatch) VALUES (@uninstalled, @upgradeId, @installDate, @userId, @versionMajor, @versionMinor, @versionPatch)", + new + { + uninstalled = Uninstalled, + upgradeId = UpgradeId, + installData = InstallDate, + userId = User.Id, + versionMajor = VersionMajor, + versionMinor = VersionMinor, + versionPath = VersionPatch, + id = Id + }); + Id = scope.Database.ExecuteScalar("SELECT MAX(id) FROM umbracoInstalledPackages"); + } + + scope.Database.Execute( + "update umbracoInstalledPackages set " + + "uninstalled = @uninstalled, " + + "upgradeId = @upgradeId, " + + "installDate = @installDate, " + + "userId = @userId, " + + "versionMajor = @versionMajor, " + + "versionMinor = @versionMinor, " + + "versionPatch = @versionPatch " + + "where id = @id", + new + { + uninstalled = Uninstalled, + upgradeId = UpgradeId, + installData = InstallDate, + userId = User.Id, + versionMajor = VersionMajor, + versionMinor = VersionMinor, + versionPath = VersionPatch, + id = Id + }); + + scope.Complete(); + } + } + + public bool Uninstalled { get; set; } + + + public IUser User { get; set; } + + + public DateTime InstallDate { get; set; } + + + public int Id { get; set; } + + + public int UpgradeId { get; set; } + + + public Guid PackageId { get; set; } + + + public int VersionPatch { get; set; } + + + public int VersionMinor { get; set; } + + + public int VersionMajor { get; set; } + } +} diff --git a/src/Umbraco.Web/_Legacy/Packager/PackageInstance/CreatedPackage.cs b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/CreatedPackage.cs new file mode 100644 index 0000000000..5d811ed3d8 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/CreatedPackage.cs @@ -0,0 +1,389 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using Lucene.Net.Documents; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using File = System.IO.File; + + +namespace umbraco.cms.businesslogic.packager +{ + public class CreatedPackage + { + + public static CreatedPackage GetById(int id) + { + var pack = new CreatedPackage(); + pack.Data = data.Package(id, IOHelper.MapPath(Settings.CreatedPackagesSettings)); + return pack; + } + + public static CreatedPackage MakeNew(string name) + { + var pack = new CreatedPackage + { + Data = data.MakeNew(name, IOHelper.MapPath(Settings.CreatedPackagesSettings)) + }; + + + return pack; + } + + public void Save() + { + data.Save(this.Data, IOHelper.MapPath(Settings.CreatedPackagesSettings)); + } + + public void Delete() + { + data.Delete(this.Data.Id, IOHelper.MapPath(Settings.CreatedPackagesSettings)); + } + + public PackageInstance Data { get; set; } + + public static List GetAllCreatedPackages() + { + var val = new List(); + + foreach (var pack in data.GetAllPackages(IOHelper.MapPath(Settings.CreatedPackagesSettings))) + { + var crPack = new CreatedPackage(); + crPack.Data = pack; + val.Add(crPack); + } + + return val; + } + + private static XmlDocument _packageManifest; + private static void CreatePackageManifest() + { + _packageManifest = new XmlDocument(); + var xmldecl = _packageManifest.CreateXmlDeclaration("1.0", "UTF-8", "no"); + + _packageManifest.AppendChild(xmldecl); + + //root node + XmlNode umbPackage = _packageManifest.CreateElement("umbPackage"); + _packageManifest.AppendChild(umbPackage); + //Files node + umbPackage.AppendChild(_packageManifest.CreateElement("files")); + } + + private static void AppendElement(XmlNode node) + { + var root = _packageManifest.SelectSingleNode("/umbPackage"); + root.AppendChild(node); + } + + + public void Publish() + { + + var package = this; + var pack = package.Data; + + var outInt = 0; + + //Path checking... + var localPath = IOHelper.MapPath(SystemDirectories.Media + "/" + pack.Folder); + + if (Directory.Exists(localPath) == false) + Directory.CreateDirectory(localPath); + + //Init package file... + CreatePackageManifest(); + //Info section.. + AppendElement(PackagerUtility.PackageInfo(pack, _packageManifest)); + + //Documents and tags... + var contentNodeId = 0; + if (string.IsNullOrEmpty(pack.ContentNodeId) == false && int.TryParse(pack.ContentNodeId, out contentNodeId)) + { + if (contentNodeId > 0) + { + //Create the Documents/DocumentSet node + XmlNode documents = _packageManifest.CreateElement("Documents"); + XmlNode documentSet = _packageManifest.CreateElement("DocumentSet"); + XmlAttribute importMode = _packageManifest.CreateAttribute("importMode", ""); + importMode.Value = "root"; + documentSet.Attributes.Append(importMode); + documents.AppendChild(documentSet); + + //load content from umbraco. + //var umbDocument = new Document(contentNodeId); + //var x = umbDocument.ToXml(_packageManifest, pack.ContentLoadChildNodes); + var udoc = Current.Services.ContentService.GetById(contentNodeId); + var xe = pack.ContentLoadChildNodes ? udoc.ToDeepXml(Current.Services.PackagingService) : udoc.ToXml(Current.Services.PackagingService); + var x = xe.GetXmlNode(_packageManifest); + documentSet.AppendChild(x); + + AppendElement(documents); + + ////Create the TagProperties node - this is used to store a definition for all + //// document properties that are tags, this ensures that we can re-import tags properly + //XmlNode tagProps = _packageManifest.CreateElement("TagProperties"); + + ////before we try to populate this, we'll do a quick lookup to see if any of the documents + //// being exported contain published tags. + //var allExportedIds = documents.SelectNodes("//@id").Cast() + // .Select(x => x.Value.TryConvertTo()) + // .Where(x => x.Success) + // .Select(x => x.Result) + // .ToArray(); + //var allContentTags = new List(); + //foreach (var exportedId in allExportedIds) + //{ + // allContentTags.AddRange( + // Current.Services.TagService.GetTagsForEntity(exportedId)); + //} + + ////This is pretty round-about but it works. Essentially we need to get the properties that are tagged + //// but to do that we need to lookup by a tag (string) + //var allTaggedEntities = new List(); + //foreach (var group in allContentTags.Select(x => x.Group).Distinct()) + //{ + // allTaggedEntities.AddRange( + // Current.Services.TagService.GetTaggedContentByTagGroup(group)); + //} + + ////Now, we have all property Ids/Aliases and their referenced document Ids and tags + //var allExportedTaggedEntities = allTaggedEntities.Where(x => allExportedIds.Contains(x.EntityId)) + // .DistinctBy(x => x.EntityId) + // .OrderBy(x => x.EntityId); + + //foreach (var taggedEntity in allExportedTaggedEntities) + //{ + // foreach (var taggedProperty in taggedEntity.TaggedProperties.Where(x => x.Tags.Any())) + // { + // XmlNode tagProp = _packageManifest.CreateElement("TagProperty"); + // var docId = _packageManifest.CreateAttribute("docId", ""); + // docId.Value = taggedEntity.EntityId.ToString(CultureInfo.InvariantCulture); + // tagProp.Attributes.Append(docId); + + // var propertyAlias = _packageManifest.CreateAttribute("propertyAlias", ""); + // propertyAlias.Value = taggedProperty.PropertyTypeAlias; + // tagProp.Attributes.Append(propertyAlias); + + // var group = _packageManifest.CreateAttribute("group", ""); + // group.Value = taggedProperty.Tags.First().Group; + // tagProp.Attributes.Append(group); + + // tagProp.AppendChild(_packageManifest.CreateCDataSection( + // JsonConvert.SerializeObject(taggedProperty.Tags.Select(x => x.Text).ToArray()))); + + // tagProps.AppendChild(tagProp); + // } + //} + + //AppendElement(tagProps); + + } + } + + //Document types.. + var dtl = new List(); + var docTypes = _packageManifest.CreateElement("DocumentTypes"); + foreach (var dtId in pack.Documenttypes) + { + if (int.TryParse(dtId, out outInt)) + { + var docT = Current.Services.ContentTypeService.Get(outInt); + //DocumentType docT = new DocumentType(outInt); + + AddDocumentType(docT, ref dtl); + + } + } + + var exporter = new EntityXmlSerializer(); + + foreach (var d in dtl) + { + var xml = exporter.Serialize(Current.Services.DataTypeService, Current.Services.ContentTypeService, d); + var xNode = xml.GetXmlNode(); + var n = (XmlElement) _packageManifest.ImportNode(xNode, true); + docTypes.AppendChild(n); + } + + AppendElement(docTypes); + + //Templates + var templates = _packageManifest.CreateElement("Templates"); + foreach (var templateId in pack.Templates) + { + if (int.TryParse(templateId, out outInt)) + { + var t = Current.Services.FileService.GetTemplate(outInt); + + var serializer = new EntityXmlSerializer(); + var serialized = serializer.Serialize(t); + var n = serialized.GetXmlNode(_packageManifest); + + + templates.AppendChild(n); + } + } + AppendElement(templates); + + //Stylesheets + var stylesheets = _packageManifest.CreateElement("Stylesheets"); + foreach (var stylesheetName in pack.Stylesheets) + { + if (stylesheetName.IsNullOrWhiteSpace()) continue; + var stylesheetXmlNode = PackagerUtility.Stylesheet(stylesheetName, true, _packageManifest); + if (stylesheetXmlNode != null) + stylesheets.AppendChild(stylesheetXmlNode); + } + AppendElement(stylesheets); + + //Macros + var macros = _packageManifest.CreateElement("Macros"); + foreach (var macroId in pack.Macros) + { + if (int.TryParse(macroId, out outInt)) + { + macros.AppendChild(PackagerUtility.Macro(int.Parse(macroId), true, localPath, _packageManifest)); + } + } + AppendElement(macros); + + //Dictionary Items + var dictionaryItems = _packageManifest.CreateElement("DictionaryItems"); + foreach (var dictionaryId in pack.DictionaryItems) + { + if (int.TryParse(dictionaryId, out outInt)) + { + var di = Current.Services.LocalizationService.GetDictionaryItemById(outInt); + var entitySerializer = new EntityXmlSerializer(); + var xmlNode = entitySerializer.Serialize(di).GetXmlNode(_packageManifest); + dictionaryItems.AppendChild(xmlNode); + } + } + AppendElement(dictionaryItems); + + //Languages + var languages = _packageManifest.CreateElement("Languages"); + foreach (var langId in pack.Languages) + { + if (int.TryParse(langId, out outInt)) + { + var lang = Current.Services.LocalizationService.GetLanguageById(outInt); + + var serializer = new EntityXmlSerializer(); + var xml = serializer.Serialize(lang); + var n = xml.GetXmlNode(_packageManifest); + + languages.AppendChild(n); + } + } + AppendElement(languages); + + //TODO: Fix this! ... actually once we use the new packager we don't need to + + ////Datatypes + //var dataTypes = _packageManifest.CreateElement("DataTypes"); + //foreach (var dtId in pack.DataTypes) + //{ + // if (int.TryParse(dtId, out outInt)) + // { + // datatype.DataTypeDefinition dtd = new datatype.DataTypeDefinition(outInt); + // dataTypes.AppendChild(dtd.ToXml(_packageManifest)); + // } + //} + //AppendElement(dataTypes); + + //Files + foreach (var fileName in pack.Files) + { + PackagerUtility.AppendFileToManifest(fileName, localPath, _packageManifest); + } + + //Load control on install... + if (string.IsNullOrEmpty(pack.LoadControl) == false) + { + XmlNode control = _packageManifest.CreateElement("control"); + control.InnerText = pack.LoadControl; + PackagerUtility.AppendFileToManifest(pack.LoadControl, localPath, _packageManifest); + AppendElement(control); + } + + //Actions + if (string.IsNullOrEmpty(pack.Actions) == false) + { + try + { + var xdActions = new XmlDocument(); + xdActions.LoadXml("" + pack.Actions + ""); + var actions = xdActions.DocumentElement.SelectSingleNode("."); + + + if (actions != null) + { + actions = _packageManifest.ImportNode(actions, true).Clone(); + AppendElement(actions); + } + } + catch { } + } + + var manifestFileName = localPath + "/package.xml"; + + if (File.Exists(manifestFileName)) + File.Delete(manifestFileName); + + _packageManifest.Save(manifestFileName); + _packageManifest = null; + + + //string packPath = Settings.PackagerRoot.Replace(System.IO.Path.DirectorySeparatorChar.ToString(), "/") + "/" + pack.Name.Replace(' ', '_') + "_" + pack.Version.Replace(' ', '_') + "." + Settings.PackageFileExtension; + + // check if there's a packages directory below media + var packagesDirectory = SystemDirectories.Media + "/created-packages"; + if (Directory.Exists(IOHelper.MapPath(packagesDirectory)) == false) + { + Directory.CreateDirectory(IOHelper.MapPath(packagesDirectory)); + } + + + var packPath = packagesDirectory + "/" + (pack.Name + "_" + pack.Version).Replace(' ', '_') + "." + Settings.PackageFileExtension; + PackagerUtility.ZipPackage(localPath, IOHelper.MapPath(packPath)); + + pack.PackagePath = packPath; + + if (pack.PackageGuid.Trim() == "") + pack.PackageGuid = Guid.NewGuid().ToString(); + + package.Save(); + + //Clean up.. + File.Delete(localPath + "/package.xml"); + Directory.Delete(localPath, true); + } + + private void AddDocumentType(IContentType dt, ref List dtl) + { + if (dt.ParentId > 0) + { + var parent = Current.Services.ContentTypeService.Get(dt.ParentId); + if (parent != null) // could be a container + { + AddDocumentType(parent, ref dtl); + } + } + + if (dtl.Contains(dt) == false) + { + dtl.Add(dt); + } + } + + + + } +} diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/IPackageInstance.cs b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/IPackageInstance.cs similarity index 97% rename from src/umbraco.cms/businesslogic/Packager/PackageInstance/IPackageInstance.cs rename to src/Umbraco.Web/_Legacy/Packager/PackageInstance/IPackageInstance.cs index 29e53339d6..28c7c05b6b 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/IPackageInstance.cs +++ b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/IPackageInstance.cs @@ -1,28 +1,28 @@ -using System; -namespace umbraco.cms.businesslogic.packager{ - public interface IPackageInstance { - string Actions { get; set; } - string Author { get; set; } - string AuthorUrl { get; set; } - bool ContentLoadChildNodes { get; set; } - string ContentNodeId { get; set; } - System.Collections.Generic.List Documenttypes { get; set; } - System.Collections.Generic.List Files { get; set; } - string Folder { get; set; } - bool HasUpdate { get; set; } - int Id { get; set; } - string License { get; set; } - string LicenseUrl { get; set; } - string LoadControl { get; set; } - System.Collections.Generic.List Macros { get; set; } - string Name { get; set; } - string PackageGuid { get; set; } - string PackagePath { get; set; } - string Readme { get; set; } - string RepositoryGuid { get; set; } - System.Collections.Generic.List Stylesheets { get; set; } - System.Collections.Generic.List Templates { get; set; } - string Url { get; set; } - string Version { get; set; } - } -} +using System; +namespace umbraco.cms.businesslogic.packager{ + public interface IPackageInstance { + string Actions { get; set; } + string Author { get; set; } + string AuthorUrl { get; set; } + bool ContentLoadChildNodes { get; set; } + string ContentNodeId { get; set; } + System.Collections.Generic.List Documenttypes { get; set; } + System.Collections.Generic.List Files { get; set; } + string Folder { get; set; } + bool HasUpdate { get; set; } + int Id { get; set; } + string License { get; set; } + string LicenseUrl { get; set; } + string LoadControl { get; set; } + System.Collections.Generic.List Macros { get; set; } + string Name { get; set; } + string PackageGuid { get; set; } + string PackagePath { get; set; } + string Readme { get; set; } + string RepositoryGuid { get; set; } + System.Collections.Generic.List Stylesheets { get; set; } + System.Collections.Generic.List Templates { get; set; } + string Url { get; set; } + string Version { get; set; } + } +} diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/InstalledPackage.cs similarity index 86% rename from src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs rename to src/Umbraco.Web/_Legacy/Packager/PackageInstance/InstalledPackage.cs index 172a9d6443..a358c69389 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/InstalledPackage.cs +++ b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/InstalledPackage.cs @@ -1,196 +1,199 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Auditing; -using Umbraco.Core.Logging; -using Umbraco.Core.IO; -using Umbraco.Core.Packaging.Models; -using Umbraco.Core.Services; - -namespace umbraco.cms.businesslogic.packager { - public class InstalledPackage - { - - private int _saveHitCount = 0; - - public static InstalledPackage GetById(int id) { - InstalledPackage pack = new InstalledPackage(); - pack.Data = data.Package(id, IOHelper.MapPath(Settings.InstalledPackagesSettings)); - return pack; - } - - public static InstalledPackage GetByGuid(string packageGuid) { - InstalledPackage pack = new InstalledPackage(); - pack.Data = data.Package(packageGuid, IOHelper.MapPath(Settings.InstalledPackagesSettings)); - return pack; - } - - public static InstalledPackage MakeNew(string name) { - InstalledPackage pack = new InstalledPackage(); - pack.Data = data.MakeNew(name, IOHelper.MapPath(Settings.InstalledPackagesSettings)); - pack.OnNew(EventArgs.Empty); - return pack; - } - - public void Save() - { -#if DEBUG - _saveHitCount++; - LogHelper.Info("The InstalledPackage class save method has been hit " + _saveHitCount + " times."); -#endif - this.FireBeforeSave(EventArgs.Empty); - data.Save(this.Data, IOHelper.MapPath(Settings.InstalledPackagesSettings)); - this.FireAfterSave(EventArgs.Empty); - } - - public static List GetAllInstalledPackages() { - - List val = new List(); - - foreach (PackageInstance pack in data.GetAllPackages(IOHelper.MapPath(Settings.InstalledPackagesSettings))) - { - InstalledPackage insPackage = new InstalledPackage(); - insPackage.Data = pack; - val.Add(insPackage); - } - - return val; - } - - private PackageInstance m_data; - public PackageInstance Data { - get { return m_data; } - set { m_data = value; } - } - - public void Delete(int userId) - { - Audit.Add(AuditTypes.PackagerUninstall, string.Format("Package '{0}' uninstalled. Package guid: {1}", Data.Name, Data.PackageGuid), userId, -1); - Delete(); - } - - public void Delete() { - this.FireBeforeDelete(EventArgs.Empty); - data.Delete(this.Data.Id, IOHelper.MapPath(Settings.InstalledPackagesSettings)); - this.FireAfterDelete(EventArgs.Empty); - } - - public static bool isPackageInstalled(string packageGuid) { - try - { - if (data.GetFromGuid(packageGuid, IOHelper.MapPath(Settings.InstalledPackagesSettings), true) == null) - return false; - else - return true; - } - catch (Exception ex) - { - LogHelper.Error("An error occured in isPackagedInstalled", ex); - return false; - } - } - - //EVENTS - public delegate void SaveEventHandler(InstalledPackage sender, EventArgs e); - public delegate void NewEventHandler(InstalledPackage sender, EventArgs e); - public delegate void DeleteEventHandler(InstalledPackage sender, EventArgs e); - - /// - /// Occurs when a macro is saved. - /// - public static event SaveEventHandler BeforeSave; - protected virtual void FireBeforeSave(EventArgs e) { - if (BeforeSave != null) - BeforeSave(this, e); - } - - public static event SaveEventHandler AfterSave; - protected virtual void FireAfterSave(EventArgs e) { - if (AfterSave != null) - AfterSave(this, e); - } - - public static event NewEventHandler New; - protected virtual void OnNew(EventArgs e) { - if (New != null) - New(this, e); - } - - public static event DeleteEventHandler BeforeDelete; - protected virtual void FireBeforeDelete(EventArgs e) { - if (BeforeDelete != null) - BeforeDelete(this, e); - } - - public static event DeleteEventHandler AfterDelete; - protected virtual void FireAfterDelete(EventArgs e) { - if (AfterDelete != null) - AfterDelete(this, e); - } - - /// - /// Used internally for creating an InstallationSummary (used in new PackagingService) representation of this InstalledPackage object. - /// - /// - /// - /// - /// - /// - /// - internal InstallationSummary GetInstallationSummary(IContentTypeService contentTypeService, IDataTypeService dataTypeService, IFileService fileService, ILocalizationService localizationService, IMacroService macroService) - { - var macros = TryGetIntegerIds(Data.Macros).Select(macroService.GetById).ToList(); - var templates = TryGetIntegerIds(Data.Templates).Select(fileService.GetTemplate).ToList(); - var contentTypes = TryGetIntegerIds(Data.Documenttypes).Select(contentTypeService.GetContentType).ToList(); - var dataTypes = TryGetIntegerIds(Data.DataTypes).Select(dataTypeService.GetDataTypeDefinitionById).ToList(); - var dictionaryItems = TryGetIntegerIds(Data.DictionaryItems).Select(localizationService.GetDictionaryItemById).ToList(); - var languages = TryGetIntegerIds(Data.Languages).Select(localizationService.GetLanguageById).ToList(); - - for (var i = 0; i < Data.Files.Count; i++) - { - var filePath = Data.Files[i]; - Data.Files[i] = filePath.GetRelativePath(); - } - - return new InstallationSummary - { - ContentTypesInstalled = contentTypes, - DataTypesInstalled = dataTypes, - DictionaryItemsInstalled = dictionaryItems, - FilesInstalled = Data.Files, - LanguagesInstalled = languages, - MacrosInstalled = macros, - MetaData = GetMetaData(), - TemplatesInstalled = templates, - }; - } - - internal MetaData GetMetaData() - { - return new MetaData() - { - AuthorName = Data.Author, - AuthorUrl = Data.AuthorUrl, - Control = Data.LoadControl, - License = Data.License, - LicenseUrl = Data.LicenseUrl, - Name = Data.Name, - Readme = Data.Readme, - Url = Data.Url, - Version = Data.Version - }; - } - - private static IEnumerable TryGetIntegerIds(IEnumerable ids) - { - var intIds = new List(); - foreach (var id in ids) - { - int parsed; - if (int.TryParse(id, out parsed)) - intIds.Add(parsed); - } - return intIds; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Packaging; +using Umbraco.Core.Services; + +namespace umbraco.cms.businesslogic.packager { + public class InstalledPackage + { + + private int _saveHitCount = 0; + + public static InstalledPackage GetById(int id) { + InstalledPackage pack = new InstalledPackage(); + pack.Data = data.Package(id, IOHelper.MapPath(Settings.InstalledPackagesSettings)); + return pack; + } + + public static InstalledPackage GetByGuid(string packageGuid) { + InstalledPackage pack = new InstalledPackage(); + pack.Data = data.Package(packageGuid, IOHelper.MapPath(Settings.InstalledPackagesSettings)); + return pack; + } + + public static InstalledPackage MakeNew(string name) { + InstalledPackage pack = new InstalledPackage(); + pack.Data = data.MakeNew(name, IOHelper.MapPath(Settings.InstalledPackagesSettings)); + pack.OnNew(EventArgs.Empty); + return pack; + } + + public void Save() + { +#if DEBUG + _saveHitCount++; + Current.Logger.Info("The InstalledPackage class save method has been hit " + _saveHitCount + " times."); +#endif + this.FireBeforeSave(EventArgs.Empty); + data.Save(this.Data, IOHelper.MapPath(Settings.InstalledPackagesSettings)); + this.FireAfterSave(EventArgs.Empty); + } + + public static List GetAllInstalledPackages() { + + List val = new List(); + + foreach (PackageInstance pack in data.GetAllPackages(IOHelper.MapPath(Settings.InstalledPackagesSettings))) + { + InstalledPackage insPackage = new InstalledPackage(); + insPackage.Data = pack; + val.Add(insPackage); + } + + return val; + } + + private PackageInstance m_data; + public PackageInstance Data { + get { return m_data; } + set { m_data = value; } + } + + public void Delete(int userId) + { + Current.Services.AuditService.Add(AuditType.PackagerUninstall, string.Format("Package '{0}' uninstalled. Package guid: {1}", Data.Name, Data.PackageGuid), userId, -1); + Delete(); + } + + public void Delete() { + this.FireBeforeDelete(EventArgs.Empty); + data.Delete(this.Data.Id, IOHelper.MapPath(Settings.InstalledPackagesSettings)); + this.FireAfterDelete(EventArgs.Empty); + } + + public static bool isPackageInstalled(string packageGuid) { + try + { + if (data.GetFromGuid(packageGuid, IOHelper.MapPath(Settings.InstalledPackagesSettings), true) == null) + return false; + else + return true; + } + catch (Exception ex) + { + Current.Logger.Error("An error occured in isPackagedInstalled", ex); + return false; + } + } + + //EVENTS + public delegate void SaveEventHandler(InstalledPackage sender, EventArgs e); + public delegate void NewEventHandler(InstalledPackage sender, EventArgs e); + public delegate void DeleteEventHandler(InstalledPackage sender, EventArgs e); + + /// + /// Occurs when a macro is saved. + /// + public static event SaveEventHandler BeforeSave; + protected virtual void FireBeforeSave(EventArgs e) { + if (BeforeSave != null) + BeforeSave(this, e); + } + + public static event SaveEventHandler AfterSave; + protected virtual void FireAfterSave(EventArgs e) { + if (AfterSave != null) + AfterSave(this, e); + } + + public static event NewEventHandler New; + protected virtual void OnNew(EventArgs e) { + if (New != null) + New(this, e); + } + + public static event DeleteEventHandler BeforeDelete; + protected virtual void FireBeforeDelete(EventArgs e) { + if (BeforeDelete != null) + BeforeDelete(this, e); + } + + public static event DeleteEventHandler AfterDelete; + protected virtual void FireAfterDelete(EventArgs e) { + if (AfterDelete != null) + AfterDelete(this, e); + } + + + /// + /// Used internally for creating an InstallationSummary (used in new PackagingService) representation of this InstalledPackage object. + /// + /// + /// + /// + /// + /// + /// + internal InstallationSummary GetInstallationSummary(IContentTypeService contentTypeService, IDataTypeService dataTypeService, IFileService fileService, ILocalizationService localizationService, IMacroService macroService) + { + var macros = TryGetIntegerIds(Data.Macros).Select(macroService.GetById).ToList(); + var templates = TryGetIntegerIds(Data.Templates).Select(fileService.GetTemplate).ToList(); + var contentTypes = TryGetIntegerIds(Data.Documenttypes).Select(contentTypeService.Get).ToList(); // fixme - media types? + var dataTypes = TryGetIntegerIds(Data.DataTypes).Select(dataTypeService.GetDataType).ToList(); + var dictionaryItems = TryGetIntegerIds(Data.DictionaryItems).Select(localizationService.GetDictionaryItemById).ToList(); + var languages = TryGetIntegerIds(Data.Languages).Select(localizationService.GetLanguageById).ToList(); + + for (var i = 0; i < Data.Files.Count; i++) + { + var filePath = Data.Files[i]; + Data.Files[i] = filePath.GetRelativePath(); + } + + return new InstallationSummary + { + ContentTypesInstalled = contentTypes, + DataTypesInstalled = dataTypes, + DictionaryItemsInstalled = dictionaryItems, + FilesInstalled = Data.Files, + LanguagesInstalled = languages, + MacrosInstalled = macros, + MetaData = GetMetaData(), + TemplatesInstalled = templates, + }; + } + + internal MetaData GetMetaData() + { + return new MetaData() + { + AuthorName = Data.Author, + AuthorUrl = Data.AuthorUrl, + Control = Data.LoadControl, + License = Data.License, + LicenseUrl = Data.LicenseUrl, + Name = Data.Name, + Readme = Data.Readme, + Url = Data.Url, + Version = Data.Version + }; + } + + private static IEnumerable TryGetIntegerIds(IEnumerable ids) + { + var intIds = new List(); + foreach (var id in ids) + { + int parsed; + if (int.TryParse(id, out parsed)) + intIds.Add(parsed); + } + return intIds; + } + } +} diff --git a/src/Umbraco.Web/_Legacy/Packager/PackageInstance/PackageActions.cs b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/PackageActions.cs new file mode 100644 index 0000000000..90645fa121 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/PackageActions.cs @@ -0,0 +1,71 @@ +using System; +using System.Xml; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core._Legacy.PackageActions; + + +namespace umbraco.cms.businesslogic.packager +{ + + /// + /// Package actions are executed on packge install / uninstall. + /// + public class PackageAction + { + + /// + /// Runs the package action with the specified action alias. + /// + /// Name of the package. + /// The action alias. + /// The action XML. + public static void RunPackageAction(string packageName, string actionAlias, XmlNode actionXml) + { + + foreach (var ipa in Current.PackageActions) + { + try + { + if (ipa.Alias() == actionAlias) + { + + ipa.Execute(packageName, actionXml); + } + } + catch (Exception ipaExp) + { + Current.Logger.Error(string.Format("Error loading package action '{0}' for package {1}", ipa.Alias(), packageName), ipaExp); + } + } + } + + /// + /// Undos the package action with the specified action alias. + /// + /// Name of the package. + /// The action alias. + /// The action XML. + public static void UndoPackageAction(string packageName, string actionAlias, System.Xml.XmlNode actionXml) + { + + foreach (IPackageAction ipa in Current.PackageActions) + { + try + { + if (ipa.Alias() == actionAlias) + { + + ipa.Undo(packageName, actionXml); + } + } + catch (Exception ipaExp) + { + Current.Logger.Error(string.Format("Error undoing package action '{0}' for package {1}", ipa.Alias(), packageName), ipaExp); + } + } + } + + } +} diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageInstance.cs b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/PackageInstance.cs similarity index 96% rename from src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageInstance.cs rename to src/Umbraco.Web/_Legacy/Packager/PackageInstance/PackageInstance.cs index 736a7c82d1..74c5a619b8 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageInstance.cs +++ b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/PackageInstance.cs @@ -1,103 +1,103 @@ -using System; - -using System.Xml; -using System.Xml.XPath; -using System.Collections.Generic; - -namespace umbraco.cms.businesslogic.packager -{ - public class PackageInstance - { - public int Id { get; set; } - - public string RepositoryGuid { get; set; } - - public string PackageGuid { get; set; } - - public bool HasUpdate { get; set; } - - public bool EnableSkins { get; set; } - - public Guid SkinRepoGuid { get; set; } - - public string Name { get; set; } - - public string Url { get; set; } - - public string Folder { get; set; } - - public string PackagePath { get; set; } - - public string Version { get; set; } - - /// - /// The minimum umbraco version that this package requires - /// - public Version UmbracoVersion { get; set; } - - public string Author { get; set; } - - public string AuthorUrl { get; set; } - - public string License { get; set; } - - public string LicenseUrl { get; set; } - - public string Readme { get; set; } - - public bool ContentLoadChildNodes { get; set; } - - public string ContentNodeId { get; set; } - - public List Macros { get; set; } - - public List Languages { get; set; } - - public List DictionaryItems { get; set; } - - public List Templates { get; set; } - - public List Documenttypes { get; set; } - - public List Stylesheets { get; set; } - - public List Files { get; set; } - - public string LoadControl { get; set; } - - public string Actions { get; set; } - - public List DataTypes { get; set; } - - public string IconUrl { get; set; } - - public PackageInstance() - { - SkinRepoGuid = Guid.Empty; - Name = string.Empty; - Url = string.Empty; - Folder = string.Empty; - PackagePath = string.Empty; - Version = string.Empty; - UmbracoVersion = null; - Author = string.Empty; - AuthorUrl = string.Empty; - License = string.Empty; - LicenseUrl = string.Empty; - Readme = string.Empty; - ContentNodeId = string.Empty; - IconUrl = string.Empty; - Macros = new List(); - Languages = new List(); - DictionaryItems = new List(); - Templates = new List(); - Documenttypes = new List(); - Stylesheets = new List(); - Files = new List(); - LoadControl = string.Empty; - DataTypes = new List(); - EnableSkins = false; - ContentLoadChildNodes = false; - } - } -} +using System; + +using System.Xml; +using System.Xml.XPath; +using System.Collections.Generic; + +namespace umbraco.cms.businesslogic.packager +{ + public class PackageInstance + { + public int Id { get; set; } + + public string RepositoryGuid { get; set; } + + public string PackageGuid { get; set; } + + public bool HasUpdate { get; set; } + + public bool EnableSkins { get; set; } + + public Guid SkinRepoGuid { get; set; } + + public string Name { get; set; } + + public string Url { get; set; } + + public string Folder { get; set; } + + public string PackagePath { get; set; } + + public string Version { get; set; } + + /// + /// The minimum umbraco version that this package requires + /// + public Version UmbracoVersion { get; set; } + + public string Author { get; set; } + + public string AuthorUrl { get; set; } + + public string License { get; set; } + + public string LicenseUrl { get; set; } + + public string Readme { get; set; } + + public bool ContentLoadChildNodes { get; set; } + + public string ContentNodeId { get; set; } + + public List Macros { get; set; } + + public List Languages { get; set; } + + public List DictionaryItems { get; set; } + + public List Templates { get; set; } + + public List Documenttypes { get; set; } + + public List Stylesheets { get; set; } + + public List Files { get; set; } + + public string LoadControl { get; set; } + + public string Actions { get; set; } + + public List DataTypes { get; set; } + + public string IconUrl { get; set; } + + public PackageInstance() + { + SkinRepoGuid = Guid.Empty; + Name = string.Empty; + Url = string.Empty; + Folder = string.Empty; + PackagePath = string.Empty; + Version = string.Empty; + UmbracoVersion = null; + Author = string.Empty; + AuthorUrl = string.Empty; + License = string.Empty; + LicenseUrl = string.Empty; + Readme = string.Empty; + ContentNodeId = string.Empty; + IconUrl = string.Empty; + Macros = new List(); + Languages = new List(); + DictionaryItems = new List(); + Templates = new List(); + Documenttypes = new List(); + Stylesheets = new List(); + Files = new List(); + LoadControl = string.Empty; + DataTypes = new List(); + EnableSkins = false; + ContentLoadChildNodes = false; + } + } +} diff --git a/src/Umbraco.Web/_Legacy/Packager/PackageInstance/PackagerUtility.cs b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/PackagerUtility.cs new file mode 100644 index 0000000000..d887a706d5 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Packager/PackageInstance/PackagerUtility.cs @@ -0,0 +1,334 @@ +using System; +using System.Collections; +using System.IO; +using System.Xml; +using ICSharpCode.SharpZipLib.Zip; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.IO; +using Umbraco.Core.Services; + +namespace umbraco.cms.businesslogic.packager +{ + /// + /// A utillity class for working with packager data. + /// It provides basic methods for adding new items to a package manifest, moving files and other misc. + /// + public class PackagerUtility + { + /// + /// Creates a package manifest containing name, license, version and other meta data. + /// + /// The packinstance. + /// The xml document. + /// + public static XmlNode PackageInfo(PackageInstance pack, XmlDocument doc) + { + XmlNode info = doc.CreateElement("info"); + + //Package info + XmlNode package = doc.CreateElement("package"); + package.AppendChild(CreateNode("name", pack.Name, doc)); + package.AppendChild(CreateNode("version", pack.Version, doc)); + package.AppendChild(CreateNode("iconUrl", pack.IconUrl, doc)); + + XmlNode license = CreateNode("license", pack.License, doc); + license.Attributes.Append(CreateAttribute("url", pack.LicenseUrl, doc)); + package.AppendChild(license); + + package.AppendChild(CreateNode("url", pack.Url, doc)); + + XmlNode requirements = doc.CreateElement("requirements"); + //NOTE: The defaults are 3.0.0 - I'm just leaving that here since that's the way it's been //SD + requirements.AppendChild(CreateNode("major", pack.UmbracoVersion == null ? "3" : pack.UmbracoVersion.Major.ToInvariantString(), doc)); + requirements.AppendChild(CreateNode("minor", pack.UmbracoVersion == null ? "0" : pack.UmbracoVersion.Minor.ToInvariantString(), doc)); + requirements.AppendChild(CreateNode("patch", pack.UmbracoVersion == null ? "0" : pack.UmbracoVersion.Build.ToInvariantString(), doc)); + if (pack.UmbracoVersion != null) + requirements.Attributes.Append(CreateAttribute("type", "strict", doc)); + + package.AppendChild(requirements); + info.AppendChild(package); + + //Author + XmlNode author = CreateNode("author", "", doc); + author.AppendChild(CreateNode("name", pack.Author, doc)); + author.AppendChild(CreateNode("website", pack.AuthorUrl, doc)); + info.AppendChild(author); + + info.AppendChild(CreateNode("readme", "", doc)); + + return info; + } + + + /// + /// Converts an umbraco template to a package xml node + /// + /// The template id. + /// The xml doc. + /// + public static XmlNode Template(int templateId, XmlDocument doc) + { + var tmpl = Current.Services.FileService.GetTemplate(templateId); + //Template tmpl = new Template(templateId); + + XmlNode template = doc.CreateElement("Template"); + template.AppendChild(CreateNode("Name", tmpl.Name, doc)); + template.AppendChild(CreateNode("Alias", tmpl.Alias, doc)); + + //if (tmpl.MasterTemplate != 0) + if (string.IsNullOrWhiteSpace(tmpl.MasterTemplateAlias) == false) + template.AppendChild(CreateNode("Master", tmpl.MasterTemplateAlias, doc)); + + template.AppendChild(CreateNode("Design", "", doc)); + + return template; + } + + /// + /// Converts a umbraco stylesheet to a package xml node + /// + /// The name of the stylesheet. + /// if set to true [incluce properties]. + /// The doc. + /// + public static XmlNode Stylesheet(string name, bool includeProperties, XmlDocument doc) + { + if (doc == null) throw new ArgumentNullException("doc"); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + + var fileService = Current.Services.FileService; + var sts = fileService.GetStylesheetByName(name); + var stylesheet = doc.CreateElement("Stylesheet"); + stylesheet.AppendChild(CreateNode("Name", sts.Alias, doc)); + stylesheet.AppendChild(CreateNode("FileName", sts.Name, doc)); + stylesheet.AppendChild(CreateNode("Content", "", doc)); + if (includeProperties) + { + var properties = doc.CreateElement("Properties"); + foreach (var ssP in sts.Properties) + { + var property = doc.CreateElement("Property"); + property.AppendChild(CreateNode("Name", ssP.Name, doc)); + property.AppendChild(CreateNode("Alias", ssP.Alias, doc)); + property.AppendChild(CreateNode("Value", ssP.Value, doc)); + } + stylesheet.AppendChild(properties); + } + return stylesheet; + } + + /// + /// Converts a macro to a package xml node + /// + /// The macro id. + /// if set to true [append file]. + /// The package directory. + /// The doc. + /// + public static XmlNode Macro(int macroId, bool appendFile, string packageDirectory, XmlDocument doc) + { + var mcr = Current.Services.MacroService.GetById(macroId); + + if (appendFile) + { + if (!string.IsNullOrEmpty(mcr.XsltPath)) + AppendFileToManifest(IOHelper.ResolveUrl(SystemDirectories.Xslt) + "/" + mcr.XsltPath, packageDirectory, doc); + + //TODO: Clearly the packager hasn't worked very well for packaging Partial Views to date since there is no logic in here for that + + //if (!string.IsNullOrEmpty(mcr.ScriptingFile)) + // AppendFileToManifest(IOHelper.ResolveUrl(SystemDirectories.MacroScripts) + "/" + mcr.ScriptingFile, packageDirectory, doc); + + if (!string.IsNullOrEmpty(mcr.ControlType)) + AppendFileToManifest(mcr.ControlType, packageDirectory, doc); + } + + var serializer = new EntityXmlSerializer(); + var xml = serializer.Serialize(mcr); + return xml.GetXmlNode(doc); + } + + + /// + /// Appends a file to package manifest and copies the file to the correct folder. + /// + /// The path. + /// The package directory. + /// The doc. + public static void AppendFileToManifest(string path, string packageDirectory, XmlDocument doc) + { + if (!path.StartsWith("~/") && !path.StartsWith("/")) + path = "~/" + path; + + string serverPath = IOHelper.MapPath(path); + + if (System.IO.File.Exists(serverPath)) + + AppendFileXml(path, packageDirectory, doc); + else if (System.IO.Directory.Exists(serverPath)) + ProcessDirectory(path, packageDirectory, doc); + } + + + + //Process files in directory and add them to package + private static void ProcessDirectory(string path, string packageDirectory, XmlDocument doc) + { + string serverPath = IOHelper.MapPath(path); + if (System.IO.Directory.Exists(serverPath)) + { + System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(serverPath); + + foreach (System.IO.FileInfo file in di.GetFiles()) + AppendFileXml(path + "/" + file.Name, packageDirectory, doc); + + foreach (System.IO.DirectoryInfo dir in di.GetDirectories()) + ProcessDirectory(path + "/" + dir.Name, packageDirectory, doc); + } + } + + private static void AppendFileXml(string path, string packageDirectory, XmlDocument doc) + { + + string serverPath = IOHelper.MapPath(path); + + string orgPath = path.Substring(0, (path.LastIndexOf('/'))); + string orgName = path.Substring((path.LastIndexOf('/') + 1)); + string newFileName = orgName; + + if (System.IO.File.Exists(packageDirectory + "/" + orgName)) + { + string fileGuid = System.Guid.NewGuid().ToString(); + newFileName = fileGuid + "_" + newFileName; + } + + //Copy file to directory for zipping... + System.IO.File.Copy(serverPath, packageDirectory + "/" + newFileName, true); + + //Append file info to files xml node + XmlNode files = doc.SelectSingleNode("/umbPackage/files"); + + XmlNode file = doc.CreateElement("file"); + file.AppendChild(CreateNode("guid", newFileName, doc)); + file.AppendChild(CreateNode("orgPath", orgPath == "" ? "/" : orgPath, doc)); + file.AppendChild(CreateNode("orgName", orgName, doc)); + + files.AppendChild(file); + } + + /// + /// Determines whether the file is in the package manifest + /// + /// The GUID. + /// The doc. + /// + /// true if [is file in manifest]; otherwise, false. + /// + public static bool IsFileInManifest(string guid, XmlDocument doc) + { + return false; + } + + private static XmlNode CreateNode(string name, string value, XmlDocument doc) + { + var node = doc.CreateElement(name); + node.InnerXml = value; + return node; + } + + private static XmlAttribute CreateAttribute(string name, string value, XmlDocument doc) + { + var attribute = doc.CreateAttribute(name); + attribute.Value = value; + return attribute; + } + + /// + /// Zips the package. + /// + /// The path. + /// The save path. + public static void ZipPackage(string Path, string savePath) + { + string OutPath = savePath; + + ArrayList ar = GenerateFileList(Path); + // generate file list + // find number of chars to remove from orginal file path + int TrimLength = (Directory.GetParent(Path)).ToString().Length; + + TrimLength += 1; + + //remove '\' + FileStream ostream; + + byte[] obuffer; + + ZipOutputStream oZipStream = new ZipOutputStream(System.IO.File.Create(OutPath)); + // create zip stream + + + oZipStream.SetLevel(9); + // 9 = maximum compression level + ZipEntry oZipEntry; + + foreach (string Fil in ar) // for each file, generate a zipentry + { + oZipEntry = new ZipEntry(Fil.Remove(0, TrimLength)); + oZipStream.PutNextEntry(oZipEntry); + + + if (!Fil.EndsWith(@"/")) // if a file ends with '/' its a directory + { + ostream = File.OpenRead(Fil); + + obuffer = new byte[ostream.Length]; + + // byte buffer + ostream.Read(obuffer, 0, obuffer.Length); + + oZipStream.Write(obuffer, 0, obuffer.Length); + ostream.Close(); + } + } + oZipStream.Finish(); + oZipStream.Close(); + oZipStream.Dispose(); + oZipStream = null; + + oZipEntry = null; + + + ostream = null; + ar.Clear(); + ar = null; + } + + private static ArrayList GenerateFileList(string Dir) + { + ArrayList mid = new ArrayList(); + + bool Empty = true; + + // add each file in directory + foreach (string file in Directory.GetFiles(Dir)) + { + mid.Add(file); + Empty = false; + } + + // if directory is completely empty, add it + if (Empty && Directory.GetDirectories(Dir).Length == 0) + mid.Add(Dir + @"/"); + + // do this recursively + foreach (string dirs in Directory.GetDirectories(Dir)) + { + foreach (object obj in GenerateFileList(dirs)) + mid.Add(obj); + } + return mid; // return file list + } + } +} diff --git a/src/umbraco.cms/businesslogic/Packager/Repositories/Repository.cs b/src/Umbraco.Web/_Legacy/Packager/Repositories/Repository.cs similarity index 86% rename from src/umbraco.cms/businesslogic/Packager/Repositories/Repository.cs rename to src/Umbraco.Web/_Legacy/Packager/Repositories/Repository.cs index 8609504313..9ee22c04ea 100644 --- a/src/umbraco.cms/businesslogic/Packager/Repositories/Repository.cs +++ b/src/Umbraco.Web/_Legacy/Packager/Repositories/Repository.cs @@ -1,299 +1,291 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml; -using System.IO; -using System.Net; -using Umbraco.Core; -using Umbraco.Core.Auditing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.IO; - -namespace umbraco.cms.businesslogic.packager.repositories -{ - [Obsolete("This should not be used and will be removed in future Umbraco versions")] - public class Repository : DisposableObjectSlim - { - public string Guid { get; private set; } - - public string Name { get; private set; } - - public string RepositoryUrl { get; private set; } - - public string WebserviceUrl { get; private set; } - - - public RepositoryWebservice Webservice - { - get - { - var repo = new RepositoryWebservice(WebserviceUrl); - return repo; - } - } - - public SubmitStatus SubmitPackage(string authorGuid, PackageInstance package, byte[] doc) - { - - string packageName = package.Name; - string packageGuid = package.PackageGuid; - string description = package.Readme; - string packageFile = package.PackagePath; - - - System.IO.FileStream fs1 = null; - - try - { - - byte[] pack = new byte[0]; - fs1 = System.IO.File.Open(IOHelper.MapPath(packageFile), FileMode.Open, FileAccess.Read); - pack = new byte[fs1.Length]; - fs1.Read(pack, 0, (int) fs1.Length); - fs1.Close(); - fs1 = null; - - byte[] thumb = new byte[0]; //todo upload thumbnail... - - return Webservice.SubmitPackage(Guid, authorGuid, packageGuid, pack, doc, thumb, packageName, "", "", description); - } - catch (Exception ex) - { - LogHelper.Error("An error occurred in SubmitPackage", ex); - - return SubmitStatus.Error; - } - } - - public static List getAll() - { - - var repositories = new List(); - - foreach (var r in UmbracoConfig.For.UmbracoSettings().PackageRepositories.Repositories) - { - var repository = new Repository - { - Guid = r.Id.ToString(), - Name = r.Name - }; - - repository.RepositoryUrl = r.RepositoryUrl; - repository.WebserviceUrl = repository.RepositoryUrl.Trim('/') + "/" + r.WebServiceUrl.Trim('/'); - if (r.HasCustomWebServiceUrl) - { - string wsUrl = r.WebServiceUrl; - - if (wsUrl.Contains("://")) - { - repository.WebserviceUrl = r.WebServiceUrl; - } - else - { - repository.WebserviceUrl = repository.RepositoryUrl.Trim('/') + "/" + wsUrl.Trim('/'); - } - } - - repositories.Add(repository); - } - - return repositories; - } - - public static Repository getByGuid(string repositoryGuid) - { - Guid id; - if (System.Guid.TryParse(repositoryGuid, out id) == false) - { - throw new FormatException("The repositoryGuid is not a valid GUID"); - } - - var found = UmbracoConfig.For.UmbracoSettings().PackageRepositories.Repositories.FirstOrDefault(x => x.Id == id); - if (found == null) - { - return null; - } - - var repository = new Repository - { - Guid = found.Id.ToString(), - Name = found.Name - }; - - repository.RepositoryUrl = found.RepositoryUrl; - repository.WebserviceUrl = repository.RepositoryUrl.Trim('/') + "/" + found.WebServiceUrl.Trim('/'); - - if (found.HasCustomWebServiceUrl) - { - string wsUrl = found.WebServiceUrl; - - if (wsUrl.Contains("://")) - { - repository.WebserviceUrl = found.WebServiceUrl; - } - else - { - repository.WebserviceUrl = repository.RepositoryUrl.Trim('/') + "/" + wsUrl.Trim('/'); - } - } - - return repository; - } - - - - //shortcut method to download pack from repo and place it on the server... - public string fetch(string packageGuid) - { - return fetch(packageGuid, string.Empty); - } - - public string fetch(string packageGuid, int userId) - { - // log - Audit.Add(AuditTypes.PackagerInstall, - string.Format("Package {0} fetched from {1}", packageGuid, this.Guid), - userId, -1); - return fetch(packageGuid); - } - - /// - /// Used to get the correct package file from the repo for the current umbraco version - /// - /// - /// - /// - /// - public string GetPackageFile(string packageGuid, int userId, System.Version currentUmbracoVersion) - { - // log - Audit.Add(AuditTypes.PackagerInstall, - string.Format("Package {0} fetched from {1}", packageGuid, this.Guid), - userId, -1); - - var fileByteArray = Webservice.GetPackageFile(packageGuid, currentUmbracoVersion.ToString(3)); - - //successfull - if (fileByteArray.Length > 0) - { - // Check for package directory - if (Directory.Exists(IOHelper.MapPath(Settings.PackagerRoot)) == false) - Directory.CreateDirectory(IOHelper.MapPath(Settings.PackagerRoot)); - - using (var fs1 = new FileStream(IOHelper.MapPath(Settings.PackagerRoot + Path.DirectorySeparatorChar + packageGuid + ".umb"), FileMode.Create)) - { - fs1.Write(fileByteArray, 0, fileByteArray.Length); - fs1.Close(); - return "packages\\" + packageGuid + ".umb"; - } - } - - return ""; - } - - public bool HasConnection() - { - - string strServer = this.RepositoryUrl; - - try - { - - HttpWebRequest reqFP = (HttpWebRequest) HttpWebRequest.Create(strServer); - HttpWebResponse rspFP = (HttpWebResponse) reqFP.GetResponse(); - - if (HttpStatusCode.OK == rspFP.StatusCode) - { - - // HTTP = 200 - Internet connection available, server online - rspFP.Close(); - - return true; - - } - else - { - - // Other status - Server or connection not available - - rspFP.Close(); - - return false; - - } - - } - catch (WebException) - { - - // Exception - connection not available - - return false; - - } - } - +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; +using System.IO; +using System.Net; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.IO; +using Umbraco.Core.Models; - /// - /// This goes and fetches the Byte array for the package from OUR, but it's pretty strange - /// - /// - /// The package ID for the package file to be returned - /// - /// - /// This is a strange Umbraco version parameter - but it's not really an umbraco version, it's a special/odd version format like Version41 - /// but it's actually not used for the 7.5+ package installs so it's obsolete/unused. - /// - /// - public string fetch(string packageGuid, string key) - { - - byte[] fileByteArray = new byte[0]; - - if (key == string.Empty) +namespace umbraco.cms.businesslogic.packager.repositories +{ + [Obsolete("This should not be used and will be removed in future Umbraco versions")] + public class Repository : DisposableObject + { + public string Guid { get; private set; } + + public string Name { get; private set; } + + public string RepositoryUrl { get; private set; } + + public string WebserviceUrl { get; private set; } + + + public RepositoryWebservice Webservice + { + get { - //SD: this is odd, not sure why it returns a different package depending on the legacy xml schema but I'll leave it for now - if (UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema) - fileByteArray = Webservice.fetchPackage(packageGuid); - else - { - fileByteArray = Webservice.fetchPackageByVersion(packageGuid, Version.Version41); - } - } - else - { - fileByteArray = Webservice.fetchProtectedPackage(packageGuid, key); - } - - //successfull - if (fileByteArray.Length > 0) - { - - // Check for package directory - if (Directory.Exists(IOHelper.MapPath(Settings.PackagerRoot)) == false) - Directory.CreateDirectory(IOHelper.MapPath(Settings.PackagerRoot)); - - using (var fs1 = new FileStream(IOHelper.MapPath(Settings.PackagerRoot + Path.DirectorySeparatorChar + packageGuid + ".umb"), FileMode.Create)) - { - fs1.Write(fileByteArray, 0, fileByteArray.Length); - fs1.Close(); - return "packages\\" + packageGuid + ".umb"; - } - } - - // log - - return ""; - } - - /// - /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic. - /// - protected override void DisposeResources() - { - Webservice.Dispose(); - } - } -} + var repo = new RepositoryWebservice(WebserviceUrl); + return repo; + } + } + + public SubmitStatus SubmitPackage(string authorGuid, PackageInstance package, byte[] doc) + { + + string packageName = package.Name; + string packageGuid = package.PackageGuid; + string description = package.Readme; + string packageFile = package.PackagePath; + + + System.IO.FileStream fs1 = null; + + try + { + + byte[] pack = new byte[0]; + fs1 = System.IO.File.Open(IOHelper.MapPath(packageFile), FileMode.Open, FileAccess.Read); + pack = new byte[fs1.Length]; + fs1.Read(pack, 0, (int) fs1.Length); + fs1.Close(); + fs1 = null; + + byte[] thumb = new byte[0]; //todo upload thumbnail... + + return Webservice.SubmitPackage(Guid, authorGuid, packageGuid, pack, doc, thumb, packageName, "", "", description); + } + catch (Exception ex) + { + Current.Logger.Error("An error occurred in SubmitPackage", ex); + + return SubmitStatus.Error; + } + } + + public static List getAll() + { + + var repositories = new List(); + + foreach (var r in UmbracoConfig.For.UmbracoSettings().PackageRepositories.Repositories) + { + var repository = new Repository + { + Guid = r.Id.ToString(), + Name = r.Name + }; + + repository.RepositoryUrl = r.RepositoryUrl; + repository.WebserviceUrl = repository.RepositoryUrl.Trim('/') + "/" + r.WebServiceUrl.Trim('/'); + if (r.HasCustomWebServiceUrl) + { + string wsUrl = r.WebServiceUrl; + + if (wsUrl.Contains("://")) + { + repository.WebserviceUrl = r.WebServiceUrl; + } + else + { + repository.WebserviceUrl = repository.RepositoryUrl.Trim('/') + "/" + wsUrl.Trim('/'); + } + } + + repositories.Add(repository); + } + + return repositories; + } + + public static Repository getByGuid(string repositoryGuid) + { + Guid id; + if (System.Guid.TryParse(repositoryGuid, out id) == false) + { + throw new FormatException("The repositoryGuid is not a valid GUID"); + } + + var found = UmbracoConfig.For.UmbracoSettings().PackageRepositories.Repositories.FirstOrDefault(x => x.Id == id); + if (found == null) + { + return null; + } + + var repository = new Repository + { + Guid = found.Id.ToString(), + Name = found.Name + }; + + repository.RepositoryUrl = found.RepositoryUrl; + repository.WebserviceUrl = repository.RepositoryUrl.Trim('/') + "/" + found.WebServiceUrl.Trim('/'); + + if (found.HasCustomWebServiceUrl) + { + string wsUrl = found.WebServiceUrl; + + if (wsUrl.Contains("://")) + { + repository.WebserviceUrl = found.WebServiceUrl; + } + else + { + repository.WebserviceUrl = repository.RepositoryUrl.Trim('/') + "/" + wsUrl.Trim('/'); + } + } + + return repository; + } + + //shortcut method to download pack from repo and place it on the server... + public string fetch(string packageGuid) + { + return fetch(packageGuid, string.Empty); + } + + public string fetch(string packageGuid, int userId) + { + // log + Current.Services.AuditService.Add(AuditType.PackagerInstall, + string.Format("Package {0} fetched from {1}", packageGuid, this.Guid), + userId, -1); + return fetch(packageGuid); + } + + /// + /// Used to get the correct package file from the repo for the current umbraco version + /// + /// + /// + /// + /// + public string GetPackageFile(string packageGuid, int userId, System.Version currentUmbracoVersion) + { + // log - obsolete + //Audit.Add(AuditTypes.PackagerInstall, + // string.Format("Package {0} fetched from {1}", packageGuid, this.Guid), + // userId, -1); + + var fileByteArray = Webservice.GetPackageFile(packageGuid, currentUmbracoVersion.ToString(3)); + + //successfull + if (fileByteArray.Length > 0) + { + // Check for package directory + if (Directory.Exists(IOHelper.MapPath(Settings.PackagerRoot)) == false) + Directory.CreateDirectory(IOHelper.MapPath(Settings.PackagerRoot)); + + using (var fs1 = new FileStream(IOHelper.MapPath(Settings.PackagerRoot + Path.DirectorySeparatorChar + packageGuid + ".umb"), FileMode.Create)) + { + fs1.Write(fileByteArray, 0, fileByteArray.Length); + fs1.Close(); + return "packages\\" + packageGuid + ".umb"; + } + } + + return ""; + } + + public bool HasConnection() + { + + string strServer = this.RepositoryUrl; + + try + { + + HttpWebRequest reqFP = (HttpWebRequest) HttpWebRequest.Create(strServer); + HttpWebResponse rspFP = (HttpWebResponse) reqFP.GetResponse(); + + if (HttpStatusCode.OK == rspFP.StatusCode) + { + + // HTTP = 200 - Internet connection available, server online + rspFP.Close(); + + return true; + + } + else + { + + // Other status - Server or connection not available + + rspFP.Close(); + + return false; + + } + + } + catch (WebException) + { + + // Exception - connection not available + + return false; + + } + } + + /// + /// This goes and fetches the Byte array for the package from OUR, but it's pretty strange + /// + /// + /// The package ID for the package file to be returned + /// + /// + /// This is a strange Umbraco version parameter - but it's not really an umbraco version, it's a special/odd version format like Version41 + /// but it's actually not used for the 7.5+ package installs so it's obsolete/unused. + /// + /// + public string fetch(string packageGuid, string key) + { + + byte[] fileByteArray = new byte[0]; + + if (key == string.Empty) + { + fileByteArray = Webservice.fetchPackageByVersion(packageGuid, Version.Version41); + } + else + { + fileByteArray = Webservice.fetchProtectedPackage(packageGuid, key); + } + + //successfull + if (fileByteArray.Length > 0) + { + + // Check for package directory + if (Directory.Exists(IOHelper.MapPath(Settings.PackagerRoot)) == false) + Directory.CreateDirectory(IOHelper.MapPath(Settings.PackagerRoot)); + + using (var fs1 = new FileStream(IOHelper.MapPath(Settings.PackagerRoot + Path.DirectorySeparatorChar + packageGuid + ".umb"), FileMode.Create)) + { + fs1.Write(fileByteArray, 0, fileByteArray.Length); + fs1.Close(); + return "packages\\" + packageGuid + ".umb"; + } + } + + // log + + return ""; + } + + /// + /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic. + /// + protected override void DisposeResources() + { + Webservice.Dispose(); + } + } +} diff --git a/src/umbraco.cms/businesslogic/Packager/Repositories/RepositoryWebservice.cs b/src/Umbraco.Web/_Legacy/Packager/Repositories/RepositoryWebservice.cs similarity index 97% rename from src/umbraco.cms/businesslogic/Packager/Repositories/RepositoryWebservice.cs rename to src/Umbraco.Web/_Legacy/Packager/Repositories/RepositoryWebservice.cs index a2da17dd76..35bf1eacb3 100644 --- a/src/umbraco.cms/businesslogic/Packager/Repositories/RepositoryWebservice.cs +++ b/src/Umbraco.Web/_Legacy/Packager/Repositories/RepositoryWebservice.cs @@ -1,599 +1,599 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Web.Services; -using System.Web.Services.Protocols; -using System.Xml.Serialization; - -namespace umbraco.cms.businesslogic.packager.repositories -{ - - - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Web.Services.WebServiceBindingAttribute(Name = "RepositorySoap", Namespace = "http://packages.umbraco.org/webservices/")] - public partial class RepositoryWebservice : System.Web.Services.Protocols.SoapHttpClientProtocol - { - - private System.Threading.SendOrPostCallback CategoriesOperationCompleted; - - private System.Threading.SendOrPostCallback NitrosOperationCompleted; - - private System.Threading.SendOrPostCallback NitrosByVersionOperationCompleted; - - private System.Threading.SendOrPostCallback NitrosCategorizedOperationCompleted; - - private System.Threading.SendOrPostCallback NitrosCategorizedByVersionOperationCompleted; - - private System.Threading.SendOrPostCallback StarterKitsOperationCompleted; - - private System.Threading.SendOrPostCallback StarterKitModulesCategorizedOperationCompleted; - - private System.Threading.SendOrPostCallback StarterKitModulesOperationCompleted; - - private System.Threading.SendOrPostCallback authenticateOperationCompleted; - +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Web.Services; +using System.Web.Services.Protocols; +using System.Xml.Serialization; + +namespace umbraco.cms.businesslogic.packager.repositories +{ + + + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Web.Services.WebServiceBindingAttribute(Name = "RepositorySoap", Namespace = "http://packages.umbraco.org/webservices/")] + public partial class RepositoryWebservice : System.Web.Services.Protocols.SoapHttpClientProtocol + { + + private System.Threading.SendOrPostCallback CategoriesOperationCompleted; + + private System.Threading.SendOrPostCallback NitrosOperationCompleted; + + private System.Threading.SendOrPostCallback NitrosByVersionOperationCompleted; + + private System.Threading.SendOrPostCallback NitrosCategorizedOperationCompleted; + + private System.Threading.SendOrPostCallback NitrosCategorizedByVersionOperationCompleted; + + private System.Threading.SendOrPostCallback StarterKitsOperationCompleted; + + private System.Threading.SendOrPostCallback StarterKitModulesCategorizedOperationCompleted; + + private System.Threading.SendOrPostCallback StarterKitModulesOperationCompleted; + + private System.Threading.SendOrPostCallback authenticateOperationCompleted; + private System.Threading.SendOrPostCallback GetPackageFileOperationCompleted; - - private System.Threading.SendOrPostCallback fetchPackageByVersionOperationCompleted; - - private System.Threading.SendOrPostCallback fetchPackageOperationCompleted; - - private System.Threading.SendOrPostCallback fetchProtectedPackageOperationCompleted; - - private System.Threading.SendOrPostCallback SubmitPackageOperationCompleted; - - private System.Threading.SendOrPostCallback PackageByGuidOperationCompleted; - - private System.Threading.SendOrPostCallback SkinByGuidOperationCompleted; - - private System.Threading.SendOrPostCallback SkinsOperationCompleted; - - /// - public RepositoryWebservice(string url) - { - this.Url = url;//"http://our.umbraco.org/umbraco/webservices/api/repository.asmx"; - } - - /// - public event CategoriesCompletedEventHandler CategoriesCompleted; - - /// - public event NitrosCompletedEventHandler NitrosCompleted; - - /// - public event NitrosByVersionCompletedEventHandler NitrosByVersionCompleted; - - /// - public event NitrosCategorizedCompletedEventHandler NitrosCategorizedCompleted; - - /// - public event NitrosCategorizedByVersionCompletedEventHandler NitrosCategorizedByVersionCompleted; - - /// - public event StarterKitsCompletedEventHandler StarterKitsCompleted; - - /// - public event StarterKitModulesCategorizedCompletedEventHandler StarterKitModulesCategorizedCompleted; - - /// - public event StarterKitModulesCompletedEventHandler StarterKitModulesCompleted; - - /// - public event authenticateCompletedEventHandler authenticateCompleted; - - /// + + private System.Threading.SendOrPostCallback fetchPackageByVersionOperationCompleted; + + private System.Threading.SendOrPostCallback fetchPackageOperationCompleted; + + private System.Threading.SendOrPostCallback fetchProtectedPackageOperationCompleted; + + private System.Threading.SendOrPostCallback SubmitPackageOperationCompleted; + + private System.Threading.SendOrPostCallback PackageByGuidOperationCompleted; + + private System.Threading.SendOrPostCallback SkinByGuidOperationCompleted; + + private System.Threading.SendOrPostCallback SkinsOperationCompleted; + + /// + public RepositoryWebservice(string url) + { + this.Url = url;//"http://our.umbraco.org/umbraco/webservices/api/repository.asmx"; + } + + /// + public event CategoriesCompletedEventHandler CategoriesCompleted; + + /// + public event NitrosCompletedEventHandler NitrosCompleted; + + /// + public event NitrosByVersionCompletedEventHandler NitrosByVersionCompleted; + + /// + public event NitrosCategorizedCompletedEventHandler NitrosCategorizedCompleted; + + /// + public event NitrosCategorizedByVersionCompletedEventHandler NitrosCategorizedByVersionCompleted; + + /// + public event StarterKitsCompletedEventHandler StarterKitsCompleted; + + /// + public event StarterKitModulesCategorizedCompletedEventHandler StarterKitModulesCategorizedCompleted; + + /// + public event StarterKitModulesCompletedEventHandler StarterKitModulesCompleted; + + /// + public event authenticateCompletedEventHandler authenticateCompleted; + + /// public event GetPackageFileCompletedEventHandler GetPackageFileCompleted; - - /// - public event fetchPackageByVersionCompletedEventHandler fetchPackageByVersionCompleted; - - /// - public event fetchPackageCompletedEventHandler fetchPackageCompleted; - - /// - public event fetchProtectedPackageCompletedEventHandler fetchProtectedPackageCompleted; - - /// - public event SubmitPackageCompletedEventHandler SubmitPackageCompleted; - - /// - public event PackageByGuidCompletedEventHandler PackageByGuidCompleted; - - /// - public event SkinByGuidCompletedEventHandler SkinByGuidCompleted; - - /// - public event SkinsCompletedEventHandler SkinsCompleted; - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Categories", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Category[] Categories(string repositoryGuid) - { - object[] results = this.Invoke("Categories", new object[] { - repositoryGuid}); - return ((Category[])(results[0])); - } - - /// - public System.IAsyncResult BeginCategories(string repositoryGuid, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("Categories", new object[] { - repositoryGuid}, callback, asyncState); - } - - /// - public Category[] EndCategories(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((Category[])(results[0])); - } - - /// - public void CategoriesAsync(string repositoryGuid) - { - this.CategoriesAsync(repositoryGuid, null); - } - - /// - public void CategoriesAsync(string repositoryGuid, object userState) - { - if ((this.CategoriesOperationCompleted == null)) - { - this.CategoriesOperationCompleted = new System.Threading.SendOrPostCallback(this.OnCategoriesOperationCompleted); - } - this.InvokeAsync("Categories", new object[] { - repositoryGuid}, this.CategoriesOperationCompleted, userState); - } - - private void OnCategoriesOperationCompleted(object arg) - { - if ((this.CategoriesCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.CategoriesCompleted(this, new CategoriesCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Nitros", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Package[] Nitros() - { - object[] results = this.Invoke("Nitros", new object[0]); - return ((Package[])(results[0])); - } - - /// - public System.IAsyncResult BeginNitros(System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("Nitros", new object[0], callback, asyncState); - } - - /// - public Package[] EndNitros(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((Package[])(results[0])); - } - - /// - public void NitrosAsync() - { - this.NitrosAsync(null); - } - - /// - public void NitrosAsync(object userState) - { - if ((this.NitrosOperationCompleted == null)) - { - this.NitrosOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosOperationCompleted); - } - this.InvokeAsync("Nitros", new object[0], this.NitrosOperationCompleted, userState); - } - - private void OnNitrosOperationCompleted(object arg) - { - if ((this.NitrosCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.NitrosCompleted(this, new NitrosCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/NitrosByVersion", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Package[] NitrosByVersion(Version version) - { - object[] results = this.Invoke("NitrosByVersion", new object[] { - version}); - return ((Package[])(results[0])); - } - - /// - public System.IAsyncResult BeginNitrosByVersion(Version version, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("NitrosByVersion", new object[] { - version}, callback, asyncState); - } - - /// - public Package[] EndNitrosByVersion(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((Package[])(results[0])); - } - - /// - public void NitrosByVersionAsync(Version version) - { - this.NitrosByVersionAsync(version, null); - } - - /// - public void NitrosByVersionAsync(Version version, object userState) - { - if ((this.NitrosByVersionOperationCompleted == null)) - { - this.NitrosByVersionOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosByVersionOperationCompleted); - } - this.InvokeAsync("NitrosByVersion", new object[] { - version}, this.NitrosByVersionOperationCompleted, userState); - } - - private void OnNitrosByVersionOperationCompleted(object arg) - { - if ((this.NitrosByVersionCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.NitrosByVersionCompleted(this, new NitrosByVersionCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/NitrosCategorized", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Category[] NitrosCategorized() - { - object[] results = this.Invoke("NitrosCategorized", new object[0]); - return ((Category[])(results[0])); - } - - /// - public System.IAsyncResult BeginNitrosCategorized(System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("NitrosCategorized", new object[0], callback, asyncState); - } - - /// - public Category[] EndNitrosCategorized(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((Category[])(results[0])); - } - - /// - public void NitrosCategorizedAsync() - { - this.NitrosCategorizedAsync(null); - } - - /// - public void NitrosCategorizedAsync(object userState) - { - if ((this.NitrosCategorizedOperationCompleted == null)) - { - this.NitrosCategorizedOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosCategorizedOperationCompleted); - } - this.InvokeAsync("NitrosCategorized", new object[0], this.NitrosCategorizedOperationCompleted, userState); - } - - private void OnNitrosCategorizedOperationCompleted(object arg) - { - if ((this.NitrosCategorizedCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.NitrosCategorizedCompleted(this, new NitrosCategorizedCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/NitrosCategorizedByVersion", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Category[] NitrosCategorizedByVersion(Version version) - { - object[] results = this.Invoke("NitrosCategorizedByVersion", new object[] { - version}); - return ((Category[])(results[0])); - } - - /// - public System.IAsyncResult BeginNitrosCategorizedByVersion(Version version, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("NitrosCategorizedByVersion", new object[] { - version}, callback, asyncState); - } - - /// - public Category[] EndNitrosCategorizedByVersion(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((Category[])(results[0])); - } - - /// - public void NitrosCategorizedByVersionAsync(Version version) - { - this.NitrosCategorizedByVersionAsync(version, null); - } - - /// - public void NitrosCategorizedByVersionAsync(Version version, object userState) - { - if ((this.NitrosCategorizedByVersionOperationCompleted == null)) - { - this.NitrosCategorizedByVersionOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosCategorizedByVersionOperationCompleted); - } - this.InvokeAsync("NitrosCategorizedByVersion", new object[] { - version}, this.NitrosCategorizedByVersionOperationCompleted, userState); - } - - private void OnNitrosCategorizedByVersionOperationCompleted(object arg) - { - if ((this.NitrosCategorizedByVersionCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.NitrosCategorizedByVersionCompleted(this, new NitrosCategorizedByVersionCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/StarterKits", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Package[] StarterKits() - { - object[] results = this.Invoke("StarterKits", new object[0]); - return ((Package[])(results[0])); - } - - /// - public System.IAsyncResult BeginStarterKits(System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("StarterKits", new object[0], callback, asyncState); - } - - /// - public Package[] EndStarterKits(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((Package[])(results[0])); - } - - /// - public void StarterKitsAsync() - { - this.StarterKitsAsync(null); - } - - /// - public void StarterKitsAsync(object userState) - { - if ((this.StarterKitsOperationCompleted == null)) - { - this.StarterKitsOperationCompleted = new System.Threading.SendOrPostCallback(this.OnStarterKitsOperationCompleted); - } - this.InvokeAsync("StarterKits", new object[0], this.StarterKitsOperationCompleted, userState); - } - - private void OnStarterKitsOperationCompleted(object arg) - { - if ((this.StarterKitsCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.StarterKitsCompleted(this, new StarterKitsCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/StarterKitModulesCategorized", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Category[] StarterKitModulesCategorized() - { - object[] results = this.Invoke("StarterKitModulesCategorized", new object[0]); - return ((Category[])(results[0])); - } - - /// - public System.IAsyncResult BeginStarterKitModulesCategorized(System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("StarterKitModulesCategorized", new object[0], callback, asyncState); - } - - /// - public Category[] EndStarterKitModulesCategorized(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((Category[])(results[0])); - } - - /// - public void StarterKitModulesCategorizedAsync() - { - this.StarterKitModulesCategorizedAsync(null); - } - - /// - public void StarterKitModulesCategorizedAsync(object userState) - { - if ((this.StarterKitModulesCategorizedOperationCompleted == null)) - { - this.StarterKitModulesCategorizedOperationCompleted = new System.Threading.SendOrPostCallback(this.OnStarterKitModulesCategorizedOperationCompleted); - } - this.InvokeAsync("StarterKitModulesCategorized", new object[0], this.StarterKitModulesCategorizedOperationCompleted, userState); - } - - private void OnStarterKitModulesCategorizedOperationCompleted(object arg) - { - if ((this.StarterKitModulesCategorizedCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.StarterKitModulesCategorizedCompleted(this, new StarterKitModulesCategorizedCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/StarterKitModules", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Package[] StarterKitModules() - { - object[] results = this.Invoke("StarterKitModules", new object[0]); - return ((Package[])(results[0])); - } - - /// - public System.IAsyncResult BeginStarterKitModules(System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("StarterKitModules", new object[0], callback, asyncState); - } - - /// - public Package[] EndStarterKitModules(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((Package[])(results[0])); - } - - /// - public void StarterKitModulesAsync() - { - this.StarterKitModulesAsync(null); - } - - /// - public void StarterKitModulesAsync(object userState) - { - if ((this.StarterKitModulesOperationCompleted == null)) - { - this.StarterKitModulesOperationCompleted = new System.Threading.SendOrPostCallback(this.OnStarterKitModulesOperationCompleted); - } - this.InvokeAsync("StarterKitModules", new object[0], this.StarterKitModulesOperationCompleted, userState); - } - - private void OnStarterKitModulesOperationCompleted(object arg) - { - if ((this.StarterKitModulesCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.StarterKitModulesCompleted(this, new StarterKitModulesCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/authenticate", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public string authenticate(string email, string md5Password) - { - object[] results = this.Invoke("authenticate", new object[] { - email, - md5Password}); - return ((string)(results[0])); - } - - /// - public System.IAsyncResult Beginauthenticate(string email, string md5Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("authenticate", new object[] { - email, - md5Password}, callback, asyncState); - } - - /// - public string Endauthenticate(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((string)(results[0])); - } - - /// - public void authenticateAsync(string email, string md5Password) - { - this.authenticateAsync(email, md5Password, null); - } - - /// - public void authenticateAsync(string email, string md5Password, object userState) - { - if ((this.authenticateOperationCompleted == null)) - { - this.authenticateOperationCompleted = new System.Threading.SendOrPostCallback(this.OnauthenticateOperationCompleted); - } - this.InvokeAsync("authenticate", new object[] { - email, - md5Password}, this.authenticateOperationCompleted, userState); - } - - private void OnauthenticateOperationCompleted(object arg) - { - if ((this.authenticateCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.authenticateCompleted(this, new authenticateCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - - - - - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/GetPackageFile", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - [return: System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] - public byte[] GetPackageFile(string packageGuid, string umbracoVersion) - { - object[] results = this.Invoke("GetPackageFile", new object[] { - packageGuid, - umbracoVersion}); - return ((byte[])(results[0])); - } - - /// - public System.IAsyncResult BeginfetchPackageByVersion(string packageGuid, string umbracoVersion, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("GetPackageFile", new object[] { - packageGuid, - umbracoVersion}, callback, asyncState); - } - - /// - public byte[] EndGetPackageFile(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((byte[])(results[0])); - } - - /// - public void GetPackageFileAsync(string packageGuid, string umbracoVersion) - { - this.GetPackageFileAsync(packageGuid, umbracoVersion, null); - } - - /// - public void GetPackageFileAsync(string packageGuid, string umbracoVersion, object userState) - { - if ((this.GetPackageFileOperationCompleted == null)) - { - this.GetPackageFileOperationCompleted = new System.Threading.SendOrPostCallback(this.OnGetPackageFileOperationCompleted); - } - this.InvokeAsync("GetPackageFile", new object[] { - packageGuid, - umbracoVersion}, this.GetPackageFileOperationCompleted, userState); - } - - private void OnGetPackageFileOperationCompleted(object arg) - { - if ((this.GetPackageFileCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.GetPackageFileCompleted(this, new GetPackageFileCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } + + /// + public event fetchPackageByVersionCompletedEventHandler fetchPackageByVersionCompleted; + + /// + public event fetchPackageCompletedEventHandler fetchPackageCompleted; + + /// + public event fetchProtectedPackageCompletedEventHandler fetchProtectedPackageCompleted; + + /// + public event SubmitPackageCompletedEventHandler SubmitPackageCompleted; + + /// + public event PackageByGuidCompletedEventHandler PackageByGuidCompleted; + + /// + public event SkinByGuidCompletedEventHandler SkinByGuidCompleted; + + /// + public event SkinsCompletedEventHandler SkinsCompleted; + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Categories", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public Category[] Categories(string repositoryGuid) + { + object[] results = this.Invoke("Categories", new object[] { + repositoryGuid}); + return ((Category[])(results[0])); + } + + /// + public System.IAsyncResult BeginCategories(string repositoryGuid, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("Categories", new object[] { + repositoryGuid}, callback, asyncState); + } + + /// + public Category[] EndCategories(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((Category[])(results[0])); + } + + /// + public void CategoriesAsync(string repositoryGuid) + { + this.CategoriesAsync(repositoryGuid, null); + } + + /// + public void CategoriesAsync(string repositoryGuid, object userState) + { + if ((this.CategoriesOperationCompleted == null)) + { + this.CategoriesOperationCompleted = new System.Threading.SendOrPostCallback(this.OnCategoriesOperationCompleted); + } + this.InvokeAsync("Categories", new object[] { + repositoryGuid}, this.CategoriesOperationCompleted, userState); + } + + private void OnCategoriesOperationCompleted(object arg) + { + if ((this.CategoriesCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.CategoriesCompleted(this, new CategoriesCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Nitros", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public Package[] Nitros() + { + object[] results = this.Invoke("Nitros", new object[0]); + return ((Package[])(results[0])); + } + + /// + public System.IAsyncResult BeginNitros(System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("Nitros", new object[0], callback, asyncState); + } + + /// + public Package[] EndNitros(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((Package[])(results[0])); + } + + /// + public void NitrosAsync() + { + this.NitrosAsync(null); + } + + /// + public void NitrosAsync(object userState) + { + if ((this.NitrosOperationCompleted == null)) + { + this.NitrosOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosOperationCompleted); + } + this.InvokeAsync("Nitros", new object[0], this.NitrosOperationCompleted, userState); + } + + private void OnNitrosOperationCompleted(object arg) + { + if ((this.NitrosCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.NitrosCompleted(this, new NitrosCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/NitrosByVersion", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public Package[] NitrosByVersion(Version version) + { + object[] results = this.Invoke("NitrosByVersion", new object[] { + version}); + return ((Package[])(results[0])); + } + + /// + public System.IAsyncResult BeginNitrosByVersion(Version version, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("NitrosByVersion", new object[] { + version}, callback, asyncState); + } + + /// + public Package[] EndNitrosByVersion(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((Package[])(results[0])); + } + + /// + public void NitrosByVersionAsync(Version version) + { + this.NitrosByVersionAsync(version, null); + } + + /// + public void NitrosByVersionAsync(Version version, object userState) + { + if ((this.NitrosByVersionOperationCompleted == null)) + { + this.NitrosByVersionOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosByVersionOperationCompleted); + } + this.InvokeAsync("NitrosByVersion", new object[] { + version}, this.NitrosByVersionOperationCompleted, userState); + } + + private void OnNitrosByVersionOperationCompleted(object arg) + { + if ((this.NitrosByVersionCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.NitrosByVersionCompleted(this, new NitrosByVersionCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/NitrosCategorized", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public Category[] NitrosCategorized() + { + object[] results = this.Invoke("NitrosCategorized", new object[0]); + return ((Category[])(results[0])); + } + + /// + public System.IAsyncResult BeginNitrosCategorized(System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("NitrosCategorized", new object[0], callback, asyncState); + } + + /// + public Category[] EndNitrosCategorized(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((Category[])(results[0])); + } + + /// + public void NitrosCategorizedAsync() + { + this.NitrosCategorizedAsync(null); + } + + /// + public void NitrosCategorizedAsync(object userState) + { + if ((this.NitrosCategorizedOperationCompleted == null)) + { + this.NitrosCategorizedOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosCategorizedOperationCompleted); + } + this.InvokeAsync("NitrosCategorized", new object[0], this.NitrosCategorizedOperationCompleted, userState); + } + + private void OnNitrosCategorizedOperationCompleted(object arg) + { + if ((this.NitrosCategorizedCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.NitrosCategorizedCompleted(this, new NitrosCategorizedCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/NitrosCategorizedByVersion", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public Category[] NitrosCategorizedByVersion(Version version) + { + object[] results = this.Invoke("NitrosCategorizedByVersion", new object[] { + version}); + return ((Category[])(results[0])); + } + + /// + public System.IAsyncResult BeginNitrosCategorizedByVersion(Version version, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("NitrosCategorizedByVersion", new object[] { + version}, callback, asyncState); + } + + /// + public Category[] EndNitrosCategorizedByVersion(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((Category[])(results[0])); + } + + /// + public void NitrosCategorizedByVersionAsync(Version version) + { + this.NitrosCategorizedByVersionAsync(version, null); + } + + /// + public void NitrosCategorizedByVersionAsync(Version version, object userState) + { + if ((this.NitrosCategorizedByVersionOperationCompleted == null)) + { + this.NitrosCategorizedByVersionOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosCategorizedByVersionOperationCompleted); + } + this.InvokeAsync("NitrosCategorizedByVersion", new object[] { + version}, this.NitrosCategorizedByVersionOperationCompleted, userState); + } + + private void OnNitrosCategorizedByVersionOperationCompleted(object arg) + { + if ((this.NitrosCategorizedByVersionCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.NitrosCategorizedByVersionCompleted(this, new NitrosCategorizedByVersionCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/StarterKits", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public Package[] StarterKits() + { + object[] results = this.Invoke("StarterKits", new object[0]); + return ((Package[])(results[0])); + } + + /// + public System.IAsyncResult BeginStarterKits(System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("StarterKits", new object[0], callback, asyncState); + } + + /// + public Package[] EndStarterKits(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((Package[])(results[0])); + } + + /// + public void StarterKitsAsync() + { + this.StarterKitsAsync(null); + } + + /// + public void StarterKitsAsync(object userState) + { + if ((this.StarterKitsOperationCompleted == null)) + { + this.StarterKitsOperationCompleted = new System.Threading.SendOrPostCallback(this.OnStarterKitsOperationCompleted); + } + this.InvokeAsync("StarterKits", new object[0], this.StarterKitsOperationCompleted, userState); + } + + private void OnStarterKitsOperationCompleted(object arg) + { + if ((this.StarterKitsCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.StarterKitsCompleted(this, new StarterKitsCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/StarterKitModulesCategorized", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public Category[] StarterKitModulesCategorized() + { + object[] results = this.Invoke("StarterKitModulesCategorized", new object[0]); + return ((Category[])(results[0])); + } + + /// + public System.IAsyncResult BeginStarterKitModulesCategorized(System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("StarterKitModulesCategorized", new object[0], callback, asyncState); + } + + /// + public Category[] EndStarterKitModulesCategorized(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((Category[])(results[0])); + } + + /// + public void StarterKitModulesCategorizedAsync() + { + this.StarterKitModulesCategorizedAsync(null); + } + + /// + public void StarterKitModulesCategorizedAsync(object userState) + { + if ((this.StarterKitModulesCategorizedOperationCompleted == null)) + { + this.StarterKitModulesCategorizedOperationCompleted = new System.Threading.SendOrPostCallback(this.OnStarterKitModulesCategorizedOperationCompleted); + } + this.InvokeAsync("StarterKitModulesCategorized", new object[0], this.StarterKitModulesCategorizedOperationCompleted, userState); + } + + private void OnStarterKitModulesCategorizedOperationCompleted(object arg) + { + if ((this.StarterKitModulesCategorizedCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.StarterKitModulesCategorizedCompleted(this, new StarterKitModulesCategorizedCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/StarterKitModules", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public Package[] StarterKitModules() + { + object[] results = this.Invoke("StarterKitModules", new object[0]); + return ((Package[])(results[0])); + } + + /// + public System.IAsyncResult BeginStarterKitModules(System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("StarterKitModules", new object[0], callback, asyncState); + } + + /// + public Package[] EndStarterKitModules(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((Package[])(results[0])); + } + + /// + public void StarterKitModulesAsync() + { + this.StarterKitModulesAsync(null); + } + + /// + public void StarterKitModulesAsync(object userState) + { + if ((this.StarterKitModulesOperationCompleted == null)) + { + this.StarterKitModulesOperationCompleted = new System.Threading.SendOrPostCallback(this.OnStarterKitModulesOperationCompleted); + } + this.InvokeAsync("StarterKitModules", new object[0], this.StarterKitModulesOperationCompleted, userState); + } + + private void OnStarterKitModulesOperationCompleted(object arg) + { + if ((this.StarterKitModulesCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.StarterKitModulesCompleted(this, new StarterKitModulesCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/authenticate", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public string authenticate(string email, string md5Password) + { + object[] results = this.Invoke("authenticate", new object[] { + email, + md5Password}); + return ((string)(results[0])); + } + + /// + public System.IAsyncResult Beginauthenticate(string email, string md5Password, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("authenticate", new object[] { + email, + md5Password}, callback, asyncState); + } + + /// + public string Endauthenticate(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((string)(results[0])); + } + + /// + public void authenticateAsync(string email, string md5Password) + { + this.authenticateAsync(email, md5Password, null); + } + + /// + public void authenticateAsync(string email, string md5Password, object userState) + { + if ((this.authenticateOperationCompleted == null)) + { + this.authenticateOperationCompleted = new System.Threading.SendOrPostCallback(this.OnauthenticateOperationCompleted); + } + this.InvokeAsync("authenticate", new object[] { + email, + md5Password}, this.authenticateOperationCompleted, userState); + } + + private void OnauthenticateOperationCompleted(object arg) + { + if ((this.authenticateCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.authenticateCompleted(this, new authenticateCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + + + + + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/GetPackageFile", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + [return: System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] + public byte[] GetPackageFile(string packageGuid, string umbracoVersion) + { + object[] results = this.Invoke("GetPackageFile", new object[] { + packageGuid, + umbracoVersion}); + return ((byte[])(results[0])); + } + + /// + public System.IAsyncResult BeginfetchPackageByVersion(string packageGuid, string umbracoVersion, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("GetPackageFile", new object[] { + packageGuid, + umbracoVersion}, callback, asyncState); + } + + /// + public byte[] EndGetPackageFile(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((byte[])(results[0])); + } + + /// + public void GetPackageFileAsync(string packageGuid, string umbracoVersion) + { + this.GetPackageFileAsync(packageGuid, umbracoVersion, null); + } + + /// + public void GetPackageFileAsync(string packageGuid, string umbracoVersion, object userState) + { + if ((this.GetPackageFileOperationCompleted == null)) + { + this.GetPackageFileOperationCompleted = new System.Threading.SendOrPostCallback(this.OnGetPackageFileOperationCompleted); + } + this.InvokeAsync("GetPackageFile", new object[] { + packageGuid, + umbracoVersion}, this.GetPackageFileOperationCompleted, userState); + } + + private void OnGetPackageFileOperationCompleted(object arg) + { + if ((this.GetPackageFileCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.GetPackageFileCompleted(this, new GetPackageFileCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } } @@ -607,1396 +607,1396 @@ namespace umbraco.cms.businesslogic.packager.repositories - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchPackageByVersion", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - [return: System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] - public byte[] fetchPackageByVersion(string packageGuid, Version schemaVersion) - { - object[] results = this.Invoke("fetchPackageByVersion", new object[] { - packageGuid, - schemaVersion}); - return ((byte[])(results[0])); - } - - /// - public System.IAsyncResult BeginfetchPackageByVersion(string packageGuid, Version schemaVersion, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("fetchPackageByVersion", new object[] { - packageGuid, - schemaVersion}, callback, asyncState); - } - - /// - public byte[] EndfetchPackageByVersion(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((byte[])(results[0])); - } - - /// - public void fetchPackageByVersionAsync(string packageGuid, Version schemaVersion) - { - this.fetchPackageByVersionAsync(packageGuid, schemaVersion, null); - } - - /// - public void fetchPackageByVersionAsync(string packageGuid, Version schemaVersion, object userState) - { - if ((this.fetchPackageByVersionOperationCompleted == null)) - { - this.fetchPackageByVersionOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchPackageByVersionOperationCompleted); - } - this.InvokeAsync("fetchPackageByVersion", new object[] { - packageGuid, - schemaVersion}, this.fetchPackageByVersionOperationCompleted, userState); - } - - private void OnfetchPackageByVersionOperationCompleted(object arg) - { - if ((this.fetchPackageByVersionCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.fetchPackageByVersionCompleted(this, new fetchPackageByVersionCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchPackage", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - [return: System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] - public byte[] fetchPackage(string packageGuid) - { - object[] results = this.Invoke("fetchPackage", new object[] { - packageGuid}); - return ((byte[])(results[0])); - } - - /// - public System.IAsyncResult BeginfetchPackage(string packageGuid, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("fetchPackage", new object[] { - packageGuid}, callback, asyncState); - } - - /// - public byte[] EndfetchPackage(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((byte[])(results[0])); - } - - /// - public void fetchPackageAsync(string packageGuid) - { - this.fetchPackageAsync(packageGuid, null); - } - - /// - public void fetchPackageAsync(string packageGuid, object userState) - { - if ((this.fetchPackageOperationCompleted == null)) - { - this.fetchPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchPackageOperationCompleted); - } - this.InvokeAsync("fetchPackage", new object[] { - packageGuid}, this.fetchPackageOperationCompleted, userState); - } - - private void OnfetchPackageOperationCompleted(object arg) - { - if ((this.fetchPackageCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.fetchPackageCompleted(this, new fetchPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchProtectedPackage", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - [return: System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] - public byte[] fetchProtectedPackage(string packageGuid, string memberKey) - { - object[] results = this.Invoke("fetchProtectedPackage", new object[] { - packageGuid, - memberKey}); - return ((byte[])(results[0])); - } - - /// - public System.IAsyncResult BeginfetchProtectedPackage(string packageGuid, string memberKey, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("fetchProtectedPackage", new object[] { - packageGuid, - memberKey}, callback, asyncState); - } - - /// - public byte[] EndfetchProtectedPackage(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((byte[])(results[0])); - } - - /// - public void fetchProtectedPackageAsync(string packageGuid, string memberKey) - { - this.fetchProtectedPackageAsync(packageGuid, memberKey, null); - } - - /// - public void fetchProtectedPackageAsync(string packageGuid, string memberKey, object userState) - { - if ((this.fetchProtectedPackageOperationCompleted == null)) - { - this.fetchProtectedPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchProtectedPackageOperationCompleted); - } - this.InvokeAsync("fetchProtectedPackage", new object[] { - packageGuid, - memberKey}, this.fetchProtectedPackageOperationCompleted, userState); - } - - private void OnfetchProtectedPackageOperationCompleted(object arg) - { - if ((this.fetchProtectedPackageCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.fetchProtectedPackageCompleted(this, new fetchProtectedPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/SubmitPackage", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public SubmitStatus SubmitPackage(string repositoryGuid, string authorGuid, string packageGuid, [System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] byte[] packageFile, [System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] byte[] packageDoc, [System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] byte[] packageThumbnail, string name, string author, string authorUrl, string description) - { - object[] results = this.Invoke("SubmitPackage", new object[] { - repositoryGuid, - authorGuid, - packageGuid, - packageFile, - packageDoc, - packageThumbnail, - name, - author, - authorUrl, - description}); - return ((SubmitStatus)(results[0])); - } - - /// - public System.IAsyncResult BeginSubmitPackage(string repositoryGuid, string authorGuid, string packageGuid, byte[] packageFile, byte[] packageDoc, byte[] packageThumbnail, string name, string author, string authorUrl, string description, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("SubmitPackage", new object[] { - repositoryGuid, - authorGuid, - packageGuid, - packageFile, - packageDoc, - packageThumbnail, - name, - author, - authorUrl, - description}, callback, asyncState); - } - - /// - public SubmitStatus EndSubmitPackage(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((SubmitStatus)(results[0])); - } - - /// - public void SubmitPackageAsync(string repositoryGuid, string authorGuid, string packageGuid, byte[] packageFile, byte[] packageDoc, byte[] packageThumbnail, string name, string author, string authorUrl, string description) - { - this.SubmitPackageAsync(repositoryGuid, authorGuid, packageGuid, packageFile, packageDoc, packageThumbnail, name, author, authorUrl, description, null); - } - - /// - public void SubmitPackageAsync(string repositoryGuid, string authorGuid, string packageGuid, byte[] packageFile, byte[] packageDoc, byte[] packageThumbnail, string name, string author, string authorUrl, string description, object userState) - { - if ((this.SubmitPackageOperationCompleted == null)) - { - this.SubmitPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSubmitPackageOperationCompleted); - } - this.InvokeAsync("SubmitPackage", new object[] { - repositoryGuid, - authorGuid, - packageGuid, - packageFile, - packageDoc, - packageThumbnail, - name, - author, - authorUrl, - description}, this.SubmitPackageOperationCompleted, userState); - } - - private void OnSubmitPackageOperationCompleted(object arg) - { - if ((this.SubmitPackageCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.SubmitPackageCompleted(this, new SubmitPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/PackageByGuid", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Package PackageByGuid(string packageGuid) - { - object[] results = this.Invoke("PackageByGuid", new object[] { - packageGuid}); - return ((Package)(results[0])); - } - - /// - public System.IAsyncResult BeginPackageByGuid(string packageGuid, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("PackageByGuid", new object[] { - packageGuid}, callback, asyncState); - } - - /// - public Package EndPackageByGuid(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((Package)(results[0])); - } - - /// - public void PackageByGuidAsync(string packageGuid) - { - this.PackageByGuidAsync(packageGuid, null); - } - - /// - public void PackageByGuidAsync(string packageGuid, object userState) - { - if ((this.PackageByGuidOperationCompleted == null)) - { - this.PackageByGuidOperationCompleted = new System.Threading.SendOrPostCallback(this.OnPackageByGuidOperationCompleted); - } - this.InvokeAsync("PackageByGuid", new object[] { - packageGuid}, this.PackageByGuidOperationCompleted, userState); - } - - private void OnPackageByGuidOperationCompleted(object arg) - { - if ((this.PackageByGuidCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.PackageByGuidCompleted(this, new PackageByGuidCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/SkinByGuid", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Skin SkinByGuid(string skinGuid) - { - object[] results = this.Invoke("SkinByGuid", new object[] { - skinGuid}); - return ((Skin)(results[0])); - } - - /// - public System.IAsyncResult BeginSkinByGuid(string skinGuid, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("SkinByGuid", new object[] { - skinGuid}, callback, asyncState); - } - - /// - public Skin EndSkinByGuid(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((Skin)(results[0])); - } - - /// - public void SkinByGuidAsync(string skinGuid) - { - this.SkinByGuidAsync(skinGuid, null); - } - - /// - public void SkinByGuidAsync(string skinGuid, object userState) - { - if ((this.SkinByGuidOperationCompleted == null)) - { - this.SkinByGuidOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSkinByGuidOperationCompleted); - } - this.InvokeAsync("SkinByGuid", new object[] { - skinGuid}, this.SkinByGuidOperationCompleted, userState); - } - - private void OnSkinByGuidOperationCompleted(object arg) - { - if ((this.SkinByGuidCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.SkinByGuidCompleted(this, new SkinByGuidCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Skins", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Skin[] Skins(string starterKitGuid) - { - object[] results = this.Invoke("Skins", new object[] { - starterKitGuid}); - return ((Skin[])(results[0])); - } - - /// - public System.IAsyncResult BeginSkins(string starterKitGuid, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("Skins", new object[] { - starterKitGuid}, callback, asyncState); - } - - /// - public Skin[] EndSkins(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((Skin[])(results[0])); - } - - /// - public void SkinsAsync(string starterKitGuid) - { - this.SkinsAsync(starterKitGuid, null); - } - - /// - public void SkinsAsync(string starterKitGuid, object userState) - { - if ((this.SkinsOperationCompleted == null)) - { - this.SkinsOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSkinsOperationCompleted); - } - this.InvokeAsync("Skins", new object[] { - starterKitGuid}, this.SkinsOperationCompleted, userState); - } - - private void OnSkinsOperationCompleted(object arg) - { - if ((this.SkinsCompleted != null)) - { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.SkinsCompleted(this, new SkinsCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - public new void CancelAsync(object userState) - { - base.CancelAsync(userState); - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://packages.umbraco.org/webservices/")] - public partial class Category - { - - private string textField; - - private string descriptionField; - - private string urlField; - - private int idField; - - private Package[] packagesField; - - /// - public string Text - { - get - { - return this.textField; - } - set - { - this.textField = value; - } - } - - /// - public string Description - { - get - { - return this.descriptionField; - } - set - { - this.descriptionField = value; - } - } - - /// - public string Url - { - get - { - return this.urlField; - } - set - { - this.urlField = value; - } - } - - /// - public int Id - { - get - { - return this.idField; - } - set - { - this.idField = value; - } - } - - /// - public Package[] Packages - { - get - { - return this.packagesField; - } - set - { - this.packagesField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://packages.umbraco.org/webservices/")] - public partial class Package - { - - private System.Guid repoGuidField; - - private string textField; - - private string descriptionField; - - private string iconField; - - private string thumbnailField; - - private string documentationField; - - private string demoField; - - private bool acceptedField; - - private bool editorsPickField; - - private bool protectedField; - - private bool hasUpgradeField; - - private string upgradeVersionField; - - private string upgradeReadMeField; - - private string urlField; - - /// - public System.Guid RepoGuid - { - get - { - return this.repoGuidField; - } - set - { - this.repoGuidField = value; - } - } - - /// - public string Text - { - get - { - return this.textField; - } - set - { - this.textField = value; - } - } - - /// - public string Description - { - get - { - return this.descriptionField; - } - set - { - this.descriptionField = value; - } - } - - /// - public string Icon - { - get - { - return this.iconField; - } - set - { - this.iconField = value; - } - } - - /// - public string Thumbnail - { - get - { - return this.thumbnailField; - } - set - { - this.thumbnailField = value; - } - } - - /// - public string Documentation - { - get - { - return this.documentationField; - } - set - { - this.documentationField = value; - } - } - - /// - public string Demo - { - get - { - return this.demoField; - } - set - { - this.demoField = value; - } - } - - /// - public bool Accepted - { - get - { - return this.acceptedField; - } - set - { - this.acceptedField = value; - } - } - - /// - public bool EditorsPick - { - get - { - return this.editorsPickField; - } - set - { - this.editorsPickField = value; - } - } - - /// - public bool Protected - { - get - { - return this.protectedField; - } - set - { - this.protectedField = value; - } - } - - /// - public bool HasUpgrade - { - get - { - return this.hasUpgradeField; - } - set - { - this.hasUpgradeField = value; - } - } - - /// - public string UpgradeVersion - { - get - { - return this.upgradeVersionField; - } - set - { - this.upgradeVersionField = value; - } - } - - /// - public string UpgradeReadMe - { - get - { - return this.upgradeReadMeField; - } - set - { - this.upgradeReadMeField = value; - } - } - - /// - public string Url - { - get - { - return this.urlField; - } - set - { - this.urlField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://packages.umbraco.org/webservices/")] - public partial class Skin - { - - private System.Guid repoGuidField; - - private string textField; - - private string thumbnailField; - - private string previewField; - - private string descriptionField; - - private string authorField; - - private string authorUrlField; - - private string licenseField; - - private string licenseUrlField; - - /// - public System.Guid RepoGuid - { - get - { - return this.repoGuidField; - } - set - { - this.repoGuidField = value; - } - } - - /// - public string Text - { - get - { - return this.textField; - } - set - { - this.textField = value; - } - } - - /// - public string Thumbnail - { - get - { - return this.thumbnailField; - } - set - { - this.thumbnailField = value; - } - } - - /// - public string Preview - { - get - { - return this.previewField; - } - set - { - this.previewField = value; - } - } - - /// - public string Description - { - get - { - return this.descriptionField; - } - set - { - this.descriptionField = value; - } - } - - /// - public string Author - { - get - { - return this.authorField; - } - set - { - this.authorField = value; - } - } - - /// - public string AuthorUrl - { - get - { - return this.authorUrlField; - } - set - { - this.authorUrlField = value; - } - } - - /// - public string License - { - get - { - return this.licenseField; - } - set - { - this.licenseField = value; - } - } - - /// - public string LicenseUrl - { - get - { - return this.licenseUrlField; - } - set - { - this.licenseUrlField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://packages.umbraco.org/webservices/")] - public enum Version - { - - /// - Version3, - - /// - Version4, - - /// - /// This is apparently the version number we pass in for all installs - /// - Version41, - - /// - Version41Legacy, - - /// - Version50, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://packages.umbraco.org/webservices/")] - public enum SubmitStatus - { - - /// - Complete, - - /// - Exists, - - /// - NoAccess, - - /// - Error, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void CategoriesCompletedEventHandler(object sender, CategoriesCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class CategoriesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal CategoriesCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public Category[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((Category[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void NitrosCompletedEventHandler(object sender, NitrosCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class NitrosCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal NitrosCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public Package[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((Package[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void NitrosByVersionCompletedEventHandler(object sender, NitrosByVersionCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class NitrosByVersionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal NitrosByVersionCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public Package[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((Package[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void NitrosCategorizedCompletedEventHandler(object sender, NitrosCategorizedCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class NitrosCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal NitrosCategorizedCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public Category[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((Category[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void NitrosCategorizedByVersionCompletedEventHandler(object sender, NitrosCategorizedByVersionCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class NitrosCategorizedByVersionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal NitrosCategorizedByVersionCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public Category[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((Category[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void StarterKitsCompletedEventHandler(object sender, StarterKitsCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class StarterKitsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal StarterKitsCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public Package[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((Package[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void StarterKitModulesCategorizedCompletedEventHandler(object sender, StarterKitModulesCategorizedCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class StarterKitModulesCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal StarterKitModulesCategorizedCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public Category[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((Category[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void StarterKitModulesCompletedEventHandler(object sender, StarterKitModulesCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class StarterKitModulesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal StarterKitModulesCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public Package[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((Package[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void authenticateCompletedEventHandler(object sender, authenticateCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class authenticateCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal authenticateCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public string Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((string)(this.results[0])); - } - } - } - - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchPackageByVersion", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + [return: System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] + public byte[] fetchPackageByVersion(string packageGuid, Version schemaVersion) + { + object[] results = this.Invoke("fetchPackageByVersion", new object[] { + packageGuid, + schemaVersion}); + return ((byte[])(results[0])); + } + + /// + public System.IAsyncResult BeginfetchPackageByVersion(string packageGuid, Version schemaVersion, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("fetchPackageByVersion", new object[] { + packageGuid, + schemaVersion}, callback, asyncState); + } + + /// + public byte[] EndfetchPackageByVersion(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((byte[])(results[0])); + } + + /// + public void fetchPackageByVersionAsync(string packageGuid, Version schemaVersion) + { + this.fetchPackageByVersionAsync(packageGuid, schemaVersion, null); + } + + /// + public void fetchPackageByVersionAsync(string packageGuid, Version schemaVersion, object userState) + { + if ((this.fetchPackageByVersionOperationCompleted == null)) + { + this.fetchPackageByVersionOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchPackageByVersionOperationCompleted); + } + this.InvokeAsync("fetchPackageByVersion", new object[] { + packageGuid, + schemaVersion}, this.fetchPackageByVersionOperationCompleted, userState); + } + + private void OnfetchPackageByVersionOperationCompleted(object arg) + { + if ((this.fetchPackageByVersionCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.fetchPackageByVersionCompleted(this, new fetchPackageByVersionCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchPackage", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + [return: System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] + public byte[] fetchPackage(string packageGuid) + { + object[] results = this.Invoke("fetchPackage", new object[] { + packageGuid}); + return ((byte[])(results[0])); + } + + /// + public System.IAsyncResult BeginfetchPackage(string packageGuid, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("fetchPackage", new object[] { + packageGuid}, callback, asyncState); + } + + /// + public byte[] EndfetchPackage(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((byte[])(results[0])); + } + + /// + public void fetchPackageAsync(string packageGuid) + { + this.fetchPackageAsync(packageGuid, null); + } + + /// + public void fetchPackageAsync(string packageGuid, object userState) + { + if ((this.fetchPackageOperationCompleted == null)) + { + this.fetchPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchPackageOperationCompleted); + } + this.InvokeAsync("fetchPackage", new object[] { + packageGuid}, this.fetchPackageOperationCompleted, userState); + } + + private void OnfetchPackageOperationCompleted(object arg) + { + if ((this.fetchPackageCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.fetchPackageCompleted(this, new fetchPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchProtectedPackage", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + [return: System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] + public byte[] fetchProtectedPackage(string packageGuid, string memberKey) + { + object[] results = this.Invoke("fetchProtectedPackage", new object[] { + packageGuid, + memberKey}); + return ((byte[])(results[0])); + } + + /// + public System.IAsyncResult BeginfetchProtectedPackage(string packageGuid, string memberKey, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("fetchProtectedPackage", new object[] { + packageGuid, + memberKey}, callback, asyncState); + } + + /// + public byte[] EndfetchProtectedPackage(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((byte[])(results[0])); + } + + /// + public void fetchProtectedPackageAsync(string packageGuid, string memberKey) + { + this.fetchProtectedPackageAsync(packageGuid, memberKey, null); + } + + /// + public void fetchProtectedPackageAsync(string packageGuid, string memberKey, object userState) + { + if ((this.fetchProtectedPackageOperationCompleted == null)) + { + this.fetchProtectedPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchProtectedPackageOperationCompleted); + } + this.InvokeAsync("fetchProtectedPackage", new object[] { + packageGuid, + memberKey}, this.fetchProtectedPackageOperationCompleted, userState); + } + + private void OnfetchProtectedPackageOperationCompleted(object arg) + { + if ((this.fetchProtectedPackageCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.fetchProtectedPackageCompleted(this, new fetchProtectedPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/SubmitPackage", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public SubmitStatus SubmitPackage(string repositoryGuid, string authorGuid, string packageGuid, [System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] byte[] packageFile, [System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] byte[] packageDoc, [System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] byte[] packageThumbnail, string name, string author, string authorUrl, string description) + { + object[] results = this.Invoke("SubmitPackage", new object[] { + repositoryGuid, + authorGuid, + packageGuid, + packageFile, + packageDoc, + packageThumbnail, + name, + author, + authorUrl, + description}); + return ((SubmitStatus)(results[0])); + } + + /// + public System.IAsyncResult BeginSubmitPackage(string repositoryGuid, string authorGuid, string packageGuid, byte[] packageFile, byte[] packageDoc, byte[] packageThumbnail, string name, string author, string authorUrl, string description, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("SubmitPackage", new object[] { + repositoryGuid, + authorGuid, + packageGuid, + packageFile, + packageDoc, + packageThumbnail, + name, + author, + authorUrl, + description}, callback, asyncState); + } + + /// + public SubmitStatus EndSubmitPackage(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((SubmitStatus)(results[0])); + } + + /// + public void SubmitPackageAsync(string repositoryGuid, string authorGuid, string packageGuid, byte[] packageFile, byte[] packageDoc, byte[] packageThumbnail, string name, string author, string authorUrl, string description) + { + this.SubmitPackageAsync(repositoryGuid, authorGuid, packageGuid, packageFile, packageDoc, packageThumbnail, name, author, authorUrl, description, null); + } + + /// + public void SubmitPackageAsync(string repositoryGuid, string authorGuid, string packageGuid, byte[] packageFile, byte[] packageDoc, byte[] packageThumbnail, string name, string author, string authorUrl, string description, object userState) + { + if ((this.SubmitPackageOperationCompleted == null)) + { + this.SubmitPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSubmitPackageOperationCompleted); + } + this.InvokeAsync("SubmitPackage", new object[] { + repositoryGuid, + authorGuid, + packageGuid, + packageFile, + packageDoc, + packageThumbnail, + name, + author, + authorUrl, + description}, this.SubmitPackageOperationCompleted, userState); + } + + private void OnSubmitPackageOperationCompleted(object arg) + { + if ((this.SubmitPackageCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.SubmitPackageCompleted(this, new SubmitPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/PackageByGuid", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public Package PackageByGuid(string packageGuid) + { + object[] results = this.Invoke("PackageByGuid", new object[] { + packageGuid}); + return ((Package)(results[0])); + } + + /// + public System.IAsyncResult BeginPackageByGuid(string packageGuid, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("PackageByGuid", new object[] { + packageGuid}, callback, asyncState); + } + + /// + public Package EndPackageByGuid(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((Package)(results[0])); + } + + /// + public void PackageByGuidAsync(string packageGuid) + { + this.PackageByGuidAsync(packageGuid, null); + } + + /// + public void PackageByGuidAsync(string packageGuid, object userState) + { + if ((this.PackageByGuidOperationCompleted == null)) + { + this.PackageByGuidOperationCompleted = new System.Threading.SendOrPostCallback(this.OnPackageByGuidOperationCompleted); + } + this.InvokeAsync("PackageByGuid", new object[] { + packageGuid}, this.PackageByGuidOperationCompleted, userState); + } + + private void OnPackageByGuidOperationCompleted(object arg) + { + if ((this.PackageByGuidCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.PackageByGuidCompleted(this, new PackageByGuidCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/SkinByGuid", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public Skin SkinByGuid(string skinGuid) + { + object[] results = this.Invoke("SkinByGuid", new object[] { + skinGuid}); + return ((Skin)(results[0])); + } + + /// + public System.IAsyncResult BeginSkinByGuid(string skinGuid, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("SkinByGuid", new object[] { + skinGuid}, callback, asyncState); + } + + /// + public Skin EndSkinByGuid(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((Skin)(results[0])); + } + + /// + public void SkinByGuidAsync(string skinGuid) + { + this.SkinByGuidAsync(skinGuid, null); + } + + /// + public void SkinByGuidAsync(string skinGuid, object userState) + { + if ((this.SkinByGuidOperationCompleted == null)) + { + this.SkinByGuidOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSkinByGuidOperationCompleted); + } + this.InvokeAsync("SkinByGuid", new object[] { + skinGuid}, this.SkinByGuidOperationCompleted, userState); + } + + private void OnSkinByGuidOperationCompleted(object arg) + { + if ((this.SkinByGuidCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.SkinByGuidCompleted(this, new SkinByGuidCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Skins", RequestNamespace = "http://packages.umbraco.org/webservices/", ResponseNamespace = "http://packages.umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public Skin[] Skins(string starterKitGuid) + { + object[] results = this.Invoke("Skins", new object[] { + starterKitGuid}); + return ((Skin[])(results[0])); + } + + /// + public System.IAsyncResult BeginSkins(string starterKitGuid, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("Skins", new object[] { + starterKitGuid}, callback, asyncState); + } + + /// + public Skin[] EndSkins(System.IAsyncResult asyncResult) + { + object[] results = this.EndInvoke(asyncResult); + return ((Skin[])(results[0])); + } + + /// + public void SkinsAsync(string starterKitGuid) + { + this.SkinsAsync(starterKitGuid, null); + } + + /// + public void SkinsAsync(string starterKitGuid, object userState) + { + if ((this.SkinsOperationCompleted == null)) + { + this.SkinsOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSkinsOperationCompleted); + } + this.InvokeAsync("Skins", new object[] { + starterKitGuid}, this.SkinsOperationCompleted, userState); + } + + private void OnSkinsOperationCompleted(object arg) + { + if ((this.SkinsCompleted != null)) + { + System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); + this.SkinsCompleted(this, new SkinsCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); + } + } + + /// + public new void CancelAsync(object userState) + { + base.CancelAsync(userState); + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://packages.umbraco.org/webservices/")] + public partial class Category + { + + private string textField; + + private string descriptionField; + + private string urlField; + + private int idField; + + private Package[] packagesField; + + /// + public string Text + { + get + { + return this.textField; + } + set + { + this.textField = value; + } + } + + /// + public string Description + { + get + { + return this.descriptionField; + } + set + { + this.descriptionField = value; + } + } + + /// + public string Url + { + get + { + return this.urlField; + } + set + { + this.urlField = value; + } + } + + /// + public int Id + { + get + { + return this.idField; + } + set + { + this.idField = value; + } + } + + /// + public Package[] Packages + { + get + { + return this.packagesField; + } + set + { + this.packagesField = value; + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://packages.umbraco.org/webservices/")] + public partial class Package + { + + private System.Guid repoGuidField; + + private string textField; + + private string descriptionField; + + private string iconField; + + private string thumbnailField; + + private string documentationField; + + private string demoField; + + private bool acceptedField; + + private bool editorsPickField; + + private bool protectedField; + + private bool hasUpgradeField; + + private string upgradeVersionField; + + private string upgradeReadMeField; + + private string urlField; + + /// + public System.Guid RepoGuid + { + get + { + return this.repoGuidField; + } + set + { + this.repoGuidField = value; + } + } + + /// + public string Text + { + get + { + return this.textField; + } + set + { + this.textField = value; + } + } + + /// + public string Description + { + get + { + return this.descriptionField; + } + set + { + this.descriptionField = value; + } + } + + /// + public string Icon + { + get + { + return this.iconField; + } + set + { + this.iconField = value; + } + } + + /// + public string Thumbnail + { + get + { + return this.thumbnailField; + } + set + { + this.thumbnailField = value; + } + } + + /// + public string Documentation + { + get + { + return this.documentationField; + } + set + { + this.documentationField = value; + } + } + + /// + public string Demo + { + get + { + return this.demoField; + } + set + { + this.demoField = value; + } + } + + /// + public bool Accepted + { + get + { + return this.acceptedField; + } + set + { + this.acceptedField = value; + } + } + + /// + public bool EditorsPick + { + get + { + return this.editorsPickField; + } + set + { + this.editorsPickField = value; + } + } + + /// + public bool Protected + { + get + { + return this.protectedField; + } + set + { + this.protectedField = value; + } + } + + /// + public bool HasUpgrade + { + get + { + return this.hasUpgradeField; + } + set + { + this.hasUpgradeField = value; + } + } + + /// + public string UpgradeVersion + { + get + { + return this.upgradeVersionField; + } + set + { + this.upgradeVersionField = value; + } + } + + /// + public string UpgradeReadMe + { + get + { + return this.upgradeReadMeField; + } + set + { + this.upgradeReadMeField = value; + } + } + + /// + public string Url + { + get + { + return this.urlField; + } + set + { + this.urlField = value; + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://packages.umbraco.org/webservices/")] + public partial class Skin + { + + private System.Guid repoGuidField; + + private string textField; + + private string thumbnailField; + + private string previewField; + + private string descriptionField; + + private string authorField; + + private string authorUrlField; + + private string licenseField; + + private string licenseUrlField; + + /// + public System.Guid RepoGuid + { + get + { + return this.repoGuidField; + } + set + { + this.repoGuidField = value; + } + } + + /// + public string Text + { + get + { + return this.textField; + } + set + { + this.textField = value; + } + } + + /// + public string Thumbnail + { + get + { + return this.thumbnailField; + } + set + { + this.thumbnailField = value; + } + } + + /// + public string Preview + { + get + { + return this.previewField; + } + set + { + this.previewField = value; + } + } + + /// + public string Description + { + get + { + return this.descriptionField; + } + set + { + this.descriptionField = value; + } + } + + /// + public string Author + { + get + { + return this.authorField; + } + set + { + this.authorField = value; + } + } + + /// + public string AuthorUrl + { + get + { + return this.authorUrlField; + } + set + { + this.authorUrlField = value; + } + } + + /// + public string License + { + get + { + return this.licenseField; + } + set + { + this.licenseField = value; + } + } + + /// + public string LicenseUrl + { + get + { + return this.licenseUrlField; + } + set + { + this.licenseUrlField = value; + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.SerializableAttribute()] + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://packages.umbraco.org/webservices/")] + public enum Version + { + + /// + Version3, + + /// + Version4, + + /// + /// This is apparently the version number we pass in for all installs + /// + Version41, + + /// + Version41Legacy, + + /// + Version50, + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.SerializableAttribute()] + [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://packages.umbraco.org/webservices/")] + public enum SubmitStatus + { + + /// + Complete, + + /// + Exists, + + /// + NoAccess, + + /// + Error, + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void CategoriesCompletedEventHandler(object sender, CategoriesCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class CategoriesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal CategoriesCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public Category[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((Category[])(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void NitrosCompletedEventHandler(object sender, NitrosCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class NitrosCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal NitrosCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public Package[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((Package[])(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void NitrosByVersionCompletedEventHandler(object sender, NitrosByVersionCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class NitrosByVersionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal NitrosByVersionCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public Package[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((Package[])(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void NitrosCategorizedCompletedEventHandler(object sender, NitrosCategorizedCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class NitrosCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal NitrosCategorizedCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public Category[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((Category[])(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void NitrosCategorizedByVersionCompletedEventHandler(object sender, NitrosCategorizedByVersionCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class NitrosCategorizedByVersionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal NitrosCategorizedByVersionCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public Category[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((Category[])(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void StarterKitsCompletedEventHandler(object sender, StarterKitsCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class StarterKitsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal StarterKitsCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public Package[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((Package[])(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void StarterKitModulesCategorizedCompletedEventHandler(object sender, StarterKitModulesCategorizedCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class StarterKitModulesCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal StarterKitModulesCategorizedCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public Category[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((Category[])(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void StarterKitModulesCompletedEventHandler(object sender, StarterKitModulesCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class StarterKitModulesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal StarterKitModulesCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public Package[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((Package[])(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void authenticateCompletedEventHandler(object sender, authenticateCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class authenticateCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal authenticateCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public string Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((string)(this.results[0])); + } + } + } + + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] public delegate void GetPackageFileCompletedEventHandler(object sender, GetPackageFileCompletedEventArgs e); - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class GetPackageFileCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal GetPackageFileCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public byte[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((byte[])(this.results[0])); - } - } + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class GetPackageFileCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal GetPackageFileCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public byte[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((byte[])(this.results[0])); + } + } } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void fetchPackageByVersionCompletedEventHandler(object sender, fetchPackageByVersionCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class fetchPackageByVersionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal fetchPackageByVersionCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public byte[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((byte[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void fetchPackageCompletedEventHandler(object sender, fetchPackageCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class fetchPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal fetchPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public byte[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((byte[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void fetchProtectedPackageCompletedEventHandler(object sender, fetchProtectedPackageCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class fetchProtectedPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal fetchProtectedPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public byte[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((byte[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void SubmitPackageCompletedEventHandler(object sender, SubmitPackageCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class SubmitPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal SubmitPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public SubmitStatus Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((SubmitStatus)(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void PackageByGuidCompletedEventHandler(object sender, PackageByGuidCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class PackageByGuidCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal PackageByGuidCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public Package Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((Package)(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void SkinByGuidCompletedEventHandler(object sender, SkinByGuidCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class SkinByGuidCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal SkinByGuidCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public Skin Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((Skin)(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - public delegate void SkinsCompletedEventHandler(object sender, SkinsCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class SkinsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs - { - - private object[] results; - - internal SkinsCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) - { - this.results = results; - } - - /// - public Skin[] Result - { - get - { - this.RaiseExceptionIfNecessary(); - return ((Skin[])(this.results[0])); - } - } - } -} + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void fetchPackageByVersionCompletedEventHandler(object sender, fetchPackageByVersionCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class fetchPackageByVersionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal fetchPackageByVersionCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public byte[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((byte[])(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void fetchPackageCompletedEventHandler(object sender, fetchPackageCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class fetchPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal fetchPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public byte[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((byte[])(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void fetchProtectedPackageCompletedEventHandler(object sender, fetchProtectedPackageCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class fetchProtectedPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal fetchProtectedPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public byte[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((byte[])(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void SubmitPackageCompletedEventHandler(object sender, SubmitPackageCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class SubmitPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal SubmitPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public SubmitStatus Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((SubmitStatus)(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void PackageByGuidCompletedEventHandler(object sender, PackageByGuidCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class PackageByGuidCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal PackageByGuidCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public Package Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((Package)(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void SkinByGuidCompletedEventHandler(object sender, SkinByGuidCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class SkinByGuidCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal SkinByGuidCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public Skin Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((Skin)(this.results[0])); + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + public delegate void SkinsCompletedEventHandler(object sender, SkinsCompletedEventArgs e); + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.0.30319.1")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public partial class SkinsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs + { + + private object[] results; + + internal SkinsCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : + base(exception, cancelled, userState) + { + this.results = results; + } + + /// + public Skin[] Result + { + get + { + this.RaiseExceptionIfNecessary(); + return ((Skin[])(this.results[0])); + } + } + } +} diff --git a/src/Umbraco.Web/_Legacy/Packager/RequirementsType.cs b/src/Umbraco.Web/_Legacy/Packager/RequirementsType.cs new file mode 100644 index 0000000000..ea63ff6979 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Packager/RequirementsType.cs @@ -0,0 +1,8 @@ +namespace umbraco.cms.businesslogic.packager +{ + public enum RequirementsType + { + Strict, + Legacy + } +} diff --git a/src/Umbraco.Web/_Legacy/Packager/Settings.cs b/src/Umbraco.Web/_Legacy/Packager/Settings.cs new file mode 100644 index 0000000000..85cfebcfee --- /dev/null +++ b/src/Umbraco.Web/_Legacy/Packager/Settings.cs @@ -0,0 +1,83 @@ +using System; +using System.Data; +using System.Configuration; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using System.IO; +using Umbraco.Core.IO; + +namespace umbraco.cms.businesslogic.packager +{ + public class Settings + { + + public static string PackagerRoot + { + get { return SystemDirectories.Packages; } + } + + public static string PackagesStorage + { + get { return SystemDirectories.Packages + IOHelper.DirSepChar + "created"; } + } + + public static string InstalledPackagesStorage + { + get { return SystemDirectories.Packages + IOHelper.DirSepChar + "installed"; } + } + + public static string InstalledPackagesSettings + { + get { return SystemDirectories.Packages + IOHelper.DirSepChar + "installed" + IOHelper.DirSepChar + "installedPackages.config"; } + } + + public static string CreatedPackagesSettings + { + get { return SystemDirectories.Packages + IOHelper.DirSepChar + "created" + IOHelper.DirSepChar + "createdPackages.config"; } + } + + public static string PackageFileExtension + { + get { return "zip"; } + } + + public static bool HasFileAccess(ref Exception exp) + { + bool hasAccess = false; + StreamWriter sw1 = null; + StreamWriter sw2 = null; + try + { + sw1 = System.IO.File.AppendText(IOHelper.MapPath(InstalledPackagesSettings)); + sw1.Close(); + + sw2 = System.IO.File.AppendText(IOHelper.MapPath(CreatedPackagesSettings)); + sw1.Close(); + + System.IO.Directory.CreateDirectory(IOHelper.MapPath(PackagesStorage) + IOHelper.DirSepChar + "__testFolder__"); + System.IO.Directory.CreateDirectory(IOHelper.MapPath(InstalledPackagesStorage) + IOHelper.DirSepChar + "__testFolder__"); + + System.IO.Directory.Delete(IOHelper.MapPath(PackagesStorage) + IOHelper.DirSepChar + "__testFolder__", true); + System.IO.Directory.Delete(IOHelper.MapPath(InstalledPackagesStorage) + IOHelper.DirSepChar + "__testFolder__", true); + + hasAccess = true; + } + finally + { + if (sw1 != null) + sw1.Close(); + if (sw2 != null) + sw2.Close(); + } + + return hasAccess; + } + + + } + +} diff --git a/src/umbraco.cms/businesslogic/Packager/data.cs b/src/Umbraco.Web/_Legacy/Packager/data.cs similarity index 94% rename from src/umbraco.cms/businesslogic/Packager/data.cs rename to src/Umbraco.Web/_Legacy/Packager/data.cs index 536be6d435..3cb1148bb4 100644 --- a/src/umbraco.cms/businesslogic/Packager/data.cs +++ b/src/Umbraco.Web/_Legacy/Packager/data.cs @@ -1,416 +1,407 @@ -using System; -using System.Xml; -using System.Xml.XPath; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; - -namespace umbraco.cms.businesslogic.packager -{ - /// - /// This is the xml data for installed packages. This is not the same xml as a pckage format! - /// - public class data - { - private static XmlDocument _source; - - public static XmlDocument Source - { - get - { - return _source; - } - } - - public static void Reload(string dataSource) - { - //do some error checking and create the folders/files if they don't exist - if (!File.Exists(dataSource)) - { - if (!Directory.Exists(IOHelper.MapPath(Settings.PackagerRoot))) - { - Directory.CreateDirectory(IOHelper.MapPath(Settings.PackagerRoot)); - } - if (!Directory.Exists(IOHelper.MapPath(Settings.PackagesStorage))) - { - Directory.CreateDirectory(IOHelper.MapPath(Settings.PackagesStorage)); - } - if (!Directory.Exists(IOHelper.MapPath(Settings.InstalledPackagesStorage))) - { - Directory.CreateDirectory(IOHelper.MapPath(Settings.InstalledPackagesStorage)); - } - - using (StreamWriter sw = File.CreateText(dataSource)) - { - sw.Write(umbraco.cms.businesslogic.Packager.FileResources.PackageFiles.Packages); - sw.Flush(); - } - - } - - if (_source == null) - { - _source = new XmlDocument(); - } - - //error checking here - if (File.Exists(dataSource)) - { - var isEmpty = false; - using (var sr = new StreamReader(dataSource)) - { - if (sr.ReadToEnd().IsNullOrWhiteSpace()) - { - isEmpty = true; - } - } - if (isEmpty) - { - File.WriteAllText(dataSource, @""); - } - } - - _source.Load(dataSource); - } - - public static XmlNode GetFromId(int Id, string dataSource, bool reload) - { - if (reload) - Reload(dataSource); - - return Source.SelectSingleNode("/packages/package [@id = '" + Id.ToString().ToUpper() + "']"); - } - - public static XmlNode GetFromGuid(string guid, string dataSource, bool reload) - { - if (reload) - Reload(dataSource); - - return Source.SelectSingleNode("/packages/package [@packageGuid = '" + guid + "']"); - } - - public static PackageInstance MakeNew(string Name, string dataSource) - { - Reload(dataSource); - - int maxId = 1; - // Find max id - foreach (XmlNode n in Source.SelectNodes("packages/package")) - { - if (int.Parse(n.Attributes.GetNamedItem("id").Value) >= maxId) - maxId = int.Parse(n.Attributes.GetNamedItem("id").Value) + 1; - } - - XmlElement instance = Source.CreateElement("package"); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "id", maxId.ToString())); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "version", "")); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "")); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "name", Name)); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "folder", Guid.NewGuid().ToString())); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "packagepath", "")); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "repositoryGuid", "")); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "iconUrl", "")); - //set to current version - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "umbVersion", UmbracoVersion.Current.ToString(3))); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "packageGuid", Guid.NewGuid().ToString())); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "hasUpdate", "false")); - - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "enableSkins", "false")); - instance.Attributes.Append(XmlHelper.AddAttribute(Source, "skinRepoGuid", "")); - - XmlElement license = Source.CreateElement("license"); - license.InnerText = "MIT License"; - license.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "http://opensource.org/licenses/MIT")); - instance.AppendChild(license); - - XmlElement author = Source.CreateElement("author"); - author.InnerText = ""; - author.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "")); - instance.AppendChild(author); - - instance.AppendChild(XmlHelper.AddTextNode(Source, "readme", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "actions", "")); - - instance.AppendChild(XmlHelper.AddTextNode(Source, "datatypes", "")); - - XmlElement content = Source.CreateElement("content"); - content.InnerText = ""; - content.Attributes.Append(XmlHelper.AddAttribute(Source, "nodeId", "")); - content.Attributes.Append(XmlHelper.AddAttribute(Source, "loadChildNodes", "false")); - instance.AppendChild(content); - - instance.AppendChild(XmlHelper.AddTextNode(Source, "templates", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "stylesheets", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "documenttypes", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "macros", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "files", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "languages", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "dictionaryitems", "")); - instance.AppendChild(XmlHelper.AddTextNode(Source, "loadcontrol", "")); - - Source.SelectSingleNode("packages").AppendChild(instance); - Source.Save(dataSource); - var retVal = data.Package(maxId, dataSource); - - return retVal; - } - - public static PackageInstance Package(int id, string datasource) - { - return ConvertXmlToPackage(GetFromId(id, datasource, true)); - } - - public static PackageInstance Package(string guid, string datasource) - { - XmlNode node = GetFromGuid(guid, datasource, true); - if (node != null) - return ConvertXmlToPackage(node); - else - return new PackageInstance(); - } - - public static List GetAllPackages(string dataSource) - { - Reload(dataSource); - XmlNodeList nList = data.Source.SelectNodes("packages/package"); - - List retVal = new List(); - - for (int i = 0; i < nList.Count; i++) - { - try - { - retVal.Add(ConvertXmlToPackage(nList[i])); - } - catch (Exception ex) - { - LogHelper.Error("An error occurred in GetAllPackages", ex); - } - } - - return retVal; - } - - private static PackageInstance ConvertXmlToPackage(XmlNode n) - { - PackageInstance retVal = new PackageInstance(); - - if (n != null) - { - retVal.Id = int.Parse(SafeAttribute("id", n)); - retVal.Name = SafeAttribute("name", n); - retVal.Folder = SafeAttribute("folder", n); - retVal.PackagePath = SafeAttribute("packagepath", n); - retVal.Version = SafeAttribute("version", n); - retVal.Url = SafeAttribute("url", n); - retVal.RepositoryGuid = SafeAttribute("repositoryGuid", n); - retVal.PackageGuid = SafeAttribute("packageGuid", n); - retVal.HasUpdate = bool.Parse(SafeAttribute("hasUpdate", n)); - - retVal.IconUrl = SafeAttribute("iconUrl", n); - var umbVersion = SafeAttribute("umbVersion", n); - Version parsedVersion; - if (umbVersion.IsNullOrWhiteSpace() == false && Version.TryParse(umbVersion, out parsedVersion)) - { - retVal.UmbracoVersion = parsedVersion; - } - - bool enableSkins = false; - bool.TryParse(SafeAttribute("enableSkins", n), out enableSkins); - retVal.EnableSkins = enableSkins; - - retVal.SkinRepoGuid = string.IsNullOrEmpty(SafeAttribute("skinRepoGuid", n)) ? Guid.Empty : new Guid(SafeAttribute("skinRepoGuid", n)); - - retVal.License = SafeNodeValue(n.SelectSingleNode("license")); - retVal.LicenseUrl = n.SelectSingleNode("license").Attributes.GetNamedItem("url").Value; - - retVal.Author = SafeNodeValue(n.SelectSingleNode("author")); - retVal.AuthorUrl = SafeAttribute("url", n.SelectSingleNode("author")); - - retVal.Readme = SafeNodeValue(n.SelectSingleNode("readme")); - retVal.Actions = SafeNodeInnerXml(n.SelectSingleNode("actions")); - - retVal.ContentNodeId = SafeAttribute("nodeId", n.SelectSingleNode("content")); - retVal.ContentLoadChildNodes = bool.Parse(SafeAttribute("loadChildNodes", n.SelectSingleNode("content"))); - - retVal.Macros = new List(SafeNodeValue(n.SelectSingleNode("macros")).Trim(',').Split(',')); - retVal.Macros = new List(SafeNodeValue(n.SelectSingleNode("macros")).Trim(',').Split(',')); - retVal.Templates = new List(SafeNodeValue(n.SelectSingleNode("templates")).Trim(',').Split(',')); - retVal.Stylesheets = new List(SafeNodeValue(n.SelectSingleNode("stylesheets")).Trim(',').Split(',')); - retVal.Documenttypes = new List(SafeNodeValue(n.SelectSingleNode("documenttypes")).Trim(',').Split(',')); - retVal.Languages = new List(SafeNodeValue(n.SelectSingleNode("languages")).Trim(',').Split(',')); - retVal.DictionaryItems = new List(SafeNodeValue(n.SelectSingleNode("dictionaryitems")).Trim(',').Split(',')); - retVal.DataTypes = new List(SafeNodeValue(n.SelectSingleNode("datatypes")).Trim(',').Split(',')); - - XmlNodeList xmlFiles = n.SelectNodes("files/file"); - retVal.Files = new List(); - - for (int i = 0; i < xmlFiles.Count; i++) - retVal.Files.Add(xmlFiles[i].InnerText); - - retVal.LoadControl = SafeNodeValue(n.SelectSingleNode("loadcontrol")); - } - - return retVal; - } - - public static void Delete(int Id, string dataSource) - { - Reload(dataSource); - // Remove physical xml file if any - //PackageInstance p = new PackageInstance(Id); - - //TODO DELETE PACKAGE FOLDER... - //p.Folder - - XmlNode n = data.GetFromId(Id, dataSource, true); - if (n != null) - { - data.Source.SelectSingleNode("/packages").RemoveChild(n); - data.Source.Save(dataSource); - } - - } - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This method is no longer in use and will be removed in the future")] - public static void UpdateValue(XmlNode n, string Value) - { - if (n.FirstChild != null) - n.FirstChild.Value = Value; - else - { - n.AppendChild(Source.CreateTextNode(Value)); - } - } - - public static void Save(PackageInstance package, string dataSource) - { - Reload(dataSource); - var xmlDef = GetFromId(package.Id, dataSource, false); - XmlHelper.SetAttribute(Source, xmlDef, "name", package.Name); - XmlHelper.SetAttribute(Source, xmlDef, "version", package.Version); - XmlHelper.SetAttribute(Source, xmlDef, "url", package.Url); - XmlHelper.SetAttribute(Source, xmlDef, "packagepath", package.PackagePath); - XmlHelper.SetAttribute(Source, xmlDef, "repositoryGuid", package.RepositoryGuid); - XmlHelper.SetAttribute(Source, xmlDef, "packageGuid", package.PackageGuid); - XmlHelper.SetAttribute(Source, xmlDef, "hasUpdate", package.HasUpdate.ToString()); - XmlHelper.SetAttribute(Source, xmlDef, "enableSkins", package.EnableSkins.ToString()); - XmlHelper.SetAttribute(Source, xmlDef, "skinRepoGuid", package.SkinRepoGuid.ToString()); - XmlHelper.SetAttribute(Source, xmlDef, "iconUrl", package.IconUrl); - if (package.UmbracoVersion != null) - { - XmlHelper.SetAttribute(Source, xmlDef, "umbVersion", package.UmbracoVersion.ToString(3)); - } - - - var licenseNode = xmlDef.SelectSingleNode("license"); - if (licenseNode == null) - { - licenseNode = Source.CreateElement("license"); - xmlDef.AppendChild(licenseNode); - } - licenseNode.InnerText = package.License; - XmlHelper.SetAttribute(Source, licenseNode, "url", package.LicenseUrl); - - var authorNode = xmlDef.SelectSingleNode("author"); - if (authorNode == null) - { - authorNode = Source.CreateElement("author"); - xmlDef.AppendChild(authorNode); - } - authorNode.InnerText = package.Author; - XmlHelper.SetAttribute(Source, authorNode, "url", package.AuthorUrl); - - XmlHelper.SetCDataNode(Source, xmlDef, "readme", package.Readme); - XmlHelper.SetInnerXmlNode(Source, xmlDef, "actions", package.Actions); - - var contentNode = xmlDef.SelectSingleNode("content"); - if (contentNode == null) - { - contentNode = Source.CreateElement("content"); - xmlDef.AppendChild(contentNode); - } - XmlHelper.SetAttribute(Source, contentNode, "nodeId", package.ContentNodeId); - XmlHelper.SetAttribute(Source, contentNode, "loadChildNodes", package.ContentLoadChildNodes.ToString()); - - XmlHelper.SetTextNode(Source, xmlDef, "macros", JoinList(package.Macros, ',')); - XmlHelper.SetTextNode(Source, xmlDef, "templates", JoinList(package.Templates, ',')); - XmlHelper.SetTextNode(Source, xmlDef, "stylesheets", JoinList(package.Stylesheets, ',')); - XmlHelper.SetTextNode(Source, xmlDef, "documenttypes", JoinList(package.Documenttypes, ',')); - XmlHelper.SetTextNode(Source, xmlDef, "languages", JoinList(package.Languages, ',')); - XmlHelper.SetTextNode(Source, xmlDef, "dictionaryitems", JoinList(package.DictionaryItems, ',')); - XmlHelper.SetTextNode(Source, xmlDef, "datatypes", JoinList(package.DataTypes, ',')); - - var filesNode = xmlDef.SelectSingleNode("files"); - if (filesNode == null) - { - filesNode = Source.CreateElement("files"); - xmlDef.AppendChild(filesNode); - } - filesNode.InnerXml = ""; - - foreach (var fileStr in package.Files) - { - if (string.IsNullOrWhiteSpace(fileStr) == false) - filesNode.AppendChild(XmlHelper.AddTextNode(Source, "file", fileStr)); - } - - XmlHelper.SetTextNode(Source, xmlDef, "loadcontrol", package.LoadControl); - - Source.Save(dataSource); - } - - - - private static string SafeAttribute(string name, XmlNode n) - { - return n.Attributes == null || n.Attributes[name] == null ? string.Empty : n.Attributes[name].Value; - } - - private static string SafeNodeValue(XmlNode n) - { - try - { - return XmlHelper.GetNodeValue(n); - } - catch - { - return string.Empty; - } - } - - private static string SafeNodeInnerXml(XmlNode n) - { - try - { - return n.InnerXml; - } - catch - { - return string.Empty; - } - } - - - private static string JoinList(List list, char seperator) - { - string retVal = ""; - foreach (string str in list) - { - retVal += str + seperator; - } - - return retVal.Trim(seperator); - } - - public data() - { - - } - } -} +using System; +using System.Xml; +using System.Xml.XPath; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Composing; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Xml; + +namespace umbraco.cms.businesslogic.packager +{ + /// + /// This is the xml data for installed packages. This is not the same xml as a pckage format! + /// + public class data + { + public static XmlDocument Source { get; private set; } + + public static void Reload(string dataSource) + { + //do some error checking and create the folders/files if they don't exist + if (!File.Exists(dataSource)) + { + if (!Directory.Exists(IOHelper.MapPath(Settings.PackagerRoot))) + { + Directory.CreateDirectory(IOHelper.MapPath(Settings.PackagerRoot)); + } + if (!Directory.Exists(IOHelper.MapPath(Settings.PackagesStorage))) + { + Directory.CreateDirectory(IOHelper.MapPath(Settings.PackagesStorage)); + } + if (!Directory.Exists(IOHelper.MapPath(Settings.InstalledPackagesStorage))) + { + Directory.CreateDirectory(IOHelper.MapPath(Settings.InstalledPackagesStorage)); + } + + using (StreamWriter sw = File.CreateText(dataSource)) + { + sw.Write($@"{Environment.NewLine}{Environment.NewLine}"); + sw.Flush(); + } + + } + + if (Source == null) + { + Source = new XmlDocument(); + } + + //error checking here + if (File.Exists(dataSource)) + { + var isEmpty = false; + using (var sr = new StreamReader(dataSource)) + { + if (sr.ReadToEnd().IsNullOrWhiteSpace()) + { + isEmpty = true; + } + } + if (isEmpty) + { + File.WriteAllText(dataSource, @""); + } + } + + Source.Load(dataSource); + } + + public static XmlNode GetFromId(int Id, string dataSource, bool reload) + { + if (reload) + Reload(dataSource); + + return Source.SelectSingleNode("/packages/package [@id = '" + Id.ToString().ToUpper() + "']"); + } + + public static XmlNode GetFromGuid(string guid, string dataSource, bool reload) + { + if (reload) + Reload(dataSource); + + return Source.SelectSingleNode("/packages/package [@packageGuid = '" + guid + "']"); + } + + public static PackageInstance MakeNew(string Name, string dataSource) + { + Reload(dataSource); + + int maxId = 1; + // Find max id + foreach (XmlNode n in Source.SelectNodes("packages/package")) + { + if (int.Parse(n.Attributes.GetNamedItem("id").Value) >= maxId) + maxId = int.Parse(n.Attributes.GetNamedItem("id").Value) + 1; + } + + XmlElement instance = Source.CreateElement("package"); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "id", maxId.ToString())); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "version", "")); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "")); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "name", Name)); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "folder", Guid.NewGuid().ToString())); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "packagepath", "")); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "repositoryGuid", "")); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "iconUrl", "")); + //set to current version + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "umbVersion", UmbracoVersion.Current.ToString(3))); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "packageGuid", Guid.NewGuid().ToString())); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "hasUpdate", "false")); + + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "enableSkins", "false")); + instance.Attributes.Append(XmlHelper.AddAttribute(Source, "skinRepoGuid", "")); + + XmlElement license = Source.CreateElement("license"); + license.InnerText = "MIT License"; + license.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "http://opensource.org/licenses/MIT")); + instance.AppendChild(license); + + XmlElement author = Source.CreateElement("author"); + author.InnerText = ""; + author.Attributes.Append(XmlHelper.AddAttribute(Source, "url", "")); + instance.AppendChild(author); + + instance.AppendChild(XmlHelper.AddTextNode(Source, "readme", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "actions", "")); + + instance.AppendChild(XmlHelper.AddTextNode(Source, "datatypes", "")); + + XmlElement content = Source.CreateElement("content"); + content.InnerText = ""; + content.Attributes.Append(XmlHelper.AddAttribute(Source, "nodeId", "")); + content.Attributes.Append(XmlHelper.AddAttribute(Source, "loadChildNodes", "false")); + instance.AppendChild(content); + + instance.AppendChild(XmlHelper.AddTextNode(Source, "templates", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "stylesheets", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "documenttypes", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "macros", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "files", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "languages", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "dictionaryitems", "")); + instance.AppendChild(XmlHelper.AddTextNode(Source, "loadcontrol", "")); + + Source.SelectSingleNode("packages").AppendChild(instance); + Source.Save(dataSource); + var retVal = data.Package(maxId, dataSource); + + return retVal; + } + + public static PackageInstance Package(int id, string datasource) + { + return ConvertXmlToPackage(GetFromId(id, datasource, true)); + } + + public static PackageInstance Package(string guid, string datasource) + { + XmlNode node = GetFromGuid(guid, datasource, true); + if (node != null) + return ConvertXmlToPackage(node); + else + return new PackageInstance(); + } + + public static List GetAllPackages(string dataSource) + { + Reload(dataSource); + XmlNodeList nList = data.Source.SelectNodes("packages/package"); + + List retVal = new List(); + + for (int i = 0; i < nList.Count; i++) + { + try + { + retVal.Add(ConvertXmlToPackage(nList[i])); + } + catch (Exception ex) + { + Current.Logger.Error("An error occurred in GetAllPackages", ex); + } + } + + return retVal; + } + + private static PackageInstance ConvertXmlToPackage(XmlNode n) + { + PackageInstance retVal = new PackageInstance(); + + if (n != null) + { + retVal.Id = int.Parse(SafeAttribute("id", n)); + retVal.Name = SafeAttribute("name", n); + retVal.Folder = SafeAttribute("folder", n); + retVal.PackagePath = SafeAttribute("packagepath", n); + retVal.Version = SafeAttribute("version", n); + retVal.Url = SafeAttribute("url", n); + retVal.RepositoryGuid = SafeAttribute("repositoryGuid", n); + retVal.PackageGuid = SafeAttribute("packageGuid", n); + retVal.HasUpdate = bool.Parse(SafeAttribute("hasUpdate", n)); + + retVal.IconUrl = SafeAttribute("iconUrl", n); + var umbVersion = SafeAttribute("umbVersion", n); + Version parsedVersion; + if (umbVersion.IsNullOrWhiteSpace() == false && Version.TryParse(umbVersion, out parsedVersion)) + { + retVal.UmbracoVersion = parsedVersion; + } + + bool enableSkins = false; + bool.TryParse(SafeAttribute("enableSkins", n), out enableSkins); + retVal.EnableSkins = enableSkins; + + retVal.SkinRepoGuid = string.IsNullOrEmpty(SafeAttribute("skinRepoGuid", n)) ? Guid.Empty : new Guid(SafeAttribute("skinRepoGuid", n)); + + retVal.License = SafeNodeValue(n.SelectSingleNode("license")); + retVal.LicenseUrl = n.SelectSingleNode("license").Attributes.GetNamedItem("url").Value; + + retVal.Author = SafeNodeValue(n.SelectSingleNode("author")); + retVal.AuthorUrl = SafeAttribute("url", n.SelectSingleNode("author")); + + retVal.Readme = SafeNodeValue(n.SelectSingleNode("readme")); + retVal.Actions = SafeNodeInnerXml(n.SelectSingleNode("actions")); + + retVal.ContentNodeId = SafeAttribute("nodeId", n.SelectSingleNode("content")); + retVal.ContentLoadChildNodes = bool.Parse(SafeAttribute("loadChildNodes", n.SelectSingleNode("content"))); + + retVal.Macros = new List(SafeNodeValue(n.SelectSingleNode("macros")).Trim(',').Split(',')); + retVal.Macros = new List(SafeNodeValue(n.SelectSingleNode("macros")).Trim(',').Split(',')); + retVal.Templates = new List(SafeNodeValue(n.SelectSingleNode("templates")).Trim(',').Split(',')); + retVal.Stylesheets = new List(SafeNodeValue(n.SelectSingleNode("stylesheets")).Trim(',').Split(',')); + retVal.Documenttypes = new List(SafeNodeValue(n.SelectSingleNode("documenttypes")).Trim(',').Split(',')); + retVal.Languages = new List(SafeNodeValue(n.SelectSingleNode("languages")).Trim(',').Split(',')); + retVal.DictionaryItems = new List(SafeNodeValue(n.SelectSingleNode("dictionaryitems")).Trim(',').Split(',')); + retVal.DataTypes = new List(SafeNodeValue(n.SelectSingleNode("datatypes")).Trim(',').Split(',')); + + XmlNodeList xmlFiles = n.SelectNodes("files/file"); + retVal.Files = new List(); + + for (int i = 0; i < xmlFiles.Count; i++) + retVal.Files.Add(xmlFiles[i].InnerText); + + retVal.LoadControl = SafeNodeValue(n.SelectSingleNode("loadcontrol")); + } + + return retVal; + } + + public static void Delete(int Id, string dataSource) + { + Reload(dataSource); + // Remove physical xml file if any + //PackageInstance p = new PackageInstance(Id); + + //TODO DELETE PACKAGE FOLDER... + //p.Folder + + XmlNode n = data.GetFromId(Id, dataSource, true); + if (n != null) + { + data.Source.SelectSingleNode("/packages").RemoveChild(n); + data.Source.Save(dataSource); + } + + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This method is no longer in use and will be removed in the future")] + public static void UpdateValue(XmlNode n, string Value) + { + if (n.FirstChild != null) + n.FirstChild.Value = Value; + else + { + n.AppendChild(Source.CreateTextNode(Value)); + } + } + + public static void Save(PackageInstance package, string dataSource) + { + Reload(dataSource); + var xmlDef = GetFromId(package.Id, dataSource, false); + XmlHelper.SetAttribute(Source, xmlDef, "name", package.Name); + XmlHelper.SetAttribute(Source, xmlDef, "version", package.Version); + XmlHelper.SetAttribute(Source, xmlDef, "url", package.Url); + XmlHelper.SetAttribute(Source, xmlDef, "packagepath", package.PackagePath); + XmlHelper.SetAttribute(Source, xmlDef, "repositoryGuid", package.RepositoryGuid); + XmlHelper.SetAttribute(Source, xmlDef, "packageGuid", package.PackageGuid); + XmlHelper.SetAttribute(Source, xmlDef, "hasUpdate", package.HasUpdate.ToString()); + XmlHelper.SetAttribute(Source, xmlDef, "enableSkins", package.EnableSkins.ToString()); + XmlHelper.SetAttribute(Source, xmlDef, "skinRepoGuid", package.SkinRepoGuid.ToString()); + XmlHelper.SetAttribute(Source, xmlDef, "iconUrl", package.IconUrl); + if (package.UmbracoVersion != null) + XmlHelper.SetAttribute(Source, xmlDef, "umbVersion", package.UmbracoVersion.ToString(3)); + + var licenseNode = xmlDef.SelectSingleNode("license"); + if (licenseNode == null) + { + licenseNode = Source.CreateElement("license"); + xmlDef.AppendChild(licenseNode); + } + licenseNode.InnerText = package.License; + XmlHelper.SetAttribute(Source, licenseNode, "url", package.LicenseUrl); + + var authorNode = xmlDef.SelectSingleNode("author"); + if (authorNode == null) + { + authorNode = Source.CreateElement("author"); + xmlDef.AppendChild(authorNode); + } + authorNode.InnerText = package.Author; + XmlHelper.SetAttribute(Source, authorNode, "url", package.AuthorUrl); + + XmlHelper.SetCDataNode(Source, xmlDef, "readme", package.Readme); + XmlHelper.SetInnerXmlNode(Source, xmlDef, "actions", package.Actions); + + var contentNode = xmlDef.SelectSingleNode("content"); + if (contentNode == null) + { + contentNode = Source.CreateElement("content"); + xmlDef.AppendChild(contentNode); + } + XmlHelper.SetAttribute(Source, contentNode, "nodeId", package.ContentNodeId); + XmlHelper.SetAttribute(Source, contentNode, "loadChildNodes", package.ContentLoadChildNodes.ToString()); + + XmlHelper.SetTextNode(Source, xmlDef, "macros", JoinList(package.Macros, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "templates", JoinList(package.Templates, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "stylesheets", JoinList(package.Stylesheets, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "documenttypes", JoinList(package.Documenttypes, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "languages", JoinList(package.Languages, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "dictionaryitems", JoinList(package.DictionaryItems, ',')); + XmlHelper.SetTextNode(Source, xmlDef, "datatypes", JoinList(package.DataTypes, ',')); + + var filesNode = xmlDef.SelectSingleNode("files"); + if (filesNode == null) + { + filesNode = Source.CreateElement("files"); + xmlDef.AppendChild(filesNode); + } + filesNode.InnerXml = ""; + + foreach (var fileStr in package.Files) + { + if (string.IsNullOrWhiteSpace(fileStr) == false) + filesNode.AppendChild(XmlHelper.AddTextNode(Source, "file", fileStr)); + } + + XmlHelper.SetTextNode(Source, xmlDef, "loadcontrol", package.LoadControl); + + Source.Save(dataSource); + } + + + + private static string SafeAttribute(string name, XmlNode n) + { + return n.Attributes == null || n.Attributes[name] == null ? string.Empty : n.Attributes[name].Value; + } + + private static string SafeNodeValue(XmlNode n) + { + try + { + return XmlHelper.GetNodeValue(n); + } + catch + { + return string.Empty; + } + } + + private static string SafeNodeInnerXml(XmlNode n) + { + try + { + return n.InnerXml; + } + catch + { + return string.Empty; + } + } + + + private static string JoinList(List list, char seperator) + { + string retVal = ""; + foreach (string str in list) + { + retVal += str + seperator; + } + + return retVal.Trim(seperator); + } + + public data() + { + + } + } +} diff --git a/src/Umbraco.Web/_Legacy/UI/ITask.cs b/src/Umbraco.Web/_Legacy/UI/ITask.cs new file mode 100644 index 0000000000..62ce4c92c8 --- /dev/null +++ b/src/Umbraco.Web/_Legacy/UI/ITask.cs @@ -0,0 +1,18 @@ +using System; + +namespace Umbraco.Web._Legacy.UI +{ + /// + /// Summary description for ITask. + /// + [Obsolete("ITask is used for legacy webforms back office editors, change to using the v7 angular approach")] + public interface ITask + { + int ParentID {set; get;} + int TypeID {set; get;} + string Alias {set; get;} + bool Save(); + bool Delete(); + int UserId {set;} + } +} diff --git a/src/Umbraco.Web/_Legacy/UI/ITaskReturnUrl.cs b/src/Umbraco.Web/_Legacy/UI/ITaskReturnUrl.cs new file mode 100644 index 0000000000..c42ad9dbdb --- /dev/null +++ b/src/Umbraco.Web/_Legacy/UI/ITaskReturnUrl.cs @@ -0,0 +1,10 @@ +using System; + +namespace Umbraco.Web._Legacy.UI +{ + [Obsolete("ITask is used for legacy webforms back office editors, change to using the v7 angular approach")] + public interface ITaskReturnUrl : ITask + { + string ReturnUrl {get;} + } +} diff --git a/src/Umbraco.Web/UI/LegacyDialogHandler.cs b/src/Umbraco.Web/_Legacy/UI/LegacyDialogHandler.cs similarity index 77% rename from src/Umbraco.Web/UI/LegacyDialogHandler.cs rename to src/Umbraco.Web/_Legacy/UI/LegacyDialogHandler.cs index 0c3d09f1a2..cfa93dc0f6 100644 --- a/src/Umbraco.Web/UI/LegacyDialogHandler.cs +++ b/src/Umbraco.Web/_Legacy/UI/LegacyDialogHandler.cs @@ -1,248 +1,228 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Web; -using System.Xml; -using Umbraco.Core; -using Umbraco.Core.IO; -using umbraco.BasePages; -using umbraco.BusinessLogic; -using umbraco.interfaces; -using System.Linq; - -namespace Umbraco.Web.UI -{ - /// - /// This is used to replace the old umbraco.presentation.create.dialogHandler_temp class which is used - /// to handle sections create/delete actions. - /// - /// - /// We need to overhaul how all of this is handled which is why this is a legacy class - /// http://issues.umbraco.org/issue/U4-1373 - /// - public static class LegacyDialogHandler - { - private enum Operation - { - Create, - Delete - } - - private const string ContextKeyCreate = "LegacyDialogHandler-Create-"; - private const string ContextKeyDelete = "LegacyDialogHandler-Delete-"; - - /// - /// Gets the ITask for the operation for the node Type - /// - /// - /// - /// - /// - /// - /// Returns the ITask if one is found and can be made, otherwise null - /// - /// - /// This will first check if we've already created the ITask in the current Http request - /// - private static ITask GetTaskForOperation(HttpContextBase httpContext, User umbracoUser, Operation op, string nodeType) - { - if (httpContext == null) throw new ArgumentNullException("httpContext"); - if (umbracoUser == null) throw new ArgumentNullException("umbracoUser"); - if (nodeType == null) throw new ArgumentNullException("nodeType"); - - var ctxKey = op == Operation.Create ? ContextKeyCreate : ContextKeyDelete; - - //check contextual cache - if (httpContext.Items[ctxKey] != null) - { - return (ITask) httpContext.Items[ctxKey]; - } - - var operationNode = op == Operation.Create ? "create" : "delete"; - var createDef = GetXmlDoc(); - var def = createDef.SelectSingleNode("//nodeType [@alias = '" + nodeType + "']"); - if (def == null) - { - return null; - } - var del = def.SelectSingleNode("./tasks/" + operationNode); - if (del == null) - { - return null; - } - if (!del.Attributes.HasAttribute("assembly")) - { - return null; - } - var taskAssembly = del.AttributeValue("assembly"); - - if (!del.Attributes.HasAttribute("type")) - { - return null; - } - var taskType = del.AttributeValue("type"); - - var assembly = Assembly.LoadFrom(IOHelper.MapPath(SystemDirectories.Bin + "/" + taskAssembly + ".dll")); - var type = assembly.GetType(taskAssembly + "." + taskType); - var typeInstance = Activator.CreateInstance(type) as ITask; - if (typeInstance == null) - { - return null; - } - - //set the user/user id for the instance - var dialogTask = typeInstance as LegacyDialogTask; - if (dialogTask != null) - { - dialogTask.User = umbracoUser; - } - else - { - typeInstance.UserId = umbracoUser.Id; - } - - //put in contextual cache - httpContext.Items[ctxKey] = typeInstance; - - return typeInstance; - } - - /// - /// Checks if the user has access to launch the ITask that matches the node type based on the app assigned - /// - /// - /// - /// - /// - /// - /// If the ITask doesn't implement LegacyDialogTask then we will return 'true' since we cannot validate - /// the application assigned. - /// - /// TODO: Create an API to assign a nodeType to an app so developers can manually secure it - /// - internal static bool UserHasCreateAccess(HttpContextBase httpContext, User umbracoUser, string nodeType) - { - var task = GetTaskForOperation(httpContext, umbracoUser, Operation.Create, nodeType); - if (task == null) +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Web; +using System.Xml; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Web._Legacy.UI +{ + /// + /// This is used to replace the old umbraco.presentation.create.dialogHandler_temp class which is used + /// to handle sections create/delete actions. + /// + /// + /// We need to overhaul how all of this is handled which is why this is a legacy class + /// http://issues.umbraco.org/issue/U4-1373 + /// + public static class LegacyDialogHandler + { + private enum Operation + { + Create, + Delete + } + + private const string ContextKeyCreate = "LegacyDialogHandler-Create-"; + private const string ContextKeyDelete = "LegacyDialogHandler-Delete-"; + + /// + /// Gets the ITask for the operation for the node Type + /// + /// + /// + /// + /// + /// + /// Returns the ITask if one is found and can be made, otherwise null + /// + /// + /// This will first check if we've already created the ITask in the current Http request + /// + private static ITask GetTaskForOperation(HttpContextBase httpContext, IUser umbracoUser, Operation op, string nodeType) + { + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); + if (umbracoUser == null) throw new ArgumentNullException(nameof(umbracoUser)); + if (nodeType == null) throw new ArgumentNullException(nameof(nodeType)); + + var ctxKey = op == Operation.Create ? ContextKeyCreate : ContextKeyDelete; + + //check contextual cache + if (httpContext.Items[ctxKey] != null) { - //if no task was found it will use the default task and we cannot validate the application assigned so return true - return true; + return (ITask) httpContext.Items[ctxKey]; } - - var dialogTask = task as LegacyDialogTask; - if (dialogTask != null) - { - return dialogTask.ValidateUserForApplication(); - } - return true; - } - - /// - /// Checks if the user has access to launch the ITask that matches the node type based on the app assigned - /// - /// - /// - /// - /// - /// - /// If the ITask doesn't implement LegacyDialogTask then we will return 'true' since we cannot validate - /// the application assigned. - /// - /// TODO: Create an API to assign a nodeType to an app so developers can manually secure it - /// - internal static bool UserHasDeleteAccess(HttpContextBase httpContext, User umbracoUser, string nodeType) - { - var task = GetTaskForOperation(httpContext, umbracoUser, Operation.Delete, nodeType); - if (task == null) - { - //if no task was found it will use the default task and we cannot validate the application assigned so return true - return true; + + var operationNode = op == Operation.Create ? "create" : "delete"; + var createDef = GetXmlDoc(); + var def = createDef.SelectSingleNode("//nodeType [@alias = '" + nodeType + "']"); + if (def == null) + { + return null; } - - var dialogTask = task as LegacyDialogTask; - if (dialogTask != null) - { - return dialogTask.ValidateUserForApplication(); - } - return true; - } - - public static void Delete(HttpContextBase httpContext, User umbracoUser, string nodeType, int nodeId, string text) - { - var typeInstance = GetTaskForOperation(httpContext, umbracoUser, Operation.Delete, nodeType); + var del = def.SelectSingleNode("./tasks/" + operationNode); + if (del == null) + { + return null; + } + if (!del.Attributes.HasAttribute("assembly")) + { + return null; + } + var taskAssembly = del.AttributeValue("assembly"); + + if (!del.Attributes.HasAttribute("type")) + { + return null; + } + var taskType = del.AttributeValue("type"); + + var assembly = Assembly.LoadFrom(IOHelper.MapPath(SystemDirectories.Bin + "/" + taskAssembly + ".dll")); + var type = assembly.GetType(taskAssembly + "." + taskType); + var typeInstance = Activator.CreateInstance(type) as ITask; if (typeInstance == null) - throw new InvalidOperationException( - string.Format("Could not task for operation {0} for node type {1}", Operation.Delete, nodeType)); - - typeInstance.ParentID = nodeId; - typeInstance.Alias = text; - - typeInstance.Delete(); - } - - public static string Create(HttpContextBase httpContext, User umbracoUser, string nodeType, int nodeId, string text, int typeId = 0) - { - var typeInstance = GetTaskForOperation(httpContext, umbracoUser, Operation.Create, nodeType); - if (typeInstance == null) - throw new InvalidOperationException( - string.Format("Could not task for operation {0} for node type {1}", Operation.Create, nodeType)); - - typeInstance.TypeID = typeId; - typeInstance.ParentID = nodeId; - typeInstance.Alias = text; - - typeInstance.Save(); - - // check for returning url - var returnUrlTask = typeInstance as ITaskReturnUrl; - return returnUrlTask != null - ? returnUrlTask.ReturnUrl - : ""; - } - - internal static string Create(HttpContextBase httpContext, User umbracoUser, string nodeType, int nodeId, string text, IDictionary additionalValues, int typeId = 0) - { - var typeInstance = GetTaskForOperation(httpContext, umbracoUser, Operation.Create, nodeType); - if (typeInstance == null) - throw new InvalidOperationException( - string.Format("Could not task for operation {0} for node type {1}", Operation.Create, nodeType)); - - typeInstance.TypeID = typeId; - typeInstance.ParentID = nodeId; - typeInstance.Alias = text; - - // check for returning url - ITaskReturnUrl returnUrlTask = typeInstance as LegacyDialogTask; - if (returnUrlTask != null) - { - // if castable to LegacyDialogTask: add in additionalValues - ((LegacyDialogTask) returnUrlTask).AdditionalValues = additionalValues; - } - else - { - // otherwise cast to returnUrl interface - returnUrlTask = typeInstance as ITaskReturnUrl; - } - - typeInstance.Save(); - - return returnUrlTask != null - ? returnUrlTask.ReturnUrl - : ""; - } - - private static XmlDocument GetXmlDoc() - { - // Load task settings - var createDef = new XmlDocument(); - using (var defReader = new XmlTextReader(IOHelper.MapPath(SystemFiles.CreateUiXml))) - { - createDef.Load(defReader); - defReader.Close(); - return createDef; - } - } - } -} + { + return null; + } + + //set the user/user id for the instance + var dialogTask = typeInstance as LegacyDialogTask; + if (dialogTask != null) + { + dialogTask.User = umbracoUser; + } + else + { + typeInstance.UserId = umbracoUser.Id; + } + + //put in contextual cache + httpContext.Items[ctxKey] = typeInstance; + + return typeInstance; + } + + /// + /// Checks if the user has access to launch the ITask that matches the node type based on the app assigned + /// + /// + /// + /// + /// + /// + /// If the ITask doesn't implement LegacyDialogTask then we will return 'true' since we cannot validate + /// the application assigned. + /// + /// TODO: Create an API to assign a nodeType to an app so developers can manually secure it + /// + internal static bool UserHasCreateAccess(HttpContextBase httpContext, IUser umbracoUser, string nodeType) + { + var task = GetTaskForOperation(httpContext, umbracoUser, Operation.Create, nodeType); + + //if no task was found it will use the default task and we cannot validate the application assigned so return true + if (task == null) + return true; + + return task is LegacyDialogTask ltask ? ltask.ValidateUserForApplication() : true; + } + + /// + /// Checks if the user has access to launch the ITask that matches the node type based on the app assigned + /// + /// + /// + /// + /// + /// + /// If the ITask doesn't implement LegacyDialogTask then we will return 'true' since we cannot validate + /// the application assigned. + /// + /// TODO: Create an API to assign a nodeType to an app so developers can manually secure it + /// + internal static bool UserHasDeleteAccess(HttpContextBase httpContext, User umbracoUser, string nodeType) + { + var task = GetTaskForOperation(httpContext, umbracoUser, Operation.Delete, nodeType); + + //if no task was found it will use the default task and we cannot validate the application assigned so return true + if (task == null) + return true; + + return task is LegacyDialogTask ltask ? ltask.ValidateUserForApplication() : true; + } + + public static void Delete(HttpContextBase httpContext, IUser umbracoUser, string nodeType, int nodeId, string text) + { + var typeInstance = GetTaskForOperation(httpContext, umbracoUser, Operation.Delete, nodeType); + if (typeInstance == null) + throw new InvalidOperationException($"Could not task for operation {Operation.Delete} for node type {nodeType}"); + + typeInstance.ParentID = nodeId; + typeInstance.Alias = text; + + typeInstance.Delete(); + } + + public static string Create(HttpContextBase httpContext, IUser umbracoUser, string nodeType, int nodeId, string text, int typeId = 0) + { + var typeInstance = GetTaskForOperation(httpContext, umbracoUser, Operation.Create, nodeType); + if (typeInstance == null) + throw new InvalidOperationException($"Could not task for operation {Operation.Create} for node type {nodeType}"); + + typeInstance.TypeID = typeId; + typeInstance.ParentID = nodeId; + typeInstance.Alias = text; + + typeInstance.Save(); + + // check for returning url + var returnUrlTask = typeInstance as ITaskReturnUrl; + return returnUrlTask != null + ? returnUrlTask.ReturnUrl + : ""; + } + + internal static string Create(HttpContextBase httpContext, IUser umbracoUser, string nodeType, int nodeId, string text, IDictionary additionalValues, int typeId = 0) + { + var typeInstance = GetTaskForOperation(httpContext, umbracoUser, Operation.Create, nodeType); + if (typeInstance == null) + throw new InvalidOperationException($"Could not task for operation {Operation.Create} for node type {nodeType}"); + + typeInstance.TypeID = typeId; + typeInstance.ParentID = nodeId; + typeInstance.Alias = text; + + // check for returning url + ITaskReturnUrl returnUrlTask = typeInstance as LegacyDialogTask; + if (returnUrlTask != null) + { + // if castable to LegacyDialogTask: add in additionalValues + ((LegacyDialogTask) returnUrlTask).AdditionalValues = additionalValues; + } + else + { + // otherwise cast to returnUrl interface + returnUrlTask = typeInstance as ITaskReturnUrl; + } + + typeInstance.Save(); + + return returnUrlTask != null + ? returnUrlTask.ReturnUrl + : ""; + } + + private static XmlDocument GetXmlDoc() + { + // Load task settings + var createDef = new XmlDocument(); + using (var defReader = new XmlTextReader(IOHelper.MapPath(SystemFiles.CreateUiXml))) + { + createDef.Load(defReader); + defReader.Close(); + return createDef; + } + } + } +} diff --git a/src/Umbraco.Web/UI/LegacyDialogTask.cs b/src/Umbraco.Web/_Legacy/UI/LegacyDialogTask.cs similarity index 80% rename from src/Umbraco.Web/UI/LegacyDialogTask.cs rename to src/Umbraco.Web/_Legacy/UI/LegacyDialogTask.cs index 1975021121..1b040970dd 100644 --- a/src/Umbraco.Web/UI/LegacyDialogTask.cs +++ b/src/Umbraco.Web/_Legacy/UI/LegacyDialogTask.cs @@ -1,95 +1,94 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Authentication; -using System.Text; -using Umbraco.Core; -using umbraco.BusinessLogic; -using umbraco.businesslogic.Exceptions; -using umbraco.interfaces; - -namespace Umbraco.Web.UI -{ - /// - /// An abstract class that is used to implement all secure ITasks - /// - /// - /// In the near future we will overhaul how create dialogs work and how deletions work as well. In the meantime - /// if you ever need to create an ITask you should just inherit from this class and do not manually implement - /// ITask or ITaskReturnUrl. If you do, you MUST also implement IAppTask which associates an ITask to an app - /// so we can validate the current user's security with the implementation. If you do not do this then your - /// implementation will not be secure. It means that if someone is logged in and doesn't have access to a - /// specific app, they'd still be able to execute code to create/delete for any ITask regardless of what app - /// they have access to. - /// - [Obsolete("ITask is used for legacy webforms back office editors, change to using the v7 angular approach")] - public abstract class LegacyDialogTask : ITaskReturnUrl, IAssignedApp - { - public virtual int ParentID { get; set; } - public int TypeID { get; set; } - public string Alias { get; set; } - - /// - /// Base class first performs authentication for the current app before proceeding - /// - /// - public bool Save() - { - if (ValidateUserForApplication()) - { - return PerformSave(); - } - throw new AuthenticationException("The current user does not have access to the required application that this task belongs to"); - } - - public abstract bool PerformSave(); - - /// - /// Base class first performs authentication for the current app before proceeding - /// - /// - public bool Delete() - { - if (ValidateUserForApplication()) - { - return PerformDelete(); - } - throw new AuthenticationException("The current user does not have access to the required application that this task belongs to"); - } - - public abstract bool PerformDelete(); - - /// - /// Gets/sets the user object for this Task - /// - /// - /// accessible by inheritors but can only be set internally - /// - protected internal User User { get; internal set; } - - /// - /// Implemented explicitly as we don't want to expose this - /// - int ITask.UserId - { - set { User = User.GetUser(value); } - } - - /// - /// Checks if the currently assigned user has access to the assigned app - /// - /// - protected internal bool ValidateUserForApplication() - { - if (User == null) - throw new InvalidOperationException("Cannot authenticate, no User object assigned"); - - return User.Applications.Any(app => app.alias.InvariantEquals(AssignedApp)); - } - - public abstract string ReturnUrl { get; } - public abstract string AssignedApp { get; } - - public IDictionary AdditionalValues { get; set; } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Authentication; +using Umbraco.Core; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Composing; +using Umbraco.Web.UI; + +namespace Umbraco.Web._Legacy.UI +{ + /// + /// An abstract class that is used to implement all secure ITasks + /// + /// + /// In the near future we will overhaul how create dialogs work and how deletions work as well. In the meantime + /// if you ever need to create an ITask you should just inherit from this class and do not manually implement + /// ITask or ITaskReturnUrl. If you do, you MUST also implement IAppTask which associates an ITask to an app + /// so we can validate the current user's security with the implementation. If you do not do this then your + /// implementation will not be secure. It means that if someone is logged in and doesn't have access to a + /// specific app, they'd still be able to execute code to create/delete for any ITask regardless of what app + /// they have access to. + /// + [Obsolete("ITask is used for legacy webforms back office editors, change to using the v7 angular approach")] + public abstract class LegacyDialogTask : ITaskReturnUrl, IAssignedApp + { + public virtual int ParentID { get; set; } + public int TypeID { get; set; } + public string Alias { get; set; } + + /// + /// Base class first performs authentication for the current app before proceeding + /// + /// + public bool Save() + { + if (ValidateUserForApplication()) + { + return PerformSave(); + } + throw new AuthenticationException("The current user does not have access to the required application that this task belongs to"); + } + + public abstract bool PerformSave(); + + /// + /// Base class first performs authentication for the current app before proceeding + /// + /// + public bool Delete() + { + if (ValidateUserForApplication()) + { + return PerformDelete(); + } + throw new AuthenticationException("The current user does not have access to the required application that this task belongs to"); + } + + public abstract bool PerformDelete(); + + /// + /// Gets/sets the user object for this Task + /// + /// + /// accessible by inheritors but can only be set internally + /// + protected internal IUser User { get; internal set; } + + /// + /// Implemented explicitly as we don't want to expose this + /// + int ITask.UserId + { + set { User = Current.Services.UserService.GetUserById(value); } + } + + /// + /// Checks if the currently assigned user has access to the assigned app + /// + /// + protected internal bool ValidateUserForApplication() + { + if (User == null) + throw new InvalidOperationException("Cannot authenticate, no User object assigned"); + + return User.AllowedSections.Any(app => app.InvariantEquals(AssignedApp)); + } + + public abstract string ReturnUrl { get; } + public abstract string AssignedApp { get; } + + public IDictionary AdditionalValues { get; set; } + } +} diff --git a/src/Umbraco.Web/app.config b/src/Umbraco.Web/app.config deleted file mode 100644 index 13d7964935..0000000000 --- a/src/Umbraco.Web/app.config +++ /dev/null @@ -1,76 +0,0 @@ - - - - -
- - - - - - - - - - http://regexlib.com/WebServices.asmx - - - http://update.umbraco.org/checkforupgrade.asmx - - - Somthing - - - http://our.umbraco.org/umbraco/webservices/api/repository.asmx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config deleted file mode 100644 index 10e9915015..0000000000 --- a/src/Umbraco.Web/packages.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/AttributeCollectionAdapter.cs b/src/Umbraco.Web/umbraco.presentation/AttributeCollectionAdapter.cs index b7c2f7907a..499c000c74 100644 --- a/src/Umbraco.Web/umbraco.presentation/AttributeCollectionAdapter.cs +++ b/src/Umbraco.Web/umbraco.presentation/AttributeCollectionAdapter.cs @@ -1,312 +1,312 @@ -using System; +using System; using System.Collections; using System.Web.UI; namespace umbraco { - /// - /// Class that adapts an to the interface. - /// - public class AttributeCollectionAdapter : IDictionary - { - private readonly AttributeCollection _collection; + /// + /// Class that adapts an to the interface. + /// + public class AttributeCollectionAdapter : IDictionary + { + private readonly AttributeCollection _collection; - /// - /// Initializes a new instance of the class. - /// - /// The collection. - public AttributeCollectionAdapter(AttributeCollection collection) - { - _collection = collection; - } + /// + /// Initializes a new instance of the class. + /// + /// The collection. + public AttributeCollectionAdapter(AttributeCollection collection) + { + _collection = collection; + } - #region IDictionary Members + #region IDictionary Members - /// - /// Adds an element with the provided key and value to the object. - /// - /// The to use as the key of the element to add. - /// The to use as the value of the element to add. - public void Add(object key, object value) - { - _collection.Add(key.ToString(), value.ToString()); - } + /// + /// Adds an element with the provided key and value to the object. + /// + /// The to use as the key of the element to add. + /// The to use as the value of the element to add. + public void Add(object key, object value) + { + _collection.Add(key.ToString(), value.ToString()); + } - /// - /// Removes all elements from the object. - /// - /// - /// The object is read-only. - /// - public void Clear() - { - _collection.Clear(); - } + /// + /// Removes all elements from the object. + /// + /// + /// The object is read-only. + /// + public void Clear() + { + _collection.Clear(); + } - /// - /// Determines whether the object contains an element with the specified key. - /// - /// The key to locate in the object. - /// - /// true if the contains an element with the key; otherwise, false. - /// - public bool Contains(object key) - { - return _collection[key.ToString()] != null; - } + /// + /// Determines whether the object contains an element with the specified key. + /// + /// The key to locate in the object. + /// + /// true if the contains an element with the key; otherwise, false. + /// + public bool Contains(object key) + { + return _collection[key.ToString()] != null; + } - /// - /// Returns an object for the object. - /// - /// - /// An object for the object. - /// - public IDictionaryEnumerator GetEnumerator() - { - return new AttributeCollectionAdapterEnumerator(this); - } + /// + /// Returns an object for the object. + /// + /// + /// An object for the object. + /// + public IDictionaryEnumerator GetEnumerator() + { + return new AttributeCollectionAdapterEnumerator(this); + } - /// - /// Gets a value indicating whether the object has a fixed size. - /// - /// - /// true if the object has a fixed size; otherwise, false. - /// - public bool IsFixedSize - { - get { return false; } - } + /// + /// Gets a value indicating whether the object has a fixed size. + /// + /// + /// true if the object has a fixed size; otherwise, false. + /// + public bool IsFixedSize + { + get { return false; } + } - /// - /// Gets a value indicating whether the object is read-only. - /// - /// - /// true if the object is read-only; otherwise, false. - /// - public bool IsReadOnly - { - get { return false; } - } + /// + /// Gets a value indicating whether the object is read-only. + /// + /// + /// true if the object is read-only; otherwise, false. + /// + public bool IsReadOnly + { + get { return false; } + } - /// - /// Gets an object containing the keys of the object. - /// - /// - /// - /// An object containing the keys of the object. - /// - public ICollection Keys - { - get { return _collection.Keys; } - } + /// + /// Gets an object containing the keys of the object. + /// + /// + /// + /// An object containing the keys of the object. + /// + public ICollection Keys + { + get { return _collection.Keys; } + } - /// - /// Removes the element with the specified key from the object. - /// - /// The key of the element to remove. - public void Remove(object key) - { - _collection.Remove(key.ToString()); - } + /// + /// Removes the element with the specified key from the object. + /// + /// The key of the element to remove. + public void Remove(object key) + { + _collection.Remove(key.ToString()); + } - /// - /// Gets an object containing the values in the object. - /// - /// - /// - /// An object containing the values in the object. - /// - public ICollection Values - { - get { throw new NotImplementedException(); } - } + /// + /// Gets an object containing the values in the object. + /// + /// + /// + /// An object containing the values in the object. + /// + public ICollection Values + { + get { throw new NotImplementedException(); } + } - /// - /// Gets or sets the with the specified key. - /// - /// - public object this[object key] - { - get { return _collection[key.ToString()]; } - set { _collection[key.ToString()] = value.ToString(); } - } + /// + /// Gets or sets the with the specified key. + /// + /// + public object this[object key] + { + get { return _collection[key.ToString()]; } + set { _collection[key.ToString()] = value.ToString(); } + } - #endregion + #endregion - #region ICollection Members + #region ICollection Members - /// Not implemented. - /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. - /// The zero-based index in at which copying begins. - public void CopyTo(Array array, int index) - { - throw new NotImplementedException(); - } + /// Not implemented. + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. + /// The zero-based index in at which copying begins. + public void CopyTo(Array array, int index) + { + throw new NotImplementedException(); + } - /// - /// Gets the number of elements contained in the . - /// - /// - /// - /// The number of elements contained in the . - /// - public int Count - { - get { return _collection.Count; } - } + /// + /// Gets the number of elements contained in the . + /// + /// + /// + /// The number of elements contained in the . + /// + public int Count + { + get { return _collection.Count; } + } - /// - /// Gets a value indicating whether access to the is synchronized (thread safe). - /// - /// - /// true if access to the is synchronized (thread safe); otherwise, false. - /// - public bool IsSynchronized - { - get { return false; } - } + /// + /// Gets a value indicating whether access to the is synchronized (thread safe). + /// + /// + /// true if access to the is synchronized (thread safe); otherwise, false. + /// + public bool IsSynchronized + { + get { return false; } + } - /// - /// Gets an object that can be used to synchronize access to the . - /// - /// - /// - /// An object that can be used to synchronize access to the . - /// - public object SyncRoot - { - get { return _collection; } - } + /// + /// Gets an object that can be used to synchronize access to the . + /// + /// + /// + /// An object that can be used to synchronize access to the . + /// + public object SyncRoot + { + get { return _collection; } + } - #endregion + #endregion - #region IEnumerable Members + #region IEnumerable Members - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - foreach (object key in _collection.Keys) - yield return _collection[(string)key]; - } + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() + { + foreach (object key in _collection.Keys) + yield return _collection[(string)key]; + } - #endregion + #endregion - /// - /// for the class. - /// - private class AttributeCollectionAdapterEnumerator : IDictionaryEnumerator - { - private readonly AttributeCollectionAdapter _adapter; - private readonly IEnumerator _enumerator; + /// + /// for the class. + /// + private class AttributeCollectionAdapterEnumerator : IDictionaryEnumerator + { + private readonly AttributeCollectionAdapter _adapter; + private readonly IEnumerator _enumerator; - /// - /// Initializes a new instance of the class. - /// - /// The adapter. - public AttributeCollectionAdapterEnumerator(AttributeCollectionAdapter adapter) - { - _adapter = adapter; - _enumerator = ((IEnumerable)adapter).GetEnumerator(); - } + /// + /// Initializes a new instance of the class. + /// + /// The adapter. + public AttributeCollectionAdapterEnumerator(AttributeCollectionAdapter adapter) + { + _adapter = adapter; + _enumerator = ((IEnumerable)adapter).GetEnumerator(); + } - #region IDictionaryEnumerator Members + #region IDictionaryEnumerator Members - /// - /// Gets both the key and the value of the current dictionary entry. - /// - /// - /// - /// A containing both the key and the value of the current dictionary entry. - /// - /// - /// The is positioned before the first entry of the dictionary or after the last entry. - /// - public DictionaryEntry Entry - { - get { return new DictionaryEntry(Key, Value); } - } + /// + /// Gets both the key and the value of the current dictionary entry. + /// + /// + /// + /// A containing both the key and the value of the current dictionary entry. + /// + /// + /// The is positioned before the first entry of the dictionary or after the last entry. + /// + public DictionaryEntry Entry + { + get { return new DictionaryEntry(Key, Value); } + } - /// - /// Gets the key of the current dictionary entry. - /// - /// - /// - /// The key of the current element of the enumeration. - /// - /// - /// The is positioned before the first entry of the dictionary or after the last entry. - /// - public object Key - { - get { return _enumerator.Current; } - } + /// + /// Gets the key of the current dictionary entry. + /// + /// + /// + /// The key of the current element of the enumeration. + /// + /// + /// The is positioned before the first entry of the dictionary or after the last entry. + /// + public object Key + { + get { return _enumerator.Current; } + } - /// - /// Gets the value of the current dictionary entry. - /// - /// - /// - /// The value of the current element of the enumeration. - /// - /// - /// The is positioned before the first entry of the dictionary or after the last entry. - /// - public object Value - { - get { return _adapter[_enumerator.Current]; } - } + /// + /// Gets the value of the current dictionary entry. + /// + /// + /// + /// The value of the current element of the enumeration. + /// + /// + /// The is positioned before the first entry of the dictionary or after the last entry. + /// + public object Value + { + get { return _adapter[_enumerator.Current]; } + } - #endregion + #endregion - #region IEnumerator Members + #region IEnumerator Members - /// - /// Gets the current element in the collection. - /// - /// - /// - /// The current element in the collection. - /// - /// - /// The enumerator is positioned before the first element of the collection or after the last element. - /// - public object Current - { - get { return _enumerator.Current; } - } + /// + /// Gets the current element in the collection. + /// + /// + /// + /// The current element in the collection. + /// + /// + /// The enumerator is positioned before the first element of the collection or after the last element. + /// + public object Current + { + get { return _enumerator.Current; } + } - /// - /// Advances the enumerator to the next element of the collection. - /// - /// - /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. - /// - /// - /// The collection was modified after the enumerator was created. - /// - public bool MoveNext() - { - return _enumerator.MoveNext(); - } + /// + /// Advances the enumerator to the next element of the collection. + /// + /// + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// + /// + /// The collection was modified after the enumerator was created. + /// + public bool MoveNext() + { + return _enumerator.MoveNext(); + } - /// - /// Sets the enumerator to its initial position, which is before the first element in the collection. - /// - /// - /// The collection was modified after the enumerator was created. - /// - public void Reset() - { - _enumerator.Reset(); - } + /// + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// + /// + /// The collection was modified after the enumerator was created. + /// + public void Reset() + { + _enumerator.Reset(); + } - #endregion - } - } -} \ No newline at end of file + #endregion + } + } +} diff --git a/src/Umbraco.Web/umbraco.presentation/BasePageExtensions.cs b/src/Umbraco.Web/umbraco.presentation/BasePageExtensions.cs deleted file mode 100644 index ff91659f33..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/BasePageExtensions.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using umbraco.BasePages; -using umbraco.uicontrols; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using ClientDependency.Core; - -namespace umbraco.presentation -{ - /// - /// Extension methods for the Umbraco BasePage - /// - public static class BasePageExtensions - { - - /// - /// Used to display an error message to the user and disable further execution. - /// This will remove all controls from being rendered and show a feedback control with an error - /// - /// - public static void DisplayFatalError(this BasePage page, string msg) - { - foreach (var ctl in page.Controls.Cast()) - { - if (!HideControls(ctl)) - { - var ctls = ctl.FlattenChildren(); - foreach (var c in ctls) - { - HideControls(c); - } - } - } - var feedback = new Feedback(); - feedback.type = Feedback.feedbacktype.error; - feedback.Text = string.Format("{0}

{1}", ui.GetText("error"), msg); - page.Controls.Add(feedback); - } - - private static bool HideControls(this Control c) - { - if (c is MasterPage) - { - return false; - } - else if (c is UserControl || c is WebControl || c is HtmlForm) - { - c.Visible = false; - return true; - } - return false; - - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs b/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs deleted file mode 100644 index 00ec43229a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.IO; -using umbraco.businesslogic; -using umbraco.interfaces; - -namespace umbraco.presentation -{ - public class EnsureSystemPathsApplicationStartupHandler : ApplicationEventHandler - { - protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - base.ApplicationInitialized(umbracoApplication, applicationContext); - - IOHelper.EnsurePathExists("~/App_Data"); - IOHelper.EnsurePathExists(SystemDirectories.Media); - IOHelper.EnsurePathExists(SystemDirectories.MvcViews); - IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/Partials"); - IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/MacroPartials"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/MacroCacheContent.cs b/src/Umbraco.Web/umbraco.presentation/MacroCacheContent.cs index 85d295f535..8e8fc5d0bb 100644 --- a/src/Umbraco.Web/umbraco.presentation/MacroCacheContent.cs +++ b/src/Umbraco.Web/umbraco.presentation/MacroCacheContent.cs @@ -1,26 +1,26 @@ -using System.Web.UI; +using System.Web.UI; namespace umbraco { - public class MacroCacheContent - { - private readonly Control _control; - private readonly string _id; + public class MacroCacheContent + { + private readonly Control _control; + private readonly string _id; - public MacroCacheContent(Control control, string ID) - { - _control = control; - _id = ID; - } + public MacroCacheContent(Control control, string ID) + { + _control = control; + _id = ID; + } - public string ID - { - get { return _id; } - } + public string ID + { + get { return _id; } + } - public Control Content - { - get { return _control; } - } - } -} \ No newline at end of file + public Control Content + { + get { return _control; } + } + } +} diff --git a/src/Umbraco.Web/umbraco.presentation/NotFoundHandlers.cs b/src/Umbraco.Web/umbraco.presentation/NotFoundHandlers.cs deleted file mode 100644 index 27e62888d4..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/NotFoundHandlers.cs +++ /dev/null @@ -1,355 +0,0 @@ -using System; -using System.Collections; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Web; -using System.Xml; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic.member; -using umbraco.cms.businesslogic.template; -using umbraco.cms.businesslogic.web; -using umbraco.interfaces; -using Umbraco.Core.IO; -using umbraco.NodeFactory; -using Umbraco.Core; -using Umbraco.Web; -using Umbraco.Web.Routing; - -namespace umbraco { - - - /// - /// THIS CLASS IS PURELY HERE TO SUPPORT THE QUERYBYXPATH METHOD WHICH IS USED BY OTHER LEGACY BITS - /// - internal class LegacyRequestHandler - { - private const string PageXPathQueryStart = "/root"; - private const string UrlName = "@urlName"; - - public static string CreateXPathQuery(string url, bool checkDomain) - { - - string _tempQuery = ""; - if (GlobalSettings.HideTopLevelNodeFromPath && checkDomain) - { - _tempQuery = "/root" + GetChildContainerName() + "/*"; - } - else if (checkDomain) - _tempQuery = "/root" + GetChildContainerName(); - - - string[] requestRawUrl = url.Split("/".ToCharArray()); - - // Check for Domain prefix - string domainUrl = ""; - if (checkDomain && Domain.Exists(HttpContext.Current.Request.ServerVariables["SERVER_NAME"])) - { - // we need to get the node based on domain - INode n = new Node(Domain.GetRootFromDomain(HttpContext.Current.Request.ServerVariables["SERVER_NAME"])); - domainUrl = n.UrlName; // we don't use niceUrlFetch as we need more control - if (n.Parent != null) - { - while (n.Parent != null) - { - n = n.Parent; - domainUrl = n.UrlName + "/" + domainUrl; - } - } - domainUrl = "/" + domainUrl; - - // If at domain root - if (url == "") - { - _tempQuery = ""; - requestRawUrl = domainUrl.Split("/".ToCharArray()); - HttpContext.Current.Trace.Write("requestHandler", - "Redirecting to domain: " + - HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + - ", nodeId: " + - Domain.GetRootFromDomain( - HttpContext.Current.Request.ServerVariables["SERVER_NAME"]). - ToString()); - } - else - { - // if it matches a domain url, skip all other xpaths and use this! - string langXpath = CreateXPathQuery(domainUrl + "/" + url, false); - if (content.Instance.XmlContent.DocumentElement.SelectSingleNode(langXpath) != null) - return langXpath; - else if (UmbracoConfig.For.UmbracoSettings().RequestHandler.UseDomainPrefixes) - return "/domainprefixes-are-used-so-i-do-not-work"; - } - } - else if (url == "" && !GlobalSettings.HideTopLevelNodeFromPath) - _tempQuery += "/*"; - - bool rootAdded = false; - if (GlobalSettings.HideTopLevelNodeFromPath && requestRawUrl.Length == 1) - { - HttpContext.Current.Trace.Write("umbracoRequestHandler", "xpath: '" + _tempQuery + "'"); - if (_tempQuery == "") - _tempQuery = "/root" + GetChildContainerName() + "/*"; - _tempQuery = "/root" + GetChildContainerName() + "/* [" + UrlName + - " = \"" + requestRawUrl[0].Replace(".aspx", "").ToLower() + "\"] | " + _tempQuery; - HttpContext.Current.Trace.Write("umbracoRequestHandler", "xpath: '" + _tempQuery + "'"); - rootAdded = true; - } - - - for (int i = 0; i <= requestRawUrl.GetUpperBound(0); i++) - { - if (requestRawUrl[i] != "") - _tempQuery += GetChildContainerName() + "/* [" + UrlName + " = \"" + requestRawUrl[i].Replace(".aspx", "").ToLower() + - "\"]"; - } - - if (GlobalSettings.HideTopLevelNodeFromPath && requestRawUrl.Length == 2) - { - _tempQuery += " | " + PageXPathQueryStart + GetChildContainerName() + "/* [" + UrlName + " = \"" + - requestRawUrl[1].Replace(".aspx", "").ToLower() + "\"]"; - } - HttpContext.Current.Trace.Write("umbracoRequestHandler", "xpath: '" + _tempQuery + "'"); - - Debug.Write(_tempQuery + "(" + PageXPathQueryStart + ")"); - - if (checkDomain) - return _tempQuery; - else if (!rootAdded) - return PageXPathQueryStart + _tempQuery; - else - return _tempQuery; - } - - private static string GetChildContainerName() - { - if (string.IsNullOrEmpty(UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME) == false) - return "/" + UmbracoSettings.TEMP_FRIENDLY_XML_CHILD_CONTAINER_NODENAME; - return ""; - } - } - - public class SearchForAlias : INotFoundHandler { - private int _redirectID = -1; - private bool _cacheUrl = true; - - #region INotFoundHandler Members - - public bool Execute(string url) { - bool _succes = false; - string tempUrl = url.Trim('/').Replace(".aspx", string.Empty).ToLower(); - if (tempUrl.Length > 0) { - HttpContext.Current.Trace.Write("urlAlias", "'" + tempUrl + "'"); - - // Check for domain - string currentDomain = System.Web.HttpContext.Current.Request.ServerVariables["SERVER_NAME"]; - string prefixXPath = ""; - if (Domain.Exists(currentDomain)) { - string xpathDomain = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "//node [@id = '{0}']" : "//* [@isDoc and @id = '{0}']"; - prefixXPath = string.Format(xpathDomain, Domain.GetRootFromDomain(currentDomain)); - _cacheUrl = false; - } - - - // the reason we have almost two identical queries in the xpath is to support scenarios where the user have - // entered "/my-url" instead of "my-url" (ie. added a beginning slash) - string xpath = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "//node [contains(concat(',',translate(data [@alias = 'umbracoUrlAlias'], ' ', ''),','),',{0},') or contains(concat(',',translate(data [@alias = 'umbracoUrlAlias'], ' ', ''),','),',{1},')]" : - "//* [@isDoc and (contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},') or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{1},'))]"; - string query = String.Format(prefixXPath + xpath, tempUrl, "/" + tempUrl); - XmlNode redir = - content.Instance.XmlContent.DocumentElement.SelectSingleNode(query); - if (redir != null) { - _succes = true; - _redirectID = int.Parse(redir.Attributes.GetNamedItem("id").Value); - } - } - return _succes; - } - - public bool CacheUrl { - get { return _cacheUrl; } - } - - public int redirectID { - get { - // TODO: Add SearchForAlias.redirectID getter implementation - return _redirectID; - } - } - - #endregion - } - - public class SearchForProfile : INotFoundHandler { - private static int _profileId = -1; - - private int _redirectID = -1; - - #region INotFoundHandler Members - - public bool CacheUrl { - get { - // Do not cache profile urls, we need to store the login name - return false; - } - } - - public bool Execute(string url) { - bool _succes = false; - url = url.Replace(".aspx", string.Empty); - if (url.Length > 0) { - if (url.Substring(0, 1) == "/") - url = url.Substring(1, url.Length - 1); - - if (url.IndexOf("/") > 0) { - // Check if we're at the profile page - if (url.Substring(0, url.IndexOf("/")) == GlobalSettings.ProfileUrl) { - if (_profileId < 0) { - // /root added to query to solve umbRuntime bug - string _tempQuery = - LegacyRequestHandler.CreateXPathQuery(url.Substring(0, url.IndexOf("/")), false); - _profileId = - int.Parse( - content.Instance.XmlContent.SelectSingleNode(_tempQuery).Attributes.GetNamedItem( - "id").Value); - } - - HttpContext.Current.Items["umbMemberLogin"] = - url.Substring(url.IndexOf("/") + 1, url.Length - url.IndexOf("/") - 1); - _succes = true; - _redirectID = _profileId; - } - } - } - return _succes; - } - - public int redirectID { - get { - // TODO: Add SearchForProfile.redirectID getter implementation - return _redirectID; - } - } - - #endregion - } - - public class SearchForTemplate : INotFoundHandler { - private int _redirectID = -1; - - #region INotFoundHandler Members - - public bool CacheUrl { - get { - // Do not cache profile urls, we need to store the login name - return false; - } - } - - public bool Execute(string url) { - bool _succes = false; - url = url.Replace(".aspx", string.Empty); - string currentDomain = HttpContext.Current.Request.ServerVariables["SERVER_NAME"]; - if (url.Length > 0) { - if (url.Substring(0, 1) == "/") - url = url.Substring(1, url.Length - 1); - - XmlNode urlNode = null; - string templateAlias = ""; - - // We're at domain root - if (url.IndexOf("/") == -1) - { - if (Domain.Exists(currentDomain)) - urlNode = content.Instance.XmlContent.GetElementById(Domain.GetRootFromDomain(currentDomain).ToString()); - else - urlNode = content.Instance.XmlContent.GetElementById(Document.GetRootDocuments()[0].Id.ToString()); - templateAlias = url.ToLower(); - } - else - { - string theRealUrl = url.Substring(0, url.LastIndexOf("/")); - string realUrlXPath = LegacyRequestHandler.CreateXPathQuery(theRealUrl, true); - urlNode = content.Instance.XmlContent.SelectSingleNode(realUrlXPath); - templateAlias = - url.Substring(url.LastIndexOf("/") + 1, url.Length - url.LastIndexOf(("/")) - 1).ToLower(); - } - - if (urlNode != null && Template.GetTemplateIdFromAlias(templateAlias) != 0) - { - _redirectID = int.Parse(urlNode.Attributes.GetNamedItem("id").Value); - - if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates == false) - { - HttpContext.Current.Items[Constants.Conventions.Url.AltTemplate] = templateAlias; - HttpContext.Current.Trace.Write("umbraco.altTemplateHandler", - string.Format("Template changed to: '{0}'", HttpContext.Current.Items[Constants.Conventions.Url.AltTemplate])); - } - - _succes = true; - } - } - return _succes; - } - - public int redirectID { - get { - // TODO: Add SearchForProfile.redirectID getter implementation - return _redirectID; - } - } - - #endregion - } - - public class handle404 : INotFoundHandler { - #region INotFoundHandler Members - - private int _redirectID = 0; - - public bool CacheUrl { - get { - return false; - } - } - - public bool Execute(string url) { - try - { - LogHelper.Info(string.Format("NotFound url {0} (from '{1}')", url, HttpContext.Current.Request.UrlReferrer)); - - // Test if the error404 not child elements - var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId( - UmbracoConfig.For.UmbracoSettings().Content.Error404Collection.ToArray(), - HttpContext.Current.Request.ServerVariables["SERVER_NAME"], - ApplicationContext.Current.Services.EntityService, - new PublishedContentQuery(UmbracoContext.Current.ContentCache, UmbracoContext.Current.MediaCache), - ApplicationContext.Current.Services.DomainService); - - if (error404.HasValue) - { - _redirectID = error404.Value; - HttpContext.Current.Response.StatusCode = 404; - return true; - } - - return false; - } - catch (Exception err) - { - LogHelper.Error("An error occurred", err); - return false; - } - } - - public int redirectID { - get { - return _redirectID; - } - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/PublishQueue.cs b/src/Umbraco.Web/umbraco.presentation/PublishQueue.cs deleted file mode 100644 index 24b23a3358..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/PublishQueue.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Data; -using System.Configuration; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; - -using umbraco.cms.businesslogic.web; - -namespace umbraco.presentation -{ - public class PublishQueue - { - /* - public static PublishQueue CurrentPublishQueue - { - get { } - } - - public void Put(int DocumentId) { - } - */ - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/ScriptingMacroResult.cs b/src/Umbraco.Web/umbraco.presentation/ScriptingMacroResult.cs deleted file mode 100644 index d634a47f54..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/ScriptingMacroResult.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace umbraco -{ - public class ScriptingMacroResult - { - public ScriptingMacroResult() - { - } - - public ScriptingMacroResult(string result, Exception resultException) - { - Result = result; - ResultException = resultException; - } - - public string Result { get; set; } - public Exception ResultException { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/UmbracoContext.cs b/src/Umbraco.Web/umbraco.presentation/UmbracoContext.cs deleted file mode 100644 index 0f423f902c..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/UmbracoContext.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System; -using System.Web; -using Umbraco.Core.Configuration; -using umbraco.BasePages; -using umbraco.cms.businesslogic.web; -using umbraco.BusinessLogic; -using System.Xml; -using umbraco.presentation.preview; -using Examine.Providers; -using Examine; -using Umbraco.Core; - -namespace umbraco.presentation -{ - /// - /// Class that encapsulates Umbraco information of a specific HTTP request. - /// - [Obsolete("Use Umbraco.Web.UmbracoContext instead")] - public class UmbracoContext - { - private UmbracoServerUtility _server; - private UmbracoRequest _request; - private UmbracoResponse _response; - private readonly HttpContextBase _httpContext; - private PreviewContent _previewContent; - - /// - /// Creates a new Umbraco context. - /// - /// - public UmbracoContext(HttpContextBase httpContext) - { - _httpContext = httpContext; - } - - /// - /// Creates a new Umbraco context. - /// - /// The HTTP context on which the Umbraco context operates. - [Obsolete("Use the contructor accepting an HttpContextBase object instead")] - public UmbracoContext(HttpContext httpContext) - { - _httpContext = new HttpContextWrapper(httpContext); - } - - /// - /// Gets the current Umbraco Context. - /// - public static UmbracoContext Current - { - get - { - if (HttpContext.Current != null) - return (UmbracoContext)HttpContext.Current.Items["UmbracoContext"]; - - return null; - } - - internal set - { - if (Current != null) - throw new ApplicationException("The current httpContext can only be set once during a request."); - - HttpContext.Current.Items["UmbracoContext"] = value; - } - } - - /// - /// Gets the current page ID, or null if no page ID is available (e.g. a custom page). - /// - public virtual int? PageId { get { return Umbraco.Web.UmbracoContext.Current.PageId; } } - - /// - /// Gets the current logged in Umbraco user (editor). - /// - /// The Umbraco user object or null - public virtual User UmbracoUser { get { return Umbraco.Web.UmbracoContext.Current.UmbracoUser; } } - - /// - /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) - /// - public virtual bool InPreviewMode { get { return Umbraco.Web.UmbracoContext.Current.InPreviewMode; } } - - public XmlDocument GetXml() - { - var umbracoContext = Umbraco.Web.UmbracoContext.Current; - var cache = umbracoContext.ContentCache.InnerCache as Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedContentCache; - if (cache == null) - throw new InvalidOperationException("Unsupported IPublishedContentCache, only the Xml one is supported."); - - return cache.GetXml(umbracoContext, umbracoContext.InPreviewMode); - } - - /// - /// Determines whether the current user has the specified permission on the current page. - /// - /// The permission token. - /// - /// true if the user has permission; otherwise, false. - /// - public virtual bool HasPermission(char permissionToken) - { - int? pageId = UmbracoContext.Current.PageId; - return pageId.HasValue - && UmbracoEnsuredPage.CurrentUser != null - && UmbracoEnsuredPage.CurrentUser.GetPermissions(new Document(pageId.Value).Path) - .Contains(permissionToken.ToString()); - } - - public virtual bool NewSchemaMode - { - get - { - return !UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema; - } - } - - /// - /// Gets the response for the current context - /// - /// The response. - public virtual UmbracoResponse Response - { - get - { - if (_response == null) - { - _response = new UmbracoResponse(this._httpContext.Response); - } - return _response; - } - } - - public virtual TraceContext Trace - { - get - { - return this._httpContext.Trace; - } - } - - /// - /// Gets the request for the current context - /// - /// The request. - public virtual UmbracoRequest Request - { - get - { - if (_request == null) - { - _request = new UmbracoRequest(this._httpContext.Request); - } - return _request; - } - } - - /// - /// Gets the base URL for the website - /// - /// - public virtual string GetBaseUrl() - { - return this.Request.Url.GetLeftPart(UriPartial.Authority); - } - - public virtual UmbracoServerUtility Server - { - get - { - if (_server == null) - { - _server = new UmbracoServerUtility(this._httpContext.Server); - } - return _server; - } - } - - /// - /// Gets the internal search provider from Examine. - /// - /// The internal search provider. - public virtual BaseSearchProvider InternalSearchProvider - { - get - { - return ExamineManager.Instance.SearchProviderCollection[Constants.Examine.InternalSearcher]; - } - } - - /// - /// Gets the internal member search provider from Examine. - /// - /// The internal search provider. - public virtual BaseSearchProvider InternalMemberSearchProvider - { - get - { - return ExamineManager.Instance.SearchProviderCollection[Constants.Examine.InternalMemberSearcher]; - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/UmbracoPage.cs b/src/Umbraco.Web/umbraco.presentation/UmbracoPage.cs deleted file mode 100644 index cccbd92f50..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/UmbracoPage.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; - -namespace umbraco.presentation -{ - public class UmbracoPage : System.Web.UI.Page - { - public int PageId { get; set; } - - protected override void OnPreInit(EventArgs e) - { - if (UmbracoContext.Current == null) - { - // Set umbraco context - UmbracoContext.Current = new UmbracoContext(HttpContext.Current); - } - - HttpContext.Current.Items["pageID"] = PageId; - - // setup page properties - page pageObject = new page(((System.Xml.IHasXmlNode) library.GetXmlNodeCurrent().Current).GetNode()); - System.Web.HttpContext.Current.Items.Add("pageElements", pageObject.Elements); - - base.OnPreInit(e); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/UmbracoRequest.cs b/src/Umbraco.Web/umbraco.presentation/UmbracoRequest.cs deleted file mode 100644 index 1201bb6a5e..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/UmbracoRequest.cs +++ /dev/null @@ -1,443 +0,0 @@ -using System; -using System.Web; - -namespace umbraco.presentation -{ - - /// - /// A custom HttpRequestBase object which exposes some additional methods - /// - /// - /// The only reason this class exists is because somebody decided that it would be a good idea to piggy - /// back the IsDebug method on top of the HttpRequest object. Unforunately when this was created it - /// inherited from HttpRequestWrapper intead of HttpRequestBase which meant that this is not unit testable - /// and still has a dependency on a real HttpRequest. So now it inherits from HttpRequestBase which - /// means we need to override all of the methods and just wrap them with the _request object passed in. - /// - /// This now needs to exist only because of backwards compatibility - /// - [Obsolete("This class is no longer used, for the IsDebug method use Umbraco.Web.UmbracoContext.IsDebug")] - public class UmbracoRequest : HttpRequestBase - { - private readonly HttpRequestBase _request; - - #region Constructor - public UmbracoRequest(HttpRequestBase request) - { - _request = request; - } - - [Obsolete("Use the alternative contructor that accepts a HttpRequestBase object instead")] - public UmbracoRequest(HttpRequest request) - { - _request = new HttpRequestWrapper(request); - } - #endregion - - #region Wrapped Methods - - public override string[] AcceptTypes - { - get - { - return _request.AcceptTypes; - } - } - - public override string AnonymousID - { - get - { - return _request.AnonymousID; - } - } - - public override string ApplicationPath - { - get - { - return _request.ApplicationPath; - } - } - - public override string AppRelativeCurrentExecutionFilePath - { - get - { - return _request.AppRelativeCurrentExecutionFilePath; - } - } - - public override byte[] BinaryRead(int count) - { - return _request.BinaryRead(count); - } - - public override HttpBrowserCapabilitiesBase Browser - { - get - { - return _request.Browser; - } - } - - public override HttpClientCertificate ClientCertificate - { - get - { - return _request.ClientCertificate; - } - } - - public override System.Text.Encoding ContentEncoding - { - get - { - return _request.ContentEncoding; - } - set - { - _request.ContentEncoding = value; - } - } - - public override int ContentLength - { - get - { - return _request.ContentLength; - } - } - - public override string ContentType - { - get - { - return _request.ContentType; - } - set - { - _request.ContentType = value; - } - } - - public override HttpCookieCollection Cookies - { - get - { - return _request.Cookies; - } - } - - public override string CurrentExecutionFilePath - { - get - { - return _request.CurrentExecutionFilePath; - } - } - - public override bool Equals(object obj) - { - return _request.Equals(obj); - } - - public override string FilePath - { - get - { - return _request.FilePath; - } - } - - public override HttpFileCollectionBase Files - { - get - { - return _request.Files; - } - } - - public override System.IO.Stream Filter - { - get - { - return _request.Filter; - } - set - { - _request.Filter = value; - } - } - - public override System.Collections.Specialized.NameValueCollection Form - { - get - { - return _request.Form; - } - } - - public override int GetHashCode() - { - return _request.GetHashCode(); - } - - public override System.Collections.Specialized.NameValueCollection Headers - { - get - { - return _request.Headers; - } - } - - public override System.Security.Authentication.ExtendedProtection.ChannelBinding HttpChannelBinding - { - get - { - return _request.HttpChannelBinding; - } - } - - public override string HttpMethod - { - get - { - return _request.HttpMethod; - } - } - - public override System.IO.Stream InputStream - { - get - { - return _request.InputStream; - } - } - - public override bool IsAuthenticated - { - get - { - return _request.IsAuthenticated; - } - } - - public override bool IsLocal - { - get - { - return _request.IsLocal; - } - } - - public override bool IsSecureConnection - { - get - { - return _request.IsSecureConnection; - } - } - - public override System.Security.Principal.WindowsIdentity LogonUserIdentity - { - get - { - return _request.LogonUserIdentity; - } - } - - public override int[] MapImageCoordinates(string imageFieldName) - { - return _request.MapImageCoordinates(imageFieldName); - } - - public override string MapPath(string virtualPath) - { - return _request.MapPath(virtualPath); - } - - public override string MapPath(string virtualPath, string baseVirtualDir, bool allowCrossAppMapping) - { - return _request.MapPath(virtualPath, baseVirtualDir, allowCrossAppMapping); - } - - public override System.Collections.Specialized.NameValueCollection Params - { - get - { - return _request.Params; - } - } - - public override string Path - { - get - { - return _request.Path; - } - } - - public override string PathInfo - { - get - { - return _request.PathInfo; - } - } - - public override string PhysicalApplicationPath - { - get - { - return _request.PhysicalApplicationPath; - } - } - - public override string PhysicalPath - { - get - { - return _request.PhysicalPath; - } - } - - public override System.Collections.Specialized.NameValueCollection QueryString - { - get - { - return _request.QueryString; - } - } - - public override string RawUrl - { - get - { - return _request.RawUrl; - } - } - - public override System.Web.Routing.RequestContext RequestContext - { - get - { - return _request.RequestContext; - } - } - - public override string RequestType - { - get - { - return _request.RequestType; - } - set - { - _request.RequestType = value; - } - } - - public override void SaveAs(string filename, bool includeHeaders) - { - _request.SaveAs(filename, includeHeaders); - } - - public override System.Collections.Specialized.NameValueCollection ServerVariables - { - get - { - return _request.ServerVariables; - } - } - - public override string this[string key] - { - get - { - return _request[key]; - } - } - - public override string ToString() - { - return _request.ToString(); - } - - public override int TotalBytes - { - get - { - return _request.TotalBytes; - } - } - - public override Uri Url - { - get - { - return _request.Url; - } - } - - public override Uri UrlReferrer - { - get - { - return _request.UrlReferrer; - } - } - - public override string UserAgent - { - get - { - return _request.UserAgent; - } - } - - public override string UserHostAddress - { - get - { - return _request.UserHostAddress; - } - } - - public override string UserHostName - { - get - { - return _request.UserHostName; - } - } - - public override string[] UserLanguages - { - get - { - return _request.UserLanguages; - } - } - - public override void ValidateInput() - { - _request.ValidateInput(); - } - - - #endregion - - /// - /// Gets a value indicating whether the request has debugging enabled - /// - /// true if this instance is debug; otherwise, false. - public bool IsDebug - { - get - { - return GlobalSettings.DebugMode && (!string.IsNullOrEmpty(this["umbdebugshowtrace"]) || !string.IsNullOrEmpty(this["umbdebug"])); - } - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/UmbracoResponse.cs b/src/Umbraco.Web/umbraco.presentation/UmbracoResponse.cs deleted file mode 100644 index e7168e8735..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/UmbracoResponse.cs +++ /dev/null @@ -1,521 +0,0 @@ -using System; -using System.Web; - -namespace umbraco.presentation -{ - /// - /// A custom HttpResponseBase class - /// - /// The only reason this class exists is for backwards compatibility. Previously this used to - /// inherit from HttpResponseWrapper which is incorrect since it is not unit testable and means that - /// it still has a reliance on the real HttpResponse object which means that the UmbracoContext still has - /// a reliance on the real HttpContext. - /// - [Obsolete("This class is no longer used")] - public class UmbracoResponse : HttpResponseBase - { - private readonly HttpResponseBase _response; - - #region Contructor - public UmbracoResponse(HttpResponseBase response) - { - _response = response; - } - - public UmbracoResponse(HttpResponse response) - { - _response = new HttpResponseWrapper(response); - } - #endregion - - #region Wrapped methods - - public override void AddCacheDependency(params System.Web.Caching.CacheDependency[] dependencies) - { - base.AddCacheDependency(dependencies); - } - - public override void AddCacheItemDependencies(string[] cacheKeys) - { - base.AddCacheItemDependencies(cacheKeys); - } - - public override void AddCacheItemDependencies(System.Collections.ArrayList cacheKeys) - { - base.AddCacheItemDependencies(cacheKeys); - } - - public override void AddCacheItemDependency(string cacheKey) - { - base.AddCacheItemDependency(cacheKey); - } - - public override void AddFileDependencies(string[] filenames) - { - base.AddFileDependencies(filenames); - } - - public override void AddFileDependencies(System.Collections.ArrayList filenames) - { - base.AddFileDependencies(filenames); - } - - public override void AddFileDependency(string filename) - { - base.AddFileDependency(filename); - } - - public override void AddHeader(string name, string value) - { - base.AddHeader(name, value); - } - - public override void AppendCookie(HttpCookie cookie) - { - base.AppendCookie(cookie); - } - - public override void AppendHeader(string name, string value) - { - base.AppendHeader(name, value); - } - - public override void AppendToLog(string param) - { - base.AppendToLog(param); - } - - public override string ApplyAppPathModifier(string virtualPath) - { - return base.ApplyAppPathModifier(virtualPath); - } - - public override void BinaryWrite(byte[] buffer) - { - base.BinaryWrite(buffer); - } - - public override bool Buffer - { - get - { - return base.Buffer; - } - set - { - base.Buffer = value; - } - } - - public override bool BufferOutput - { - get - { - return base.BufferOutput; - } - set - { - base.BufferOutput = value; - } - } - public override HttpCachePolicyBase Cache - { - get - { - return base.Cache; - } - } - - public override string CacheControl - { - get - { - return base.CacheControl; - } - set - { - base.CacheControl = value; - } - } - - public override string Charset - { - get - { - return base.Charset; - } - set - { - base.Charset = value; - } - } - - - public override void Clear() - { - base.Clear(); - } - - public override void ClearContent() - { - base.ClearContent(); - } - - public override void ClearHeaders() - { - base.ClearHeaders(); - } - - public override void Close() - { - base.Close(); - } - - public override System.Text.Encoding ContentEncoding - { - get - { - return base.ContentEncoding; - } - set - { - base.ContentEncoding = value; - } - } - - public override string ContentType - { - get - { - return base.ContentType; - } - set - { - base.ContentType = value; - } - } - - public override HttpCookieCollection Cookies - { - get - { - return base.Cookies; - } - } - - public override void DisableKernelCache() - { - base.DisableKernelCache(); - } - - public override void End() - { - base.End(); - } - - public override bool Equals(object obj) - { - return base.Equals(obj); - } - - public override int Expires - { - get - { - return base.Expires; - } - set - { - base.Expires = value; - } - } - - public override DateTime ExpiresAbsolute - { - get - { - return base.ExpiresAbsolute; - } - set - { - base.ExpiresAbsolute = value; - } - } - - public override System.IO.Stream Filter - { - get - { - return base.Filter; - } - set - { - base.Filter = value; - } - } - - public override void Flush() - { - base.Flush(); - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - - public override System.Text.Encoding HeaderEncoding - { - get - { - return base.HeaderEncoding; - } - set - { - base.HeaderEncoding = value; - } - } - - public override System.Collections.Specialized.NameValueCollection Headers - { - get - { - return base.Headers; - } - } - - public override bool IsClientConnected - { - get - { - return base.IsClientConnected; - } - } - - public override bool IsRequestBeingRedirected - { - get - { - return base.IsRequestBeingRedirected; - } - } - - - public override System.IO.TextWriter Output - { - get - { - return base.Output; - } - set - { - base.Output = value; - } - } - public override System.IO.Stream OutputStream - { - get - { - return base.OutputStream; - } - } - public override void Pics(string value) - { - base.Pics(value); - } - public override void Redirect(string url) - { - base.Redirect(url); - } - public override void Redirect(string url, bool endResponse) - { - base.Redirect(url, endResponse); - } - public override string RedirectLocation - { - get - { - return base.RedirectLocation; - } - set - { - base.RedirectLocation = value; - } - } - public override void RedirectPermanent(string url) - { - base.RedirectPermanent(url); - } - public override void RedirectPermanent(string url, bool endResponse) - { - base.RedirectPermanent(url, endResponse); - } - public override void RedirectToRoute(object routeValues) - { - base.RedirectToRoute(routeValues); - } - public override void RedirectToRoute(string routeName) - { - base.RedirectToRoute(routeName); - } - public override void RedirectToRoute(string routeName, object routeValues) - { - base.RedirectToRoute(routeName, routeValues); - } - public override void RedirectToRoute(string routeName, System.Web.Routing.RouteValueDictionary routeValues) - { - base.RedirectToRoute(routeName, routeValues); - } - public override void RedirectToRoute(System.Web.Routing.RouteValueDictionary routeValues) - { - base.RedirectToRoute(routeValues); - } - public override void RedirectToRoutePermanent(object routeValues) - { - base.RedirectToRoutePermanent(routeValues); - } - public override void RedirectToRoutePermanent(string routeName) - { - base.RedirectToRoutePermanent(routeName); - } - public override void RedirectToRoutePermanent(string routeName, object routeValues) - { - base.RedirectToRoutePermanent(routeName, routeValues); - } - public override void RedirectToRoutePermanent(string routeName, System.Web.Routing.RouteValueDictionary routeValues) - { - base.RedirectToRoutePermanent(routeName, routeValues); - } - public override void RedirectToRoutePermanent(System.Web.Routing.RouteValueDictionary routeValues) - { - base.RedirectToRoutePermanent(routeValues); - } - public override void RemoveOutputCacheItem(string path) - { - base.RemoveOutputCacheItem(path); - } - public override void RemoveOutputCacheItem(string path, string providerName) - { - base.RemoveOutputCacheItem(path, providerName); - } - public override void SetCookie(HttpCookie cookie) - { - base.SetCookie(cookie); - } - public override string Status - { - get - { - return base.Status; - } - set - { - base.Status = value; - } - } - public override int StatusCode - { - get - { - return base.StatusCode; - } - set - { - base.StatusCode = value; - } - } - public override string StatusDescription - { - get - { - return base.StatusDescription; - } - set - { - base.StatusDescription = value; - } - } - public override int SubStatusCode - { - get - { - return base.SubStatusCode; - } - set - { - base.SubStatusCode = value; - } - } - public override bool SuppressContent - { - get - { - return base.SuppressContent; - } - set - { - base.SuppressContent = value; - } - } - public override string ToString() - { - return base.ToString(); - } - public override void TransmitFile(string filename) - { - base.TransmitFile(filename); - } - public override void TransmitFile(string filename, long offset, long length) - { - base.TransmitFile(filename, offset, length); - } - public override bool TrySkipIisCustomErrors - { - get - { - return base.TrySkipIisCustomErrors; - } - set - { - base.TrySkipIisCustomErrors = value; - } - } - public override void Write(char ch) - { - base.Write(ch); - } - public override void Write(char[] buffer, int index, int count) - { - base.Write(buffer, index, count); - } - public override void Write(object obj) - { - base.Write(obj); - } - public override void Write(string s) - { - base.Write(s); - } - public override void WriteFile(IntPtr fileHandle, long offset, long size) - { - base.WriteFile(fileHandle, offset, size); - } - public override void WriteFile(string filename) - { - base.WriteFile(filename); - } - public override void WriteFile(string filename, bool readIntoMemory) - { - base.WriteFile(filename, readIntoMemory); - } - public override void WriteFile(string filename, long offset, long size) - { - base.WriteFile(filename, offset, size); - } - public override void WriteSubstitution(HttpResponseSubstitutionCallback callback) - { - base.WriteSubstitution(callback); - } - #endregion - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/UmbracoServerUtility.cs b/src/Umbraco.Web/umbraco.presentation/UmbracoServerUtility.cs deleted file mode 100644 index 1e0a77f273..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/UmbracoServerUtility.cs +++ /dev/null @@ -1,303 +0,0 @@ -using System; -using System.Web; -using System.Xml.Linq; -using Umbraco.Core.IO; -using umbraco.presentation.preview; -using umbraco.BusinessLogic; -using System.Xml; - -namespace umbraco.presentation -{ - - /// - /// A custom HttpServerUtilityBase object which exposes some additional methods and also overrides - /// some such as MapPath to use the IOHelper. - /// - /// - /// Unforunately when this class was created it - /// inherited from HttpServerUtilityWrapper intead of HttpServerUtilityBase which meant that this is not unit testable - /// and still has a dependency on a real HttpRequest. So now it inherits from HttpRequestBase which - /// means we need to override all of the methods and just wrap them with the _server object passed in. - /// - /// This now needs to exist only because of backwards compatibility - /// - [Obsolete("This class is no longer used, for the overridden MapPath methods and custom methods use IOHelper")] - public class UmbracoServerUtility : HttpServerUtilityBase - { - private readonly HttpServerUtilityBase _server; - - #region Constructor - - public UmbracoServerUtility(HttpServerUtilityBase server) - { - _server = server; - } - - [Obsolete("Use the alternate constructor which accepts a HttpServerUtilityBase object instead")] - public UmbracoServerUtility(HttpServerUtility server) - { - _server = new HttpServerUtilityWrapper(server); - } - - #endregion - - #region Wrapped methods - - public override object CreateObject(string progID) - { - return _server.CreateObject(progID); - } - - public override object CreateObject(Type type) - { - return _server.CreateObject(type); - } - - public override object CreateObjectFromClsid(string clsid) - { - return _server.CreateObjectFromClsid(clsid); - } - - public override bool Equals(object obj) - { - return _server.Equals(obj); - } - - public override void Execute(IHttpHandler handler, System.IO.TextWriter writer, bool preserveForm) - { - _server.Execute(handler, writer, preserveForm); - } - - public override void Execute(string path) - { - _server.Execute(path); - } - - public override void Execute(string path, bool preserveForm) - { - _server.Execute(path, preserveForm); - } - - public override void Execute(string path, System.IO.TextWriter writer) - { - _server.Execute(path, writer); - } - - public override void Execute(string path, System.IO.TextWriter writer, bool preserveForm) - { - _server.Execute(path, writer, preserveForm); - } - - public override int GetHashCode() - { - return _server.GetHashCode(); - } - - public override Exception GetLastError() - { - return _server.GetLastError(); - } - - public override string HtmlDecode(string s) - { - return _server.HtmlDecode(s); - } - - public override void HtmlDecode(string s, System.IO.TextWriter output) - { - _server.HtmlDecode(s, output); - } - - public override string HtmlEncode(string s) - { - return _server.HtmlEncode(s); - } - - public override void ClearError() - { - _server.ClearError(); - } - - public override void HtmlEncode(string s, System.IO.TextWriter output) - { - _server.HtmlEncode(s, output); - } - - public override string MachineName - { - get - { - return _server.MachineName; - } - } - - public override int ScriptTimeout - { - get - { - return _server.ScriptTimeout; - } - set - { - _server.ScriptTimeout = value; - } - } - - public override string ToString() - { - return _server.ToString(); - } - - public override void Transfer(IHttpHandler handler, bool preserveForm) - { - _server.Transfer(handler, preserveForm); - } - - public override void Transfer(string path) - { - _server.Transfer(path); - } - - public override void Transfer(string path, bool preserveForm) - { - _server.Transfer(path, preserveForm); - } - - public override void TransferRequest(string path) - { - _server.TransferRequest(path); - } - - public override void TransferRequest(string path, bool preserveForm) - { - _server.TransferRequest(path, preserveForm); - } - - public override void TransferRequest(string path, bool preserveForm, string method, System.Collections.Specialized.NameValueCollection headers) - { - _server.TransferRequest(path, preserveForm, method, headers); - } - - public override string UrlDecode(string s) - { - return _server.UrlDecode(s); - } - - public override void UrlDecode(string s, System.IO.TextWriter output) - { - _server.UrlDecode(s, output); - } - - public override string UrlEncode(string s) - { - return _server.UrlEncode(s); - } - - public override void UrlEncode(string s, System.IO.TextWriter output) - { - _server.UrlEncode(s, output); - } - - public override string UrlPathEncode(string s) - { - return _server.UrlPathEncode(s); - } - - public override byte[] UrlTokenDecode(string input) - { - return _server.UrlTokenDecode(input); - } - - public override string UrlTokenEncode(byte[] input) - { - return _server.UrlTokenEncode(input); - } - - #endregion - - #region Overridden methods - - /// - /// Returns the physical file path that corresponds to the specified virtual path on the Web server. - /// - /// The virtual path of the Web server. - /// - /// The physical file path that corresponds to . - /// - /// - /// The current is null. - /// - public override string MapPath(string path) - { - return IOHelper.MapPath(path); - } - - #endregion - - public string UmbracoPath - { - get - { - return IOHelper.ResolveUrl( SystemDirectories.Umbraco ); - } - } - - public string ContentXmlPath - { - get - { - return IOHelper.ResolveUrl( SystemFiles.ContentCacheXml ); - } - } - - [Obsolete("This is no longer used in the codebase and will be removed. ")] - private const string XDocumentCacheKey = "XDocumentCache"; - - /// - /// Gets the Umbraco XML cache - /// - /// The content XML. - [Obsolete("This is no longer used in the codebase and will be removed. If you need to access the current XML cache document you can use the Umbraco.Web.Umbraco.Context.GetXml() method.")] - public XDocument ContentXml - { - get - { - if (UmbracoContext.Current.InPreviewMode) - { - var pc = new PreviewContent(new Guid(StateHelper.Cookies.Preview.GetValue())); - pc.LoadPreviewset(); - return XmlDocumentToXDocument(pc.XmlContent); - } - else - { - if (HttpContext.Current == null) - return XDocument.Load(ContentXmlPath); - var xml = HttpContext.Current.Items[XDocumentCacheKey] as XDocument; - if (xml == null) - { - xml = XmlDocumentToXDocument(content.Instance.XmlContent); - HttpContext.Current.Items[XDocumentCacheKey] = xml; - } - return xml; - } - } - } - - private XDocument XmlDocumentToXDocument(XmlDocument xml) - { - using (var nodeReader = new XmlNodeReader(xml)) - { - nodeReader.MoveToContent(); - return XDocument.Load(nodeReader); - } - } - public string DataFolder - { - get - { - return IOHelper.ResolveUrl( SystemDirectories.Data ); - } - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/XsltExtensionAttribute.cs b/src/Umbraco.Web/umbraco.presentation/XsltExtensionAttribute.cs index 1eb422949a..b3e0e7dce8 100644 --- a/src/Umbraco.Web/umbraco.presentation/XsltExtensionAttribute.cs +++ b/src/Umbraco.Web/umbraco.presentation/XsltExtensionAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Security.Permissions; using System.Web; @@ -16,13 +16,13 @@ namespace umbraco { public XsltExtensionAttribute() : base() { - + } public XsltExtensionAttribute(string ns) : base(ns) { - + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs deleted file mode 100644 index c8daf43715..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ /dev/null @@ -1,1266 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Text; -using System.Web; -using System.Xml; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.web; -using umbraco.DataLayer; -using umbraco.presentation.nodeFactory; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Profiling; -using Umbraco.Core.Scoping; -using Umbraco.Web; -using Umbraco.Web.PublishedCache.XmlPublishedCache; -using Umbraco.Web.Scheduling; -using File = System.IO.File; -using Node = umbraco.NodeFactory.Node; - -namespace umbraco -{ - /// - /// Represents the Xml storage for the Xml published cache. - /// - public class content - { - private readonly IScopeProviderInternal _scopeProvider = (IScopeProviderInternal) ApplicationContext.Current.ScopeProvider; - private XmlCacheFilePersister _persisterTask; - - private volatile bool _released; - - #region Constructors - - private content() - { - if (SyncToXmlFile) - { - var logger = LoggerResolver.HasCurrent ? LoggerResolver.Current.Logger : new DebugDiagnosticsLogger(); - var profingLogger = new ProfilingLogger( - logger, - ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : new LogProfiler(logger)); - - // prepare the persister task - // there's always be one task keeping a ref to the runner - // so it's safe to just create it as a local var here - var runner = new BackgroundTaskRunner("XmlCacheFilePersister", new BackgroundTaskRunnerOptions - { - LongRunning = true, - KeepAlive = true, - Hosted = false // main domain will take care of stopping the runner (see below) - }, logger); - - // create (and add to runner) - _persisterTask = new XmlCacheFilePersister(runner, this, profingLogger); - - var registered = ApplicationContext.Current.MainDom.Register( - null, - () => - { - // once released, the cache still works but does not write to file anymore, - // which is OK with database server messenger but will cause data loss with - // another messenger... - - runner.Shutdown(false, true); // wait until flushed - _released = true; - }); - - // failed to become the main domain, we will never use the file - if (registered == false) - runner.Shutdown(false, true); - - _released = (registered == false); - } - - // initialize content - populate the cache - using (var safeXml = GetSafeXmlWriter()) - { - bool registerXmlChange; - - // if we don't use the file then LoadXmlLocked will not even - // read from the file and will go straight to database - LoadXmlLocked(safeXml, out registerXmlChange); - // if we use the file and registerXmlChange is true this will - // write to file, else it will not - safeXml.AcceptChanges(registerXmlChange); - } - } - - #endregion - - #region Singleton - - private static readonly Lazy LazyInstance = new Lazy(() => new content()); - - public static content Instance - { - get - { - return LazyInstance.Value; - } - } - - #endregion - - #region Legacy & Stuff - - // sync database access - // (not refactoring that part at the moment) - private static readonly object DbReadSyncLock = new object(); - - internal const string XmlContextContentItemKey = "UmbracoXmlContextContent"; - private static string _umbracoXmlDiskCacheFileName = string.Empty; - // internal for SafeXmlReaderWriter - internal volatile XmlDocument _xmlContent; - - /// - /// Gets the path of the umbraco XML disk cache file. - /// - /// The name of the umbraco XML disk cache file. - public static string GetUmbracoXmlDiskFileName() - { - if (string.IsNullOrEmpty(_umbracoXmlDiskCacheFileName)) - { - _umbracoXmlDiskCacheFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml); - } - return _umbracoXmlDiskCacheFileName; - } - - [Obsolete("Use the safer static GetUmbracoXmlDiskFileName() method instead to retrieve this value")] - public string UmbracoXmlDiskCacheFileName - { - get { return GetUmbracoXmlDiskFileName(); } - set { _umbracoXmlDiskCacheFileName = value; } - } - - //NOTE: We CANNOT use this for a double check lock because it is a property, not a field and to do double - // check locking in c# you MUST have a volatile field. Even thoug this wraps a volatile field it will still - // not work as expected for a double check lock because properties are treated differently in the clr. - public virtual bool isInitializing - { - get - { - // ok to access _xmlContent here - return _xmlContent == null; - } - } - - /// - /// Unused, please do not use - /// - [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] - protected static ISqlHelper SqlHelper - { - get { return Application.SqlHelper; } - } - - #endregion - - #region Public Methods - - [Obsolete("This is no longer used and will be removed in future versions, if you use this method it will not refresh 'async' it will perform the refresh on the current thread which is how it should be doing it")] - public virtual void RefreshContentFromDatabaseAsync() - { - RefreshContentFromDatabase(); - } - - /// - /// Load content from database and replaces active content when done. - /// - public virtual void RefreshContentFromDatabase() - { - var e = new RefreshContentEventArgs(); - FireBeforeRefreshContent(e); - - if (e.Cancel) return; - - using (var safeXml = GetSafeXmlWriter()) - { - safeXml.Xml = LoadContentFromDatabase(); - safeXml.AcceptChanges(); - } - } - - internal static bool TestingUpdateSitemapProvider = true; - - /// - /// Used by all overloaded publish methods to do the actual "noderepresentation to xml" - /// - /// - /// - /// - public static XmlDocument PublishNodeDo(Document d, XmlDocument xmlContentCopy, bool updateSitemapProvider) - { - updateSitemapProvider &= TestingUpdateSitemapProvider; - - // check if document *is* published, it could be unpublished by an event - if (d.Published) - { - var parentId = d.Level == 1 ? -1 : d.ParentId; - - // fix sortOrder - see note in UpdateSortOrder - var node = GetPreviewOrPublishedNode(d, xmlContentCopy, false); - var attr = ((XmlElement)node).GetAttributeNode("sortOrder"); - attr.Value = d.sortOrder.ToString(); - xmlContentCopy = GetAddOrUpdateXmlNode(xmlContentCopy, d.Id, d.Level, parentId, node); - - // update sitemapprovider - if (updateSitemapProvider && SiteMap.Provider is UmbracoSiteMapProvider) - { - try - { - var prov = (UmbracoSiteMapProvider)SiteMap.Provider; - var n = new Node(d.Id, true); - if (string.IsNullOrEmpty(n.Url) == false && n.Url != "/#") - { - prov.UpdateNode(n); - } - else - { - LogHelper.Debug(string.Format("Can't update Sitemap Provider due to empty Url in node id: {0}", d.Id)); - } - } - catch (Exception ee) - { - LogHelper.Error(string.Format("Error adding node to Sitemap Provider in PublishNodeDo(): {0}", d.Id), ee); - } - } - } - - return xmlContentCopy; - } - - private static XmlNode GetPreviewOrPublishedNode(Document d, XmlDocument xmlContentCopy, bool isPreview) - { - if (isPreview) - { - return d.ToPreviewXml(xmlContentCopy); - } - else - { - return d.ToXml(xmlContentCopy, false); - } - } - - /// - /// Sorts the documents. - /// - /// The parent node identifier. - public void SortNodes(int parentId) - { - using (var safeXml = GetSafeXmlWriter()) - { - var parentNode = parentId == -1 - ? safeXml.Xml.DocumentElement - : safeXml.Xml.GetElementById(parentId.ToString(CultureInfo.InvariantCulture)); - - if (parentNode == null) return; - - var sorted = XmlHelper.SortNodesIfNeeded( - parentNode, - ChildNodesXPath, - x => x.AttributeValue("sortOrder")); - - if (sorted == false) return; - - safeXml.AcceptChanges(); - } - } - - /// - /// Updates the document cache. - /// - /// The page id. - public virtual void UpdateDocumentCache(int pageId) - { - var d = new Document(pageId); - UpdateDocumentCache(d); - } - - /// - /// Updates the document cache. - /// - /// The d. - public virtual void UpdateDocumentCache(Document d) - { - var e = new DocumentCacheEventArgs(); - FireBeforeUpdateDocumentCache(d, e); - - if (e.Cancel) return; - - // lock the xml cache so no other thread can write to it at the same time - // note that some threads could read from it while we hold the lock, though - using (var safeXml = GetSafeXmlWriter()) - { - safeXml.Xml = PublishNodeDo(d, safeXml.Xml, true); - safeXml.AcceptChanges(); - } - - var cachedFieldKeyStart = string.Format("{0}{1}_", CacheKeys.ContentItemCacheKey, d.Id); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(cachedFieldKeyStart); - - FireAfterUpdateDocumentCache(d, e); - } - - internal virtual void UpdateSortOrder(int contentId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(contentId); - if (content == null) return; - UpdateSortOrder(content); - } - - internal virtual void UpdateSortOrder(IContent c) - { - if (c == null) throw new ArgumentNullException("c"); - - // the XML in database is updated only when content is published, and then - // it contains the sortOrder value at the time the XML was generated. when - // a document with unpublished changes is sorted, then it is simply saved - // (see ContentService) and so the sortOrder has changed but the XML has - // not been updated accordingly. - - // this updates the published cache to take care of the situation - // without ContentService having to ... what exactly? - - // no need to do it if - // - the content is published without unpublished changes (XML will be re-gen anyways) - // - the content has no published version (not in XML) - // - the sort order has not changed - // note that - // - if it is a new entity is has not published version - // - if Published is dirty and false it's getting unpublished and has no published version - // - if (c.Published) return; - if (c.HasPublishedVersion == false) return; - if (c.WasPropertyDirty("SortOrder") == false) return; - - using (var safeXml = GetSafeXmlWriter()) - { - //TODO: This can be null: safeXml.Xml!!!! - - - var node = safeXml.Xml.GetElementById(c.Id.ToString(CultureInfo.InvariantCulture)); - if (node == null) return; - var attr = node.GetAttributeNode("sortOrder"); - if (attr == null) return; - var sortOrder = c.SortOrder.ToString(CultureInfo.InvariantCulture); - if (attr.Value == sortOrder) return; - - // only if node was actually modified - attr.Value = sortOrder; - - safeXml.AcceptChanges(); - } - } - - /// - /// Updates the document cache for multiple documents - /// - /// The documents. - [Obsolete("This is not used and will be removed from the codebase in future versions")] - public virtual void UpdateDocumentCache(List Documents) - { - using (var safeXml = GetSafeXmlWriter()) - { - foreach (var d in Documents) - { - safeXml.Xml = PublishNodeDo(d, safeXml.Xml, true); - } - safeXml.AcceptChanges(); - } - } - - [Obsolete("Method obsolete in version 4.1 and later, please use UpdateDocumentCache", true)] - public virtual void UpdateDocumentCacheAsync(int documentId) - { - UpdateDocumentCache(documentId); - } - - [Obsolete("Method obsolete in version 4.1 and later, please use ClearDocumentCache", true)] - public virtual void ClearDocumentCacheAsync(int documentId) - { - ClearDocumentCache(documentId); - } - - public virtual void ClearDocumentCache(int documentId) - { - ClearDocumentCache(documentId, true); - } - - internal virtual void ClearDocumentCache(int documentId, bool removeDbXmlEntry) - { - // Get the document - Document d; - try - { - d = new Document(documentId); - } - catch - { - // if we need the document to remove it... this cannot be LB?! - // shortcut everything here - ClearDocumentXmlCache(documentId); - return; - } - ClearDocumentCache(d, removeDbXmlEntry); - } - - /// - /// Clears the document cache and removes the document from the xml db cache. - /// This means the node gets unpublished from the website. - /// - /// The document - /// - internal void ClearDocumentCache(Document doc, bool removeDbXmlEntry) - { - var e = new DocumentCacheEventArgs(); - FireBeforeClearDocumentCache(doc, e); - - if (!e.Cancel) - { - XmlNode x; - - //Hack: this is here purely for backwards compat if someone for some reason is using the - // ClearDocumentCache(int documentId) method and expecting it to remove the xml - if (removeDbXmlEntry) - { - // remove from xml db cache - doc.XmlRemoveFromDB(); - } - - // clear xml cache - ClearDocumentXmlCache(doc.Id); - - //SD: changed to fire event BEFORE running the sitemap!! argh. - FireAfterClearDocumentCache(doc, e); - - // update sitemapprovider - if (SiteMap.Provider is UmbracoSiteMapProvider) - { - var prov = (UmbracoSiteMapProvider)SiteMap.Provider; - prov.RemoveNode(doc.Id); - } - } - } - - internal void ClearDocumentXmlCache(int id) - { - // We need to lock content cache here, because we cannot allow other threads - // making changes at the same time, they need to be queued - using (var safeXml = GetSafeXmlReader()) - { - // Check if node present, before cloning - var x = safeXml.Xml.GetElementById(id.ToString()); - if (x == null) - return; - - if (safeXml.IsWriter == false) - safeXml.UpgradeToWriter(); - - // Find the document in the xml cache - x = safeXml.Xml.GetElementById(id.ToString()); - if (x != null) - { - // The document already exists in cache, so repopulate it - x.ParentNode.RemoveChild(x); - safeXml.AcceptChanges(); - } - } - } - - /// - /// Unpublishes the node. - /// - /// The document id. - [Obsolete("Please use: umbraco.content.ClearDocumentCache", true)] - public virtual void UnPublishNode(int documentId) - { - ClearDocumentCache(documentId); - } - - #endregion - - #region Protected & Private methods - - // this is for tests exclusively until we have a proper accessor in v8 - internal static Func HttpContextItemsGetter { get; set; } - - private static IDictionary HttpContextItems - { - get - { - return HttpContextItemsGetter == null - ? (HttpContext.Current == null ? null : HttpContext.Current.Items) - : HttpContextItemsGetter(); - } - } - - // clear the current xml capture in http context - // used when applying changes from SafeXmlReaderWriter, - // to force a new capture - so that changes become - // visible for the current request - private void ClearContextCache() - { - var items = HttpContextItems; - if (items == null || items.Contains(XmlContextContentItemKey) == false) return; - items.Remove(XmlContextContentItemKey); - } - - // replaces the current xml capture in http context - // used for temp changes from SafeXmlReaderWriter - // so the current request immediately sees changes - private void SetContextCache(XmlDocument xml) - { - var items = HttpContextItems; - if (items == null) return; - items[XmlContextContentItemKey] = xml; - } - - /// - /// Load content from database - /// - private XmlDocument LoadContentFromDatabase() - { - try - { - LogHelper.Info("Loading content from database..."); - - lock (DbReadSyncLock) - { - var xmlDoc = ApplicationContext.Current.Services.ContentService.BuildXmlCache(); - LogHelper.Debug("Done republishing Xml Index"); - return xmlDoc; - } - } - catch (Exception ee) - { - LogHelper.Error("Error Republishing", ee); - } - - // An error of some sort must have stopped us from successfully generating - // the content tree, so lets return null signifying there is no content available - return null; - } - - [Obsolete("This method should not be used and does nothing, xml file persistence is done in a queue using a BackgroundTaskRunner")] - public void PersistXmlToFile() - { - } - - internal DateTime GetCacheFileUpdateTime() - { - //TODO: Should there be a try/catch here in case the file is being written to while this is trying to be executed? - - if (File.Exists(GetUmbracoXmlDiskFileName())) - { - return new FileInfo(GetUmbracoXmlDiskFileName()).LastWriteTimeUtc; - } - - return DateTime.MinValue; - } - - #endregion - - #region Configuration - - // gathering configuration options here to document what they mean - - private readonly bool _xmlFileEnabled = true; - - // whether the disk cache is enabled - private bool XmlFileEnabled - { - get { return _xmlFileEnabled && UmbracoConfig.For.UmbracoSettings().Content.XmlCacheEnabled; } - } - - // whether the disk cache is enabled and to update the disk cache when xml changes - private bool SyncToXmlFile - { - get { return XmlFileEnabled && UmbracoConfig.For.UmbracoSettings().Content.ContinouslyUpdateXmlDiskCache; } - } - - // whether the disk cache is enabled and to reload from disk cache if it changes - private bool SyncFromXmlFile - { - get { return XmlFileEnabled && UmbracoConfig.For.UmbracoSettings().Content.XmlContentCheckForDiskChanges; } - } - - // whether to use the legacy schema - private static bool UseLegacySchema - { - get { return UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema; } - } - - #endregion - - #region Xml - - // internal for SafeXmlReaderWriter - internal readonly AsyncLock _xmlLock = new AsyncLock(); // protects _xml - - /// - /// Get content. First call to this property will initialize xmldoc - /// subsequent calls will be blocked until initialization is done - /// Further we cache (in context) xmlContent for each request to ensure that - /// we always have the same XmlDoc throughout the whole request. - /// - public virtual XmlDocument XmlContent - { - get - { - // if there's a current enlisted reader/writer, use its xml - var safeXml = SafeXmlReaderWriter.Get(_scopeProvider); - if (safeXml != null) return safeXml.Xml; - - var items = HttpContextItems; - if (items == null) - return XmlContentInternal; - - // capture or return the current xml in http context - // so that it remains stable over the entire request - var content = (XmlDocument) items[XmlContextContentItemKey]; - if (content == null) - { - content = XmlContentInternal; - items[XmlContextContentItemKey] = content; - } - return content; - } - } - - [Obsolete("Please use: content.Instance.XmlContent")] - public static XmlDocument xmlContent - { - get { return Instance.XmlContent; } - } - - // to be used by content.Instance - // ok to access _xmlContent here - just capturing - protected internal virtual XmlDocument XmlContentInternal - { - get - { - ReloadXmlFromFileIfChanged(); - return _xmlContent; - } - } - - // assumes xml lock - // ok to access _xmlContent here since this is called from the safe reader/writer - // internal for SafeXmlReaderWriter - internal void SetXmlLocked(XmlDocument xml, bool registerXmlChange) - { - // this is the ONLY place where we write to _xmlContent - _xmlContent = xml; - - if (registerXmlChange == false || SyncToXmlFile == false) - return; - - //_lastXmlChange = DateTime.UtcNow; - _persisterTask = _persisterTask.Touch(); // _persisterTask != null because SyncToXmlFile == true - } - - private static XmlDocument EnsureSchema(string contentTypeAlias, XmlDocument xml) - { - string subset = null; - - // get current doctype - var n = xml.FirstChild; - while (n.NodeType != XmlNodeType.DocumentType && n.NextSibling != null) - n = n.NextSibling; - if (n.NodeType == XmlNodeType.DocumentType) - subset = ((XmlDocumentType)n).InternalSubset; - - // ensure it contains the content type - if (subset != null && subset.Contains(string.Format("", contentTypeAlias))) - return xml; - - // alas, that does not work, replacing a doctype is ignored and GetElementById fails - // - //// remove current doctype, set new doctype - //xml.RemoveChild(n); - //subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset); - //var doctype = xml.CreateDocumentType("root", null, null, subset); - //xml.InsertAfter(doctype, xml.FirstChild); - - var xml2 = new XmlDocument(); - subset = string.Format("{0}{0}{2}", Environment.NewLine, contentTypeAlias, subset); - var doctype = xml2.CreateDocumentType("root", null, null, subset); - xml2.AppendChild(doctype); - xml2.AppendChild(xml2.ImportNode(xml.DocumentElement, true)); - return xml2; - } - - // try to load from file, otherwise database - // assumes xml lock (file is always locked) - private void LoadXmlLocked(SafeXmlReaderWriter safeXml, out bool registerXmlChange) - { - LogHelper.Debug("Loading Xml..."); - - // try to get it from the file - if (XmlFileEnabled && (safeXml.Xml = LoadXmlFromFile()) != null) - { - registerXmlChange = false; // loaded from disk, do NOT write back to disk! - return; - } - - // get it from the database, and register - safeXml.Xml = LoadContentFromDatabase(); - registerXmlChange = true; - } - - // NOTE - // - this is NOT a reader/writer lock and each lock is exclusive - // - these locks are NOT reentrant / recursive - - // gets a locked safe read access to the main xml - private SafeXmlReaderWriter GetSafeXmlReader() - { - return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xmlContent, - SetContextCache, - (xml, registerXmlChange) => - { - SetXmlLocked(xml, registerXmlChange); - ClearContextCache(); - }, false); - } - - // gets a locked safe write access to the main xml (cloned) - private SafeXmlReaderWriter GetSafeXmlWriter() - { - return SafeXmlReaderWriter.Get(_scopeProvider, _xmlLock, _xmlContent, - SetContextCache, - (xml, registerXmlChange) => - { - SetXmlLocked(xml, registerXmlChange); - ClearContextCache(); - }, true); - } - - private static string ChildNodesXPath - { - get - { - return UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema - ? "./node" - : "./* [@id]"; - } - } - - private static string DataNodesXPath - { - get - { - return UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema - ? "./data" - : "./* [not(@id)]"; - } - } - - #endregion - - #region File - - private readonly string _xmlFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml); - private DateTime _lastFileRead; // last time the file was read - private DateTime _nextFileCheck; // last time we checked whether the file was changed - - // not used - just try to read the file - //private bool XmlFileExists - //{ - // get - // { - // // check that the file exists and has content (is not empty) - // var fileInfo = new FileInfo(_xmlFileName); - // return fileInfo.Exists && fileInfo.Length > 0; - // } - //} - - private DateTime XmlFileLastWriteTime - { - get - { - var fileInfo = new FileInfo(_xmlFileName); - return fileInfo.Exists ? fileInfo.LastWriteTimeUtc : DateTime.MinValue; - } - } - - // invoked by XmlCacheFilePersister ONLY and that one manages the MainDom, ie it - // will NOT try to save once the current app domain is not the main domain anymore - // (no need to test _released) - internal void SaveXmlToFile() - { - LogHelper.Info("Save Xml to file..."); - try - { - // ok to access _xmlContent here - capture (atomic + volatile), immutable anyway - var xml = _xmlContent; - if (xml == null) return; - - // delete existing file, if any - DeleteXmlFile(); - - // ensure cache directory exists - var directoryName = Path.GetDirectoryName(_xmlFileName); - if (directoryName == null) - throw new Exception(string.Format("Invalid XmlFileName \"{0}\".", _xmlFileName)); - if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false) - Directory.CreateDirectory(directoryName); - - // save - using (var fs = new FileStream(_xmlFileName, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - SaveXmlToStream(xml, fs); - } - - LogHelper.Info("Saved Xml to file."); - } - catch (Exception e) - { - // if something goes wrong remove the file - try - { - DeleteXmlFile(); - } - catch - { - // don't make it worse: could be that we failed to write because we cannot - // access the file, in which case we won't be able to delete it either - } - LogHelper.Error("Failed to save Xml to file.", e); - } - } - - private void SaveXmlToStream(XmlDocument xml, Stream writeStream) - { - // using that one method because we want to have proper indent - // and in addition, writing async is never fully async because - // althouth the writer is async, xml.WriteTo() will not async - - // that one almost works but... "The elements are indented as long as the element - // does not contain mixed content. Once the WriteString or WriteWhitespace method - // is called to write out a mixed element content, the XmlWriter stops indenting. - // The indenting resumes once the mixed content element is closed." - says MSDN - // about XmlWriterSettings.Indent - - // so ImportContent must also make sure of ignoring whitespaces! - - if (writeStream.CanSeek) - { - writeStream.Position = 0; - } - - using (var xmlWriter = XmlWriter.Create(writeStream, new XmlWriterSettings - { - Indent = true, - Encoding = Encoding.UTF8, - //OmitXmlDeclaration = true - })) - { - //xmlWriter.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""); - xml.WriteTo(xmlWriter); // already contains the xml declaration - } - } - - private XmlDocument LoadXmlFromFile() - { - // do NOT try to load if we are not the main domain anymore - if (_released) return null; - - LogHelper.Info("Load Xml from file..."); - - try - { - var xml = new XmlDocument(); - using (var fs = new FileStream(_xmlFileName, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - xml.Load(fs); - } - _lastFileRead = DateTime.UtcNow; - LogHelper.Info("Loaded Xml from file."); - return xml; - } - catch (FileNotFoundException) - { - LogHelper.Warn("Failed to load Xml, file does not exist."); - return null; - } - catch (Exception e) - { - LogHelper.Error("Failed to load Xml from file.", e); - try - { - DeleteXmlFile(); - } - catch - { - // don't make it worse: could be that we failed to read because we cannot - // access the file, in which case we won't be able to delete it either - } - return null; - } - } - - private void DeleteXmlFile() - { - if (File.Exists(_xmlFileName) == false) return; - File.SetAttributes(_xmlFileName, FileAttributes.Normal); - File.Delete(_xmlFileName); - } - - private void ReloadXmlFromFileIfChanged() - { - if (SyncFromXmlFile == false) return; - - var now = DateTime.UtcNow; - if (now < _nextFileCheck) return; - - // time to check - _nextFileCheck = now.AddSeconds(1); // check every 1s - if (XmlFileLastWriteTime <= _lastFileRead) return; - - LogHelper.Debug("Xml file change detected, reloading."); - - // time to read - - using (var safeXml = GetSafeXmlWriter()) - { - bool registerXmlChange; - LoadXmlLocked(safeXml, out registerXmlChange); // updates _lastFileRead - safeXml.AcceptChanges(registerXmlChange); - } - } - - #endregion - - #region Manage change - - //TODO remove as soon as we can break backward compatibility - [Obsolete("Use GetAddOrUpdateXmlNode which returns an updated Xml document.", false)] - public static void AddOrUpdateXmlNode(XmlDocument xml, int id, int level, int parentId, XmlNode docNode) - { - GetAddOrUpdateXmlNode(xml, id, level, parentId, docNode); - } - - // adds or updates a node (docNode) into a cache (xml) - public static XmlDocument GetAddOrUpdateXmlNode(XmlDocument xml, int id, int level, int parentId, XmlNode docNode) - { - // sanity checks - if (id != docNode.AttributeValue("id")) - throw new ArgumentException("Values of id and docNode/@id are different."); - if (parentId != docNode.AttributeValue("parentID")) - throw new ArgumentException("Values of parentId and docNode/@parentID are different."); - - // find the document in the cache - XmlNode currentNode = xml.GetElementById(id.ToInvariantString()); - - // if the document is not there already then it's a new document - // we must make sure that its document type exists in the schema - if (currentNode == null && UseLegacySchema == false) - { - var xml2 = EnsureSchema(docNode.Name, xml); - if (ReferenceEquals(xml, xml2) == false) - docNode = xml2.ImportNode(docNode, true); - xml = xml2; - } - - // find the parent - XmlNode parentNode = level == 1 - ? xml.DocumentElement - : xml.GetElementById(parentId.ToInvariantString()); - - // no parent = cannot do anything - if (parentNode == null) - return xml; - - // insert/move the node under the parent - if (currentNode == null) - { - // document not there, new node, append - currentNode = docNode; - parentNode.AppendChild(currentNode); - } - else - { - // document found... we could just copy the currentNode children nodes over under - // docNode, then remove currentNode and insert docNode... the code below tries to - // be clever and faster, though only benchmarking could tell whether it's worth the - // pain... - - // first copy current parent ID - so we can compare with target parent - var moving = currentNode.AttributeValue("parentID") != parentId; - - if (docNode.Name == currentNode.Name) - { - // name has not changed, safe to just update the current node - // by transfering values eg copying the attributes, and importing the data elements - TransferValuesFromDocumentXmlToPublishedXml(docNode, currentNode); - - // if moving, move the node to the new parent - // else it's already under the right parent - // (but maybe the sort order has been updated) - if (moving) - parentNode.AppendChild(currentNode); // remove then append to parentNode - } - else - { - // name has changed, must use docNode (with new name) - // move children nodes from currentNode to docNode (already has properties) - var children = currentNode.SelectNodes(ChildNodesXPath); - if (children == null) throw new Exception("oops"); - foreach (XmlNode child in children) - docNode.AppendChild(child); // remove then append to docNode - - // and put docNode in the right place - if parent has not changed, then - // just replace, else remove currentNode and insert docNode under the right parent - // (but maybe not at the right position due to sort order) - if (moving) - { - if (currentNode.ParentNode == null) throw new Exception("oops"); - currentNode.ParentNode.RemoveChild(currentNode); - parentNode.AppendChild(docNode); - } - else - { - // replacing might screw the sort order - parentNode.ReplaceChild(docNode, currentNode); - } - - currentNode = docNode; - } - } - - // if the nodes are not ordered, must sort - // (see U4-509 + has to work with ReplaceChild too) - //XmlHelper.SortNodesIfNeeded(parentNode, childNodesXPath, x => x.AttributeValue("sortOrder")); - - // but... - // if we assume that nodes are always correctly sorted - // then we just need to ensure that currentNode is at the right position. - // should be faster that moving all the nodes around. - XmlHelper.SortNode(parentNode, ChildNodesXPath, currentNode, x => x.AttributeValue("sortOrder")); - return xml; - } - - private static void TransferValuesFromDocumentXmlToPublishedXml(XmlNode documentNode, XmlNode publishedNode) - { - // remove all attributes from the published node - if (publishedNode.Attributes == null) throw new Exception("oops"); - publishedNode.Attributes.RemoveAll(); - - // remove all data nodes from the published node - //TODO: This could be faster, might as well just iterate all children and filter - // instead of selecting matching children (i.e. iterating all) and then iterating the - // filtered items to remove, this also allocates more memory to store the list of children. - // Below we also then do another filtering of child nodes, if we just iterate all children we - // can perform both functions more efficiently - var dataNodes = publishedNode.SelectNodes(DataNodesXPath); - if (dataNodes == null) throw new Exception("oops"); - foreach (XmlNode n in dataNodes) - publishedNode.RemoveChild(n); - - // append all attributes from the document node to the published node - if (documentNode.Attributes == null) throw new Exception("oops"); - foreach (XmlAttribute att in documentNode.Attributes) - ((XmlElement)publishedNode).SetAttribute(att.Name, att.Value); - - // find the first child node, if any - var childNodes = publishedNode.SelectNodes(ChildNodesXPath); - if (childNodes == null) throw new Exception("oops"); - var firstChildNode = childNodes.Count == 0 ? null : childNodes[0]; - - // append all data nodes from the document node to the published node - dataNodes = documentNode.SelectNodes(DataNodesXPath); - if (dataNodes == null) throw new Exception("oops"); - foreach (XmlNode n in dataNodes) - { - if (publishedNode.OwnerDocument == null) throw new Exception("oops"); - var imported = publishedNode.OwnerDocument.ImportNode(n, true); - if (firstChildNode == null) - publishedNode.AppendChild(imported); - else - publishedNode.InsertBefore(imported, firstChildNode); - } - } - - #endregion - - #region Events - - /// - /// Occurs when [after loading the xml string from the database]. - /// - public delegate void ContentCacheDatabaseLoadXmlStringEventHandler( - ref string xml, ContentCacheLoadNodeEventArgs e); - - /// - /// Occurs when [after loading the xml string from the database and creating the xml node]. - /// - public delegate void ContentCacheLoadNodeEventHandler(XmlNode xmlNode, ContentCacheLoadNodeEventArgs e); - - public delegate void DocumentCacheEventHandler(Document sender, DocumentCacheEventArgs e); - - public delegate void RefreshContentEventHandler(Document sender, RefreshContentEventArgs e); - - /// - /// Occurs when [before document cache update]. - /// - public static event DocumentCacheEventHandler BeforeUpdateDocumentCache; - - /// - /// Fires the before document cache. - /// - /// The sender. - /// The instance containing the event data. - protected virtual void FireBeforeUpdateDocumentCache(Document sender, DocumentCacheEventArgs e) - { - if (BeforeUpdateDocumentCache != null) - { - BeforeUpdateDocumentCache(sender, e); - } - } - - /// - /// Occurs when [after document cache update]. - /// - public static event DocumentCacheEventHandler AfterUpdateDocumentCache; - - /// - /// Fires after document cache updater. - /// - /// The sender. - /// The instance containing the event data. - protected virtual void FireAfterUpdateDocumentCache(Document sender, DocumentCacheEventArgs e) - { - if (AfterUpdateDocumentCache != null) - { - AfterUpdateDocumentCache(sender, e); - } - } - - /// - /// Occurs when [before document cache unpublish]. - /// - public static event DocumentCacheEventHandler BeforeClearDocumentCache; - - /// - /// Fires the before document cache unpublish. - /// - /// The sender. - /// The instance containing the event data. - protected virtual void FireBeforeClearDocumentCache(Document sender, DocumentCacheEventArgs e) - { - if (BeforeClearDocumentCache != null) - { - BeforeClearDocumentCache(sender, e); - } - } - - public static event DocumentCacheEventHandler AfterClearDocumentCache; - - /// - /// Fires the after document cache unpublish. - /// - /// The sender. - /// The instance containing the event data. - protected virtual void FireAfterClearDocumentCache(Document sender, DocumentCacheEventArgs e) - { - if (AfterClearDocumentCache != null) - { - AfterClearDocumentCache(sender, e); - } - } - - /// - /// Occurs when [before refresh content]. - /// - public static event RefreshContentEventHandler BeforeRefreshContent; - - /// - /// Fires the content of the before refresh. - /// - /// The sender. - /// The instance containing the event data. - protected virtual void FireBeforeRefreshContent(RefreshContentEventArgs e) - { - if (BeforeRefreshContent != null) - { - BeforeRefreshContent(null, e); - } - } - - /// - /// Occurs when [after refresh content]. - /// - public static event RefreshContentEventHandler AfterRefreshContent; - - /// - /// Fires the content of the after refresh. - /// - /// The sender. - /// The instance containing the event data. - protected virtual void FireAfterRefreshContent(RefreshContentEventArgs e) - { - if (AfterRefreshContent != null) - { - AfterRefreshContent(null, e); - } - } - - [Obsolete("This is no used, do not use this for any reason")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static event ContentCacheDatabaseLoadXmlStringEventHandler AfterContentCacheDatabaseLoadXmlString; - - /// - /// Occurs when [before when creating the document cache from database]. - /// - public static event ContentCacheLoadNodeEventHandler BeforeContentCacheLoadNode; - - /// - /// Fires the before when creating the document cache from database - /// - /// The sender. - /// The instance containing the event data. - internal static void FireBeforeContentCacheLoadNode(XmlNode node, ContentCacheLoadNodeEventArgs e) - { - if (BeforeContentCacheLoadNode != null) - { - BeforeContentCacheLoadNode(node, e); - } - } - - [Obsolete("This is no used, do not use this for any reason")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static event ContentCacheLoadNodeEventHandler AfterContentCacheLoadNodeFromDatabase; - - /// - /// Occurs when [before a publish action updates the content cache]. - /// - public static event ContentCacheLoadNodeEventHandler BeforePublishNodeToContentCache; - - /// - /// Fires the before a publish action updates the content cache - /// - /// The sender. - /// The instance containing the event data. - public static void FireBeforePublishNodeToContentCache(XmlNode node, ContentCacheLoadNodeEventArgs e) - { - if (BeforePublishNodeToContentCache != null) - { - BeforePublishNodeToContentCache(node, e); - } - } - - #endregion - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/default.aspx.cs b/src/Umbraco.Web/umbraco.presentation/default.aspx.cs index 0ae9c52af5..0e825408e4 100644 --- a/src/Umbraco.Web/umbraco.presentation/default.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/default.aspx.cs @@ -1,23 +1,17 @@ using System; -using System.Threading; using System.Web; using System.Web.Mvc; using System.Web.Routing; using System.Web.UI; using System.IO; -using System.Xml; -using System.Text.RegularExpressions; -using StackExchange.Profiling; using Umbraco.Core; using Umbraco.Core.Logging; -using Umbraco.Core.Profiling; using Umbraco.Web; using Umbraco.Web.Routing; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Web.Templates; -using umbraco.cms.businesslogic.web; -using umbraco.cms.businesslogic; +using Umbraco.Web.Composing; namespace umbraco { @@ -25,7 +19,7 @@ namespace umbraco /// The codebehind class for the main default.aspx page that does the webforms rendering in Umbraco /// /// - /// We would move this to the UI project but there is a public API property and some protected properties which people may be using so + /// We would move this to the UI project but there is a public API property and some protected properties which people may be using so /// we cannot move it. /// public class UmbracoDefault : Page @@ -38,31 +32,26 @@ namespace umbraco } private page _upage; - private PublishedContentRequest _docRequest; - bool _validateRequest = true; + private PublishedRequest _frequest; /// /// To turn off request validation set this to false before the PageLoad event. This equivalent to the validateRequest page directive /// and has nothing to do with "normal" validation controls. Default value is true. /// - public bool ValidateRequest - { - get { return _validateRequest; } - set { _validateRequest = value; } - } + public bool ValidateRequest { get; set; } = true; protected override void OnPreInit(EventArgs e) { base.OnPreInit(e); - using (DisposableTimer.DebugDuration("PreInit")) + using (Current.ProfilingLogger.DebugDuration("PreInit")) { // handle the infamous umbDebugShowTrace, etc Page.Trace.IsEnabled &= GlobalSettings.DebugMode && string.IsNullOrWhiteSpace(Request["umbDebugShowTrace"]) == false; // get the document request and the page - _docRequest = UmbracoContext.Current.PublishedContentRequest; - _upage = _docRequest.UmbracoPage; + _frequest = UmbracoContext.Current.PublishedRequest; + _upage = _frequest.UmbracoPage; //we need to check this for backwards compatibility in case people still arent' using master pages if (UmbracoConfig.For.UmbracoSettings().Templates.UseAspNetMasterPages) @@ -80,7 +69,7 @@ namespace umbraco this.MasterPageFile = template.GetMasterPageName(_upage.Template); - // reset the friendly path so it's used by forms, etc. + // reset the friendly path so it's used by forms, etc. Context.RewritePath(UmbracoContext.Current.OriginalRequestUrl.PathAndQuery); //fire the init finished event @@ -91,7 +80,7 @@ namespace umbraco protected override void OnInit(EventArgs e) { - using (DisposableTimer.DebugDuration("Init")) + using (Current.ProfilingLogger.DebugDuration("Init")) { base.OnInit(e); @@ -106,7 +95,7 @@ namespace umbraco var ctx = new ControllerContext(new HttpContextWrapper(Context), new RouteData(), new TempDataController()); provider.LoadTempData(ctx); - //This is only here for legacy if people arent' using master pages... + //This is only here for legacy if people arent' using master pages... //TODO: We need to test that this still works!! Or do we ?? if (!UmbracoConfig.For.UmbracoSettings().Templates.UseAspNetMasterPages) { @@ -138,10 +127,10 @@ namespace umbraco protected override void OnLoad(EventArgs e) { - using (DisposableTimer.DebugDuration("Load")) + using (Current.ProfilingLogger.DebugDuration("Load")) { base.OnLoad(e); - + if (ValidateRequest) Request.ValidateInput(); } @@ -149,7 +138,7 @@ namespace umbraco protected override void Render(HtmlTextWriter writer) { - using (DisposableTimer.DebugDuration("Render")) + using (Current.ProfilingLogger.DebugDuration("Render")) { // do the original rendering @@ -163,7 +152,7 @@ namespace umbraco // filter / add preview banner if (UmbracoContext.Current.InPreviewMode) { - LogHelper.Debug("Umbraco is running in preview mode.", true); + Current.Logger.Debug("Umbraco is running in preview mode."); if (Response.ContentType.InvariantEquals("text/html")) // ASP.NET default value { diff --git a/src/Umbraco.Web/umbraco.presentation/helper.cs b/src/Umbraco.Web/umbraco.presentation/helper.cs index ffb6151887..e745f7cc95 100644 --- a/src/Umbraco.Web/umbraco.presentation/helper.cs +++ b/src/Umbraco.Web/umbraco.presentation/helper.cs @@ -1,217 +1,37 @@ -using System; +using System; using System.Collections; -using System.Linq; -using System.Text.RegularExpressions; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.CodeAnnotations; -using Umbraco.Core.Configuration; -using Umbraco.Core.Profiling; -using umbraco.BusinessLogic; -using System.Xml; -using umbraco.presentation; +using Umbraco.Web.Macros; namespace umbraco { /// /// Summary description for helper. /// + [Obsolete("This needs to be removed, do not use")] public class helper { - public static bool IsNumeric(string Number) + public static bool IsNumeric(string number) { int result; - return int.TryParse(Number, out result); + return int.TryParse(number, out result); } - public static User GetCurrentUmbracoUser() - { - return umbraco.BasePages.UmbracoEnsuredPage.CurrentUser; - } - - [Obsolete("This method has been superceded. Use the extension method for HttpRequest or HttpRequestBase method: GetItemAsString instead.")] - public static string Request(string text) - { - if (HttpContext.Current == null) - return string.Empty; - - if (HttpContext.Current.Request[text.ToLower()] != null) - if (HttpContext.Current.Request[text] != string.Empty) - return HttpContext.Current.Request[text]; - - return String.Empty; - } - - [Obsolete("Has been superceded by Umbraco.Core.XmlHelper.GetAttributesFromElement")] - public static Hashtable ReturnAttributes(String tag) - { - var h = new Hashtable(); - foreach(var i in Umbraco.Core.XmlHelper.GetAttributesFromElement(tag)) - { - h.Add(i.Key, i.Value); - } - return h; - } - - public static String FindAttribute(IDictionary attributes, String key) + public static string FindAttribute(IDictionary attributes, string key) { return FindAttribute(null, attributes, key); } - public static String FindAttribute(IDictionary pageElements, IDictionary attributes, String key) + public static string FindAttribute(IDictionary pageElements, IDictionary attributes, string key) { // fix for issue 14862: lowercase for case insensitive matching key = key.ToLower(); - string attributeValue = string.Empty; + var attributeValue = string.Empty; if (attributes[key] != null) attributeValue = attributes[key].ToString(); - attributeValue = parseAttribute(pageElements, attributeValue); + attributeValue = MacroRenderer.ParseAttribute(pageElements, attributeValue); return attributeValue; } - - /// - /// This method will parse the attribute value to look for some special syntax such as - /// [@requestKey] - /// [%sessionKey] - /// [#pageElement] - /// [$recursiveValue] - /// - /// - /// - /// - /// - /// You can even apply fallback's separated by comma's like: - /// - /// [@requestKey],[%sessionKey] - /// - /// - public static string parseAttribute(IDictionary pageElements, string attributeValue) - { - // Check for potential querystring/cookie variables - // SD: not sure why we are checking for len 3 here? - if (attributeValue.Length > 3 && attributeValue.StartsWith("[")) - { - var attributeValueSplit = (attributeValue).Split(','); - - attributeValueSplit = attributeValueSplit.Select(x => x.Trim()).ToArray(); - - // before proceeding, we don't want to process anything here unless each item starts/ends with a [ ] - // this is because the attribute value could actually just be a json array like [1,2,3] which we don't want to parse - // - // however, the last one can be a literal, must take care of this! - // so here, don't check the last one, which can be just anything - if (attributeValueSplit.Take(attributeValueSplit.Length - 1).All(x => - //must end with [ - x.EndsWith("]") && - //must start with [ and a special char - (x.StartsWith("[@") || x.StartsWith("[%") || x.StartsWith("[#") || x.StartsWith("[$"))) == false) - { - return attributeValue; - } - - foreach (var attributeValueItem in attributeValueSplit) - { - attributeValue = attributeValueItem; - var trimmedValue = attributeValue.Trim(); - - // Check for special variables (always in square-brackets like [name]) - if (trimmedValue.StartsWith("[") && - trimmedValue.EndsWith("]")) - { - attributeValue = trimmedValue; - - // find key name - var keyName = attributeValue.Substring(2, attributeValue.Length - 3); - var keyType = attributeValue.Substring(1, 1); - - switch (keyType) - { - case "@": - attributeValue = HttpContext.Current.Request[keyName]; - break; - case "%": - attributeValue = StateHelper.GetSessionValue(keyName); - if (String.IsNullOrEmpty(attributeValue)) - attributeValue = StateHelper.GetCookieValue(keyName); - break; - case "#": - if (pageElements == null) - pageElements = GetPageElements(); - if (pageElements[keyName] != null) - attributeValue = pageElements[keyName].ToString(); - else - attributeValue = ""; - break; - case "$": - if (pageElements == null) - pageElements = GetPageElements(); - if (pageElements[keyName] != null && pageElements[keyName].ToString() != string.Empty) - { - attributeValue = pageElements[keyName].ToString(); - } - else - { - // reset attribute value in case no value has been found on parents - attributeValue = String.Empty; - XmlDocument umbracoXML = presentation.UmbracoContext.Current.GetXml(); - - String[] splitpath = (String[])pageElements["splitpath"]; - for (int i = 0; i < splitpath.Length - 1; i++) - { - XmlNode element = umbracoXML.GetElementById(splitpath[splitpath.Length - i - 1].ToString()); - if (element == null) - continue; - string xpath = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "./data [@alias = '{0}']" : "{0}"; - XmlNode currentNode = element.SelectSingleNode(string.Format(xpath, - keyName)); - if (currentNode != null && currentNode.FirstChild != null && - !string.IsNullOrEmpty(currentNode.FirstChild.Value) && - !string.IsNullOrEmpty(currentNode.FirstChild.Value.Trim())) - { - HttpContext.Current.Trace.Write("parameter.recursive", "Item loaded from " + splitpath[splitpath.Length - i - 1]); - attributeValue = currentNode.FirstChild.Value; - break; - } - } - } - break; - } - - if (attributeValue != null) - { - attributeValue = attributeValue.Trim(); - if (attributeValue != string.Empty) - break; - } - else - attributeValue = string.Empty; - } - } - } - - return attributeValue; - } - - private static IDictionary GetPageElements() - { - IDictionary pageElements = null; - if (HttpContext.Current.Items["pageElements"] != null) - pageElements = (IDictionary)HttpContext.Current.Items["pageElements"]; - return pageElements; - } - - [UmbracoWillObsolete("We should really obsolete that one.")] - public static string SpaceCamelCasing(string text) - { - return text.SplitPascalCasing().ToFirstUpperInvariant(); - } - - [Obsolete("Use umbraco.presentation.UmbracContext.Current.GetBaseUrl()")] - public static string GetBaseUrl(HttpContext Context) - { - return Context.Request.Url.GetLeftPart(UriPartial.Authority); - } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/umbraco.presentation/item.cs b/src/Umbraco.Web/umbraco.presentation/item.cs index 1edc6cca17..80089afde7 100644 --- a/src/Umbraco.Web/umbraco.presentation/item.cs +++ b/src/Umbraco.Web/umbraco.presentation/item.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Web; using System.Xml; @@ -6,14 +6,16 @@ using StackExchange.Profiling; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; using Umbraco.Core.Profiling; using Umbraco.Core.Strings; +using Umbraco.Web.Composing; namespace umbraco { /// - /// + /// /// public class item { @@ -73,7 +75,7 @@ namespace umbraco //check for published content and get its value using that if (publishedContent != null && (publishedContent.HasProperty(_fieldName) || recursive)) { - var pval = publishedContent.GetPropertyValue(_fieldName, recursive); + var pval = publishedContent.Value(_fieldName, recursive); var rval = pval == null ? string.Empty : pval.ToString(); _fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval; } @@ -93,7 +95,7 @@ namespace umbraco { if (publishedContent != null && (publishedContent.HasProperty(altFieldName) || recursive)) { - var pval = publishedContent.GetPropertyValue(altFieldName, recursive); + var pval = publishedContent.Value(altFieldName, recursive); var rval = pval == null ? string.Empty : pval.ToString(); _fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval; } @@ -119,11 +121,15 @@ namespace umbraco /// private string GetRecursiveValueLegacy(IDictionary elements) { - using (DisposableTimer.DebugDuration("Checking recusively")) + using (Current.ProfilingLogger.DebugDuration("Checking recusively")) { var content = ""; - var umbracoXml = presentation.UmbracoContext.Current.GetXml(); + var umbracoContext = UmbracoContext.Current; + var cache = umbracoContext.ContentCache as Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedContentCache; + if (cache == null) + throw new InvalidOperationException("Unsupported IPublishedContentCache, only the Xml one is supported."); + var umbracoXml = cache.GetXml(umbracoContext.InPreviewMode); var splitpath = (String[])elements["splitpath"]; for (int i = 0; i < splitpath.Length - 1; i++) @@ -133,7 +139,7 @@ namespace umbraco if (element == null) continue; - var xpath = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "./data [@alias = '{0}']" : "./{0}"; + var xpath = "./{0}"; var currentNode = element.SelectSingleNode(string.Format(xpath, _fieldName)); //continue if all is null @@ -151,7 +157,7 @@ namespace umbraco private void ParseItem(IDictionary attributes) { - using (DisposableTimer.DebugDuration("Start parsing " + _fieldName)) + using (Current.ProfilingLogger.DebugDuration("Start parsing " + _fieldName)) { HttpContext.Current.Trace.Write("item", "Start parsing '" + _fieldName + "'"); if (helper.FindAttribute(attributes, "textIfEmpty") != "" && _fieldContent == "") @@ -222,4 +228,4 @@ namespace umbraco } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs b/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs deleted file mode 100644 index abc3425f3f..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/keepAliveService.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.Logging; - -namespace umbraco.presentation -{ - [Obsolete("This is no longer used and will be removed in future versions")] - public class keepAliveService - { - //NOTE: sender will be the umbraco ApplicationContext - public static void PingUmbraco(object sender) - { - if (sender == null || !(sender is ApplicationContext)) - return; - - var appContext = (ApplicationContext) sender; - - var url = appContext.UmbracoApplicationUrl + "/ping.aspx"; - try - { - using (var wc = new WebClient()) - { - wc.DownloadString(url); - } - } - catch(Exception ee) - { - LogHelper.Debug(string.Format("Error in ping({0}) -> {1}", url, ee)); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index 5118de709e..d85fce59cd 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Globalization; using System.IO; @@ -16,46 +16,36 @@ using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web; -using Umbraco.Web.Cache; using Umbraco.Web.Templates; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic; -using umbraco.cms.businesslogic.web; -using umbraco.cms.helpers; -using umbraco.scripting; -using umbraco.DataLayer; using Umbraco.Core.IO; -using Language = umbraco.cms.businesslogic.language.Language; -using Media = umbraco.cms.businesslogic.media.Media; -using Member = umbraco.cms.businesslogic.member.Member; -using PropertyType = umbraco.cms.businesslogic.propertytype.PropertyType; -using Relation = umbraco.cms.businesslogic.relation.Relation; -using UmbracoContext = umbraco.presentation.UmbracoContext; +using Umbraco.Core.Xml; +using Umbraco.Web.Composing; +using Umbraco.Web.PublishedCache.XmlPublishedCache; namespace umbraco { /// /// Function library for umbraco. Includes various helper-methods and methods to - /// save and load data from umbraco. - /// + /// save and load data from umbraco. + /// /// Especially usefull in XSLT where any of these methods can be accesed using the umbraco.library name-space. Example: /// <xsl:value-of select="umbraco.library:NiceUrl(@id)"/> /// + [Obsolete("v8.kill.kill")] public class library { - /// - /// Returns a new UmbracoHelper so that we can start moving the logic from some of these methods to it - /// - /// - private static UmbracoHelper GetUmbracoHelper() - { - return new UmbracoHelper(Umbraco.Web.UmbracoContext.Current); - } + /// + /// Returns a new UmbracoHelper so that we can start moving the logic from some of these methods to it + /// + /// + private static UmbracoHelper GetUmbracoHelper() + { + return new UmbracoHelper(Current.UmbracoContext, Current.Services, Current.ApplicationCache); + } #region Declarations @@ -72,20 +62,6 @@ namespace umbraco /// public static DateTime PublishStart; private page _page; - private static readonly object libraryCacheLock = new object(); - - #endregion - - #region Properties - - /// - /// Unused, please do not use - /// - [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] - protected static ISqlHelper SqlHelper - { - get { return Application.SqlHelper; } - } #endregion @@ -100,7 +76,8 @@ namespace umbraco public library(int id) { - _page = new page(((System.Xml.IHasXmlNode)GetXmlNodeById(id.ToString()).Current).GetNode()); + var content = GetSafeContentCache().GetById(id); + _page = new page(content); } /// @@ -114,134 +91,6 @@ namespace umbraco #endregion - #region Python Helper functions - - /// - /// Executes the given python script and returns the standardoutput. - /// The Globals known from python macros are not accessible in this context. - /// Neither macro or page nor the globals known from python macros are - /// accessible in this context. Only stuff we initialized in site.py - /// can be used. - /// - /// The filename of the python script including the extension .py - /// Returns the StandardOutput - public static string PythonExecuteFile(string file) - { - try - { - string path = IOHelper.MapPath(SystemDirectories.MacroScripts + "/" + file); - object res = python.executeFile(path); - return res.ToString(); - } - catch (Exception e) - { - return e.Message; - } - } - - /// - /// Executes the given python expression and returns the standardoutput. - /// The Globals known from python macros are not accessible in this context. - /// Neighter macro or page nor the globals known from python macros are - /// accessible in this context. Only stuff we initialized in site.py - /// can be used. - /// - /// Python expression to execute - /// Returns the StandardOutput - public static string PythonExecute(string expression) - { - try - { - object res = python.execute(expression); - return res.ToString(); - } - catch (Exception e) - { - return e.Message; - } - } - - #endregion - - #region Publish Helper Methods - - - /// - /// Unpublish a node, by removing it from the runtime xml index. Note, prior to this the Document should be - /// marked unpublished by setting the publish property on the document object to false - /// - /// The Id of the Document to be unpublished - [Obsolete("This method is no longer used, a document's cache will be removed automatically when the document is deleted or unpublished")] - public static void UnPublishSingleNode(int DocumentId) - { - DistributedCache.Instance.RemovePageCache(DocumentId); - } - - /// - /// Publishes a Document by adding it to the runtime xml index. Note, prior to this the Document should be - /// marked published by calling Publish(User u) on the document object. - /// - /// The Id of the Document to be published - [Obsolete("This method is no longer used, a document's cache will be updated automatically when the document is published")] - public static void UpdateDocumentCache(int documentId) - { - DistributedCache.Instance.RefreshPageCache(documentId); - } - - /// - /// Publishes the single node, this method is obsolete - /// - /// The document id. - [Obsolete("Please use: umbraco.library.UpdateDocumentCache")] - public static void PublishSingleNode(int DocumentId) - { - UpdateDocumentCache(DocumentId); - } - - /// - /// Refreshes the xml cache for all nodes - /// - public static void RefreshContent() - { - DistributedCache.Instance.RefreshAllPageCache(); - } - - /// - /// Re-publishes all nodes under a given node - /// - /// The ID of the node and childnodes that should be republished - [Obsolete("Please use: umbraco.library.RefreshContent")] - public static string RePublishNodes(int nodeID) - { - DistributedCache.Instance.RefreshAllPageCache(); - - return string.Empty; - } - - /// - /// Re-publishes all nodes under a given node - /// - /// The ID of the node and childnodes that should be republished - [Obsolete("Please use: umbraco.library.RefreshContent")] - public static void RePublishNodesDotNet(int nodeID) - { - DistributedCache.Instance.RefreshAllPageCache(); - } - - /// - /// Refreshes the runtime xml index. - /// Note: This *doesn't* mark any non-published document objects as published - /// - /// Always use -1 - /// Not used - [Obsolete("Please use: content.Instance.RefreshContentFromDatabaseAsync")] - public static void RePublishNodesDotNet(int nodeID, bool SaveToDisk) - { - DistributedCache.Instance.RefreshAllPageCache(); - } - - #endregion - #region Xslt Helper functions /// @@ -269,27 +118,6 @@ namespace umbraco } } - /// - /// Add a session variable to the current user - /// - /// The Key of the variable - /// The Value - public static void setSession(string key, string value) - { - if (HttpContext.Current.Session != null) - HttpContext.Current.Session[key] = value; - } - - /// - /// Add a cookie variable to the current user - /// - /// The Key of the variable - /// The Value of the variable - public static void setCookie(string key, string value) - { - StateHelper.SetCookieValue(key, value); - } - /// /// Returns a string with a friendly url from a node. /// IE.: Instead of having /482 (id) as an url, you can have @@ -299,48 +127,36 @@ namespace umbraco /// String with a friendly url from a node public static string NiceUrl(int nodeID) { - return GetUmbracoHelper().NiceUrl(nodeID); + return GetUmbracoHelper().NiceUrl(nodeID); } /// - /// This method will always add the root node to the path. You should always use NiceUrl, as that is the - /// only one who checks for toplevel node settings in the web.config + /// This method will always add the domain to the path if the hostnames are set up correctly. /// - /// Identifier for the node that should be returned - /// String with a friendly url from a node - [Obsolete] - public static string NiceUrlFullPath(int nodeID) - { - throw new NotImplementedException("It was broken anyway..."); - } - - /// - /// This method will always add the domain to the path if the hostnames are set up correctly. - /// - /// Identifier for the node that should be returned + /// Identifier for the node that should be returned /// String with a friendly url with full domain from a node - public static string NiceUrlWithDomain(int nodeID) + public static string NiceUrlWithDomain(int nodeId) { - return GetUmbracoHelper().NiceUrlWithDomain(nodeID); + return GetUmbracoHelper().NiceUrlWithDomain(nodeId); } /// - /// This method will always add the domain to the path. + /// This method will always add the domain to the path. /// - /// Identifier for the node that should be returned + /// Identifier for the node that should be returned /// Ignores the umbraco hostnames and returns the url prefixed with the requested host (including scheme and port number) /// String with a friendly url with full domain from a node - internal static string NiceUrlWithDomain(int nodeID, bool ignoreUmbracoHostNames) + internal static string NiceUrlWithDomain(int nodeId, bool ignoreUmbracoHostNames) { if (ignoreUmbracoHostNames) - return HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority) + NiceUrl(nodeID); + return HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority) + NiceUrl(nodeId); - return NiceUrlWithDomain(nodeID); + return NiceUrlWithDomain(nodeId); } public static string ResolveVirtualPath(string path) { - return Umbraco.Core.IO.IOHelper.ResolveUrl(path); + return IOHelper.ResolveUrl(path); } @@ -350,45 +166,43 @@ namespace umbraco /// getItem(1, nodeName) will return a string with the name of the node with id=1 even though /// nodeName is a property and not an element (data-field). /// - /// Identifier for the node that should be returned + /// Identifier for the node that should be returned /// The element that should be returned /// Returns a string with the data from the given element of a node - public static string GetItem(int nodeID, String alias) + public static string GetItem(int nodeId, string alias) { - var doc = Umbraco.Web.UmbracoContext.Current.ContentCache.GetById(nodeID); + var doc = UmbracoContext.Current.ContentCache.GetById(nodeId); - if (doc == null) - return string.Empty; + if (doc == null) + return string.Empty; - switch (alias) - { - case "id": - return doc.Id.ToString(); - case "version": - return doc.Version.ToString(); - case "parentID": - return doc.Parent.Id.ToString(); - case "level": - return doc.Level.ToString(); - case "writerID": - return doc.WriterId.ToString(); - case "template": - return doc.TemplateId.ToString(); - case "sortOrder": - return doc.SortOrder.ToString(); - case "createDate": - return doc.CreateDate.ToString("yyyy-MM-dd'T'HH:mm:ss"); - case "updateDate": - return doc.UpdateDate.ToString("yyyy-MM-dd'T'HH:mm:ss"); - case "nodeName": - return doc.Name; - case "writerName": - return doc.WriterName; - case "path": - return doc.Path; - case "creatorName": - return doc.CreatorName; - } + switch (alias) + { + case "id": + return doc.Id.ToString(); + case "parentID": + return doc.Parent.Id.ToString(); + case "level": + return doc.Level.ToString(); + case "writerID": + return doc.WriterId.ToString(); + case "template": + return doc.TemplateId.ToString(); + case "sortOrder": + return doc.SortOrder.ToString(); + case "createDate": + return doc.CreateDate.ToString("yyyy-MM-dd'T'HH:mm:ss"); + case "updateDate": + return doc.UpdateDate.ToString("yyyy-MM-dd'T'HH:mm:ss"); + case "nodeName": + return doc.Name; + case "writerName": + return doc.WriterName; + case "path": + return doc.Path; + case "creatorName": + return doc.CreatorName; + } // in 4.9.0 the method returned the raw XML from the cache, unparsed // starting with 5c20f4f (4.10?) the method returns prop.Value.ToString() @@ -398,27 +212,8 @@ namespace umbraco // if we return Value.ToString() we'll have macros parsed and that's nice // // so, use Value.ToString() here. - var prop = doc.GetProperty(alias); - return prop == null ? string.Empty : prop.Value.ToString(); - } - - /// - /// Checks with the Assigned domains settings and retuns an array the the Domains matching the node - /// - /// Identifier for the node that should be returned - /// A Domain array with all the Domains that matches the nodeId - public static Domain[] GetCurrentDomains(int NodeId) - { - string[] pathIds = GetItem(NodeId, "path").Split(','); - for (int i = pathIds.Length - 1; i > 0; i--) - { - Domain[] retVal = Domain.GetDomainsById(int.Parse(pathIds[i])); - if (retVal.Length > 0) - { - return retVal; - } - } - return null; + var prop = doc.GetProperty(alias); + return prop == null ? string.Empty : prop.GetValue().ToString(); } /// @@ -429,7 +224,7 @@ namespace umbraco /// /// /// - public static string GetItem(String alias) + public static string GetItem(string alias) { try { @@ -443,105 +238,63 @@ namespace umbraco } } - /// - /// Returns that name of a generic property - /// - /// The Alias of the content type (ie. Document Type, Member Type or Media Type) - /// The Alias of the Generic property (ie. bodyText or umbracoNaviHide) - /// A string with the name. If nothing matches the alias, an empty string is returned - public static string GetPropertyTypeName(string ContentTypeAlias, string PropertyTypeAlias) - { - try - { - umbraco.cms.businesslogic.ContentType ct = umbraco.cms.businesslogic.ContentType.GetByAlias(ContentTypeAlias); - PropertyType pt = ct.getPropertyType(PropertyTypeAlias); - return pt.Name; - } - catch - { - return string.Empty; - } - } - - /// - /// Returns the Member Name from an umbraco member object - /// - /// The identifier of the Member - /// The Member name matching the MemberId, an empty string is member isn't found - public static string GetMemberName(int MemberId) - { - if (MemberId != 0) - { - try - { - Member m = new Member(MemberId); - return m.Text; - } - catch - { - return string.Empty; - } - } - else - return string.Empty; - } - /// /// Get a media object as an xml object /// /// The identifier of the media object to be returned - /// If true, children of the media object is returned + /// If true, children of the media object is returned /// An umbraco xml node of the media (same format as a document node) - public static XPathNodeIterator GetMedia(int MediaId, bool Deep) + public static XPathNodeIterator GetMedia(int MediaId, bool deep) { try { if (UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration > 0) { - var xml = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( - string.Format( - "{0}_{1}_{2}", CacheKeys.MediaCacheKey, MediaId, Deep), + var xml = Current.ApplicationCache.RuntimeCache.GetCacheItem( + $"{CacheKeys.MediaCacheKey}_{MediaId}_{deep}", timeout: TimeSpan.FromSeconds(UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration), - getCacheItem: () => GetMediaDo(MediaId, Deep)); + getCacheItem: () => GetMediaDo(MediaId, deep).Item1); if (xml != null) - { + { //returning the root element of the Media item fixes the problem return xml.CreateNavigator().Select("/"); } - + } else { - var xml = GetMediaDo(MediaId, Deep); - + var xml = GetMediaDo(MediaId, deep).Item1; + //returning the root element of the Media item fixes the problem return xml.CreateNavigator().Select("/"); } } catch(Exception ex) { - LogHelper.Error("An error occurred looking up media", ex); + Current.Logger.Error("An error occurred looking up media", ex); } - LogHelper.Debug("No media result for id {0}", () => MediaId); + Current.Logger.Debug(() => $"No media result for id {MediaId}"); var errorXml = new XElement("error", string.Format("No media is maching '{0}'", MediaId)); return errorXml.CreateNavigator().Select("/"); } - private static XElement GetMediaDo(int mediaId, bool deep) + private static Tuple GetMediaDo(int mediaId, bool deep) { - var media = ApplicationContext.Current.Services.MediaService.GetById(mediaId); + var media = Current.Services.MediaService.GetById(mediaId); if (media == null) return null; - var serializer = new EntityXmlSerializer(); - var serialized = serializer.Serialize( - ApplicationContext.Current.Services.MediaService, - ApplicationContext.Current.Services.DataTypeService, - ApplicationContext.Current.Services.UserService, - media, + + var serialized = EntityXmlSerializer.Serialize( + Current.Services.MediaService, + Current.Services.DataTypeService, + Current.Services.UserService, + Current.Services.LocalizationService, + Current.UrlSegmentProviders, + media, deep); - return serialized; + return Tuple.Create(serialized, media.Path); } /// @@ -557,7 +310,7 @@ namespace umbraco { if (UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration > 0) { - var xml = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( + var xml = Current.ApplicationCache.RuntimeCache.GetCacheItem( string.Format( "{0}_{1}", CacheKeys.MemberLibraryCacheKey, MemberId), timeout: TimeSpan.FromSeconds(UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration), @@ -575,10 +328,10 @@ namespace umbraco } catch (Exception ex) { - LogHelper.Error("An error occurred looking up member", ex); + Current.Logger.Error("An error occurred looking up member", ex); } - LogHelper.Debug("No member result for id {0}", () => MemberId); + Current.Logger.Debug(() => $"No member result for id {MemberId}"); var xd = new XmlDocument(); xd.LoadXml(string.Format("No member is maching '{0}'", MemberId)); @@ -587,53 +340,42 @@ namespace umbraco private static XElement GetMemberDo(int MemberId) { - var member = ApplicationContext.Current.Services.MemberService.GetById(MemberId); + var member = Current.Services.MemberService.GetById(MemberId); if (member == null) return null; - var serializer = new EntityXmlSerializer(); - var serialized = serializer.Serialize( - ApplicationContext.Current.Services.DataTypeService, member); + + var serialized = EntityXmlSerializer.Serialize( + Current.Services.DataTypeService, Current.Services.LocalizationService, member); return serialized; } - /// - /// Get the current member as an xml node - /// - /// Look in documentation for umbraco.library.GetMember(MemberId) for more information - public static XPathNodeIterator GetCurrentMember() - { - Member m = Member.GetCurrentMember(); - if (m != null) - { - XmlDocument mXml = new XmlDocument(); - mXml.LoadXml(m.ToXml(mXml, false).OuterXml); - XPathNavigator xp = mXml.CreateNavigator(); - return xp.Select("/node()"); - } - - XmlDocument xd = new XmlDocument(); - xd.LoadXml( - "No current member exists (best practice is to validate with 'isloggedon()' prior to this call)"); - return xd.CreateNavigator().Select("/"); - } - /// /// Whether or not the current user is logged in (as a member) /// /// True is the current user is logged in public static bool IsLoggedOn() { - return GetUmbracoHelper().MemberIsLoggedOn(); + return GetUmbracoHelper().MemberIsLoggedOn(); } public static XPathNodeIterator AllowedGroups(int documentId, string path) { XmlDocument xd = new XmlDocument(); xd.LoadXml(""); - foreach (string role in Access.GetAccessingMembershipRoles(documentId, path)) - xd.DocumentElement.AppendChild(xmlHelper.addTextNode(xd, "role", role)); + foreach (string role in GetAccessingMembershipRoles(documentId, path)) + xd.DocumentElement.AppendChild(XmlHelper.AddTextNode(xd, "role", role)); return xd.CreateNavigator().Select("."); } + private static string[] GetAccessingMembershipRoles(int documentId, string path) + { + var entry = Current.Services.PublicAccessService.GetEntryForContent(path.EnsureEndsWith("," + documentId)); + if (entry == null) return new string[] { }; + + var memberGroupRoleRules = entry.Rules.Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType); + return memberGroupRoleRules.Select(x => x.RuleValue).ToArray(); + + } + /// /// Check if a document object is protected by the "Protect Pages" functionality in umbraco /// @@ -642,7 +384,7 @@ namespace umbraco /// True if the document object is protected public static bool IsProtected(int DocumentId, string Path) { - return GetUmbracoHelper().IsProtected(DocumentId, Path); + return GetUmbracoHelper().IsProtected(DocumentId, Path); } /// @@ -653,7 +395,7 @@ namespace umbraco /// True if the current user has access or if the current document isn't protected public static bool HasAccess(int NodeId, string Path) { - return GetUmbracoHelper().MemberHasAccess(NodeId, Path); + return GetUmbracoHelper().MemberHasAccess(NodeId, Path); } @@ -670,7 +412,7 @@ namespace umbraco } /// - /// Generates a hash based on the text string passed in. This method will detect the + /// Generates a hash based on the text string passed in. This method will detect the /// security requirements (is FIPS enabled) and return an appropriate hash. /// /// The text to create a hash from @@ -1010,12 +752,12 @@ namespace umbraco /// The text with text line breaks replaced with html linebreaks (
)
public static string ReplaceLineBreaks(string text) { - return GetUmbracoHelper().ReplaceLineBreaksForHtml(text); + return GetUmbracoHelper().ReplaceLineBreaksForHtml(text); } /// /// Renders the content of a macro. Uses the normal template umbraco macro markup as input. - /// This only works properly with xslt macros. + /// This only works properly with xslt macros. /// Python and .ascx based macros will not render properly, as viewstate is not included. /// /// The macro markup to be rendered. @@ -1025,7 +767,7 @@ namespace umbraco { try { - page p = new page(((IHasXmlNode)GetXmlNodeById(PageId.ToString()).Current).GetNode()); + var p = new page(GetSafeContentCache().GetById(PageId)); template t = new template(p.Template); Control c = t.parseStringBuilder(new StringBuilder(Text), p); @@ -1069,18 +811,17 @@ namespace umbraco } else { - - var p = new page(((IHasXmlNode)GetXmlNodeById(PageId.ToString()).Current).GetNode()); + var p = new page(GetSafeContentCache().GetById(PageId)); p.RenderPage(TemplateId); var c = p.PageContentControl; - - using (var sw = new StringWriter()) + + using (var sw = new StringWriter()) using(var hw = new HtmlTextWriter(sw)) { - c.RenderControl(hw); - return sw.ToString(); + c.RenderControl(hw); + return sw.ToString(); } - + } } @@ -1091,7 +832,7 @@ namespace umbraco /// The rendered template as a string. public static string RenderTemplate(int PageId) { - return RenderTemplate(PageId, -1); + return RenderTemplate(PageId, -1); } /// @@ -1229,7 +970,7 @@ namespace umbraco xd.LoadXml(""); foreach (string id in values) { - XmlNode node = xmlHelper.addTextNode(xd, "value", id); + XmlNode node = XmlHelper.AddTextNode(xd, "value", id); xd.DocumentElement.AppendChild(node); } XPathNavigator xp = xd.CreateNavigator(); @@ -1279,104 +1020,6 @@ namespace umbraco return Text.LastIndexOf(Value); } - /// - /// Gets the prevalues from a umbraco DataType with the specified data type id. - /// - /// The data type id. - /// Returns the prevalues as a XPathNodeIterator in the format: - /// - /// [value] - /// - /// - public static XPathNodeIterator GetPreValues(int DataTypeId) - { - XmlDocument xd = new XmlDocument(); - xd.LoadXml(""); - - using (var sqlHelper = Application.SqlHelper) - using (IRecordsReader dr = sqlHelper.ExecuteReader("Select id, [value] from cmsDataTypeprevalues where DataTypeNodeId = @dataTypeId order by sortorder", - sqlHelper.CreateParameter("@dataTypeId", DataTypeId))) - { - while (dr.Read()) - { - XmlNode n = xmlHelper.addTextNode(xd, "preValue", dr.GetString("value")); - n.Attributes.Append(xmlHelper.addAttribute(xd, "id", dr.GetInt("id").ToString())); - xd.DocumentElement.AppendChild(n); - } - } - XPathNavigator xp = xd.CreateNavigator(); - return xp.Select("/preValues"); - } - - /// - /// Gets the umbraco data type prevalue with the specified Id as string. - /// - /// The id. - /// Returns the prevalue as a string - public static string GetPreValueAsString(int Id) - { - try - { - using (var sqlHelper = Application.SqlHelper) - return sqlHelper.ExecuteScalar("select [value] from cmsDataTypePreValues where id = @id", - sqlHelper.CreateParameter("@id", Id)); - } - catch - { - return string.Empty; - } - } - - /// - /// Gets the dictionary item with the specified key and it's child dictionary items. - /// The language version is based on the culture of the current Url. - /// - /// The key. - /// A XpathNodeIterator in the format: - /// - /// [dictionaryItemValue] - /// - /// - public static XPathNodeIterator GetDictionaryItems(string Key) - { - XmlDocument xd = new XmlDocument(); - xd.LoadXml(""); - - try - { - //int languageId = GetCurrentLanguageId(); - int languageId = Language.GetByCultureCode(System.Threading.Thread.CurrentThread.CurrentUICulture.Name).id; - - Dictionary.DictionaryItem di = new Dictionary.DictionaryItem(Key); - - foreach (Dictionary.DictionaryItem item in di.Children) - { - XmlNode xe; - try - { - if (languageId != 0) - xe = xmlHelper.addTextNode(xd, "DictionaryItem", item.Value(languageId)); - else - xe = xmlHelper.addTextNode(xd, "DictionaryItem", item.Value()); - } - catch - { - xe = xmlHelper.addTextNode(xd, "DictionaryItem", string.Empty); - } - xe.Attributes.Append(xmlHelper.addAttribute(xd, "key", item.key)); - xd.DocumentElement.AppendChild(xe); - } - } - catch (Exception ee) - { - xd.DocumentElement.AppendChild( - xmlHelper.addTextNode(xd, "Error", ee.ToString())); - } - - XPathNavigator xp = xd.CreateNavigator(); - return xp.Select("/"); - } - /// /// Gets the dictionary item with the specified key. /// @@ -1384,7 +1027,7 @@ namespace umbraco /// A dictionary items value as a string. public static string GetDictionaryItem(string Key) { - return GetUmbracoHelper().GetDictionaryValue(Key); + return GetUmbracoHelper().GetDictionaryValue(Key); } /// @@ -1397,21 +1040,18 @@ namespace umbraco try { - var nav = Umbraco.Web.UmbracoContext.Current.ContentCache.GetXPathNavigator(); - var pageIdItem = HttpContext.Current.Items["pageID"]; + var nav = UmbracoContext.Current.ContentCache.CreateNavigator(); + pageId = HttpContext.Current.Items["pageID"]?.ToString(); - if (pageIdItem == null) - { + if (pageId == null) throw new NullReferenceException("pageID not found in the current HTTP context"); - } - pageId = pageIdItem.ToString(); nav.MoveToId(pageId); return nav.Select("."); } catch (Exception ex) { - LogHelper.Error(string.Concat("Could not retrieve current xml node for page Id ",pageId), ex); + Current.Logger.Error($"Could not retrieve current xml node for page Id {pageId}.", ex); } XmlDocument xd = new XmlDocument(); @@ -1426,30 +1066,39 @@ namespace umbraco /// Returns the node with the specified id as xml in the form of a XPathNodeIterator public static XPathNodeIterator GetXmlNodeById(string id) { - // 4.7.1 UmbracoContext is null if we're running in publishing thread which we need to support - XmlDocument xmlDoc = GetThreadsafeXmlDocument(); + var nav = GetSafeContentCache().CreateNavigator(); - if (xmlDoc.GetElementById(id) != null) + if (nav.MoveToId(id)) + return nav.Select("."); + + var xd = new XmlDocument(); + xd.LoadXml(string.Format("No published item exist with id {0}", id)); + return xd.CreateNavigator().Select("."); + } + + // legacy would access the raw XML from content.Instance ie a static thing + // now that we use a PublishedSnapshotService, we need to have a "context" to handle a cache. + // UmbracoContext does it for most cases but in some cases we might not have an + // UmbracoContext. For backward compatibility, try to do something here... + internal static PublishedContentCache GetSafeContentCache() + { + PublishedContentCache contentCache; + + if (UmbracoContext.Current != null) { - XPathNavigator xp = xmlDoc.CreateNavigator(); - xp.MoveToId(id); - return xp.Select("."); + contentCache = UmbracoContext.Current.ContentCache as PublishedContentCache; } else { - XmlDocument xd = new XmlDocument(); - xd.LoadXml(string.Format("No published item exist with id {0}", id)); - return xd.CreateNavigator().Select("."); + var publishedSnapshot = Current.PublishedSnapshot + ?? Current.PublishedSnapshotService.CreatePublishedSnapshot(null); + contentCache = publishedSnapshot.Content as PublishedContentCache; } - } - //TODO: WTF, why is this here? This won't matter if there's an UmbracoContext or not, it will call the same underlying method! - // only difference is that the UmbracoContext way will check if its in preview mode. - private static XmlDocument GetThreadsafeXmlDocument() - { - return UmbracoContext.Current != null - ? UmbracoContext.Current.GetXml() - : content.Instance.XmlContent; + if (contentCache == null) + throw new InvalidOperationException("Unsupported IPublishedContentCache, only the Xml one is supported."); + + return contentCache; } /// @@ -1459,9 +1108,7 @@ namespace umbraco /// Returns nodes matching the xpath query as a XpathNodeIterator public static XPathNodeIterator GetXmlNodeByXPath(string xpathQuery) { - XPathNavigator xp = GetThreadsafeXmlDocument().CreateNavigator(); - - return xp.Select(xpathQuery); + return GetSafeContentCache().CreateNavigator().Select(xpathQuery); } /// @@ -1470,8 +1117,7 @@ namespace umbraco /// Returns the entire umbraco Xml cache as a XPathNodeIterator public static XPathNodeIterator GetXmlAll() { - XPathNavigator xp = GetThreadsafeXmlDocument().CreateNavigator(); - return xp.Select("/root"); + return GetSafeContentCache().CreateNavigator().Select("/root"); } /// @@ -1562,21 +1208,23 @@ namespace umbraco /// The Xpath query for the node with the specified id as a string public static string QueryForNode(string id) { - string XPathQuery = string.Empty; - if (UmbracoContext.Current.GetXml().GetElementById(id) != null) + var xpathQuery = string.Empty; + var preview = UmbracoContext.Current != null && UmbracoContext.Current.InPreviewMode; + var xml = GetSafeContentCache().GetXml(preview); + var elt = xml.GetElementById(id); + + if (elt == null) return xpathQuery; + + var path = elt.Attributes["path"].Value.Split((",").ToCharArray()); + for (var i = 1; i < path.Length; i++) { - string[] path = - UmbracoContext.Current.GetXml().GetElementById(id).Attributes["path"].Value.Split((",").ToCharArray()); - for (int i = 1; i < path.Length; i++) - { - if (i > 1) - XPathQuery += "/node [@id = " + path[i] + "]"; - else - XPathQuery += " [@id = " + path[i] + "]"; - } + if (i > 1) + xpathQuery += "/node [@id = " + path[i] + "]"; + else + xpathQuery += " [@id = " + path[i] + "]"; } - return XPathQuery; + return xpathQuery; } /// @@ -1613,7 +1261,7 @@ namespace umbraco public static void SendMail(string fromMail, string toMail, string subject, string body, bool isHtml) { try - { + { var mailSender = new EmailSender(); using (var mail = new MailMessage()) { @@ -1628,21 +1276,21 @@ namespace umbraco } catch (Exception ee) { - LogHelper.Error("umbraco.library.SendMail: Error sending mail.", ee); + Current.Logger.Error("umbraco.library.SendMail: Error sending mail.", ee); } } - /// + /// /// These random methods are from Eli Robillards blog - kudos for the work :-) /// http://weblogs.asp.net/erobillard/archive/2004/05/06/127374.aspx - /// - /// Get a Random object which is cached between requests. - /// The advantage over creating the object locally is that the .Next - /// call will always return a new value. If creating several times locally - /// with a generated seed (like millisecond ticks), the same number can be - /// returned. - /// - /// A Random object which is cached between calls. + /// + /// Get a Random object which is cached between requests. + /// The advantage over creating the object locally is that the .Next + /// call will always return a new value. If creating several times locally + /// with a generated seed (like millisecond ticks), the same number can be + /// returned. + /// + /// A Random object which is cached between calls. public static Random GetRandom(int seed) { Random r = (Random)HttpContext.Current.Cache.Get("RandomNumber"); @@ -1657,10 +1305,10 @@ namespace umbraco return r; } - /// - /// GetRandom with no parameters. - /// - /// A Random object which is cached between calls. + /// + /// GetRandom with no parameters. + /// + /// A Random object which is cached between calls. public static Random GetRandom() { return GetRandom(0); @@ -1752,7 +1400,7 @@ namespace umbraco public static string RequestCookies(string key) { // zb-00004 #29956 : refactor cookies handling - var value = StateHelper.GetCookieValue(key); + var value = HttpContext.Current.Request.GetCookieValue(key); return value ?? ""; } @@ -1795,7 +1443,7 @@ namespace umbraco } /// - /// URL-encodes a string + /// URL-encodes a string /// /// The string to be encoded /// A URL-encoded string @@ -1805,7 +1453,7 @@ namespace umbraco } /// - /// HTML-encodes a string + /// HTML-encodes a string /// /// The string to be encoded /// A HTML-encoded string @@ -1814,33 +1462,9 @@ namespace umbraco return HttpUtility.HtmlEncode(Text); } - public static Relation[] GetRelatedNodes(int NodeId) + public static IRelation[] GetRelatedNodes(int nodeId) { - return new CMSNode(NodeId).Relations; - } - - [Obsolete("Use DistributedCache.Instance.RemoveMediaCache instead")] - public static void ClearLibraryCacheForMedia(int mediaId) - { - DistributedCache.Instance.RemoveMediaCache(mediaId); - } - - [Obsolete("Use DistributedCache.Instance.RemoveMediaCache instead")] - public static void ClearLibraryCacheForMediaDo(int mediaId) - { - DistributedCache.Instance.RemoveMediaCache(mediaId); - } - - [Obsolete("Use DistributedCache.Instance.RefreshMemberCache instead")] - public static void ClearLibraryCacheForMember(int mediaId) - { - DistributedCache.Instance.RefreshMemberCache(mediaId); - } - - [Obsolete("Use DistributedCache.Instance.RefreshMemberCache instead")] - public static void ClearLibraryCacheForMemberDo(int memberId) - { - DistributedCache.Instance.RefreshMemberCache(memberId); + return Current.Services.RelationService.GetByParentOrChildId(nodeId).ToArray(); } /// @@ -1858,22 +1482,50 @@ namespace umbraco { XmlDocument xd = new XmlDocument(); xd.LoadXml(""); - var rels = new CMSNode(NodeId).Relations; - foreach (Relation r in rels) + var rels = Current.Services.RelationService.GetByParentOrChildId(NodeId); + + const bool published = true; // work with published versions? + + foreach (var r in rels) { XmlElement n = xd.CreateElement("relation"); - n.AppendChild(xmlHelper.addCDataNode(xd, "comment", r.Comment)); - n.Attributes.Append(xmlHelper.addAttribute(xd, "typeId", r.RelType.Id.ToString())); - n.Attributes.Append(xmlHelper.addAttribute(xd, "typeName", r.RelType.Name)); - n.Attributes.Append(xmlHelper.addAttribute(xd, "createDate", r.CreateDate.ToString())); - n.Attributes.Append(xmlHelper.addAttribute(xd, "parentId", r.Parent.Id.ToString())); - n.Attributes.Append(xmlHelper.addAttribute(xd, "childId", r.Child.Id.ToString())); + n.AppendChild(XmlHelper.AddCDataNode(xd, "comment", r.Comment)); + n.Attributes.Append(XmlHelper.AddAttribute(xd, "typeId", r.RelationTypeId.ToString())); + n.Attributes.Append(XmlHelper.AddAttribute(xd, "typeName", r.RelationType.Name)); + n.Attributes.Append(XmlHelper.AddAttribute(xd, "createDate", r.CreateDate.ToString(CultureInfo.InvariantCulture))); + n.Attributes.Append(XmlHelper.AddAttribute(xd, "parentId", r.ParentId.ToString())); + n.Attributes.Append(XmlHelper.AddAttribute(xd, "childId", r.ChildId.ToString())); // Append the node that isn't the one we're getting the related nodes from - if (NodeId == r.Child.Id) - n.AppendChild(r.Parent.ToXml(xd, false)); + if (NodeId == r.ChildId) + { + var parent = Current.Services.ContentService.GetById(r.ParentId); + if (parent != null) + { + var x = EntityXmlSerializer.Serialize( + Current.Services.ContentService, + Current.Services.DataTypeService, + Current.Services.UserService, + Current.Services.LocalizationService, + Current.UrlSegmentProviders, parent, published).GetXmlNode(xd); + n.AppendChild(x); + } + } else - n.AppendChild(r.Child.ToXml(xd, false)); + { + var child = Current.Services.ContentService.GetById(r.ChildId); + if (child != null) + { + var x = EntityXmlSerializer.Serialize( + Current.Services.ContentService, + Current.Services.DataTypeService, + Current.Services.UserService, + Current.Services.LocalizationService, + Current.UrlSegmentProviders, child, published).GetXmlNode(xd); + n.AppendChild(x); + } + } + xd.DocumentElement.AppendChild(n); } XPathNavigator xp = xd.CreateNavigator(); @@ -1924,18 +1576,6 @@ namespace umbraco - /// - /// Cleans the spified string with tidy - /// - /// The string to tidy. - /// if set to true [Live Editing]. - /// - public static string Tidy(string StringToTidy, bool LiveEditing) - { - return cms.helpers.xhtml.TidyHtml(StringToTidy); - } - - #endregion diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs deleted file mode 100644 index dac3c0e1bf..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ /dev/null @@ -1,1928 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Net.Security; -using System.Reflection; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Text.RegularExpressions; -using System.Web; -using System.Web.Caching; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Xml; -using System.Xml.XPath; -using System.Xml.Xsl; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Events; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Macros; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Core.Xml.XPath; -using Umbraco.Core.Profiling; -using umbraco.interfaces; -using Umbraco.Web; -using Umbraco.Web.Cache; -using Umbraco.Web.Macros; -using Umbraco.Web.Models; -using Umbraco.Web.Templates; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic.macro; -using umbraco.DataLayer; -using umbraco.NodeFactory; -using umbraco.presentation.templateControls; -using Umbraco.Web.umbraco.presentation; -using Content = umbraco.cms.businesslogic.Content; -using Macro = umbraco.cms.businesslogic.macro.Macro; -using MacroErrorEventArgs = Umbraco.Core.Events.MacroErrorEventArgs; -using System.Linq; -using File = System.IO.File; -using MacroTypes = umbraco.cms.businesslogic.macro.MacroTypes; -using Member = umbraco.cms.businesslogic.member.Member; -using UserControl = System.Web.UI.UserControl; - -namespace umbraco -{ - /// - /// Summary description for macro. - /// - public class macro - { - #region private properties - - /// Cache for . - private static Dictionary _predefinedExtensions; - private static XsltSettings _xsltSettings; - private const string LoadUserControlKey = "loadUserControl"; - private readonly StringBuilder _content = new StringBuilder(); - private const string MacrosAddedKey = "macrosAdded"; - public IList Exceptions = new List(); - - /// - /// Unused, please do not use - /// - [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] - protected static ISqlHelper SqlHelper - { - get { return Application.SqlHelper; } - } - - static macro() - { - _xsltSettings = GlobalSettings.ApplicationTrustLevel > AspNetHostingPermissionLevel.Medium - ? XsltSettings.TrustedXslt - : XsltSettings.Default; - } - - #endregion - - #region public properties - - public bool CacheByPersonalization - { - get { return Model.CacheByMember; } - } - - public bool CacheByPage - { - get { return Model.CacheByPage; } - } - - public bool DontRenderInEditor - { - get { return !Model.RenderInEditor; } - } - - public int RefreshRate - { - get { return Model.CacheDuration; } - } - - public String Alias - { - get { return Model.Alias; } - } - - public String Name - { - get { return Model.Name; } - } - - public String XsltFile - { - get { return Model.Xslt; } - } - - public String ScriptFile - { - get { return Model.ScriptName; } - } - - public String ScriptType - { - get { return Model.TypeName; } - } - - public String ScriptAssembly - { - get { return Model.TypeName; } - } - - public int MacroType - { - get { return (int)Model.MacroType; } - } - - public String MacroContent - { - set { _content.Append(value); } - get { return _content.ToString(); } - } - - #endregion - - #region REFACTOR - - /// - /// Creates a macro object - /// - /// Specify the macro-id which should be loaded (from table macro) - public macro(int id) - { - Macro m = Macro.GetById(id); - Model = new MacroModel(m); - } - - public macro(string alias) - { - Macro m = Macro.GetByAlias(alias); - Model = new MacroModel(m); - } - - public MacroModel Model { get; set; } - - public static macro GetMacro(string alias) - { - return new macro(alias); - } - - public static macro GetMacro(int id) - { - return new macro(id); - } - - #endregion - - private const string XsltExtensionsCacheKey = "UmbracoXsltExtensions"; - - /// - /// Creates an empty macro object. - /// - public macro() - { - Model = new MacroModel(); - } - - - public override string ToString() - { - return Model.Name; - } - - /// - /// Deletes macro definition from cache. - /// - /// True if succesfull, false if nothing has been removed - [Obsolete("Use DistributedCache.Instance.RemoveMacroCache instead, macro cache will automatically be cleared and shouldn't need to be manually cleared.")] - public bool removeFromCache() - { - if (this.Model != null) - { - DistributedCache.Instance.RemoveMacroCache(this); - } - - //this always returned false... hrm. oh well i guess we leave it like that - return false; - } - - string GetCacheIdentifier(MacroModel model, Hashtable pageElements, int pageId) - { - var id = new StringBuilder(); - - var alias = string.IsNullOrEmpty(model.ScriptCode) ? model.Alias : Macro.GenerateCacheKeyFromCode(model.ScriptCode); - id.AppendFormat("{0}-", alias); - - if (CacheByPage) - { - id.AppendFormat("{0}-", pageId); - } - - if (CacheByPersonalization) - { - object memberId = 0; - if (HttpContext.Current.User.Identity.IsAuthenticated) - { - var provider = Umbraco.Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - var member = Umbraco.Core.Security.MembershipProviderExtensions.GetCurrentUser(provider); - if (member != null) - { - memberId = member.ProviderUserKey ?? 0; - } - } - id.AppendFormat("m{0}-", memberId); - } - - foreach (var prop in model.Properties) - { - var propValue = prop.Value; - id.AppendFormat("{0}-", propValue.Length <= 255 ? propValue : propValue.Substring(0, 255)); - } - - return id.ToString(); - } - - public Control renderMacro(Hashtable attributes, Hashtable pageElements, int pageId) - { - // TODO: Parse attributes - UpdateMacroModel(attributes); - return renderMacro(pageElements, pageId); - } - - /// - /// An event that is raised just before the macro is rendered allowing developers to modify the macro before it executes. - /// - public static event TypedEventHandler MacroRendering; - - /// - /// Raises the MacroRendering event - /// - /// - protected void OnMacroRendering(MacroRenderingEventArgs e) - { - if (MacroRendering != null) - MacroRendering(this, e); - } - - /// - /// Renders the macro - /// - /// - /// - /// - public Control renderMacro(Hashtable pageElements, int pageId) - { - // Event to allow manipulation of Macro Model - OnMacroRendering(new MacroRenderingEventArgs(pageElements, pageId)); - - var macroInfo = (Model.MacroType == MacroTypes.Script && Model.Name.IsNullOrWhiteSpace()) - ? string.Format("Render Inline Macro, Cache: {0})", Model.CacheDuration) - : string.Format("Render Macro: {0}, type: {1}, cache: {2})", Name, Model.MacroType, Model.CacheDuration); - - using (DisposableTimer.DebugDuration(macroInfo)) - { - TraceInfo("renderMacro", macroInfo, excludeProfiling: true); - - StateHelper.SetContextValue(MacrosAddedKey, StateHelper.GetContextValue(MacrosAddedKey) + 1); - - // zb-00037 #29875 : parse attributes here (and before anything else) - foreach (MacroPropertyModel prop in Model.Properties) - prop.Value = helper.parseAttribute(pageElements, prop.Value); - - Model.CacheIdentifier = GetCacheIdentifier(Model, pageElements, pageId); - - string macroHtml; - Control macroControl; - //get the macro from cache if it is there - GetMacroFromCache(out macroHtml, out macroControl); - - // FlorisRobbemont: Empty macroHtml (not null, but "") doesn't mean a re-render is necessary - if (macroHtml == null && macroControl == null) - { - var renderFailed = false; - var macroType = Model.MacroType != MacroTypes.Unknown - ? (int)Model.MacroType - : MacroType; - var textService = ApplicationContext.Current.Services.TextService; - - switch (macroType) - { - case (int)MacroTypes.PartialView: - - //error handler for partial views, is an action because we need to re-use it twice below - Func handleError = e => - { - LogHelper.WarnWithException("Error loading Partial View (file: " + ScriptFile + ")", true, e); - // Invoke any error handlers for this macro - var macroErrorEventArgs = - new MacroErrorEventArgs - { - Name = Model.Name, - Alias = Model.Alias, - ItemKey = Model.ScriptName, - Exception = e, - Behaviour = UmbracoConfig.For.UmbracoSettings().Content.MacroErrorBehaviour - }; - - var errorMessage = textService.Localize("errors/macroErrorLoadingPartialView", new[] { ScriptFile }); - return GetControlForErrorBehavior(errorMessage, macroErrorEventArgs); - }; - - using (DisposableTimer.DebugDuration("Executing Partial View: " + Model.TypeName)) - { - TraceInfo("umbracoMacro", "Partial View added (" + Model.TypeName + ")", excludeProfiling: true); - try - { - var result = LoadPartialViewMacro(Model); - macroControl = new LiteralControl(result.Result); - if (result.ResultException != null) - { - renderFailed = true; - Exceptions.Add(result.ResultException); - macroControl = handleError(result.ResultException); - //if it is null, then we are supposed to throw the exception - if (macroControl == null) - { - throw result.ResultException; - } - } - } - catch (Exception e) - { - LogHelper.WarnWithException( - "Error loading partial view macro (View: " + Model.ScriptName + ")", true, e); - - renderFailed = true; - Exceptions.Add(e); - macroControl = handleError(e); - //if it is null, then we are supposed to throw the (original) exception - // see: http://issues.umbraco.org/issue/U4-497 at the end - if (macroControl == null) - { - throw; - } - } - - break; - } - case (int)MacroTypes.UserControl: - - using (DisposableTimer.DebugDuration("Executing UserControl: " + Model.TypeName)) - { - try - { - TraceInfo("umbracoMacro", "Usercontrol added (" + Model.TypeName + ")", excludeProfiling: true); - - // Add tilde for v4 defined macros - if (string.IsNullOrEmpty(Model.TypeName) == false && - Model.TypeName.StartsWith("~") == false) - Model.TypeName = "~/" + Model.TypeName; - - macroControl = loadUserControl(ScriptType, Model, pageElements); - break; - } - catch (Exception e) - { - renderFailed = true; - Exceptions.Add(e); - LogHelper.WarnWithException("Error loading userControl (" + Model.TypeName + ")", true, e); - - // Invoke any error handlers for this macro - var macroErrorEventArgs = new MacroErrorEventArgs - { - Name = Model.Name, - Alias = Model.Alias, - ItemKey = Model.TypeName, - Exception = e, - Behaviour = UmbracoConfig.For.UmbracoSettings().Content.MacroErrorBehaviour - }; - - var errorMessage = textService.Localize("errors/macroErrorLoadingUsercontrol", new[] { Model.TypeName }); - macroControl = GetControlForErrorBehavior(errorMessage, macroErrorEventArgs); - //if it is null, then we are supposed to throw the (original) exception - // see: http://issues.umbraco.org/issue/U4-497 at the end - if (macroControl == null) - { - throw; - } - - break; - } - } - - case (int)MacroTypes.CustomControl: - - using (DisposableTimer.DebugDuration("Executing CustomControl: " + Model.TypeName + "." + Model.TypeAssembly)) - { - try - { - TraceInfo("umbracoMacro", "Custom control added (" + Model.TypeName + "), ScriptAssembly: " + Model.TypeAssembly, excludeProfiling: true); - macroControl = loadControl(Model.TypeAssembly, ScriptType, Model, pageElements); - break; - } - catch (Exception e) - { - renderFailed = true; - Exceptions.Add(e); - - LogHelper.WarnWithException( - "Error loading customControl (Assembly: " + Model.TypeAssembly + ", Type: '" + - Model.TypeName + "'", true, e); - - // Invoke any error handlers for this macro - var macroErrorEventArgs = new MacroErrorEventArgs - { - Name = Model.Name, - Alias = Model.Alias, - ItemKey = Model.TypeAssembly, - Exception = e, - Behaviour = UmbracoConfig.For.UmbracoSettings().Content.MacroErrorBehaviour - }; - - var errorMessage = textService.Localize("errors/macroErrorLoadingCustomControl", new[] { Model.TypeAssembly, Model.TypeName }); - macroControl = GetControlForErrorBehavior(errorMessage, macroErrorEventArgs); - //if it is null, then we are supposed to throw the (original) exception - // see: http://issues.umbraco.org/issue/U4-497 at the end - if (macroControl == null) - { - throw; - } - - break; - } - } - case (int)MacroTypes.XSLT: - macroControl = LoadMacroXslt(this, Model, pageElements, true); - break; - case (int)MacroTypes.Script: - - //error handler for partial views, is an action because we need to re-use it twice below - Func handleMacroScriptError = e => - { - LogHelper.WarnWithException("Error loading MacroEngine script (file: " + ScriptFile + ", Type: '" + Model.TypeName + "'", true, e); - - // Invoke any error handlers for this macro - var macroErrorEventArgs = - new MacroErrorEventArgs - { - Name = Model.Name, - Alias = Model.Alias, - ItemKey = ScriptFile, - Exception = e, - Behaviour = UmbracoConfig.For.UmbracoSettings().Content.MacroErrorBehaviour - }; - - var errorMessage = textService.Localize("errors/macroErrorLoadingMacroEngineScript", new[] { ScriptFile }); - return GetControlForErrorBehavior(errorMessage, macroErrorEventArgs); - }; - - using (DisposableTimer.DebugDuration("Executing MacroEngineScript: " + ScriptFile)) - { - try - { - TraceInfo("umbracoMacro", "MacroEngine script added (" + ScriptFile + ")", excludeProfiling: true); - ScriptingMacroResult result = loadMacroScript(Model); - macroControl = new LiteralControl(result.Result); - if (result.ResultException != null) - { - renderFailed = true; - Exceptions.Add(result.ResultException); - macroControl = handleMacroScriptError(result.ResultException); - //if it is null, then we are supposed to throw the exception - if (macroControl == null) - { - throw result.ResultException; - } - } - break; - } - catch (Exception e) - { - renderFailed = true; - Exceptions.Add(e); - - macroControl = handleMacroScriptError(e); - //if it is null, then we are supposed to throw the (original) exception - // see: http://issues.umbraco.org/issue/U4-497 at the end - if (macroControl == null) - { - throw; - } - break; - } - } - case (int)MacroTypes.Unknown: - default: - if (GlobalSettings.DebugMode) - { - macroControl = new LiteralControl("<Macro: " + Name + " (" + ScriptAssembly + "," + ScriptType + ")>"); - } - break; - } - - //add to cache if render is successful - if (renderFailed == false) - { - macroControl = AddMacroResultToCache(macroControl); - } - - } - else if (macroControl == null) - { - macroControl = new LiteralControl(macroHtml); - } - - return macroControl; - } - } - - /// - /// Adds the macro result to cache and returns the control since it might be updated - /// - /// - /// - private Control AddMacroResultToCache(Control macroControl) - { - // Add result to cache if successful (and cache is enabled) - if (UmbracoContext.Current.InPreviewMode == false && Model.CacheDuration > 0) - { - // do not add to cache if there's no member and it should cache by personalization - if (!Model.CacheByMember || (Model.CacheByMember && Member.IsLoggedOn())) - { - if (macroControl != null) - { - string dateAddedCacheKey; - - using (DisposableTimer.DebugDuration("Saving MacroContent To Cache: " + Model.CacheIdentifier)) - { - - // NH: Scripts and XSLT can be generated as strings, but not controls as page events wouldn't be hit (such as Page_Load, etc) - if (CacheMacroAsString(Model)) - { - var outputCacheString = ""; - - using (var sw = new StringWriter()) - { - var hw = new HtmlTextWriter(sw); - macroControl.RenderControl(hw); - - outputCacheString = sw.ToString(); - } - - //insert the cache string result - ApplicationContext.Current.ApplicationCache.RuntimeCache.InsertCacheItem( - CacheKeys.MacroHtmlCacheKey + Model.CacheIdentifier, - priority: CacheItemPriority.NotRemovable, - timeout: new TimeSpan(0, 0, Model.CacheDuration), - getCacheItem: () => outputCacheString); - - dateAddedCacheKey = CacheKeys.MacroHtmlDateAddedCacheKey + Model.CacheIdentifier; - - // zb-00003 #29470 : replace by text if not already text - // otherwise it is rendered twice - if (!(macroControl is LiteralControl)) - macroControl = new LiteralControl(outputCacheString); - - TraceInfo("renderMacro", - string.Format("Macro Content saved to cache '{0}'.", Model.CacheIdentifier)); - } - else - { - //insert the cache control result - ApplicationContext.Current.ApplicationCache.RuntimeCache.InsertCacheItem( - CacheKeys.MacroControlCacheKey + Model.CacheIdentifier, - priority: CacheItemPriority.NotRemovable, - timeout: new TimeSpan(0, 0, Model.CacheDuration), - getCacheItem: () => new MacroCacheContent(macroControl, macroControl.ID)); - - dateAddedCacheKey = CacheKeys.MacroControlDateAddedCacheKey + Model.CacheIdentifier; - - TraceInfo("renderMacro", - string.Format("Macro Control saved to cache '{0}'.", Model.CacheIdentifier)); - } - - //insert the date inserted (so we can check file modification date) - ApplicationContext.Current.ApplicationCache.RuntimeCache.InsertCacheItem( - dateAddedCacheKey, - priority: CacheItemPriority.NotRemovable, - timeout: new TimeSpan(0, 0, Model.CacheDuration), - getCacheItem: () => DateTime.Now); - - } - - } - } - } - - return macroControl; - } - - /// - /// Returns the cached version of this macro either as a string or as a Control - /// - /// - /// - /// - /// - /// Depending on the type of macro, this will return the result as a string or as a control. This also - /// checks to see if preview mode is activated, if it is then we don't return anything from cache. - /// - private void GetMacroFromCache(out string macroHtml, out Control macroControl) - { - macroHtml = null; - macroControl = null; - - if (UmbracoContext.Current.InPreviewMode == false && Model.CacheDuration > 0) - { - var macroFile = GetMacroFile(Model); - var fileInfo = new FileInfo(HttpContext.Current.Server.MapPath(macroFile)); - - if (CacheMacroAsString(Model)) - { - macroHtml = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( - CacheKeys.MacroHtmlCacheKey + Model.CacheIdentifier); - - // FlorisRobbemont: - // An empty string means: macroHtml has been cached before, but didn't had any output (Macro doesn't need to be rendered again) - // An empty reference (null) means: macroHtml has NOT been cached before - if (macroHtml != null) - { - if (MacroNeedsToBeClearedFromCache(Model, CacheKeys.MacroHtmlDateAddedCacheKey + Model.CacheIdentifier, fileInfo)) - { - macroHtml = null; - TraceInfo("renderMacro", - string.Format("Macro removed from cache due to file change '{0}'.", - Model.CacheIdentifier)); - } - else - { - TraceInfo("renderMacro", - string.Format("Macro Content loaded from cache '{0}'.", - Model.CacheIdentifier)); - } - - } - } - else - { - var cacheContent = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( - CacheKeys.MacroControlCacheKey + Model.CacheIdentifier); - - if (cacheContent != null) - { - macroControl = cacheContent.Content; - macroControl.ID = cacheContent.ID; - - if (MacroNeedsToBeClearedFromCache(Model, CacheKeys.MacroControlDateAddedCacheKey + Model.CacheIdentifier, fileInfo)) - { - TraceInfo("renderMacro", - string.Format("Macro removed from cache due to file change '{0}'.", - Model.CacheIdentifier)); - macroControl = null; - } - else - { - TraceInfo("renderMacro", - string.Format("Macro Control loaded from cache '{0}'.", - Model.CacheIdentifier)); - } - - } - } - } - } - - /// - /// Raises the error event and based on the error behavior either return a control to display or throw the exception - /// - /// - /// - /// - private Control GetControlForErrorBehavior(string msg, MacroErrorEventArgs args) - { - OnError(args); - - switch (args.Behaviour) - { - case MacroErrorBehaviour.Inline: - return new LiteralControl(msg); - case MacroErrorBehaviour.Silent: - return new LiteralControl(""); - case MacroErrorBehaviour.Throw: - default: - return null; - } - } - - /// - /// check that the file has not recently changed - /// - /// - /// - /// - /// - /// - /// The only reason this is necessary is because a developer might update a file associated with the - /// macro, we need to ensure if that is the case that the cache not be used and it is refreshed. - /// - internal static bool MacroNeedsToBeClearedFromCache(MacroModel model, string dateAddedKey, FileInfo macroFile) - { - if (MacroIsFileBased(model)) - { - var cacheResult = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem(dateAddedKey); - - if (cacheResult != null) - { - var dateMacroAdded = cacheResult; - - if (macroFile.LastWriteTime.CompareTo(dateMacroAdded) == 1) - { - TraceInfo("renderMacro", string.Format("Macro needs to be removed from cache due to file change '{0}'.", model.CacheIdentifier)); - return true; - } - } - } - - return false; - } - - internal static string GetMacroFile(MacroModel model) - { - switch (model.MacroType) - { - case MacroTypes.XSLT: - return string.Concat("~/xslt/", model.Xslt); - case MacroTypes.Python: - case MacroTypes.Script: - return string.Concat("~/macroScripts/", model.ScriptName); - case MacroTypes.PartialView: - return model.ScriptName; //partial views are saved with the full virtual path - case MacroTypes.UserControl: - return model.TypeName; //user controls saved with the full virtual path - case MacroTypes.CustomControl: - case MacroTypes.Unknown: - default: - return "/" + model.TypeName; - } - } - - internal static bool MacroIsFileBased(MacroModel model) - { - return model.MacroType != MacroTypes.CustomControl && model.MacroType != MacroTypes.Unknown; - } - - /// - /// Determine if macro can be cached as string - /// - /// - /// - /// - /// Scripts and XSLT can be generated as strings, but not controls as page events wouldn't be hit (such as Page_Load, etc) - /// - internal static bool CacheMacroAsString(MacroModel model) - { - switch (model.MacroType) - { - case MacroTypes.XSLT: - case MacroTypes.Python: - case MacroTypes.Script: - case MacroTypes.PartialView: - return true; - case MacroTypes.UserControl: - case MacroTypes.CustomControl: - case MacroTypes.Unknown: - default: - return false; - } - } - - public static XslCompiledTransform getXslt(string XsltFile) - { - //TODO: SD: Do we really need to cache this?? - return ApplicationContext.Current.ApplicationCache.GetCacheItem( - CacheKeys.MacroXsltCacheKey + XsltFile, - CacheItemPriority.Default, - new CacheDependency(IOHelper.MapPath(SystemDirectories.Xslt + "/" + XsltFile)), - () => - { - using (var xslReader = new XmlTextReader(IOHelper.MapPath(SystemDirectories.Xslt.EnsureEndsWith('/') + XsltFile))) - { - return CreateXsltTransform(xslReader, GlobalSettings.DebugMode); - } - }); - } - - public void UpdateMacroModel(Hashtable attributes) - { - foreach (MacroPropertyModel mp in Model.Properties) - { - if (attributes.ContainsKey(mp.Key.ToLowerInvariant())) - { - var item = attributes[mp.Key.ToLowerInvariant()]; - - mp.Value = item == null ? string.Empty : item.ToString(); - } - else - { - mp.Value = string.Empty; - } - } - } - - public void GenerateMacroModelPropertiesFromAttributes(Hashtable attributes) - { - foreach (string key in attributes.Keys) - { - Model.Properties.Add(new MacroPropertyModel(key, attributes[key].ToString())); - } - } - - - public static XslCompiledTransform CreateXsltTransform(XmlTextReader xslReader, bool debugMode) - { - var macroXslt = new XslCompiledTransform(debugMode); - var xslResolver = new XmlUrlResolver - { - Credentials = CredentialCache.DefaultCredentials - }; - - xslReader.EntityHandling = EntityHandling.ExpandEntities; - - try - { - macroXslt.Load(xslReader, _xsltSettings, xslResolver); - } - finally - { - xslReader.Close(); - } - - return macroXslt; - } - - [Obsolete("This is no longer used in the codebase and will be removed in future versions")] - public static void unloadXslt(string XsltFile) - { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.MacroXsltCacheKey + XsltFile); - } - - #region LoadMacroXslt - - // gets the control for the macro, using GetXsltTransform methods for execution - // will pick XmlDocument or Navigator mode depending on the capabilities of the published caches - internal Control LoadMacroXslt(macro macro, MacroModel model, Hashtable pageElements, bool throwError) - { - if (XsltFile.Trim() == string.Empty) - { - TraceWarn("macro", "Xslt is empty"); - return new LiteralControl(string.Empty); - } - - using (DisposableTimer.DebugDuration("Executing XSLT: " + XsltFile)) - { - XmlDocument macroXml = null; - MacroNavigator macroNavigator = null; - NavigableNavigator contentNavigator = null; - - var canNavigate = - UmbracoContext.Current.ContentCache.XPathNavigatorIsNavigable && - UmbracoContext.Current.MediaCache.XPathNavigatorIsNavigable; - - if (!canNavigate) - { - // get master xml document - var cache = UmbracoContext.Current.ContentCache.InnerCache as Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); - XmlDocument umbracoXml = cache.GetXml(UmbracoContext.Current, UmbracoContext.Current.InPreviewMode); - macroXml = new XmlDocument(); - macroXml.LoadXml(""); - foreach (var prop in macro.Model.Properties) - { - AddMacroXmlNode(umbracoXml, macroXml, prop.Key, prop.Type, prop.Value); - } - } - else - { - var parameters = new List(); - contentNavigator = UmbracoContext.Current.ContentCache.GetXPathNavigator() as NavigableNavigator; - var mediaNavigator = UmbracoContext.Current.MediaCache.GetXPathNavigator() as NavigableNavigator; - foreach (var prop in macro.Model.Properties) - { - AddMacroParameter(parameters, contentNavigator, mediaNavigator, prop.Key, prop.Type, prop.Value); - } - macroNavigator = new MacroNavigator(parameters); - } - - if (HttpContext.Current.Request.QueryString["umbDebug"] != null && GlobalSettings.DebugMode) - { - var outerXml = macroXml == null ? macroNavigator.OuterXml : macroXml.OuterXml; - return - new LiteralControl("
Debug from " + - macro.Name + - "

" + HttpContext.Current.Server.HtmlEncode(outerXml) + - "

"); - } - - var textService = ApplicationContext.Current.Services.TextService; - try - { - var xsltFile = getXslt(XsltFile); - - using (DisposableTimer.DebugDuration("Performing transformation")) - { - try - { - var transformed = canNavigate - ? GetXsltTransformResult(macroNavigator, contentNavigator, xsltFile) // better? - : GetXsltTransformResult(macroXml, xsltFile); // document - var result = CreateControlsFromText(transformed); - - return result; - } - catch (Exception e) - { - Exceptions.Add(e); - LogHelper.WarnWithException("Error parsing XSLT file", e); - - var macroErrorEventArgs = new MacroErrorEventArgs { Name = Model.Name, Alias = Model.Alias, ItemKey = Model.Xslt, Exception = e, Behaviour = UmbracoConfig.For.UmbracoSettings().Content.MacroErrorBehaviour }; - - var errorMessage = textService.Localize("errors/macroErrorParsingXSLTFile", new[] { XsltFile }); - var macroControl = GetControlForErrorBehavior(errorMessage, macroErrorEventArgs); - //if it is null, then we are supposed to throw the (original) exception - // see: http://issues.umbraco.org/issue/U4-497 at the end - if (macroControl == null && throwError) - { - throw; - } - return macroControl; - } - } - } - catch (Exception e) - { - Exceptions.Add(e); - LogHelper.WarnWithException("Error loading XSLT " + Model.Xslt, true, e); - - // Invoke any error handlers for this macro - var macroErrorEventArgs = new MacroErrorEventArgs { Name = Model.Name, Alias = Model.Alias, ItemKey = Model.Xslt, Exception = e, Behaviour = UmbracoConfig.For.UmbracoSettings().Content.MacroErrorBehaviour }; - var errorMessage = textService.Localize("errors/macroErrorReadingXSLTFile", new[] { XsltFile }); - var macroControl = GetControlForErrorBehavior(errorMessage + XsltFile, macroErrorEventArgs); - //if it is null, then we are supposed to throw the (original) exception - // see: http://issues.umbraco.org/issue/U4-497 at the end - if (macroControl == null && throwError) - { - throw; - } - return macroControl; - } - } - } - - // gets the control for the macro, using GetXsltTransform methods for execution - public Control loadMacroXSLT(macro macro, MacroModel model, Hashtable pageElements) - { - return LoadMacroXslt(macro, model, pageElements, false); - } - - #endregion - - /// - /// Parses the text for umbraco Item controls that need to be rendered. - /// - /// The text to parse. - /// A control containing the parsed text. - protected Control CreateControlsFromText(string text) - { - // the beginning and end tags - const string tagStart = "[[[[umbraco:Item"; - const string tagEnd = "]]]]"; - - // container that will hold parsed controls - var container = new PlaceHolder(); - - // loop through all text - int textPos = 0; - while (textPos < text.Length) - { - // try to find an item tag, carefully staying inside the string bounds (- 1) - int tagStartPos = text.IndexOf(tagStart, textPos); - int tagEndPos = tagStartPos < 0 ? -1 : text.IndexOf(tagEnd, tagStartPos + tagStart.Length - 1); - - // item tag found? - if (tagStartPos >= 0 && tagEndPos >= 0) - { - // add the preceding text as a literal control - if (tagStartPos > textPos) - container.Controls.Add(new LiteralControl(text.Substring(textPos, tagStartPos - textPos))); - - // extract the tag and parse it - string tag = text.Substring(tagStartPos, (tagEndPos + tagEnd.Length) - tagStartPos); - Hashtable attributes = helper.ReturnAttributes(tag); - - // create item with the parameters specified in the tag - var item = new Item(); - item.NodeId = helper.FindAttribute(attributes, "nodeid"); - item.Field = helper.FindAttribute(attributes, "field"); - item.Xslt = helper.FindAttribute(attributes, "xslt"); - item.XsltDisableEscaping = helper.FindAttribute(attributes, "xsltdisableescaping") == "true"; - container.Controls.Add(item); - - // advance past the end of the tag - textPos = tagEndPos + tagEnd.Length; - } - else - { - // no more tags found, just add the remaning text - container.Controls.Add(new LiteralControl(text.Substring(textPos))); - textPos = text.Length; - } - } - return container; - } - - #region GetXsltTransform - - // gets the result of the xslt transform with no parameters - XmlDocument mode - public static string GetXsltTransformResult(XmlDocument macroXml, XslCompiledTransform xslt) - { - return GetXsltTransformResult(macroXml, xslt, null); - } - - // gets the result of the xslt transform - XmlDocument mode - public static string GetXsltTransformResult(XmlDocument macroXml, XslCompiledTransform xslt, Dictionary parameters) - { - TextWriter tw = new StringWriter(); - - XsltArgumentList xslArgs; - - using (DisposableTimer.DebugDuration("Adding XSLT Extensions")) - { - xslArgs = AddXsltExtensions(); - var lib = new library(); - xslArgs.AddExtensionObject("urn:umbraco.library", lib); - } - - // Add parameters - if (parameters == null || !parameters.ContainsKey("currentPage")) - { - xslArgs.AddParam("currentPage", string.Empty, library.GetXmlNodeCurrent()); - } - if (parameters != null) - { - foreach (var parameter in parameters) - xslArgs.AddParam(parameter.Key, string.Empty, parameter.Value); - } - - // Do transformation - using (DisposableTimer.DebugDuration("Executing XSLT transform")) - { - xslt.Transform(macroXml.CreateNavigator(), xslArgs, tw); - } - return TemplateUtilities.ResolveUrlsFromTextString(tw.ToString()); - } - - // gets the result of the xslt transform with no parameters - Navigator mode - public static string GetXsltTransformResult(XPathNavigator macroNavigator, XPathNavigator contentNavigator, - XslCompiledTransform xslt) - { - return GetXsltTransformResult(macroNavigator, contentNavigator, xslt, null); - } - - // gets the result of the xslt transform - Navigator mode - public static string GetXsltTransformResult(XPathNavigator macroNavigator, XPathNavigator contentNavigator, - XslCompiledTransform xslt, Dictionary parameters) - { - TextWriter tw = new StringWriter(); - - XsltArgumentList xslArgs; - using (DisposableTimer.DebugDuration("Adding XSLT Extensions")) - { - xslArgs = AddXsltExtensions(); - var lib = new library(); - xslArgs.AddExtensionObject("urn:umbraco.library", lib); - } - - // Add parameters - if (parameters == null || !parameters.ContainsKey("currentPage")) - { - var current = contentNavigator.Clone().Select("//* [@id=" + HttpContext.Current.Items["pageID"] + "]"); - xslArgs.AddParam("currentPage", string.Empty, current); - } - if (parameters != null) - { - foreach (var parameter in parameters) - xslArgs.AddParam(parameter.Key, string.Empty, parameter.Value); - } - - // Do transformation - using (DisposableTimer.DebugDuration("Executing XSLT transform")) - { - xslt.Transform(macroNavigator, xslArgs, tw); - } - return TemplateUtilities.ResolveUrlsFromTextString(tw.ToString()); - } - - #endregion - - public static XsltArgumentList AddXsltExtensions() - { - return AddMacroXsltExtensions(); - } - - /// - /// Gets a collection of all XSLT extensions for macros, including predefined extensions. - /// - /// A dictionary of name/extension instance pairs. - public static Dictionary GetXsltExtensions() - { - return XsltExtensionsResolver.Current.XsltExtensions - .ToDictionary(x => x.Namespace, x => x.ExtensionObject); - } - - /// - /// Returns an XSLT argument list with all XSLT extensions added, - /// both predefined and configured ones. - /// - /// A new XSLT argument list. - public static XsltArgumentList AddMacroXsltExtensions() - { - var xslArgs = new XsltArgumentList(); - - foreach (var extension in GetXsltExtensions()) - { - string extensionNamespace = "urn:" + extension.Key; - xslArgs.AddExtensionObject(extensionNamespace, extension.Value); - TraceInfo("umbracoXsltExtension", - String.Format("Extension added: {0}, {1}", - extensionNamespace, extension.Value.GetType().Name)); - } - - return xslArgs; - } - - #region LoadMacroXslt (2) - - // add elements to the root node, corresponding to parameters - private void AddMacroXmlNode(XmlDocument umbracoXml, XmlDocument macroXml, - string macroPropertyAlias, string macroPropertyType, string macroPropertyValue) - { - XmlNode macroXmlNode = macroXml.CreateNode(XmlNodeType.Element, macroPropertyAlias, string.Empty); - var x = new XmlDocument(); - - // if no value is passed, then use the current "pageID" as value - var contentId = macroPropertyValue == string.Empty ? UmbracoContext.Current.PageId.ToString() : macroPropertyValue; - - TraceInfo("umbracoMacro", - "Xslt node adding search start (" + macroPropertyAlias + ",'" + - macroPropertyValue + "')"); - - //TODO: WE need to fix this so that we give control of this stuff over to the actual parameter editors! - - switch (macroPropertyType) - { - case "contentTree": - var nodeId = macroXml.CreateAttribute("nodeID"); - nodeId.Value = contentId; - macroXmlNode.Attributes.SetNamedItem(nodeId); - - // Get subs - try - { - macroXmlNode.AppendChild(macroXml.ImportNode(umbracoXml.GetElementById(contentId), true)); - } - catch - { } - break; - - case "contentCurrent": - var importNode = macroPropertyValue == string.Empty - ? umbracoXml.GetElementById(contentId) - : umbracoXml.GetElementById(macroPropertyValue); - - var currentNode = macroXml.ImportNode(importNode, true); - - // remove all sub content nodes - foreach (XmlNode n in currentNode.SelectNodes("node|*[@isDoc]")) - currentNode.RemoveChild(n); - - macroXmlNode.AppendChild(currentNode); - - break; - - case "contentAll": - macroXmlNode.AppendChild(macroXml.ImportNode(umbracoXml.DocumentElement, true)); - break; - - case "contentRandom": - XmlNode source = umbracoXml.GetElementById(contentId); - if (source != null) - { - var sourceList = source.SelectNodes("node|*[@isDoc]"); - if (sourceList.Count > 0) - { - int rndNumber; - var r = library.GetRandom(); - lock (r) - { - rndNumber = r.Next(sourceList.Count); - } - var node = macroXml.ImportNode(sourceList[rndNumber], true); - // remove all sub content nodes - foreach (XmlNode n in node.SelectNodes("node|*[@isDoc]")) - node.RemoveChild(n); - - macroXmlNode.AppendChild(node); - } - else - TraceWarn("umbracoMacro", - "Error adding random node - parent (" + macroPropertyValue + - ") doesn't have children!"); - } - else - TraceWarn("umbracoMacro", - "Error adding random node - parent (" + macroPropertyValue + - ") doesn't exists!"); - break; - - case "mediaCurrent": - if (string.IsNullOrEmpty(macroPropertyValue) == false) - { - var c = new Content(int.Parse(macroPropertyValue)); - macroXmlNode.AppendChild(macroXml.ImportNode(c.ToXml(umbraco.content.Instance.XmlContent, false), true)); - } - break; - - default: - macroXmlNode.InnerText = HttpContext.Current.Server.HtmlDecode(macroPropertyValue); - break; - } - macroXml.FirstChild.AppendChild(macroXmlNode); - } - - // add parameters to the macro parameters collection - private void AddMacroParameter(ICollection parameters, - NavigableNavigator contentNavigator, NavigableNavigator mediaNavigator, - string macroPropertyAlias, string macroPropertyType, string macroPropertyValue) - { - // if no value is passed, then use the current "pageID" as value - var contentId = macroPropertyValue == string.Empty ? UmbracoContext.Current.PageId.ToString() : macroPropertyValue; - - TraceInfo("umbracoMacro", - "Xslt node adding search start (" + macroPropertyAlias + ",'" + - macroPropertyValue + "')"); - - // beware! do not use the raw content- or media- navigators, but clones !! - - switch (macroPropertyType) - { - case "contentTree": - parameters.Add(new MacroNavigator.MacroParameter( - macroPropertyAlias, - contentNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty - attributes: new Dictionary { { "nodeID", contentId } })); - - break; - - case "contentPicker": - parameters.Add(new MacroNavigator.MacroParameter( - macroPropertyAlias, - contentNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty - 0)); - break; - - case "contentSubs": - parameters.Add(new MacroNavigator.MacroParameter( - macroPropertyAlias, - contentNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty - 1)); - break; - - case "contentAll": - parameters.Add(new MacroNavigator.MacroParameter(macroPropertyAlias, contentNavigator.Clone())); - break; - - case "contentRandom": - var nav = contentNavigator.Clone(); - if (nav.MoveToId(contentId)) - { - var descendantIterator = nav.Select("./* [@isDoc]"); - if (descendantIterator.MoveNext()) - { - // not empty - and won't change - var descendantCount = descendantIterator.Count; - - int index; - var r = library.GetRandom(); - lock (r) - { - index = r.Next(descendantCount); - } - - while (index > 0 && descendantIterator.MoveNext()) - index--; - - var node = descendantIterator.Current.UnderlyingObject as INavigableContent; - if (node != null) - { - nav = contentNavigator.CloneWithNewRoot(node.Id.ToString(CultureInfo.InvariantCulture)); - parameters.Add(new MacroNavigator.MacroParameter(macroPropertyAlias, nav, 0)); - } - else - throw new InvalidOperationException("Iterator contains non-INavigableContent elements."); - } - else - TraceWarn("umbracoMacro", - "Error adding random node - parent (" + macroPropertyValue + - ") doesn't have children!"); - } - else - TraceWarn("umbracoMacro", - "Error adding random node - parent (" + macroPropertyValue + - ") doesn't exists!"); - break; - - case "mediaCurrent": - parameters.Add(new MacroNavigator.MacroParameter( - macroPropertyAlias, - mediaNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty - 0)); - break; - - default: - parameters.Add(new MacroNavigator.MacroParameter(macroPropertyAlias, HttpContext.Current.Server.HtmlDecode(macroPropertyValue))); - break; - } - } - - #endregion - - /// - /// Renders a Partial View Macro - /// - /// - /// - internal ScriptingMacroResult LoadPartialViewMacro(MacroModel macro) - { - var retVal = new ScriptingMacroResult(); - IMacroEngine engine = null; - - engine = MacroEngineFactory.GetEngine(PartialViewMacroEngine.EngineName); - var ret = engine.Execute(macro, GetCurrentNode()); - - // if the macro engine supports success reporting and executing failed, then return an empty control so it's not cached - if (engine is IMacroEngineResultStatus) - { - var result = engine as IMacroEngineResultStatus; - if (!result.Success) - { - retVal.ResultException = result.ResultException; - } - } - retVal.Result = ret; - return retVal; - } - - public ScriptingMacroResult loadMacroScript(MacroModel macro) - { - var retVal = new ScriptingMacroResult(); - string ret = String.Empty; - IMacroEngine engine = null; - if (!String.IsNullOrEmpty(macro.ScriptCode)) - { - engine = MacroEngineFactory.GetByExtension(macro.ScriptLanguage); - ret = engine.Execute( - macro, - GetCurrentNode()); - } - else - { - string path = IOHelper.MapPath(SystemDirectories.MacroScripts + "/" + macro.ScriptName); - engine = MacroEngineFactory.GetByFilename(path); - ret = engine.Execute(macro, GetCurrentNode()); - } - - // if the macro engine supports success reporting and executing failed, then return an empty control so it's not cached - if (engine is IMacroEngineResultStatus) - { - var result = engine as IMacroEngineResultStatus; - if (!result.Success) - { - retVal.ResultException = result.ResultException; - } - } - retVal.Result = ret; - return retVal; - } - - /// - /// Loads a custom or webcontrol using reflection into the macro object - /// - /// The assembly to load from - /// Name of the control - /// - public Control loadControl(string fileName, string controlName, MacroModel model) - { - return loadControl(fileName, controlName, model, null); - } - - /// - /// Loads a custom or webcontrol using reflection into the macro object - /// - /// The assembly to load from - /// Name of the control - /// - public Control loadControl(string fileName, string controlName, MacroModel model, Hashtable pageElements) - { - Type type; - Assembly asm; - try - { - string currentAss = IOHelper.MapPath(string.Format("{0}/{1}.dll", SystemDirectories.Bin, fileName)); - - if (!File.Exists(currentAss)) - return new LiteralControl("Unable to load user control because is does not exist: " + fileName); - asm = Assembly.LoadFrom(currentAss); - - TraceInfo("umbracoMacro", "Assembly file " + currentAss + " LOADED!!"); - } - catch - { - throw new ArgumentException(string.Format("ASSEMBLY NOT LOADED PATH: {0} NOT FOUND!!", - IOHelper.MapPath(SystemDirectories.Bin + "/" + fileName + - ".dll"))); - } - - TraceInfo("umbracoMacro", string.Format("Assembly Loaded from ({0}.dll)", fileName)); - type = asm.GetType(controlName); - if (type == null) - return new LiteralControl(string.Format("Unable to get type {0} from assembly {1}", - controlName, asm.FullName)); - - var control = Activator.CreateInstance(type) as Control; - if (control == null) - return new LiteralControl(string.Format("Unable to create control {0} from assembly {1}", - controlName, asm.FullName)); - - AddCurrentNodeToControl(control); - - // Properties - UpdateControlProperties(control, model); - return control; - } - - //TODO: SD : We *really* need to get macro's rendering properly with a real macro engine - // and move logic like this (after it is completely overhauled) to a UserControlMacroEngine. - internal static void UpdateControlProperties(Control control, MacroModel model) - { - var type = control.GetType(); - - foreach (var mp in model.Properties) - { - var prop = type.GetProperty(mp.Key); - if (prop == null) - { - TraceWarn("macro", string.Format("control property '{0}' doesn't exist or aren't accessible (public)", mp.Key)); - continue; - } - - var tryConvert = mp.Value.TryConvertTo(prop.PropertyType); - if (tryConvert.Success) - { - try - { - prop.SetValue(control, tryConvert.Result, null); - } - catch (Exception ex) - { - LogHelper.WarnWithException(string.Format("Error adding property '{0}' with value '{1}'", mp.Key, mp.Value), ex); - if (GlobalSettings.DebugMode) - { - TraceWarn("macro.loadControlProperties", string.Format("Error adding property '{0}' with value '{1}'", mp.Key, mp.Value), ex); - } - } - - if (GlobalSettings.DebugMode) - { - TraceInfo("macro.UpdateControlProperties", string.Format("Property added '{0}' with value '{1}'", mp.Key, mp.Value)); - } - } - else - { - LogHelper.Warn(string.Format("Error adding property '{0}' with value '{1}'", mp.Key, mp.Value)); - if (GlobalSettings.DebugMode) - { - TraceWarn("macro.loadControlProperties", string.Format("Error adding property '{0}' with value '{1}'", mp.Key, mp.Value)); - } - } - } - } - - /// - /// Loads an usercontrol using reflection into the macro object - /// - /// Filename of the usercontrol - ie. ~wulff.ascx - /// - /// The page elements. - /// - public Control loadUserControl(string fileName, MacroModel model, Hashtable pageElements) - { - Mandate.ParameterNotNullOrEmpty(fileName, "fileName"); - Mandate.ParameterNotNull(model, "model"); - - try - { - string userControlPath = IOHelper.FindFile(fileName); - - if (!File.Exists(IOHelper.MapPath(userControlPath))) - throw new UmbracoException(string.Format("UserControl {0} does not exist.", fileName)); - - var oControl = (UserControl)new UserControl().LoadControl(userControlPath); - - int slashIndex = fileName.LastIndexOf("/") + 1; - if (slashIndex < 0) - slashIndex = 0; - - if (!String.IsNullOrEmpty(model.MacroControlIdentifier)) - oControl.ID = model.MacroControlIdentifier; - else - oControl.ID = - string.Format("{0}_{1}", fileName.Substring(slashIndex, fileName.IndexOf(".ascx") - slashIndex), - StateHelper.GetContextValue(MacrosAddedKey)); - - TraceInfo(LoadUserControlKey, string.Format("Usercontrol added with id '{0}'", oControl.ID)); - - AddCurrentNodeToControl(oControl); - UpdateControlProperties(oControl, model); - return oControl; - } - catch (Exception e) - { - LogHelper.WarnWithException(string.Format("Error creating usercontrol ({0})", fileName), true, e); - throw; - } - } - - private static void AddCurrentNodeToControl(Control control) - { - var type = control.GetType(); - - PropertyInfo currentNodeProperty = type.GetProperty("CurrentNode"); - if (currentNodeProperty != null && currentNodeProperty.CanWrite && - currentNodeProperty.PropertyType.IsAssignableFrom(typeof(Node))) - { - currentNodeProperty.SetValue(control, GetCurrentNode(), null); - } - currentNodeProperty = type.GetProperty("currentNode"); - if (currentNodeProperty != null && currentNodeProperty.CanWrite && - currentNodeProperty.PropertyType.IsAssignableFrom(typeof(Node))) - { - currentNodeProperty.SetValue(control, GetCurrentNode(), null); - } - } - - private static void TraceInfo(string category, string message, bool excludeProfiling = false) - { - if (HttpContext.Current != null) - HttpContext.Current.Trace.Write(category, message); - - //Trace out to profiling... doesn't actually profile, just for informational output. - if (excludeProfiling == false) - { - using (ApplicationContext.Current.ProfilingLogger.DebugDuration(string.Format("{0}", message))) - { - } - } - } - - private static void TraceWarn(string category, string message, bool excludeProfiling = false) - { - if (HttpContext.Current != null) - HttpContext.Current.Trace.Warn(category, message); - - //Trace out to profiling... doesn't actually profile, just for informational output. - if (excludeProfiling == false) - { - using (ApplicationContext.Current.ProfilingLogger.TraceDuration(string.Format("Warning: {0}", message))) - { - } - } - } - - private static void TraceWarn(string category, string message, Exception ex, bool excludeProfiling = false) - { - if (HttpContext.Current != null) - HttpContext.Current.Trace.Warn(category, message, ex); - - //Trace out to profiling... doesn't actually profile, just for informational output. - if (excludeProfiling == false) - { - using (ApplicationContext.Current.ProfilingLogger.TraceDuration(string.Format("{0}, Error: {1}", message, ex))) - { - } - } - } - - public static string renderMacroStartTag(Hashtable attributes, int pageId, Guid versionId) - { - string div = "
"; - - return div; - } - - private static string EncodeMacroAttribute(string attributeContents) - { - // Replace linebreaks - attributeContents = attributeContents.Replace("\n", "\\n").Replace("\r", "\\r"); - - // Replace quotes - attributeContents = - attributeContents.Replace("\"", """); - - // Replace tag start/ends - attributeContents = - attributeContents.Replace("<", "<").Replace(">", ">"); - - - return attributeContents; - } - - public static string renderMacroEndTag() - { - return "
"; - } - - public static string GetRenderedMacro(int MacroId, page umbPage, Hashtable attributes, int pageId) - { - macro m = GetMacro(MacroId); - Control c = m.renderMacro(attributes, umbPage.Elements, pageId); - TextWriter writer = new StringWriter(); - var ht = new HtmlTextWriter(writer); - c.RenderControl(ht); - string result = writer.ToString(); - - // remove hrefs - string pattern = "href=\"([^\"]*)\""; - MatchCollection hrefs = - Regex.Matches(result, pattern, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - foreach (Match href in hrefs) - result = result.Replace(href.Value, "href=\"javascript:void(0)\""); - - return result; - } - - public static string MacroContentByHttp(int PageID, Guid PageVersion, Hashtable attributes) - { - - if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted) - { - return "Cannot render macro content in the rich text editor when the application is running in a Partial Trust environment"; - } - - string tempAlias = (attributes["macroalias"] != null) - ? attributes["macroalias"].ToString() - : attributes["macroAlias"].ToString(); - macro currentMacro = GetMacro(tempAlias); - if (!currentMacro.DontRenderInEditor) - { - string querystring = "umbPageId=" + PageID + "&umbVersionId=" + PageVersion; - IDictionaryEnumerator ide = attributes.GetEnumerator(); - while (ide.MoveNext()) - querystring += "&umb_" + ide.Key + "=" + HttpContext.Current.Server.UrlEncode((ide.Value ?? string.Empty).ToString()); - - // Create a new 'HttpWebRequest' Object to the mentioned URL. - string retVal = string.Empty; - string protocol = GlobalSettings.UseSSL ? "https" : "http"; - string url = string.Format("{0}://{1}:{2}{3}/macroResultWrapper.aspx?{4}", protocol, - HttpContext.Current.Request.ServerVariables["SERVER_NAME"], - HttpContext.Current.Request.ServerVariables["SERVER_PORT"], - IOHelper.ResolveUrl(SystemDirectories.Umbraco), querystring); - - var myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); - - // allows for validation of SSL conversations (to bypass SSL errors in debug mode!) - ServicePointManager.ServerCertificateValidationCallback += ValidateRemoteCertificate; - - // propagate the user's context - // zb-00004 #29956 : refactor cookies names & handling - - //TODO: This is the worst thing ever. This will also not work if people decide to put their own - // custom auth system in place. - - HttpCookie inCookie = StateHelper.Cookies.UserContext.RequestCookie; - var cookie = new Cookie(inCookie.Name, inCookie.Value, inCookie.Path, - HttpContext.Current.Request.ServerVariables["SERVER_NAME"]); - myHttpWebRequest.CookieContainer = new CookieContainer(); - myHttpWebRequest.CookieContainer.Add(cookie); - - // Assign the response object of 'HttpWebRequest' to a 'HttpWebResponse' variable. - HttpWebResponse myHttpWebResponse = null; - try - { - myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); - if (myHttpWebResponse.StatusCode == HttpStatusCode.OK) - { - Stream streamResponse = myHttpWebResponse.GetResponseStream(); - var streamRead = new StreamReader(streamResponse); - var readBuff = new Char[256]; - int count = streamRead.Read(readBuff, 0, 256); - while (count > 0) - { - var outputData = new String(readBuff, 0, count); - retVal += outputData; - count = streamRead.Read(readBuff, 0, 256); - } - // Close the Stream object. - streamResponse.Close(); - streamRead.Close(); - - // Find the content of a form - string grabStart = ""; - string grabEnd = ""; - int grabStartPos = retVal.IndexOf(grabStart) + grabStart.Length; - int grabEndPos = retVal.IndexOf(grabEnd) - grabStartPos; - retVal = retVal.Substring(grabStartPos, grabEndPos); - } - else - retVal = ShowNoMacroContent(currentMacro); - - // Release the HttpWebResponse Resource. - myHttpWebResponse.Close(); - } - catch (Exception) - { - retVal = ShowNoMacroContent(currentMacro); - } - finally - { - // Release the HttpWebResponse Resource. - if (myHttpWebResponse != null) - myHttpWebResponse.Close(); - } - - return retVal.Replace("\n", string.Empty).Replace("\r", string.Empty); - } - - return ShowNoMacroContent(currentMacro); - } - - private static string ShowNoMacroContent(macro currentMacro) - { - return "" + currentMacro.Name + - "
No macro content available for WYSIWYG editing
"; - } - - private static bool ValidateRemoteCertificate( - object sender, - X509Certificate certificate, - X509Chain chain, - SslPolicyErrors policyErrors - ) - { - if (GlobalSettings.DebugMode) - { - // allow any old dodgy certificate... - return true; - } - else - { - return policyErrors == SslPolicyErrors.None; - } - } - - /// - /// Adds the XSLT extension namespaces to the XSLT header using - /// {0} as the container for the namespace references and - /// {1} as the container for the exclude-result-prefixes - /// - /// The XSLT - /// - public static string AddXsltExtensionsToHeader(string xslt) - { - var namespaceList = new StringBuilder(); - var namespaceDeclaractions = new StringBuilder(); - foreach (var extension in GetXsltExtensions()) - { - namespaceList.Append(extension.Key).Append(' '); - namespaceDeclaractions.AppendFormat("xmlns:{0}=\"urn:{0}\" ", extension.Key); - } - - // parse xslt - xslt = xslt.Replace("{0}", namespaceDeclaractions.ToString()); - xslt = xslt.Replace("{1}", namespaceList.ToString()); - return xslt; - } - - [Obsolete("Please stop using these as they'll be removed in v4.8")] - public static bool TryGetColumnString(IRecordsReader reader, string columnName, out string value) - { - if (reader.ContainsField(columnName) && !reader.IsNull(columnName)) - { - value = reader.GetString(columnName); - return true; - } - - value = string.Empty; - return false; - } - - [Obsolete("Please stop using these as they'll be removed in v4.8")] - public static bool TryGetColumnInt32(IRecordsReader reader, string columnName, out int value) - { - if (reader.ContainsField(columnName) && !reader.IsNull(columnName)) - { - value = reader.GetInt(columnName); - return true; - } - - value = -1; - return false; - } - - [Obsolete("Please stop using these as they'll be removed in v4.8")] - public static bool TryGetColumnBool(IRecordsReader reader, string columnName, out bool value) - { - if (reader.ContainsField(columnName) && !reader.IsNull(columnName)) - { - value = reader.GetBoolean(columnName); - return true; - } - - value = false; - return false; - } - - private static INode GetCurrentNode() - { - //Get the current content request - - IPublishedContent content; - if (UmbracoContext.Current.IsFrontEndUmbracoRequest) - { - content = UmbracoContext.Current.PublishedContentRequest != null - ? UmbracoContext.Current.PublishedContentRequest.PublishedContent - : null; - } - else - { - var pageId = UmbracoContext.Current.PageId; - content = pageId.HasValue ? UmbracoContext.Current.ContentCache.GetById(pageId.Value) : null; - } - - return content == null ? null : LegacyNodeHelper.ConvertToNode(content); - } - - #region Events - - /// - /// Occurs when a macro error is raised. - /// - public static event EventHandler Error; - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected void OnError(MacroErrorEventArgs e) - { - if (Error != null) - { - Error(this, e); - } - } - - #endregion - } - - /// - /// Event arguments used for the MacroRendering event - /// - public class MacroRenderingEventArgs : EventArgs - { - public Hashtable PageElements { get; private set; } - public int PageId { get; private set; } - - public MacroRenderingEventArgs(Hashtable pageElements, int pageId) - { - PageElements = pageElements; - PageId = pageId; - } - } - -} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index e71a290754..ad1ac877be 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -1,7 +1,6 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; -using System.Data; using System.Linq; using System.Text; using System.Web; @@ -13,287 +12,199 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Editors; using Umbraco.Web.Routing; -using umbraco.cms.businesslogic.property; -using umbraco.cms.businesslogic.template; -using umbraco.cms.businesslogic.web; -using umbraco.interfaces; using Umbraco.Core.Configuration; -using Property = umbraco.cms.businesslogic.property.Property; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web; +using Umbraco.Web.Composing; namespace umbraco { - /// - /// Summary description for page. - /// - public class page - { + /// + /// Summary description for page. + /// + public class page + { - #region Private members and properties + #region Private members and properties - string _pageName; - int _parentId; - string _writerName; - string _creatorName; - string _path; - int _nodeType; - string _nodeTypeAlias; - string[] _splitpath; - DateTime _createDate; - DateTime _updateDate; - int _pageId; - Guid _pageVersion; - readonly int _template; + string _pageName; + int _parentId; + string _writerName; + string _creatorName; + string _path; + int _nodeType; + string _nodeTypeAlias; + string[] _splitpath; + DateTime _createDate; + DateTime _updateDate; + int _pageId; + Guid _pageVersion; + readonly int _template; - readonly Hashtable _elements = new Hashtable(); - readonly StringBuilder _pageContent = new StringBuilder(); - Control _pageContentControl = new Control(); + readonly Hashtable _elements = new Hashtable(); + readonly StringBuilder _pageContent = new StringBuilder(); + Control _pageContentControl = new Control(); - #endregion + #endregion - #region Constructors + #region Constructors - /// - /// Initializes a new instance of the class for a yet unpublished document, identified by its id and version. - /// - /// The identifier of the document. - /// The version to be displayed. - public page(int id, Guid version) - : this(new Document(id, version)) - { } + /// + /// Initializes a new instance of the class for a published document request. + /// + /// The pointing to the document. + /// + /// The difference between creating the page with PublishedContentRequest vs an IPublishedContent item is + /// that the PublishedContentRequest takes into account how a template is assigned during the routing process whereas + /// with an IPublishedContent item, the template id is asssigned purely based on the default. + /// + internal page(PublishedRequest frequest) + { - /// - /// Initializes a new instance of the class for a yet unpublished document. - /// - /// The document. - public page(Document document) - { - var docParentId = -1; - try - { - docParentId = document.ParentId; - } - catch (ArgumentException) - { - //ignore if no parent - } + if (!frequest.HasPublishedContent) + throw new ArgumentException("Document request has no node.", "frequest"); - populatePageData(document.Id, - document.Text, document.ContentType.Id, document.ContentType.Alias, - document.User.Name, document.Creator.Name, document.CreateDateTime, document.UpdateDate, - document.Path, document.Version, docParentId); + populatePageData(frequest.PublishedContent.Id, + frequest.PublishedContent.Name, frequest.PublishedContent.DocumentTypeId, frequest.PublishedContent.DocumentTypeAlias, + frequest.PublishedContent.WriterName, frequest.PublishedContent.CreatorName, frequest.PublishedContent.CreateDate, frequest.PublishedContent.UpdateDate, + frequest.PublishedContent.Path, frequest.PublishedContent.Parent == null ? -1 : frequest.PublishedContent.Parent.Id); - foreach (Property prop in document.GenericProperties) - { - string value = prop.Value != null ? prop.Value.ToString() : String.Empty; - _elements.Add(prop.PropertyType.Alias, value); - } + if (frequest.HasTemplate) + { - _template = document.Template; - } + this._template = frequest.TemplateModel.Id; + _elements["template"] = _template.ToString(); + } - /// - /// Initializes a new instance of the class for a published document request. - /// - /// The pointing to the document. - /// - /// The difference between creating the page with PublishedContentRequest vs an IPublishedContent item is - /// that the PublishedContentRequest takes into account how a template is assigned during the routing process whereas - /// with an IPublishedContent item, the template id is asssigned purely based on the default. - /// - internal page(PublishedContentRequest docreq) - { + PopulateElementData(frequest.PublishedContent); - if (!docreq.HasPublishedContent) - throw new ArgumentException("Document request has no node.", "docreq"); - - populatePageData(docreq.PublishedContent.Id, - docreq.PublishedContent.Name, docreq.PublishedContent.DocumentTypeId, docreq.PublishedContent.DocumentTypeAlias, - docreq.PublishedContent.WriterName, docreq.PublishedContent.CreatorName, docreq.PublishedContent.CreateDate, docreq.PublishedContent.UpdateDate, - docreq.PublishedContent.Path, docreq.PublishedContent.Version, docreq.PublishedContent.Parent == null ? -1 : docreq.PublishedContent.Parent.Id); + } - if (docreq.HasTemplate) - { + /// + /// Initializes a new instance of the page for a published document + /// + /// + internal page(IPublishedContent doc) + { + if (doc == null) throw new ArgumentNullException("doc"); - this._template = docreq.TemplateModel.Id; - _elements["template"] = _template.ToString(); - } + populatePageData(doc.Id, + doc.Name, doc.DocumentTypeId, doc.DocumentTypeAlias, + doc.WriterName, doc.CreatorName, doc.CreateDate, doc.UpdateDate, + doc.Path, doc.Parent == null ? -1 : doc.Parent.Id); - PopulateElementData(docreq.PublishedContent); + if (doc.TemplateId > 0) + { + //set the template to whatever is assigned to the doc + _template = doc.TemplateId; + _elements["template"] = _template.ToString(); + } - } - - /// - /// Initializes a new instance of the page for a published document - /// - /// - internal page(IPublishedContent doc) - { - if (doc == null) throw new ArgumentNullException("doc"); - - populatePageData(doc.Id, - doc.Name, doc.DocumentTypeId, doc.DocumentTypeAlias, - doc.WriterName, doc.CreatorName, doc.CreateDate, doc.UpdateDate, - doc.Path, doc.Version, doc.Parent == null ? -1 : doc.Parent.Id); - - if (doc.TemplateId > 0) - { - //set the template to whatever is assigned to the doc - _template = doc.TemplateId; - _elements["template"] = _template.ToString(); - } - - PopulateElementData(doc); - } - - /// - /// Initializes a new instance of the class for a published document. - /// - /// The XmlNode representing the document. - public page(XmlNode node) - { - populatePageData(node); - - if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates == false) - { - // Check for alternative template - if (HttpContext.Current.Items[Constants.Conventions.Url.AltTemplate] != null && - HttpContext.Current.Items[Constants.Conventions.Url.AltTemplate].ToString() != String.Empty) - { - _template = - umbraco.cms.businesslogic.template.Template.GetTemplateIdFromAlias( - HttpContext.Current.Items[Constants.Conventions.Url.AltTemplate].ToString()); - _elements.Add("template", _template.ToString()); - } - else if (helper.Request(Constants.Conventions.Url.AltTemplate) != String.Empty) - { - _template = - umbraco.cms.businesslogic.template.Template.GetTemplateIdFromAlias( - helper.Request(Constants.Conventions.Url.AltTemplate).ToLower()); - _elements.Add("template", _template.ToString()); - } - } - - if (_template == 0) - { - try - { - _template = Convert.ToInt32(node.Attributes.GetNamedItem("template").Value); - _elements.Add("template", node.Attributes.GetNamedItem("template").Value); - } - catch - { - HttpContext.Current.Trace.Warn("umbracoPage", "No template defined"); - } - } - - populateElementData(node); - - } + PopulateElementData(doc); + } /// /// Initializes a new instance of the page for a content. /// /// The content. /// This is for usage only. - internal page(IContent content) + internal page(IContent content) : this(new PagePublishedContent(content)) - { } + { } - #endregion + #endregion - #region Initialize + #region Initialize - void populatePageData(int pageID, - string pageName, int nodeType, string nodeTypeAlias, - string writerName, string creatorName, DateTime createDate, DateTime updateDate, - string path, Guid pageVersion, int parentId) - { - this._pageId = pageID; - this._pageName = pageName; - this._nodeType = nodeType; - this._nodeTypeAlias = nodeTypeAlias; - this._writerName = writerName; - this._creatorName = creatorName; - this._createDate = createDate; - this._updateDate = updateDate; - this._parentId = parentId; - this._path = path; - this._splitpath = path.Split(','); - this._pageVersion = pageVersion; + void populatePageData(int pageID, + string pageName, int nodeType, string nodeTypeAlias, + string writerName, string creatorName, DateTime createDate, DateTime updateDate, + string path, int parentId) + { + this._pageId = pageID; + this._pageName = pageName; + this._nodeType = nodeType; + this._nodeTypeAlias = nodeTypeAlias; + this._writerName = writerName; + this._creatorName = creatorName; + this._createDate = createDate; + this._updateDate = updateDate; + this._parentId = parentId; + this._path = path; + this._splitpath = path.Split(','); - // Update the elements hashtable - _elements.Add("pageID", pageID); - _elements.Add("parentID", parentId); - _elements.Add("pageName", pageName); - _elements.Add("nodeType", nodeType); - _elements.Add("nodeTypeAlias", nodeTypeAlias); - _elements.Add("writerName", writerName); - _elements.Add("creatorName", creatorName); - _elements.Add("createDate", createDate); - _elements.Add("updateDate", updateDate); - _elements.Add("path", path); - _elements.Add("splitpath", _splitpath); - _elements.Add("pageVersion", pageVersion); - } + // Update the elements hashtable + _elements.Add("pageID", pageID); + _elements.Add("parentID", parentId); + _elements.Add("pageName", pageName); + _elements.Add("nodeType", nodeType); + _elements.Add("nodeTypeAlias", nodeTypeAlias); + _elements.Add("writerName", writerName); + _elements.Add("creatorName", creatorName); + _elements.Add("createDate", createDate); + _elements.Add("updateDate", updateDate); + _elements.Add("path", path); + _elements.Add("splitpath", _splitpath); + } - void populatePageData(XmlNode node) - { - String s; - DateTime dt; - Guid guid; - int i; + void populatePageData(XmlNode node) + { + String s; + DateTime dt; + Guid guid; + int i; - if (int.TryParse(attrValue(node, "id"), out i)) - _elements["pageID"] = this._pageId = i; + if (int.TryParse(attrValue(node, "id"), out i)) + _elements["pageID"] = this._pageId = i; - if ((s = attrValue(node, "nodeName")) != null) - _elements["pageName"] = this._pageName = s; + if ((s = attrValue(node, "nodeName")) != null) + _elements["pageName"] = this._pageName = s; - if (int.TryParse(attrValue(node, "parentId"), out i)) - _elements["parentId"] = this._parentId = i; + if (int.TryParse(attrValue(node, "parentId"), out i)) + _elements["parentId"] = this._parentId = i; - if (int.TryParse(attrValue(node, "nodeType"), out i)) - _elements["nodeType"] = this._nodeType = i; - if ((s = attrValue(node, "nodeTypeAlias")) != null) - _elements["nodeTypeAlias"] = this._nodeTypeAlias = s; + if (int.TryParse(attrValue(node, "nodeType"), out i)) + _elements["nodeType"] = this._nodeType = i; + if ((s = attrValue(node, "nodeTypeAlias")) != null) + _elements["nodeTypeAlias"] = this._nodeTypeAlias = s; - if ((s = attrValue(node, "writerName")) != null) - _elements["writerName"] = this._writerName = s; - if ((s = attrValue(node, "creatorName")) != null) - _elements["creatorName"] = this._creatorName = s; + if ((s = attrValue(node, "writerName")) != null) + _elements["writerName"] = this._writerName = s; + if ((s = attrValue(node, "creatorName")) != null) + _elements["creatorName"] = this._creatorName = s; - if (DateTime.TryParse(attrValue(node, "createDate"), out dt)) - _elements["createDate"] = this._createDate = dt; - if (DateTime.TryParse(attrValue(node, "updateDate"), out dt)) - _elements["updateDate"] = this._updateDate = dt; + if (DateTime.TryParse(attrValue(node, "createDate"), out dt)) + _elements["createDate"] = this._createDate = dt; + if (DateTime.TryParse(attrValue(node, "updateDate"), out dt)) + _elements["updateDate"] = this._updateDate = dt; - if (Guid.TryParse(attrValue(node, "pageVersion"), out guid)) - _elements["pageVersion"] = this._pageVersion = guid; + if (Guid.TryParse(attrValue(node, "pageVersion"), out guid)) + _elements["pageVersion"] = this._pageVersion = guid; - if ((s = attrValue(node, "path")) != null) - { - _elements["path"] = this._path = s; - _elements["splitpath"] = this._splitpath = _path.Split(','); - } - } + if ((s = attrValue(node, "path")) != null) + { + _elements["path"] = this._path = s; + _elements["splitpath"] = this._splitpath = _path.Split(','); + } + } - string attrValue(XmlNode node, string attributeName) - { - var attr = node.Attributes.GetNamedItem(attributeName); - var value = attr != null ? attr.Value : null; - return value; - } + string attrValue(XmlNode node, string attributeName) + { + var attr = node.Attributes.GetNamedItem(attributeName); + var value = attr != null ? attr.Value : null; + return value; + } - /// - /// Puts the properties of the node into the elements table - /// - /// - void PopulateElementData(IPublishedContent node) - { - foreach(var p in node.Properties) - { - if (_elements.ContainsKey(p.PropertyTypeAlias) == false) - { + /// + /// Puts the properties of the node into the elements table + /// + /// + void PopulateElementData(IPublishedContent node) + { + foreach(var p in node.Properties) + { + if (_elements.ContainsKey(p.Alias) == false) + { // note: legacy used the raw value (see populating from an Xml node below) // so we're doing the same here, using DataValue. If we use Value then every // value will be converted NOW - including RTEs that may contain macros that @@ -302,193 +213,188 @@ namespace umbraco // to properly fix this, we'd need to turn the elements collection into some // sort of collection of lazy values. - _elements[p.PropertyTypeAlias] = p.DataValue; - } - } - } + _elements[p.Alias] = p.GetSourceValue(); + } + } + } - void populateElementData(XmlNode node) - { - string xpath = "./* [not(@isDoc)]"; + void populateElementData(XmlNode node) + { + string xpath = "./* [not(@isDoc)]"; - foreach (XmlNode data in node.SelectNodes(xpath)) - { - // ignore empty elements - if (data.ChildNodes.Count == 0) - continue; + foreach (XmlNode data in node.SelectNodes(xpath)) + { + // ignore empty elements + if (data.ChildNodes.Count == 0) + continue; - string alias = data.Name; - string value = data.FirstChild.Value; + string alias = data.Name; + string value = data.FirstChild.Value; - // moved to PublishedContentRequest + UmbracoModule - //if (alias == "umbracoRedirect") - //{ - // int i; - // if (int.TryParse(value, out i)) - // HttpContext.Current.Response.Redirect(library.NiceUrl(int.Parse(data.FirstChild.Value)), true); - //} + // moved to PublishedContentRequest + UmbracoModule + //if (alias == "umbracoRedirect") + //{ + // int i; + // if (int.TryParse(value, out i)) + // HttpContext.Current.Response.Redirect(library.NiceUrl(int.Parse(data.FirstChild.Value)), true); + //} - if (_elements.ContainsKey(alias)) - { - LogHelper.Debug( - string.Format("Aliases must be unique, an element with alias \"{0}\" has already been loaded!", alias), - true); - } - else - { - _elements[alias] = value; - LogHelper.Debug( - string.Format("Load element \"{0}\"", alias), - true); - } - } - } + if (_elements.ContainsKey(alias)) + { + Current.Logger.Debug( + string.Format("Aliases must be unique, an element with alias \"{0}\" has already been loaded!", alias)); + } + else + { + _elements[alias] = value; + Current.Logger.Debug( + string.Format("Load element \"{0}\"", alias)); + } + } + } - #endregion + #endregion - #region Wtf? + #region Wtf? - public void RenderPage(int templateId) - { - if (templateId != 0) - { - template templateDesign = new template(templateId); - - _pageContentControl = templateDesign.ParseWithControls(this); - _pageContent.Append(templateDesign.TemplateContent); - } - } + public void RenderPage(int templateId) + { + if (templateId != 0) + { + template templateDesign = new template(templateId); - #endregion + _pageContentControl = templateDesign.ParseWithControls(this); + _pageContent.Append(templateDesign.TemplateContent); + } + } - #region Public properties + #endregion - public Control PageContentControl - { - get { return _pageContentControl; } - } + #region Public properties - public string PageName - { - get { return _pageName; } - } + public Control PageContentControl + { + get { return _pageContentControl; } + } - public int ParentId - { - get { return _parentId; } - } + public string PageName + { + get { return _pageName; } + } - public string NodeTypeAlias - { - get { return _nodeTypeAlias; } - } + public int ParentId + { + get { return _parentId; } + } - public int NodeType - { - get { return _nodeType; } - } + public string NodeTypeAlias + { + get { return _nodeTypeAlias; } + } - public string WriterName - { - get { return _writerName; } - } + public int NodeType + { + get { return _nodeType; } + } - public string CreatorName - { - get { return _creatorName; } - } + public string WriterName + { + get { return _writerName; } + } - public DateTime CreateDate - { - get { return _createDate; } - } + public string CreatorName + { + get { return _creatorName; } + } - public DateTime UpdateDate - { - get { return _updateDate; } - } + public DateTime CreateDate + { + get { return _createDate; } + } - public int PageID - { - get { return _pageId; } - } + public DateTime UpdateDate + { + get { return _updateDate; } + } - public int Template - { - get { return _template; } - } + public int PageID + { + get { return _pageId; } + } - public Hashtable Elements - { - get { return _elements; } - } + public int Template + { + get { return _template; } + } - public string PageContent - { - get { return _pageContent.ToString(); } - } + public Hashtable Elements + { + get { return _elements; } + } - public string[] SplitPath - { - get { return this._splitpath; } - } + public string PageContent + { + get { return _pageContent.ToString(); } + } - #endregion + public string[] SplitPath + { + get { return this._splitpath; } + } - #region ToString + #endregion - public override string ToString() - { - return _pageName; - } + #region ToString - #endregion + public override string ToString() + { + return _pageName; + } + + #endregion #region PublishedContent private class PagePublishedProperty : PublishedPropertyBase { - private readonly object _dataValue; + private readonly object _sourceValue; private readonly IPublishedContent _content; public PagePublishedProperty(PublishedPropertyType propertyType, IPublishedContent content) - : base(propertyType) + : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { - _dataValue = null; + _sourceValue = null; _content = content; } public PagePublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, Umbraco.Core.Models.Property property) - : base(propertyType) + : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { - _dataValue = property.Value; + _sourceValue = property.GetValue(); _content = content; } - public override bool HasValue + public override bool HasValue(int? languageId = null, string segment = null) { - get { return _dataValue != null && ((_dataValue is string) == false || string.IsNullOrWhiteSpace((string)_dataValue) == false); } + return _sourceValue != null && ((_sourceValue is string) == false || string.IsNullOrWhiteSpace((string)_sourceValue) == false); } - public override object DataValue + public override object GetSourceValue(int? languageId = null, string segment = null) { - get { return _dataValue; } + return _sourceValue; } - public override object Value + public override object GetValue(int? languageId = null, string segment = null) { - get - { - // isPreviewing is true here since we want to preview anyway... - const bool isPreviewing = true; - var source = PropertyType.ConvertDataToSource(_dataValue, isPreviewing); - return PropertyType.ConvertSourceToObject(source, isPreviewing); - } + // isPreviewing is true here since we want to preview anyway... + const bool isPreviewing = true; + var source = PropertyType.ConvertSourceToInter(_content, _sourceValue, isPreviewing); + return PropertyType.ConvertInterToObject(_content, PropertyCacheLevel.Unknown, source, isPreviewing); } - public override object XPathValue + public override object GetXPathValue(int? languageId = null, string segment = null) { - get { throw new NotImplementedException(); } + throw new NotImplementedException(); } } @@ -496,6 +402,7 @@ namespace umbraco { private readonly IContent _inner; private readonly int _id; + private readonly Guid _key; private readonly string _creatorName; private readonly string _writerName; private readonly PublishedContentType _contentType; @@ -514,16 +421,18 @@ namespace umbraco _inner = inner; _id = _inner.Id; + _key = _inner.Key; + //TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current _creatorName = _inner.GetCreatorProfile().Name; _writerName = _inner.GetWriterProfile().Name; - _contentType = new PublishedContentType(_inner.ContentType); + _contentType = Current.PublishedContentTypeFactory.CreateContentType(_inner.ContentType); _properties = _contentType.PropertyTypes .Select(x => { - var p = _inner.Properties.SingleOrDefault(xx => xx.Alias == x.PropertyTypeAlias); + var p = _inner.Properties.SingleOrDefault(xx => xx.Alias == x.Alias); return p == null ? new PagePublishedProperty(x, this) : new PagePublishedProperty(x, this, p); }) .Cast() @@ -532,11 +441,6 @@ namespace umbraco _parent = new PagePublishedContent(_inner.ParentId); } - public IEnumerable ContentSet - { - get { throw new NotImplementedException(); } - } - public PublishedContentType ContentType { get { return _contentType; } @@ -547,6 +451,11 @@ namespace umbraco get { return _id; } } + public Guid Key + { + get { return _key; } + } + public int TemplateId { get { return _inner.Template == null ? 0 : _inner.Template.Id; } @@ -612,11 +521,6 @@ namespace umbraco get { return _inner.UpdateDate; } } - public Guid Version - { - get { return _inner.Version; } - } - public int Level { get { return _inner.Level; } @@ -637,11 +541,6 @@ namespace umbraco get { throw new NotImplementedException(); } } - public int GetIndex() - { - throw new NotImplementedException(); - } - public IPublishedContent Parent { get { return _parent; } @@ -652,7 +551,7 @@ namespace umbraco get { throw new NotImplementedException(); } } - public ICollection Properties + public IEnumerable Properties { get { return _properties; } } @@ -666,11 +565,6 @@ namespace umbraco { throw new NotImplementedException(); } - - public object this[string alias] - { - get { throw new NotImplementedException(); } - } } #endregion diff --git a/src/Umbraco.Web/umbraco.presentation/publishingService.cs b/src/Umbraco.Web/umbraco.presentation/publishingService.cs deleted file mode 100644 index e9becdc5ef..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/publishingService.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections; -using System.Diagnostics; -using System.Net; -using System.Web; -using System.Xml; -using Umbraco.Core.Configuration; -using Umbraco.Core; -using Umbraco.Core.Logging; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic.web; -using Umbraco.Core.Publishing; - -namespace umbraco.presentation -{ - [Obsolete("This is no longer used and will be removed in future versions")] - public class publishingService - { - private static readonly Hashtable ScheduledTaskTimes = new Hashtable(); - private static bool _isPublishingRunning = false; - - //NOTE: sender will be the umbraco ApplicationContext - public static void CheckPublishing(object sender) - { - if(_isPublishingRunning) - return; - _isPublishingRunning = true; - try - { - //run the scheduled publishing - we need to determine if this server - - var publisher = new ScheduledPublisher(ApplicationContext.Current.Services.ContentService); - publisher.CheckPendingAndProcess(); - - // run scheduled url tasks - try - { - foreach (var t in UmbracoConfig.For.UmbracoSettings().ScheduledTasks.Tasks) - { - bool runTask = false; - if (!ScheduledTaskTimes.ContainsKey(t.Alias)) - { - runTask = true; - ScheduledTaskTimes.Add(t.Alias, DateTime.Now); - } - // Add 1 second to timespan to compensate for differencies in timer - else if ( - new TimeSpan( - DateTime.Now.Ticks - ((DateTime)ScheduledTaskTimes[t.Alias]).Ticks).TotalSeconds + 1 >= t.Interval) - { - runTask = true; - ScheduledTaskTimes[t.Alias] = DateTime.Now; - } - - if (runTask) - { - bool taskResult = GetTaskByHttp(t.Url); - if (t.Log) - LogHelper.Info(string.Format("{0} has been called with response: {1}", t.Alias, taskResult)); - } - } - } - catch(Exception ee) - { - LogHelper.Error("Error executing scheduled task", ee); - } - } - catch(Exception x) - { - LogHelper.Error("Error executing scheduled publishing", x); - } - finally - { - _isPublishingRunning = false; - } - } - - private static bool GetTaskByHttp(string url) - { - var myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); - - try - { - using (var myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse()) - { - return myHttpWebResponse.StatusCode == HttpStatusCode.OK; - } - } - catch (Exception ex) - { - LogHelper.Error("Error sending url request for scheduled task", ex); - } - - return false; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/python.cs b/src/Umbraco.Web/umbraco.presentation/python.cs deleted file mode 100644 index 80b80c78a6..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/python.cs +++ /dev/null @@ -1,233 +0,0 @@ -using System; -using Umbraco.Core.IO; - -namespace umbraco.scripting -{ - - /// - /// Something like a proxy to IronPython. Does some initial settings and calls. - /// Maps IronPython's StandardOutput and StandardError to a simple string. - /// - public class python - { - protected internal static PythonEngine Engine; - protected static System.IO.MemoryStream ms; - protected static System.IO.StreamReader sr; - protected internal static System.Collections.Hashtable scripts; - - static python() - { - Engine = new PythonEngine(); - - initEnv(); - loadScripts(); - - ms = new System.IO.MemoryStream(); - sr = new System.IO.StreamReader(ms); - Engine.SetStandardOutput(ms); - Engine.SetStandardError(ms); - } - - - /// - /// To be able to import umbraco dll's we have to append the umbraco path to python. - /// It should also be possible to import other python scripts from umbracos python folder. - /// And finally to run run some custom init stuff the script site.py in umbraco's - /// root folder will be executed. - /// - /// - private static void initEnv() - { - // Add umbracos bin folder to python's path - string path = IOHelper.MapPath(SystemDirectories.Bin); - Engine.AddToPath(path); - - // Add umbracos python folder to python's path - path = IOHelper.MapPath(SystemDirectories.MacroScripts); - Engine.AddToPath(path); - - // execute the site.py to do all the initial stuff - string initFile = IOHelper.MapPath(SystemDirectories.Root + "/site.py"); - Engine.ExecuteFile(initFile); - } - - /// - /// - /// - /// - private static void loadScripts() - { - scripts = new System.Collections.Hashtable(); - string path = IOHelper.MapPath(SystemDirectories.MacroScripts); - System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(path); - foreach (System.IO.FileInfo f in dir.GetFiles("*.py")) - { - if (!f.Name.EndsWith("_temp.py")) - { - try - { - scripts[f.FullName] = Engine.CompileFile(f.FullName ); - } - catch - { - scripts[f.FullName] = Engine.Compile("print 'error in file " + f.Name + "'"); - } - } - } - } - - - /// - /// Executes a python command like in console - /// - /// command to execute - /// returns standard out of executed command - public static string execute(string expression) - { - if (!(expression == null)) - { - string ret; - ms.SetLength(0); - ms.Flush(); - try - { - Engine.Execute(expression); - ms.Position = 0; - ret = sr.ReadToEnd(); - } - catch (Exception ex) - { - ret = ex.Message; - } - return ret; - } - else - { - return string.Empty; - } - } - - - /// - /// Executes a python script like in console - /// - /// absolute path to script - /// returns standard out of executed script - public static string executeFile(string file) - { - if (System.IO.File.Exists(file)) - { - string ret; - ms.SetLength(0); - ms.Flush(); - - scripts[file].GetType().InvokeMember("Execute", System.Reflection.BindingFlags.InvokeMethod, null, scripts[file], null); - - ms.Position = 0; - ret = sr.ReadToEnd(); - return ret; - } - else - { - return "The File " + file + " could not be found."; - } - } - - - /// - /// Compiles a python script and add it to umbraco's script collection. - /// If compilation fails then an exception will be raised. - /// - /// absolute path to script - /// - public static void compileFile(string file) - { - loadScripts(); - scripts[file] = Engine.CompileFile(file); - } - - - /// - /// Compiles a python script. - /// If compilation fails then an exception will be raised. - /// - /// absolute path to script - /// - public static void tryCompile(string file) - { - Engine.CompileFile(file); - } - - } - - - /// - /// The Class PythonEngine is just a wrapper for the real class IronPython.Hosting.PythonEngine - /// in IronPython. In this manner we does not need a hard reference to the IronPython assembly. - /// I've implemented only the methods i need for my purpose. - /// - public class PythonEngine - { - protected object Engine; - - public PythonEngine() - { - string path = IOHelper.MapPath(SystemDirectories.Bin + "/IronPython.dll"); - System.Reflection.Assembly asm = System.Reflection.Assembly.LoadFile(path); - System.Type EngineType = asm.GetType("IronPython.Hosting.PythonEngine"); - Engine = System.Activator.CreateInstance(EngineType); - } - - public void AddToPath(string path) - { - Engine.GetType().InvokeMember("AddToPath", System.Reflection.BindingFlags.InvokeMethod, null, Engine, new object[] { path }); - } - - public void SetStandardOutput(System.IO.Stream stream) - { - Engine.GetType().InvokeMember("SetStandardOutput", System.Reflection.BindingFlags.InvokeMethod, null, Engine, new object[] { stream }); - } - - public void SetStandardError(System.IO.Stream stream) - { - Engine.GetType().InvokeMember("SetStandardError", System.Reflection.BindingFlags.InvokeMethod, null, Engine, new object[] { stream }); - } - - public void ExecuteFile(string FileName) - { - Engine.GetType().InvokeMember("ExecuteFile", System.Reflection.BindingFlags.InvokeMethod, null, Engine, new object[] { FileName }); - } - - public void Execute(string ScriptCode) - { - Engine.GetType().InvokeMember("Execute", System.Reflection.BindingFlags.InvokeMethod, null, Engine, new object[] { ScriptCode }); - } - - public Object CompileFile(string FileName) - { - return Engine.GetType().InvokeMember("CompileFile", System.Reflection.BindingFlags.InvokeMethod, null, Engine, new object[] { FileName }); - } - - public Object Compile(string Expression) - { - return Engine.GetType().InvokeMember("Compile", System.Reflection.BindingFlags.InvokeMethod, null, Engine, new object[] { Expression }); - } - - public Object CreateModule(string Modulename, bool publish) - { - return Engine.GetType().InvokeMember("CreateModule", System.Reflection.BindingFlags.InvokeMethod, null, Engine, new object[] { Modulename, publish }); - } - - public System.Collections.IDictionary Globals - { - set - { - Engine.GetType().InvokeMember("Globals", System.Reflection.BindingFlags.SetProperty, null, Engine, new object[] { value }); - } - get - { - return (System.Collections.IDictionary)Engine.GetType().InvokeMember("Globals", System.Reflection.BindingFlags.GetProperty, null, Engine, null); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/requestModule.cs b/src/Umbraco.Web/umbraco.presentation/requestModule.cs deleted file mode 100644 index 9e2eeff4e6..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/requestModule.cs +++ /dev/null @@ -1,476 +0,0 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Threading; -using System.Web; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using umbraco.BusinessLogic; -using System.Collections.Generic; -using umbraco.BusinessLogic.Utils; -using umbraco.businesslogic; -using umbraco.cms.businesslogic.cache; -using System.Web.Caching; -using Umbraco.Core.IO; -using umbraco.interfaces; - - -namespace umbraco.presentation -{ - /// - /// Summary description for requestModule. - /// - [Obsolete("This class is not used anymore and will be removed from the codebase in future versions.")] - public class requestModule : IHttpModule - { - protected static Timer publishingTimer; - protected static Timer pingTimer; - - private HttpApplication mApp; - private IContainer components = null; - - private readonly IList startupHandlers = new List(); - - /// True if the module is currently handling an error. - private static object handlingError = false; - /// List of errors that occurred since the last error was being handled. - private List unhandledErrors = new List(); - - public const string ORIGINAL_URL_CXT_KEY = "umbOriginalUrl"; - - private static string LOG_SCRUBBER_TASK_NAME = "ScrubLogs"; - private static CacheItemRemovedCallback OnCacheRemove = null; - - protected void ApplicationStart(HttpApplication HttpApp) - { - //starting the application. Application doesn't support HttpContext in integrated mode (standard mode on IIS7) - //So everything is moved to beginRequest. - } - - protected void Application_PostResolveRequestCache(object sender, EventArgs e) - { - // process rewrite here so forms authentication can Authorize based on url before the original url is discarded - this.UmbracoRewrite(sender, e); - } - - protected void Application_AuthorizeRequest(object sender, EventArgs e) - { - // nothing needs to be done here - } - - protected void Application_PreRequestHandlerExecute(object sender, EventArgs e) - { - - HttpContext context = mApp.Context; - //httpContext.RewritePath( (string) httpContext.Items[ORIGINAL_URL_CXT_KEY] + "?" + httpContext.Request.QueryString ); - } - - /// - /// Handles the BeginRequest event of the Application control. - /// - /// The source of the event. - /// The instance containing the event data. - protected void Application_BeginRequest(Object sender, EventArgs e) - { - - HttpApplication app = (HttpApplication)sender; - - //first time init, starts timers, and sets httpContext - InitializeApplication(app); - - // grab the original url before anything modifies it - HttpContext httpContext = mApp.Context; - httpContext.Items[ORIGINAL_URL_CXT_KEY] = rewrite404Url(httpContext.Request.Url.AbsolutePath, httpContext.Request.Url.Query, false); - - // create the Umbraco context - UmbracoContext.Current = new UmbracoContext(httpContext); - - // rewrite will happen after authorization - } - - protected string rewrite404Url(string url, string querystring, bool returnQuery) - { - // adding endswith and contains checks to ensure support for custom 404 messages (only 404 parse directory and aspx requests) - if (querystring.StartsWith("?404") && (!querystring.Contains(".") || querystring.EndsWith(".aspx") || querystring.Contains(".aspx&"))) - { - Uri u = new Uri(querystring.Substring(5, querystring.Length - 5)); - string path = u.AbsolutePath; - if (returnQuery) - { - return u.Query; - } - else - { - return path; - } - } - - if (returnQuery) - { - return querystring; - } - else - { - return url; - } - } - - /// - /// Performs path rewriting. - /// - /// The source of the event. - /// The instance containing the event data. - protected void UmbracoRewrite(Object sender, EventArgs e) - { - HttpContext context = mApp.Context; - string path = rewrite404Url(context.Request.Path.ToLower(), context.Request.Url.Query, false); - string query = String.Empty; - - if (GlobalSettings.UseDirectoryUrls) - { - // zb-00017 #29930 : do not change url casing when rewriting - string requestPath = context.Request.Path; // not lowercased - int asmxPos = path.IndexOf(".asmx/"); - if (asmxPos >= 0) - context.RewritePath(path.Substring(0, asmxPos + 5), - requestPath.Substring(asmxPos + 5), - context.Request.QueryString.ToString()); - } - - if (path.IndexOf(".aspx") > -1 || path.IndexOf('.') == -1) - { - // validate configuration - if (mApp.Application["umbracoNeedConfiguration"] == null) - mApp.Application["umbracoNeedConfiguration"] = !GlobalSettings.Configured; - - if (!GlobalSettings.IsReservedPathOrUrl(path)) - { - // redirect if Umbraco needs configuration - Nullable needsConfiguration = (Nullable)mApp.Application["umbracoNeedConfiguration"]; - - if (needsConfiguration.HasValue && needsConfiguration.Value) - { - string url = SystemDirectories.Install; - string meh = IOHelper.ResolveUrl(url); - string installUrl = string.Format("{0}/default.aspx?redir=true&url={1}", IOHelper.ResolveUrl( SystemDirectories.Install ), context.Request.Path.ToLower()); - context.Response.Redirect(installUrl, true); - } - - // show splash? - else if (UmbracoConfig.For.UmbracoSettings().Content.EnableSplashWhileLoading && content.Instance.isInitializing) - context.RewritePath(string.Format("{0}/splashes/booting.aspx", SystemDirectories.Config)); - // rewrite page path - else - { - string receivedQuery = rewrite404Url(context.Request.Path.ToLower(), context.Request.Url.Query, true); - if (receivedQuery.Length > 0) - { - // Clean umbPage from querystring, caused by .NET 2.0 default Auth Controls - if (receivedQuery.IndexOf("umbPage") > 0) - { - int ampPos = receivedQuery.IndexOf('&'); - // query contains no ampersand? - if (ampPos < 0) - { - // no ampersand means no original query string - query = String.Empty; - // ampersand would occur past then end the of received query - ampPos = receivedQuery.Length; - } - else - { - // original query string past ampersand - query = receivedQuery.Substring(ampPos + 1, - receivedQuery.Length - ampPos - 1); - } - // get umbPage out of query string (9 = "&umbPage".Length() + 1) - path = receivedQuery.Substring(9, ampPos - 9); //this will fail if there are < 9 characters before the &umbPage query string - } - else - { - // strip off question mark - query = receivedQuery.Substring(1); - } - } - - // Add questionmark to query string if it's not empty - if (!String.IsNullOrEmpty(query)) - query = "?" + query; - - // save original URL - context.Items["UmbPage"] = path; - context.Items["VirtualUrl"] = String.Format("{0}{1}", path, query); - // rewrite to the new URL - context.RewritePath(string.Format("{0}/default.aspx{2}", - SystemDirectories.Root, path, query)); - } - } - } - } - - - /// - /// Handles the Error event of the Application control. - /// - /// The source of the event. - /// The instance containing the event data. - protected void Application_Error(Object sender, EventArgs e) - { - // return immediately if an error is already been handled, to avoid infinite recursion - if ((bool)handlingError) - { - lock (unhandledErrors) - { - unhandledErrors.Add(mApp.Server.GetLastError()); - } - return; - } - // make sure only one thread at a time can handle an error - lock (handlingError) - { - Debug.Assert(!(bool)handlingError, "Two errors are being handled at the same time."); - handlingError = true; - - // make sure handlingError always gets set to false - try - { - if (GlobalSettings.Configured) - { - // log the error - // zb-00017 #29930 : could have been cleared, though: take care, .GetLastError() may return null - Exception ex = mApp.Server.GetLastError(); - if (ex != null) - ex = ex.InnerException; - - string error; - if (mApp.Context.Request != null) - error = string.Format("At {0} (Referred by: {1}): {2}", - mApp.Context.Request.RawUrl, - mApp.Context.Request.UrlReferrer, - ex); - else - error = "No Context available -> " - + ex; - - // Hide error if getting the user throws an error (e.g. corrupt / blank db) - User staticUser = null; - try - { - User.GetCurrent(); - } - catch - { - } - - LogHelper.Debug(error); - - Trace.TraceError(error); - lock (unhandledErrors) - { - if (unhandledErrors.Count > 0) - Trace.TraceError("New errors occurred while an error was being handled. The error handler Application_Error possibly raised another error, but was protected against an infinite loop."); - foreach (Exception unhandledError in unhandledErrors) - Trace.TraceError(unhandledError.StackTrace); - } - } - } - finally - { - // clear unhandled errors - lock (unhandledErrors) - { - unhandledErrors.Clear(); - } - // flag we're done with the error handling - handlingError = false; - } - } - } - - #region IHttpModule Members - - /// - ///Initializes a module and prepares it to handle requests. - /// - /// - ///An that provides access to the methods, properties, and events common to all application objects within an ASP.NET application - public void Init(HttpApplication context) - { - InitializeComponent(); - - ApplicationStart(context); - context.BeginRequest += new EventHandler(Application_BeginRequest); - context.AuthorizeRequest += new EventHandler(Application_AuthorizeRequest); - - // Alex Norcliffe - 2010 02 - Changed this behaviour as it disables OutputCaching due to Rewrite happening too early in the chain - // context.PostAuthorizeRequest += new EventHandler(Application_PostAuthorizeRequest); - context.PostResolveRequestCache += new EventHandler(Application_PostResolveRequestCache); - - context.PreRequestHandlerExecute += new EventHandler(Application_PreRequestHandlerExecute); - - // Alex Norcliffe - 2010 06 - Added a check at the end of the page lifecycle to see if we should persist Xml cache to disk - // (a replacement for all those parallel Async methods launching ThreadPool threads) - context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute); - context.Error += new EventHandler(Application_Error); - mApp = context; - } - - void context_PostRequestHandlerExecute(object sender, EventArgs e) - { - //if (content.Instance.IsXmlQueuedForPersistenceToFile) - //{ - // content.Instance.PersistXmlToFile(); - //} - } - - - - - private void InitializeComponent() - { - components = new Container(); - } - - /// - ///Disposes of the resources (other than memory) used by the module that implements . - /// - /// - public void Dispose() - { - } - - - //this makes sure that times and other stuff is started on the first request, instead of depending on - // application_start, which was inteded for being a httpContext-agnostic state. - private static bool s_InitializedAlready = false; - private static Object s_lock = new Object(); - // Initialize only on the first request - public void InitializeApplication(HttpApplication HttpApp) - { - if (s_InitializedAlready) - return; - - lock (s_lock) - { - if (s_InitializedAlready) - return; - - // Perform first-request initialization here ... - - try - { - LogHelper.Info(string.Format("Application started at {0}", DateTime.Now)); - - if (UmbracoConfig.For.UmbracoSettings().Logging.AutoCleanLogs) - { - AddTask(LOG_SCRUBBER_TASK_NAME, GetLogScrubbingInterval()); - } - } - catch - { - } - - // Trigger startup handlers - ApplicationStartupHandler.RegisterHandlers(); - - // Check for configured key, checking for currentversion to ensure that a request with - // no httpcontext don't set the whole app in configure mode - if (UmbracoVersion.Current != null && !GlobalSettings.Configured) - { - HttpApp.Application["umbracoNeedConfiguration"] = true; - } - - /* This section is needed on start-up because timer objects - * might initialize before these are initialized without a traditional - * request, and therefore lacks information on application paths */ - - /* Initialize SECTION END */ - - // add current default url - HttpApp.Application["umbracoUrl"] = string.Format("{0}:{1}{2}", HttpApp.Context.Request.ServerVariables["SERVER_NAME"], HttpApp.Context.Request.ServerVariables["SERVER_PORT"], IOHelper.ResolveUrl( SystemDirectories.Umbraco )); - - // Start ping / keepalive timer - pingTimer = new Timer(new TimerCallback(keepAliveService.PingUmbraco), HttpApp.Context, 60000, 300000); - - // Start publishingservice - publishingTimer = new Timer(new TimerCallback(publishingService.CheckPublishing), HttpApp.Context, 30000, 60000); - - //Find Applications and event handlers and hook-up the events - //BusinessLogic.Application.RegisterIApplications(); - - //define the base settings for the dependency loader to use the global path settings - //if (!CompositeDependencyHandler.HandlerFileName.StartsWith(GlobalSettings_Path)) - // CompositeDependencyHandler.HandlerFileName = GlobalSettings_Path + "/" + CompositeDependencyHandler.HandlerFileName; - - // Backwards compatibility - set the path and URL type for ClientDependency 1.5.1 [LK] - ClientDependency.Core.CompositeFiles.Providers.XmlFileMapper.FileMapVirtualFolder = "~/App_Data/TEMP/ClientDependency"; - ClientDependency.Core.CompositeFiles.Providers.BaseCompositeFileProcessingProvider.UrlTypeDefault = ClientDependency.Core.CompositeFiles.Providers.CompositeUrlType.Base64QueryStrings; - - // init done... - s_InitializedAlready = true; - - } - - } - - #endregion - - #region Inteval tasks - private static int GetLogScrubbingInterval() - { - int interval = 24 * 60 * 60; //24 hours - try - { - if (UmbracoConfig.For.UmbracoSettings().Logging.CleaningMiliseconds > -1) - interval = UmbracoConfig.For.UmbracoSettings().Logging.CleaningMiliseconds; - } - catch (Exception) - { - LogHelper.Info("Unable to locate a log scrubbing interval. Defaulting to 24 hours"); - } - return interval; - } - - private static int GetLogScrubbingMaximumAge() - { - int maximumAge = 24 * 60 * 60; - try - { - if (UmbracoConfig.For.UmbracoSettings().Logging.MaxLogAge > -1) - maximumAge = UmbracoConfig.For.UmbracoSettings().Logging.MaxLogAge; - } - catch (Exception) - { - LogHelper.Info("Unable to locate a log scrubbing maximum age. Defaulting to 24 hours"); - } - return maximumAge; - - } - - private void AddTask(string name, int seconds) - { - OnCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved); - HttpRuntime.Cache.Insert(name, seconds, null, - DateTime.Now.AddSeconds(seconds), System.Web.Caching.Cache.NoSlidingExpiration, - CacheItemPriority.NotRemovable, OnCacheRemove); - } - - public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r) - { - if (k.Equals(LOG_SCRUBBER_TASK_NAME)) - { - ScrubLogs(); - } - AddTask(k, Convert.ToInt32(v)); - } - - private static void ScrubLogs() - { - Log.CleanLogs(GetLogScrubbingMaximumAge()); - } - - #endregion - } - - -} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/showField.cs b/src/Umbraco.Web/umbraco.presentation/showField.cs deleted file mode 100644 index 726610009f..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/showField.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.ComponentModel; - -namespace umbraco.layoutControls -{ - /// - /// Summary description for ShowField. - /// - [DefaultProperty("FieldName"), - ToolboxData("<{0}:ShowField runat=server>")] - public class ShowField : System.Web.UI.WebControls.WebControl - { - private string _fieldName; - - [Bindable(true), - Category("umbraco"), - DefaultValue("PageName")] - public string FieldName - { - get - { - return _fieldName; - } - - set - { - _fieldName = value; - } - } - - /// - /// Render this control to the output parameter specified. - /// - /// - protected override void Render(HtmlTextWriter output) - { - layoutControls.umbracoPageHolder umbPageHolder = - (layoutControls.umbracoPageHolder) Page.FindControl("umbPageHolder"); - try - { - output.Write(umbPageHolder.Elements[_fieldName].ToString()); - } - catch - { - output.Write("Field not found ('" + _fieldName + "')"); - } - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/template.cs b/src/Umbraco.Web/umbraco.presentation/template.cs index 50d3876f40..98747b9ad7 100644 --- a/src/Umbraco.Web/umbraco.presentation/template.cs +++ b/src/Umbraco.Web/umbraco.presentation/template.cs @@ -1,23 +1,19 @@ -using System; -using System.Xml; -using System.Web.Caching; +using System; using System.Text; using System.IO; using System.Text.RegularExpressions; - -using System.Data; using System.Web.UI; using System.Collections; -using System.Collections.Generic; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Web; using Umbraco.Web.Cache; -using umbraco.DataLayer; -using umbraco.BusinessLogic; using Umbraco.Core.IO; using System.Web; +using Umbraco.Core.Models; +using Umbraco.Core.Xml; +using Umbraco.Web.Composing; +using Umbraco.Web.Macros; namespace umbraco { @@ -56,7 +52,7 @@ namespace umbraco get { return _masterTemplate; } } - //added fallback to the default template to avoid nasty .net errors. + //added fallback to the default template to avoid nasty .net errors. //This is referenced in /default.aspx.cs during page rendering. public string MasterPageFile { @@ -92,7 +88,7 @@ namespace umbraco using (var fs = new FileStream(originalPath, FileMode.Open, FileAccess.ReadWrite)) using (var f = new StreamReader(fs)) { - newFile = f.ReadToEnd(); + newFile = f.ReadToEnd(); } newFile = newFile.Replace("MasterPageFile=\"~/masterpages/", "MasterPageFile=\""); @@ -225,7 +221,7 @@ namespace umbraco Control pageContent = new Control(); bool stop = false; - bool debugMode = umbraco.presentation.UmbracoContext.Current.Request.IsDebug; + bool debugMode = UmbracoContext.Current.HttpContext.IsDebuggingEnabled; while (!stop) { @@ -239,7 +235,7 @@ namespace umbraco tempOutput.Remove(0, tagIndex); String tag = tempOutput.ToString().Substring(0, tempOutput.ToString().IndexOf(">") + 1); - Hashtable attributes = helper.ReturnAttributes(tag); + Hashtable attributes = new Hashtable(XmlHelper.GetAttributesFromElement(tag)); // Check whether it's a single tag () or a tag with children (...) if (tag.Substring(tag.Length - 2, 1) != "/" && tag.IndexOf(" ") > -1) @@ -278,19 +274,16 @@ namespace umbraco } else { - macro tempMacro; - String macroID = helper.FindAttribute(attributes, "macroid"); - if (macroID != String.Empty) - tempMacro = getMacro(macroID); - else - tempMacro = macro.GetMacro(helper.FindAttribute(attributes, "macroalias")); - + var macroId = helper.FindAttribute(attributes, "macroid"); + if (macroId == string.Empty) macroId = helper.FindAttribute(attributes, "macroalias"); + var tempMacro = GetMacro(macroId); if (tempMacro != null) { try { - Control c = tempMacro.renderMacro(attributes, umbPage.Elements, umbPage.PageID); + var renderer = new MacroRenderer(Current.ProfilingLogger); + var c = renderer.Render(tempMacro, umbPage.Elements, umbPage.PageID, attributes).GetAsControl(); if (c != null) pageContent.Controls.Add(c); else @@ -326,32 +319,34 @@ namespace umbraco { try { - if (helper.FindAttribute(attributes, "nodeId") != "" && int.Parse(helper.FindAttribute(attributes, "nodeId")) != 0) - { - cms.businesslogic.Content c = new umbraco.cms.businesslogic.Content(int.Parse(helper.FindAttribute(attributes, "nodeId"))); - item umbItem = new item(c.getProperty(helper.FindAttribute(attributes, "field")).Value.ToString(), attributes); - tempElementContent = umbItem.FieldContent; + //TODO: Make this work again - but let's make sure this whole class is overhauled and useful - // Check if the content is published - if (c.nodeObjectType == cms.businesslogic.web.Document._objectType) - { - try - { - cms.businesslogic.web.Document d = (cms.businesslogic.web.Document)c; - if (!d.Published) - tempElementContent = ""; - } - catch { } - } + //if (helper.FindAttribute(attributes, "nodeId") != "" && int.Parse(helper.FindAttribute(attributes, "nodeId")) != 0) + //{ + // cms.businesslogic.Content c = new umbraco.cms.businesslogic.Content(int.Parse(helper.FindAttribute(attributes, "nodeId"))); + // item umbItem = new item(c.getProperty(helper.FindAttribute(attributes, "field")).Value.ToString(), attributes); + // tempElementContent = umbItem.FieldContent; - } - else - { - // NH adds Live Editing test stuff - item umbItem = new item(umbPage.Elements, attributes); - // item umbItem = new item(umbPage.PageElements[helper.FindAttribute(attributes, "field")].ToString(), attributes); - tempElementContent = umbItem.FieldContent; - } + // // Check if the content is published + // if (c.nodeObjectType == cms.businesslogic.web.Document._objectType) + // { + // try + // { + // cms.businesslogic.web.Document d = (cms.businesslogic.web.Document)c; + // if (!d.Published) + // tempElementContent = ""; + // } + // catch { } + // } + + //} + //else + //{ + // // NH adds Live Editing test stuff + // item umbItem = new item(umbPage.Elements, attributes); + // // item umbItem = new item(umbPage.PageElements[helper.FindAttribute(attributes, "field")].ToString(), attributes); + // tempElementContent = umbItem.FieldContent; + //} if (debugMode) tempElementContent = @@ -379,15 +374,10 @@ namespace umbraco } - [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ParseInternalLinks instead")] - public static string ParseInternalLinks(string pageContents) - { - return Umbraco.Web.Templates.TemplateUtilities.ParseInternalLinks(pageContents); - } /// /// Parses the content of the templateOutput stringbuilder, and matches any tags given in the - /// XML-file /umbraco/config/umbracoTemplateTags.xml. + /// XML-file /umbraco/config/umbracoTemplateTags.xml. /// Replaces the found tags in the StringBuilder object, with "real content" /// /// @@ -402,17 +392,14 @@ namespace umbraco foreach (Match tag in tags) { - Hashtable attributes = helper.ReturnAttributes(tag.Value.ToString()); + Hashtable attributes = new Hashtable(XmlHelper.GetAttributesFromElement(tag.Value)); if (tag.ToString().ToLower().IndexOf("umbraco_macro") > -1) { - String macroID = helper.FindAttribute(attributes, "macroid"); - if (macroID != "") - { - macro tempMacro = getMacro(macroID); - _templateOutput.Replace(tag.Value.ToString(), tempMacro.MacroContent.ToString()); - } + var macroId = helper.FindAttribute(attributes, "macroid"); + if (macroId != "") + _templateOutput.Replace(tag.Value, string.Empty); } else { @@ -420,21 +407,17 @@ namespace umbraco { try { - String tempElementContent = umbPage.Elements[helper.FindAttribute(attributes, "field")].ToString(); - MatchCollection tempMacros = Regex.Matches(tempElementContent, "<\\?UMBRACO_MACRO(?[^>]*)>]*><\\/\\?UMBRACO_MACRO>", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + var tempElementContent = umbPage.Elements[helper.FindAttribute(attributes, "field")].ToString(); + var tempMacros = Regex.Matches(tempElementContent, "<\\?UMBRACO_MACRO(?[^>]*)>]*><\\/\\?UMBRACO_MACRO>", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); foreach (Match tempMacro in tempMacros) { - Hashtable tempAttributes = helper.ReturnAttributes(tempMacro.Groups["attributes"].Value.ToString()); - String macroID = helper.FindAttribute(tempAttributes, "macroid"); - if (Convert.ToInt32(macroID) > 0) - { - macro tempContentMacro = getMacro(macroID); - _templateOutput.Replace(tag.Value.ToString(), tempContentMacro.MacroContent.ToString()); - } - + var tempAttributes = new Hashtable(XmlHelper.GetAttributesFromElement(tempMacro.Groups["attributes"].Value)); + var macroId = helper.FindAttribute(tempAttributes, "macroid"); + if (Convert.ToInt32(macroId) > 0) + _templateOutput.Replace(tag.Value, string.Empty); } - _templateOutput.Replace(tag.Value.ToString(), tempElementContent); + _templateOutput.Replace(tag.Value, tempElementContent); } catch (Exception e) { @@ -453,30 +436,16 @@ namespace umbraco #region private methods - private macro getMacro(String macroID) + private static MacroModel GetMacro(string macroId) { - System.Web.HttpContext.Current.Trace.Write("umbracoTemplate", "Starting macro (" + macroID.ToString() + ")"); - return macro.GetMacro(Convert.ToInt16(macroID)); + HttpContext.Current.Trace.Write("umbracoTemplate", "Starting macro (" + macroId + ")"); + // it's all obsolete anyways... + var macro = Current.Services.MacroService.GetByAlias(macroId); + return macro == null ? null : new MacroModel(macro); } - private String FindAttribute(Hashtable attributes, String key) - { - if (attributes[key] != null) - return attributes[key].ToString(); - else - return ""; - } - - #endregion - /// - /// Unused, please do not use - /// - [Obsolete("Obsolete, For querying the database use the new UmbracoDatabase object ApplicationContext.Current.DatabaseContext.Database", false)] - protected static ISqlHelper SqlHelper - { - get { return Application.SqlHelper; } - } + #region constructors public static string GetMasterPageName(int templateID) @@ -487,9 +456,9 @@ namespace umbraco public static string GetMasterPageName(int templateID, string templateFolder) { var t = new template(templateID); - - return !string.IsNullOrEmpty(templateFolder) - ? t.AlternateMasterPageFile(templateFolder) + + return !string.IsNullOrEmpty(templateFolder) + ? t.AlternateMasterPageFile(templateFolder) : t.MasterPageFile; } @@ -497,29 +466,32 @@ namespace umbraco { var tId = templateID; - var t = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem
Set the title of the overlay.
model.subTitlemodel.subtitle String Set the subtitle of the overlay.
String Set an alternate submit button label key for localized texts
model.submitButtonStateStringSet the state for the submit button
model.hideSubmitButton Boolean

- Please notice that due to templating and unpublished content, the 2 pages can have differences in layout. + Please notice that due to templating and unpublished content, the 2 pages can have differences in layout.

- + - +
containing the day names */ - border-bottom: 1px solid #797979; - padding: 2px; - text-align: center; - color: #000; -} - -.calendar thead .weekend { /* How a weekend day name shows in header */ - color: #c44; -} - -.calendar thead .hilite { /* How do the buttons in header appear when hover */ - border-bottom: 1px solid #797979; - padding: 2px 2px 1px 2px; -} - -.calendar thead .active { /* Active (pressed) buttons in header */ - padding: 3px 1px 0px 3px; - border-bottom: 1px solid #797979; -} - -.calendar thead .daynames { /* Row
containing month days dates */ - font-family: verdana,tahoma,sans-serif; - width: 2em; - color: #000; - text-align: right; - padding: 2px 4px 2px 2px; -} -.calendar tbody .day.othermonth { - font-size: 80%; - color: #999; -} -.calendar tbody .day.othermonth.oweekend { - color: #f99; -} - -.calendar table .wn { - padding: 2px 3px 2px 2px; - border-right: 1px solid #797979; -} - -.calendar tbody .rowhilite td, -.calendar tbody .rowhilite td.wn { -} - -.calendar tbody td.today { font-weight: bold; } - -.calendar tbody td.hilite { /* Hovered cells */ - padding: 1px 3px 1px 1px; - border: 1px solid #bbb; -} - -.calendar tbody td.active { /* Active (pressed) cells */ - padding: 2px 2px 0px 2px; -} - -.calendar tbody td.weekend { /* Cells showing weekend days */ - color: #c44; -} - -.calendar tbody td.selected { /* Cell showing selected date */ - font-weight: bold; - border: 1px solid #797979; - padding: 1px 3px 1px 1px; -} - -.calendar tbody .disabled { color: #999; } - -.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ - visibility: hidden; -} - -.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ - display: none; -} - -/* The footer part -- status bar and "Close" button */ - -.calendar tfoot .footrow { /* The
*/ - padding: 2px; - color: #000; -} - -.calendar tfoot .hilite { /* Hover style for buttons in footer */ - background: #afa; - border: 1px solid #084; - color: #000; - padding: 1px; -} - -.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ - background: #7c7; - padding: 2px 0px 0px 2px; -} - -/* Combo boxes (menus that display months/years for direct selection) */ - -.calendar .combo { - position: absolute; - display: none; - top: 0px; - left: 0px; - width: 4em; - cursor: default; - border-width: 0 1px 1px 1px; - border-style: solid; - border-color: #797979; - color: #000; - z-index: 100; - font-size: 90%; -} - -.calendar .combo .label, -.calendar .combo .label-IEfix { - text-align: center; - padding: 1px; -} - -.calendar .combo .label-IEfix { - width: 4em; -} - -.calendar .combo .hilite { -} - -.calendar .combo .active { - font-weight: bold; -} - -.calendar td.time { - border-top: 1px solid #797979; - padding: 1px 0px; - text-align: center; - -} - -.calendar td.time .hour, -.calendar td.time .minute, -.calendar td.time .ampm { - padding: 0px 5px 0px 6px; - font-weight: bold; -} - -.calendar td.time .hour, -.calendar td.time .minute { - font-family: monospace; -} - -.calendar td.time .ampm { - text-align: center; -} - -.calendar td.time .colon { - padding: 0px 2px 0px 3px; - font-weight: bold; -} - -.calendar td.time span.hilite { -} - -.calendar td.time span.active { -} diff --git a/src/Umbraco.Web.UI/umbraco_client/images/progressbar.gif b/src/Umbraco.Web.UI/umbraco_client/images/progressbar.gif deleted file mode 100644 index ea7e3c2503..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/images/progressbar.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/progressBar/images/container_left.gif b/src/Umbraco.Web.UI/umbraco_client/progressBar/images/container_left.gif deleted file mode 100644 index b6e0ba092b..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/progressBar/images/container_left.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/progressBar/images/container_right.gif b/src/Umbraco.Web.UI/umbraco_client/progressBar/images/container_right.gif deleted file mode 100644 index c898edfa8b..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/progressBar/images/container_right.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/progressBar/images/indicator.gif b/src/Umbraco.Web.UI/umbraco_client/progressBar/images/indicator.gif deleted file mode 100644 index 62fc77ca99..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/progressBar/images/indicator.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/progressBar/javascript.js b/src/Umbraco.Web.UI/umbraco_client/progressBar/javascript.js deleted file mode 100644 index 061af4e4db..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/progressBar/javascript.js +++ /dev/null @@ -1,25 +0,0 @@ -var step = 8; -var padding = 10; - -function progressBarUpdate(id, percent) { - var total = document.getElementById("progressBar" + id).style.width; - total = Math.round(total.substring(0, total.length-2))-padding; - var onePercent = total / 100; - var progress = Math.round(onePercent*percent); - if (progress % step == 0 || percent > 99) { - document.getElementById("progressBar" + id + "_indicator").style.width = progress; - } -} - -function progressBarUpdateLabel(id, text) { - document.getElementById("progressBar" + id + "_text").innerHTML = text; -} - -function progressBarTest(id, percent) { - progressBarUpdate(id, percent); - progressBarUpdateLabel(id, percent + '%'); - if (percent < 100) { - percent++; - setTimeout("progressBarTest('" + id + "', " + percent + ")", 100); - } -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/progressBar/style.css b/src/Umbraco.Web.UI/umbraco_client/progressBar/style.css deleted file mode 100644 index 959648fa39..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/progressBar/style.css +++ /dev/null @@ -1,27 +0,0 @@ -.progressIndicator { - background: url(images/indicator.gif) repeat-x; - height: 8px; -} - -.progressContainerLeft { - background: url(images/container_left.gif) repeat-x; - padding: 3px 0px 2px 5px; - float: left; - margin: 0px; - height: 13px; -} - -.progressContainerRight { - background: url(images/container_right.gif) no-repeat; - padding: 0px; - width: 4px; - margin: 0px; - float: left; - height: 13px; -} - -.progressBar { - margin: 0px; - padding: 0px; - display: block; -} diff --git a/src/Umbraco.Web.UI/umbraco_client/propertypane/images/propertyBackground.gif b/src/Umbraco.Web.UI/umbraco_client/propertypane/images/propertyBackground.gif deleted file mode 100644 index c21c0d5362..0000000000 Binary files a/src/Umbraco.Web.UI/umbraco_client/propertypane/images/propertyBackground.gif and /dev/null differ diff --git a/src/Umbraco.Web.UI/umbraco_client/propertypane/style.css b/src/Umbraco.Web.UI/umbraco_client/propertypane/style.css deleted file mode 100644 index f92118e9c3..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/propertypane/style.css +++ /dev/null @@ -1,103 +0,0 @@ - - .propertypane { - _width: 95%; - position: relative; - display: block; - line-height: 1.1; - background: #fff url(images/propertyBackground.gif) top repeat-x; - padding: 5px; - margin:7px 0px 0px 0px; - border: 1px solid #d9d7d7; - text-align:left; - clear: both; - float: none; - } - - .propertypane th - { - vertical-align: top; - text-align:left; - font-weight:bold; - font-family: Trebuchet MS, Lucida Grande, verdana, arial; - font-size:12px; - width: 16%; - } - - .propertypane,.propertypane td - { - font-family: Trebuchet MS, Lucida Grande, verdana, arial; - font-size:12px; - } - - - - .propertypane small - { - font-weight: normal; - color: #666; - font-size: .9em; - } - .propertypane small strong { - color: #000; - } - - .propertypane div.propertyItem{ - padding-bottom: 5px; - clear: both; - font-family: Trebuchet MS, Lucida Grande, verdana, arial; - font-size:12px; - } - - .propertypane div.propertyItem .propertyItemheader{ - width: 16%; - padding-right: 1%; - padding-bottom: 10px; - float: left; - clear: left; - font-weight:bold; - } - - .propertypane div.propertyItem .propertyItemContent{ - float: left; - padding-bottom: 5px; - clear: right; - } - - .propertypane div.propertyItem .propertyItemContent #body_NameTxt - { - width: 400px; - } - - h2.propertypaneTitel{font-size: 14px; color: #999;margin: 7px 0px 0px 0px; padding-bottom: 0px; line-height: 14px;} - - div.propertyPaneFooter{clear: both; height: 1px; overflow: hidden; color: #fff;} - - /* table styles for members section */ - - .members_table - { - width:100%; - border:0; - border-collapse:collapse; - } - - .members_table th, - .members_table td - { - padding:.5em; - } - - .members_table th - { - border-bottom:2px solid #D9D7D7; - } - - .members_table td - { - border-bottom:1px solid #ccc; - } - - .members_table .alt - { - background:#efefef; - } \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco_client/tablesorting/tableDragAndDrop.js b/src/Umbraco.Web.UI/umbraco_client/tablesorting/tableDragAndDrop.js index 0dc576185a..2b3d1e829b 100644 --- a/src/Umbraco.Web.UI/umbraco_client/tablesorting/tableDragAndDrop.js +++ b/src/Umbraco.Web.UI/umbraco_client/tablesorting/tableDragAndDrop.js @@ -326,7 +326,6 @@ jQuery.tableDnD = { return null; } } - return row; } } return null; diff --git a/src/Umbraco.Web.UI/umbraco_client/tablesorting/tableFilter.js b/src/Umbraco.Web.UI/umbraco_client/tablesorting/tableFilter.js deleted file mode 100644 index 3bb73a36d2..0000000000 --- a/src/Umbraco.Web.UI/umbraco_client/tablesorting/tableFilter.js +++ /dev/null @@ -1,523 +0,0 @@ -(function($) { - $.extend({ - tablesorter: new function() { - var parsers = [], widgets = []; - this.defaults = { cssHeader: "header", cssAsc: "headerSortUp", cssDesc: "headerSortDown", sortInitialOrder: "asc", sortMultiSortKey: "shiftKey", sortForce: null, sortAppend: null, textExtraction: "simple", parsers: {}, widgets: [], widgetZebra: { css: ["even", "odd"] }, headers: {}, widthFixed: false, cancelSelection: true, sortList: [], headerList: [], dateFormat: "us", decimal: '.', debug: false }; - - function benchmark(s, d) { log(s + "," + (new Date().getTime() - d.getTime()) + "ms"); } - - this.benchmark = benchmark; - - function log(s) { - if (typeof console != "undefined" && typeof console.debug != "undefined") { - console.log(s); - } - else { - alert(s); - } - } - - function buildParserCache(table, $headers) { - if (table.config.debug) { - var parsersDebug = ""; - } - var rows = table.tBodies[0].rows; - if (table.tBodies[0].rows[0]) { - var list = [], cells = rows[0].cells, l = cells.length; - for (var i = 0; i < l; i++) { - var p = false; - if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) { - p = getParserById($($headers[i]).metadata().sorter); - } - else if ((table.config.headers[i] && table.config.headers[i].sorter)) { - p = getParserById(table.config.headers[i].sorter); - } - if (!p) { - p = detectParserForColumn(table, cells[i]); - } - if (table.config.debug) { - parsersDebug += "column:" + i + " parser:" + p.id + "\n"; - } - list.push(p); - } - } - if (table.config.debug) { - log(parsersDebug); - } - return list; - } - - ; - - function detectParserForColumn(table, node) { - var l = parsers.length; - for (var i = 1; i < l; i++) { - if (parsers[i].is($.trim(getElementText(table.config, node)), table, node)) { - return parsers[i]; - } - } - return parsers[0]; - } - - function getParserById(name) { - var l = parsers.length; - for (var i = 0; i < l; i++) { - if (parsers[i].id.toLowerCase() == name.toLowerCase()) { - return parsers[i]; - } - } - return false; - } - - function buildCache(table) { - if (table.config.debug) { - var cacheTime = new Date(); - } - var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0, totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0, parsers = table.config.parsers, cache = { row: [], normalized: [] }; - for (var i = 0; i < totalRows; ++i) { - var c = table.tBodies[0].rows[i], cols = []; - cache.row.push($(c)); - for (var j = 0; j < totalCells; ++j) { - cols.push(parsers[j].format(getElementText(table.config, c.cells[j]), table, c.cells[j])); - } - cols.push(i); - cache.normalized.push(cols); - cols = null; - } - ; - if (table.config.debug) { - benchmark("Building cache for " + totalRows + " rows:", cacheTime); - } - return cache; - } - - ; - - function getElementText(config, node) { - if (!node) return ""; - var t = ""; - if (config.textExtraction == "simple") { - if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) { - t = node.childNodes[0].innerHTML; - } - else { - t = node.innerHTML; - } - } - else { - if (typeof(config.textExtraction) == "function") { - t = config.textExtraction(node); - } - else { - t = $(node).text(); - } - } - return t; - } - - function appendToTable(table, cache) { - if (table.config.debug) { - var appendTime = new Date(); - } - var c = cache, r = c.row, n = c.normalized, totalRows = n.length, checkCell = (n[0].length - 1), tableBody = $(table.tBodies[0]), rows = []; - for (var i = 0; i < totalRows; i++) { - rows.push(r[n[i][checkCell]]); - if (!table.config.appender) { - var o = r[n[i][checkCell]]; - var l = o.length; - for (var j = 0; j < l; j++) { - tableBody[0].appendChild(o[j]); - } - } - } - if (table.config.appender) { - table.config.appender(table, rows); - } - rows = null; - if (table.config.debug) { - benchmark("Rebuilt table:", appendTime); - } - applyWidget(table); - setTimeout(function() { $(table).trigger("sortEnd"); }, 0); - } - - ; - - function buildHeaders(table) { - if (table.config.debug) { - var time = new Date(); - } - var meta = ($.metadata) ? true : false, tableHeadersRows = []; - for (var i = 0; i < table.tHead.rows.length; i++) { - tableHeadersRows[i] = 0; - } - ; - $tableHeaders = $("thead th", table); - $tableHeaders.each(function(index) { - this.count = 0; - this.column = index; - this.order = formatSortingOrder(table.config.sortInitialOrder); - if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true; - if (!this.sortDisabled) { - $(this).addClass(table.config.cssHeader); - } - table.config.headerList[index] = this; - }); - if (table.config.debug) { - benchmark("Built headers:", time); - log($tableHeaders); - } - return $tableHeaders; - } - - ; - - function checkCellColSpan(table, rows, row) { - var arr = [], r = table.tHead.rows, c = r[row].cells; - for (var i = 0; i < c.length; i++) { - var cell = c[i]; - if (cell.colSpan > 1) { - arr = arr.concat(checkCellColSpan(table, headerArr, row++)); - } - else { - if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) { - arr.push(cell); - } - } - } - return arr; - } - - ; - - function checkHeaderMetadata(cell) { - if (($.metadata) && ($(cell).metadata().sorter === false)) { - return true; - } - ; - return false; - } - - function checkHeaderOptions(table, i) { - if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) { - return true; - } - ; - return false; - } - - function applyWidget(table) { - var c = table.config.widgets; - var l = c.length; - for (var i = 0; i < l; i++) { - getWidgetById(c[i]).format(table); - } - } - - function getWidgetById(name) { - var l = widgets.length; - for (var i = 0; i < l; i++) { - if (widgets[i].id.toLowerCase() == name.toLowerCase()) { - return widgets[i]; - } - } - } - - ; - - function formatSortingOrder(v) { - var i; - if (typeof v != "number") { - i = (v.toLowerCase() == "desc") ? 1 : 0; - } - else { - i = (v == (0 || 1)) ? v : 0; - } - return i; - } - - function isValueInArray(v, a) { - var l = a.length; - for (var i = 0; i < l; i++) { - if (a[i][0] == v) { - return true; - } - } - return false; - } - - function setHeadersCss(table, $headers, list, css) { - $headers.removeClass(css[0]).removeClass(css[1]); - var h = []; - $headers.each(function(offset) { - if (!this.sortDisabled) { - h[this.column] = $(this); - } - }); - var l = list.length; - for (var i = 0; i < l; i++) { - h[list[i][0]].addClass(css[list[i][1]]); - } - } - - function fixColumnWidth(table, $headers) { - var c = table.config; - if (c.widthFixed) { - var colgroup = $('